diff --git a/.github/ISSUE_TEMPLATE/10-bug-report.yml b/.github/ISSUE_TEMPLATE/10-bug-report.yml index 599c07615104..9d8c2873ee6a 100644 --- a/.github/ISSUE_TEMPLATE/10-bug-report.yml +++ b/.github/ISSUE_TEMPLATE/10-bug-report.yml @@ -45,6 +45,7 @@ body: label: Version options: - trunk/main + - 7.2.16 - 7.2.14 - 7.2.12 - 7.2.10 diff --git a/Config.kmk b/Config.kmk index 84f392fb43e2..e1333d4af226 100644 --- a/Config.kmk +++ b/Config.kmk @@ -1,4 +1,4 @@ -# $Id: Config.kmk 114504 2026-06-23 16:56:50Z aleksey.ilyushin@oracle.com $ +# $Id: Config.kmk 115021 2026-08-12 23:45:19Z knut.osmundsen@oracle.com $ ## @file # The global build configuration file for VBox. # @@ -568,11 +568,21 @@ endif if1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), darwin.amd64 darwin.arm64 linux.amd64 linux.arm64 win.arm64) VBOX_WITH_DRIVERLESS_NEM_FALLBACK = 1 endif +# Enable local IPC testcase coverage together with the production transport. +VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES = $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),1,) # Enable forced driverless mode by default. if1of ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH), darwin.amd64 darwin.arm64) VBOX_WITH_DRIVERLESS_FORCED = 1 if1of ($(KBUILD_TARGET), darwin) VBOX_WITH_INTNET_SERVICE_IN_R3 = 1 + VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES = 1 + endif +endif +# The local IPC implementation of the R3 IntNet service is +# experimental and intentionally disabled by default for now, see @bugref{11149} +if1of ($(KBUILD_TARGET), win linux) + ifdef VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC + VBOX_WITH_INTNET_SERVICE_IN_R3 := 1 endif endif # Enables the new breakpoint handling code, see @bugref{8650} @@ -966,7 +976,7 @@ VBOX_WITH_SHARED_FOLDERS = 1 # Enable shared clipboard VBOX_WITH_SHARED_CLIPBOARD = 1 # Enable shared clipboard (file) transfers -if1of ($(KBUILD_TARGET), win linux) +if1of ($(KBUILD_TARGET), win linux darwin) VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS = 1 if1of ($(KBUILD_TARGET), linux) VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP = 1 @@ -2054,7 +2064,8 @@ ASTOOL := $(VBOX_ASTOOL) # INCS += $(PATH_ROOT)/include $(PATH_OUT) DEFS += VBOX -DEFS.debug := DEBUG DEBUG_$(subst $(subst _, ,_),_,$(USERNAME)) DEBUG_USERNAME=$(subst $(subst _, ,_),_,$(USERNAME)) +USERNAME_FIXUP := $(subst -,_,$(subst $(subst _, ,_),_,$(USERNAME))) +DEFS.debug := DEBUG DEBUG_$(USERNAME_FIXUP) DEBUG_USERNAME=$(USERNAME_FIXUP) DEFS.dbgopt = $(DEFS.debug) DEFS.profile = VBOX_WITH_STATISTICS DEFS.strict = RT_STRICT VBOX_STRICT @@ -3200,9 +3211,6 @@ VBOX_PATH_X11_ROOT = $(PATH_ROOT)/src/VBox/Additions/x11/x11include # Miscellaneous includes # VBOX_GRAPHICS_INCS = $(PATH_ROOT)/include/VBox/Graphics -VBOX_JPEG_INCS = \ - $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0/src \ - $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0 # The icons to use. @@ -3478,6 +3486,7 @@ ifndef VBOX_NOINC_DYNAMIC_CONFIG_KMK $(QUIET)$(APPEND) '$@' 'VBOX_GCC_msse4.1 ?= $(call VBOX_GCC_CHECK_CC,-msse4.1,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_mavx ?= $(call VBOX_GCC_CHECK_CC,-mavx,)' $(QUIET)$(APPEND) '$@' 'VBOX_GCC_mavx2 ?= $(call VBOX_GCC_CHECK_CC,-mavx2,)' + $(QUIET)$(APPEND) '$@' 'VBOX_GCC_mharden-sls-all ?= $(call VBOX_GCC_CHECK_CXX,-mharden-sls=all,)' ifdef VBOX_USE_CLANG $(QUIET)$(APPEND) '$@' 'VBOX_GCC_no-pie ?= $(call VBOX_GCC_CHECK_LD,--no-pie,)' else @@ -3862,6 +3871,9 @@ if1of (win, $(KBUILD_TARGET) $(KBUILD_HOST)) VBOX_VCC_LD_GUARD_CF := VBOX_VCC_CC_GUARD_EHCONT := VBOX_VCC_LD_GUARD_EHCONT := + VBOX_VCC_LD_GUARD_RETPOLINE := + VBOX_VCC_LD_GUARD_RETPOLINE_IGNORE := + VBOX_VCC_LD_GUARD_RETPOLINE_WITH_IGNORE := VBOX_VCC_LD_HIGH_ENTRYOPY_VA := VBOX_VCC_LD_CET_COMPAT := @@ -3876,6 +3888,11 @@ if1of (win, $(KBUILD_TARGET) $(KBUILD_HOST)) VBOX_VCC_CC_GUARD_EHCONT := -guard:ehcont VBOX_VCC_LD_GUARD_EHCONT := -Guard:ehcont endif + if $(VBOX_VCC_TOOL_STEM) >= VCC142 # Since when? (Also present in older versions.) + VBOX_VCC_LD_GUARD_RETPOLINE := -Guard:retpoline + VBOX_VCC_LD_GUARD_RETPOLINE_IGNORE := -Ignore:4287 + VBOX_VCC_LD_GUARD_RETPOLINE_WITH_IGNORE := -Guard:retpoline -Ignore:4287 + endif endif endif @@ -5271,7 +5288,7 @@ SDK_VBoxSoftFloatGuestR3Shared_LIBS.$(KBUILD_TARGET_ARCH) = \ ifdef VBOX_WITH_LIBSSH SDK_VBoxLibSsh := libssh for dynamic dll loading with assembly stubs. - SDK_VBoxLibSsh_INCS ?= $(PATH_ROOT)/src/libs/libssh-0.11.4/include + SDK_VBoxLibSsh_INCS ?= $(PATH_ROOT)/src/libs/libssh-0.12.2/include # SDK_VBoxLibSsh_LIBS is not defined, as VBoxLibSsh dll is not linked, but loaded explicitly # by auto-generated stub code (lazy loading) endif @@ -5322,6 +5339,15 @@ SDK_VBoxLibXml2_DEFS.win ?= WIN32 _WINDOWS _MBCS HAVE_WIN32_THREADS HAVE_CO # Note: no linking to LIB here, we do that explicitly in src/VBox/Runtime/Makefile.kmk to link # libxml against VBoxRT +# libjpeg +SDK_VBoxLibJpeg := libjpeg for dll linking. +SDK_VBoxLibJpeg_DEFAULT_INCS ?= $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0/src \ + $(PATH_ROOT)/src/libs/libjpeg-turbo-3.1.0 +SDK_VBoxLibJpeg_INCS ?= $(SDK_VBoxLibJpeg_DEFAULT_INCS) +SDK_VBoxLibJpeg_LIBS ?= $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ + $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) + # zlib SDK_VBoxZlib := zlib for dll linking. SDK_VBoxZlib_VBOX_DEFAULT_INCS := $(PATH_ROOT)/src/libs/zlib-1.3.2 @@ -5669,6 +5695,13 @@ ifeq ($(VBOX_LDR_FMT),pe) if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Don't waste space on x86/amd64-on-arm emulation optimizations. TEMPLATE_VBoxR0_CXXFLAGS += /volatileMetadata- endif + if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Speculative execution hardening. + TEMPLATE_VBoxR0_CXXFLAGS += -Qspectre + if1of ($(KBUILD_TARGET_ARCH), amd64 x86) + TEMPLATE_VBoxR0_CXXFLAGS += -Qspectre-jmp + #TEMPLATE_VBoxR0_CXXFLAGS += -d2guardretpoline - requires RTLdr support or the VM fails to start due to mismatches. + endif + endif ifdef VBOX_WITH_MSC_ANALYZE_THIS TEMPLATE_VBoxR0_CXXFLAGS += /analyze endif @@ -5695,6 +5728,9 @@ ifeq ($(VBOX_LDR_FMT),pe) TEMPLATE_VBoxR0_LDFLAGS.win.x86 += -Include:___security_init_cookie -Export:ModuleInitSecurityCookie=__security_init_cookie TEMPLATE_VBoxR0_LDFLAGS.win.arm64 += -Include:__security_init_cookie -Export:ModuleInitSecurityCookie=__security_init_cookie endif + if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Speculative execution hardening. + #TEMPLATE_VBoxR0_LDFLAGS.win.amd64 += $(VBOX_VCC_LD_GUARD_RETPOLINE) - requires RTLdr support or the VM fails to start due to mismatches. + endif TEMPLATE_VBoxR0_LATE_LIBS = \ $(PATH_STAGE_LIB)/RuntimeR0Stub$(VBOX_SUFF_LIB) ifneq ($(VBOX_VCC_CC_GUARD_CF),) @@ -5722,8 +5758,8 @@ ifeq ($(VBOX_LDR_FMT),elf) if $(VBOX_GCC_VERSION_CC) >= 40500 # 4.1.2 complains, 4.5.2 is okay, didn't check which version inbetween made it okay with g++. TEMPLATE_VBoxR0_CXXFLAGS += -ffreestanding endif - TEMPLATE_VBoxR0_CFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables -ffreestanding - TEMPLATE_VBoxR0_CXXFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables + TEMPLATE_VBoxR0_CFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables $(VBOX_GCC_mharden-sls-all) -ffreestanding + TEMPLATE_VBoxR0_CXXFLAGS.amd64 = -m64 -mno-red-zone -mno-sse -mno-mmx -mno-sse2 -mno-3dnow -fasynchronous-unwind-tables $(VBOX_GCC_mharden-sls-all) TEMPLATE_VBoxR0_CXXFLAGS.arm64 = -mabi=lp64 -mgeneral-regs-only -ffixed-x18 -fconserve-stack \ -fno-stack-check $(VBOX_GCC_fno-stack-clash-protection) $(VBOX_GCC_fno-allow-store-data-races) -fno-delete-null-pointer-checks # (linux 6.11) # arm64: Add -mbranch-protection=pac-ret ? @@ -5901,10 +5937,17 @@ ifeq ($(KBUILD_TARGET),win) if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Don't waste space on x86/amd64-on-arm emulation optimizations. TEMPLATE_VBoxR0Drv_CXXFLAGS += /volatileMetadata- endif + if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Speculative execution hardening. + TEMPLATE_VBoxR0Drv_CXXFLAGS += -Qspectre + TEMPLATE_VBoxR0Drv_CXXFLAGS.win.x86 += -Qspectre-jmp + TEMPLATE_VBoxR0Drv_CXXFLAGS.win.amd64 += -Qspectre-jmp -d2guardretpoline + endif ifdef VBOX_WITH_MSC_ANALYZE_THIS TEMPLATE_VBoxR0Drv_CXXFLAGS += /analyze endif TEMPLATE_VBoxR0Drv_CFLAGS = $(TEMPLATE_VBoxR0Drv_CXXFLAGS) + TEMPLATE_VBoxR0Drv_CFLAGS.win.x86 = $(TEMPLATE_VBoxR0Drv_CXXFLAGS.win.x86) + TEMPLATE_VBoxR0Drv_CFLAGS.win.amd64 = $(TEMPLATE_VBoxR0Drv_CXXFLAGS.win.amd64) TEMPLATE_VBoxR0Drv_LDFLAGS = -WX -Ignore:4197 \ -Driver -Subsystem:NATIVE -Incremental:NO -Align:4096 -MapInfo:Exports -NoD -Release -Debug -Opt:Ref -Opt:Icf \ $(VBOX_VCC_LD_GUARD_CF) \ @@ -5922,6 +5965,9 @@ ifeq ($(KBUILD_TARGET),win) TEMPLATE_VBoxR0Drv_LDFLAGS.win.amd64 = $(VBOX_VCC_LD_HIGH_ENTRYOPY_VA) $(VBOX_VCC_LD_GUARD_EHCONT) $(VBOX_VCC_LD_CET_COMPAT) TEMPLATE_VBoxR0Drv_LDFLAGS.win.arm64 = $(VBOX_VCC_LD_HIGH_ENTRYOPY_VA) $(VBOX_VCC_LD_GUARD_EHCONT) TEMPLATE_VBoxR0Drv_LDFLAGS.win.x86 = $(VBOX_VCC_LD_CET_COMPAT) + if "$(VBOX_VCC_TOOL_STEM)" >= "VCC142" # Speculative execution hardening. + TEMPLATE_VBoxR0Drv_LDFLAGS.win.amd64 += $(VBOX_VCC_LD_GUARD_RETPOLINE) + endif TEMPLATE_VBoxR0Drv_ORDERDEPS = $(VBOX_SIGN_DRIVER_ORDERDEPS) TEMPLATE_VBoxR0Drv_POST_CMDS = $(VBOX_SIGN_DRIVER_CMDS) endif @@ -9591,7 +9637,7 @@ SVN ?= svn$(HOSTSUFF_EXE) GIT ?= git$(HOSTSUFF_EXE) VBOX_SVN_REV_KMK = $(PATH_OUT)/revision.kmk ifndef VBOX_SVN_REV - VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 114504 $ ) + VBOX_SVN_REV_CONFIG_FALLBACK := $(patsubst %:,, $Rev: 115021 $ ) VBOX_SVN_REV_FALLBACK := $(if-expr $(VBOX_SVN_REV_CONFIG_FALLBACK) > $(VBOX_SVN_REV_VERSION_FALLBACK),$(VBOX_SVN_REV_CONFIG_FALLBACK),$(VBOX_SVN_REV_VERSION_FALLBACK)) VBOX_SVN_DEP := $(firstword $(wildcard $(PATH_ROOT)/.svn/wc.db $(abspath $(PATH_ROOT)/../.svn/wc.db) $(abspath $(PATH_ROOT)/../../.svn/wc.db) $(PATH_ROOT)/.svn/entries)) VBOX_GIT_DEP := $(firstword $(wildcard $(PATH_ROOT)/.git/config $(abspath $(PATH_ROOT)/../.git/config) $(abspath $(PATH_ROOT)/../../.git/config))) @@ -9605,7 +9651,7 @@ ifndef VBOX_SVN_REV ifneq ($(VBOX_GIT_DEP),) # # VBOX_SVN_REV_CONFIG_FALLBACK is not properly populated for a git checkout, - # as the $Rev: 114504 $ is not expanded, so we have to set it based on the information in the repo. + # as the $Rev: 115021 $ is not expanded, so we have to set it based on the information in the repo. # VBOX_SVN_REV_FALLBACK := $(shell $(GIT) log -1 $(PATH_ROOT)/Config.kmk | $(SED) -e 's/^[ \t]*//' -e '/svn:sync-xref-src-repo-rev\: r/!d' -e 's/svn:sync-xref-src-repo-rev\: r*//; t a; :a q') endif diff --git a/configure.py b/configure.py index bd5e2feda4bc..4d9ef0559e68 100755 --- a/configure.py +++ b/configure.py @@ -11,7 +11,7 @@ # pylint: disable=invalid-name # pylint: disable=multiple-statements # pylint: disable=line-too-long -# $Id: configure.py 114640 2026-07-07 17:56:17Z klaus.espenlaub@oracle.com $ +# $Id: configure.py 114902 2026-08-07 21:57:38Z klaus.espenlaub@oracle.com $ # # The following checks for the right (i.e. most recent) Python binary available # and re-starts the script using that binary (like a shell wrapper). @@ -90,7 +90,7 @@ # External Python modules or other dependencies are not allowed! # -__revision__ = "$Revision: 114640 $" +__revision__ = ''.join(c for c in "$Revision: 114902 $" if c.isdigit()) import argparse import collections; @@ -1287,6 +1287,23 @@ def compileAndRun(self, fErrorsAsWarnings): self.sVer = sStdOut; return fRc, sStdOut, sStdErr; + def getSdkLibs(self): + """ + Returns the library references in the form expected by kBuild SDKs. + """ + asSdkLibs = []; + for sLib in self.asLibFiles: + if self.enmBuildTarget == BuildTarget.WINDOWS: + asSdkLibs.append(withLibSuff(sLib)); + elif os.path.dirname(sLib): + asSdkLibs.append(sLib); + else: + sLibName = os.path.basename(sLib); + if sLibName.startswith('lib'): + sLibName = sLibName[3:]; + asSdkLibs.append(sLibName); + return asSdkLibs; + def setArgs(self, args): """ Applies argparse options for disabling and custom paths. @@ -1297,6 +1314,8 @@ def setArgs(self, args): self.fUseInTree = fUseInTree; # Only set if explicitly specified on command line -- otherwise take the lib's default. self.fDisabled = getattr(args, f'config_libs_disable_{sAttr}', False); self.sRootPath = getattr(args, f'config_libs_path_{sAttr}', None); + if self.sRootPath: + self.fUseInTree = False; # An explicitly specified root path overrides using in-tree library. return True; @@ -1534,7 +1553,8 @@ def checkHdr(self): self.printVerbose(1, 'Found header files:'); for sHdr, sPath in setHdrFound.items(): self.printVerbose(1, f'\t{os.path.join(sPath, sHdr)}'); - asIncPaths.extend([ sPath ]); + if sPath != '/usr/include': + asIncPaths.extend([ sPath ]); for sHdr in asHdrToSearch: if sHdr not in setHdrFound: @@ -1605,31 +1625,6 @@ def checkLib(self, fStatic = False): return False, None, None; - def checkPackage(self, sPackageName): - """" - Checks a given package. - """ - if not self.sSdkName: # No SDK (our term for package in our dev tools)? Bail out. - return True; - - self.printVerbose(1, f"Package Information for {sPackageName}:"); - fRc, sBinDir = getPackageVar(sPackageName, PkgMgrVar.BINDIR); - self.printVerbose(1, f' BINDIR: {sBinDir if fRc else ""}'); - fRc, sLibDir = getPackageVar(sPackageName, PkgMgrVar.LIBDIR); - self.printVerbose(1, f' LIBDIR: {sLibDir if fRc else ""}'); - fRc, sCFlags = getPackageVar(sPackageName, PkgMgrVar.CFLAGS); - self.printVerbose(1, f' CFLAGS: {sCFlags if fRc else ""}'); - - #if self.sRootPath: - # g_oEnv.set(f'PATH_SDK_{self.sSdkName}', self.sRootPath); - # sPathLibExec = os.path.join(sPathBase, 'libexec'); -# - #if self.asIncPaths: - # g_oEnv.set(f'PATH_SDK_{self.sSdkName}_LIB', self.asLibPaths[0]); - #if self.asLibPaths: - # g_oEnv.set(f'PATH_SDK_{self.sSdkName}_INC', self.asIncPaths[0]); - return True; - def performCheck(self): """ Run library detection. @@ -1677,6 +1672,11 @@ def performCheck(self): if self.fUseInTree and not self.fIsInTree: self.printWarn('Library needs to be used from in-tree sources but was not detected there -- might lead to build errors'); + ## @todo r=klaus the hack with skipping this for QT6 is fixing the build (the include paths appear wrong) but needs a proper solution. + if self.fHave and self.sSdkName and self.sSdkName != 'QT6' and not self.fIsInTree: + g_oEnv.set(f'SDK_{self.sSdkName}_INCS', ' '.join(self.asIncPaths)); + g_oEnv.set(f'SDK_{self.sSdkName}_LIBS', ' '.join(self.getSdkLibs())); + if not fRc: if self.dictArgsToSetIfFailed: # Implies being optional. self.printWarn('Library check failed and is optional'); @@ -3463,8 +3463,9 @@ def show_syntax_help(): sSdkName = "VBoxLibCurl"), LibraryCheck("libdevmapper", [ "libdevmapper.h" ], [ "libdevmapper" ], aeTargets = [ BuildTarget.LINUX ], sCode = '#include \nint main() { char v[64]; dm_get_library_version(v, sizeof(v)); printf("%s", v); return 0; }\n'), - LibraryCheck("libjpeg-turbo", [ "turbojpeg.h" ], [ "libturbojpeg" ], aeTargets = [ BuildTarget.ANY ], fUseInTree = True, - sCode = '#include \nint main() { tjInitCompress(); printf(""); return 0; }\n'), + LibraryCheck("libjpeg-turbo", [ "jpeglib.h" ], [ "libjpeg" ], aeTargets = [ BuildTarget.ANY ], fUseInTree = True, + sCode = '#include \n#ifndef LIBJPEG_TURBO_VERSION\n#error "libjpeg-turbo required"\n#endif\n#define VBOX_JPEG_STRINGIFY_INNER(a) #a\n#define VBOX_JPEG_STRINGIFY(a) VBOX_JPEG_STRINGIFY_INNER(a)\nint main() { struct jpeg_error_mgr error; jpeg_std_error(&error); printf("%s", VBOX_JPEG_STRINGIFY(LIBJPEG_TURBO_VERSION)); return 0; }\n', + sSdkName = "VBoxLibJpeg"), LibraryCheck("liblzf", [ "lzf.h" ], [ "liblzf" ], aeTargets = [ BuildTarget.ANY ], fUseInTree = True, sCode = '#include \nint main() { printf("%d.%d", LZF_VERSION >> 8, LZF_VERSION & 0xff);\n#if LZF_VERSION >= 0x0105\nreturn 0;\n#else\nreturn 1;\n#endif\n }\n'), LibraryCheck("liblzma", [ "lzma.h" ], [ "liblzma" ], aeTargets = [ BuildTarget.ANY ], fUseInTree = True, @@ -3816,13 +3817,14 @@ def main(): oParser.add_argument('-v', '--verbose', help="Enables verbose output", action='count', default=0, dest='config_verbose'); oParser.add_argument('-V', '--version', help="Prints the version of this script", action='store_true'); for oLibCur in g_aoLibs: - oParser.add_argument(f'--build-{oLibCur.sName}', help=f'Explicitly build {oLibCur.sName} from in-tree sources', action='store_true', default=None, dest=f'config_libs_build_{oLibCur.sName}'); - oParser.add_argument(f'--disable-{oLibCur.sName}', f'--without-{oLibCur.sName}', help=f'Disables using {oLibCur.sName}', action='store_true', default=None, dest=f'config_libs_disable_{oLibCur.sName}'); - oParser.add_argument(f'--with-{oLibCur.sName}-path', help=f'Sets the (root) path for {oLibCur.sName}', dest=f'config_libs_path_{oLibCur.sName}'); + sLibName = oLibCur.name2Attr(); # So that we can use variables directly w/o getattr. + oParser.add_argument(f'--build-{oLibCur.sName}', help=f'Explicitly build {oLibCur.sName} from in-tree sources', action='store_true', default=None, dest=f'config_libs_build_{sLibName}'); + oParser.add_argument(f'--disable-{oLibCur.sName}', f'--without-{oLibCur.sName}', help=f'Disables using {oLibCur.sName}', action='store_true', default=None, dest=f'config_libs_disable_{sLibName}'); + oParser.add_argument(f'--with-{oLibCur.sName}-path', help=f'Sets the (root) path for {oLibCur.sName}', dest=f'config_libs_path_{sLibName}'); # For debugging / development only. We don't expose this in the syntax help. - oParser.add_argument(f'--only-{oLibCur.sName}', help=argparse.SUPPRESS, action='store_true', default=None, dest=f'config_libs_only_{oLibCur.sName}'); + oParser.add_argument(f'--only-{oLibCur.sName}', help=argparse.SUPPRESS, action='store_true', default=None, dest=f'config_libs_only_{sLibName}'); for oToolCur in g_aoTools: - sToolName = oToolCur.sName.replace("-", "_"); # So that we can use variables directly w/o getattr. + sToolName = oToolCur.name2Attr(); # So that we can use variables directly w/o getattr. oParser.add_argument(f'--disable-{oToolCur.sName}', f'--without-{oToolCur.sName}', help=f'Disables using {oToolCur.sName}', action='store_true', default=None, dest=f'config_tools_disable_{sToolName}'); oParser.add_argument(f'--with-{oToolCur.sName}-path', help=f'Sets the (root) path for {oToolCur.sName}', dest=f'config_tools_path_{sToolName}'); # For debugging / development only. We don't expose this in the syntax help. @@ -4023,8 +4025,8 @@ def main(): # Filter libs and tools based on --only-XXX flags. # Replace '-' with '_' so that we can use variables directly w/o getattr lateron. - aoOnlyLibs = [lib for lib in g_aoLibs if getattr(g_oArgs, f'config_libs_only_{lib.sName.replace("-", "_")}', False)]; - aoOnlyTools = [tool for tool in g_aoTools if getattr(g_oArgs, f'config_tools_only_{tool.sName.replace("-", "_")}', False)]; + aoOnlyLibs = [lib for lib in g_aoLibs if getattr(g_oArgs, f'config_libs_only_{lib.name2Attr()}', False)]; + aoOnlyTools = [tool for tool in g_aoTools if getattr(g_oArgs, f'config_tools_only_{tool.name2Attr()}', False)]; aoLibsToCheck = aoOnlyLibs if aoOnlyLibs else g_aoLibs; aoToolsToCheck = aoOnlyTools if aoOnlyTools else g_aoTools; # Filter libs and tools based on build target. diff --git a/doc/iuml/clipboard_api.iuml b/doc/iuml/clipboard_api.iuml index d0f49ac3c20d..2f59cb463c63 100644 --- a/doc/iuml/clipboard_api.iuml +++ b/doc/iuml/clipboard_api.iuml @@ -1,4 +1,4 @@ -' $Id: clipboard_api.iuml 114611 2026-07-03 15:34:02Z andreas.loeffler@oracle.com $ +' $Id: clipboard_api.iuml 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ '' @file ' UML definition for PlantUML - VirtualBox Clipboard API - Draft ' @@ -289,8 +289,7 @@ package "Clipboard control" { abstract class IClipboardTransferManager { +getTransfers(direction, flags) : SafeArray - +createTransfer(direction, source, action) : IClipboardTransfer - +add(transfer) : HRESULT + +create(direction, source, action) : IClipboardTransfer +remove(transfer) : HRESULT +cancel(transfer) : HRESULT +approve(transfer, flags) : HRESULT @@ -300,6 +299,10 @@ package "Clipboard control" { +resume(transfer) : HRESULT +reset() : HRESULT } + note right of IClipboardTransferManager + Clients obtain manager-owned transfers through + create() or getTransfers(); controls reject foreign objects. + end note abstract class IClipboardSettings { diff --git a/doc/iuml/clipboard_transfer_sequence.iuml b/doc/iuml/clipboard_transfer_sequence.iuml index 3ac70512c15d..19cc4f08b6b8 100644 --- a/doc/iuml/clipboard_transfer_sequence.iuml +++ b/doc/iuml/clipboard_transfer_sequence.iuml @@ -1,4 +1,4 @@ -' $Id: clipboard_transfer_sequence.iuml 114611 2026-07-03 15:34:02Z andreas.loeffler@oracle.com $ +' $Id: clipboard_transfer_sequence.iuml 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ '' @file ' UML definition for PlantUML - VirtualBox Clipboard Transfer Sequence - Draft ' @@ -53,6 +53,8 @@ end box legend right IClipboardTransfer is the high-level filesystem-like transfer tree API. + Clients obtain manager-owned transfers through create() or getTransfers(). + Manager controls reject foreign transfer objects. sourcePaths configure local producer-side filesystem objects. roots() returns receiver-visible transfer-relative nodes. Roots, query, list, openDirectory and openFile are preferred for normal clients. @@ -62,10 +64,14 @@ endlegend == Configure a local host-to-guest transfer == -Client -> Manager: createTransfer(ToGuest, Host, Copy) -Manager -> VBoxC: create unpublished transfer wrapper +Client -> Manager: create(ToGuest, Host, Copy) +Manager -> VBoxC: create and track manager-owned transfer wrapper VBoxC --> Manager: transfer Manager --> Client: transfer +note over Manager, VBoxC + Main ownership and tracking do not publish + the transfer to the service or platform backend. +end note Client -> Transfer: setSourcePaths(["/home/user/report.txt", "/home/user/photos"]) note right of Client @@ -82,15 +88,10 @@ Core --> VBoxC: initialized transfer tree VBoxC --> Transfer: success Transfer --> Client: success -Client -> Manager: add(transfer) -Manager -> VBoxC: publish configured transfer -VBoxC -> Core: make transfer available -Core -> Provider: advertise roots report.txt and photos - == Discover and approve a transfer == Client -> Manager: getTransfers(direction, 0) -Manager -> VBoxC: find matching live transfers +Manager -> VBoxC: find matching tracked transfers VBoxC --> Manager: matching transfers Manager --> Client: transfer[] diff --git a/doc/manual/en_US/man_VBoxManage-clipboard.xml b/doc/manual/en_US/man_VBoxManage-clipboard.xml index 423a1ef30597..6f214c9b2694 100644 --- a/doc/manual/en_US/man_VBoxManage-clipboard.xml +++ b/doc/manual/en_US/man_VBoxManage-clipboard.xml @@ -30,7 +30,7 @@ ]> - $Date: 2026-07-03 17:22:37 +0200 (Fri, 03 Jul 2026) $ + $Date: 2026-08-05 17:08:05 +0200 (Wed, 05 Aug 2026) $ VBoxManage clipboard @@ -576,20 +576,28 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 The VBoxManage clipboard listen command listens for - live clipboard events from the virtual machine console. It prints event - metadata by default, including revision and - clientId fields when they are supplied by the clipboard - API. The revision is a monotonic event revision for the live console - clipboard. A clientId value of 0 - means no IClipboardSession originated the event; a - non-zero value identifies the originating clipboard API session. The - command is observational by default; use VBoxManage clipboard - copy to publish host clipboard data to the guest. With repeated - or options, the command may - read guest data for diagnostic output. Waitable clipboard events are - acknowledged automatically before event output is written. Pressing + live clipboard events from the virtual machine console. Human output + uses compact logfmt-style fields in the order rev, + src when available, event, and + action when available, without a command-name prefix. + The revision is a monotonic event revision for the live console clipboard. + With verbose output, cid is inserted between + src and event. A client ID value of + 0 means no IClipboardSession + originated the event; a non-zero value identifies the originating + clipboard API session. The command is observational by default; use + VBoxManage clipboard copy to publish host clipboard + data to the guest. Waitable clipboard events are acknowledged + automatically before event output is written. Pressing Ctrl+C stops the command. + + If Shared Clipboard is disabled when listening starts, the first output + record is a warning with event=warning and + mode=disabled. This startup record is emitted + regardless of and does not count toward + . + @@ -610,13 +618,14 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 Specifies the event output format. JSON output is written as one - JSON object per line. When event identity metadata is available, - human output includes revision=<n> and - clientId=<id>, machine-readable output - includes revision="<n>" and - clientId="<id>", and JSON output includes - numeric revision and clientId - members. + JSON object per line. Human output labels the revision, source, + and client ID fields rev, src, + and cid, and quotes and escapes MIME values. + Machine-readable and JSON output retain the full field names revision, + source, and clientId; + machine-readable values are quoted, while JSON revision and client + ID values are numeric. The client ID field is included only with + verbose output. @@ -642,9 +651,12 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 - Increases verbosity. Repeating this option includes payload data - in data-change output where available and may read guest data for - diagnostics. Payload data is bounded and escaped where possible. + Increases verbosity. The first occurrence includes the + client ID field. Repeating this option includes + available payload data in data-change output, available host text + in host data-requested output, and may read + guest data for format-change diagnostics. Payload data is bounded + and escaped. Clipboard payloads can contain sensitive data. diff --git a/doc/manual/ru_RU/man_VBoxManage-clipboard.xml b/doc/manual/ru_RU/man_VBoxManage-clipboard.xml index 46fc46e6d2e4..77e0d2aaaed9 100644 --- a/doc/manual/ru_RU/man_VBoxManage-clipboard.xml +++ b/doc/manual/ru_RU/man_VBoxManage-clipboard.xml @@ -30,7 +30,7 @@ ]> - $Date: 2026-07-03 17:22:37 +0200 (Fri, 03 Jul 2026) $ + $Date: 2026-08-05 17:08:05 +0200 (Wed, 05 Aug 2026) $ VBoxManage clipboard @@ -576,20 +576,28 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 The VBoxManage clipboard listen command listens for - live clipboard events from the virtual machine console. It prints event - metadata by default, including revision and - clientId fields when they are supplied by the clipboard - API. The revision is a monotonic event revision for the live console - clipboard. A clientId value of 0 - means no IClipboardSession originated the event; a - non-zero value identifies the originating clipboard API session. The - command is observational by default; use VBoxManage clipboard - copy to publish host clipboard data to the guest. With repeated - or options, the command may - read guest data for diagnostic output. Waitable clipboard events are - acknowledged automatically before event output is written. Pressing + live clipboard events from the virtual machine console. Human output + uses compact logfmt-style fields in the order rev, + src when available, event, and + action when available, without a command-name prefix. + The revision is a monotonic event revision for the live console clipboard. + With verbose output, cid is inserted between + src and event. A client ID value of + 0 means no IClipboardSession + originated the event; a non-zero value identifies the originating + clipboard API session. The command is observational by default; use + VBoxManage clipboard copy to publish host clipboard + data to the guest. Waitable clipboard events are acknowledged + automatically before event output is written. Pressing Ctrl+C stops the command. + + If Shared Clipboard is disabled when listening starts, the first output + record is a warning with event=warning and + mode=disabled. This startup record is emitted + regardless of and does not count toward + . + @@ -610,13 +618,14 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 Specifies the event output format. JSON output is written as one - JSON object per line. When event identity metadata is available, - human output includes revision=<n> and - clientId=<id>, machine-readable output - includes revision="<n>" and - clientId="<id>", and JSON output includes - numeric revision and clientId - members. + JSON object per line. Human output labels the revision, source, + and client ID fields rev, src, + and cid, and quotes and escapes MIME values. + Machine-readable and JSON output retain the full field names revision, + source, and clientId; + machine-readable values are quoted, while JSON revision and client + ID values are numeric. The client ID field is included only with + verbose output. @@ -642,9 +651,12 @@ $ VBoxManage clipboard "Ubuntu VM" serve -vv --timeout=30000 - Increases verbosity. Repeating this option includes payload data - in data-change output where available and may read guest data for - diagnostics. Payload data is bounded and escaped where possible. + Increases verbosity. The first occurrence includes the + client ID field. Repeating this option includes + available payload data in data-change output, available host text + in host data-requested output, and may read + guest data for format-change diagnostics. Payload data is bounded + and escaped. Clipboard payloads can contain sensitive data. diff --git a/doc/manual/user_ChangeLogImpl.xml b/doc/manual/user_ChangeLogImpl.xml index df2c6ce54203..7a7d54120dc4 100644 --- a/doc/manual/user_ChangeLogImpl.xml +++ b/doc/manual/user_ChangeLogImpl.xml @@ -111,6 +111,12 @@ Rules for adding a changelog entry to make them look more uniform: 'modifyvm' clipboard options are now marked as being deprecated. + + Shared Clipboard: Fixed plain-text copy and paste on macOS hosts and improved + VBoxManage clipboard listen event and payload output, including compact logfmt-style + human records and a startup warning when Shared Clipboard is disabled + + placeholder diff --git a/include/Makefile.kmk b/include/Makefile.kmk index ed321b28d54c..cf4b6fbe9630 100644 --- a/include/Makefile.kmk +++ b/include/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114158 2026-05-20 15:21:01Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ ## @file # Some hacks to allow syntax and prerequisite include checking of headers. # This makefile doesn't and shouldn't build successfully. @@ -45,6 +45,7 @@ VBOX_HDRS_CPP_FEATURES := \ VBox/GuestHost/GuestControl.h \ VBox/GuestHost/DragAndDrop.h \ VBox/GuestHost/SharedClipboard-transfers.h \ + VBox/GuestHost/clipboard-transfers-http.h \ VBox/dbus.h \ VBox/xrandr.h \ VBox/VBoxCrHgsmi.h \ @@ -90,6 +91,7 @@ VBOX_HDRS_R3_ONLY := \ VBox/vscsi.h \ VBox/ExtPack/% \ VBox/GuestHost/SharedClipboard-transfers.h \ + VBox/GuestHost/clipboard-transfers-http.h \ VBox/GuestHost/SharedClipboard-win.h \ VBox/GuestHost/SharedClipboard-x11.h \ VBox/GuestHost/DragAndDrop.h \ diff --git a/include/VBox/GuestHost/SharedClipboard-transfers.h b/include/VBox/GuestHost/SharedClipboard-transfers.h index c05099553522..d8aa108871a3 100644 --- a/include/VBox/GuestHost/SharedClipboard-transfers.h +++ b/include/VBox/GuestHost/SharedClipboard-transfers.h @@ -1,4 +1,4 @@ -/* $Id: SharedClipboard-transfers.h 114830 2026-07-31 10:02:47Z andreas.loeffler@oracle.com $ */ +/* $Id: SharedClipboard-transfers.h 115086 2026-08-19 13:09:34Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Shared transfer functions between host and guest. */ @@ -475,7 +475,7 @@ typedef SHCLLISTENTRY *PSHCLLISTENTRY; /** Pointer to a const Shared Clipboard list entry. */ typedef SHCLLISTENTRY *PCSHCLLISTENTRY; -/** Maximum length (in UTF-8 characters) of a list entry name. Includes terminator. */ +/** Maximum size (in bytes) of a UTF-8 list entry name. Includes terminator. */ #define SHCLLISTENTRY_MAX_NAME 4096 /** @@ -506,19 +506,6 @@ typedef struct _SHCLOBJDATACHUNK /** Pointer to a Shared Clipboard transfer object data chunk. */ typedef SHCLOBJDATACHUNK *PSHCLOBJDATACHUNK; -/** - * Structure for handling a single transfer object context. - */ -typedef struct _SHCLCLIENTTRANSFEROBJCTX -{ - /** Pointer to the actual transfer object of this context. */ - SHCLTRANSFER *pTransfer; - /** Object handle of this transfer context. */ - SHCLOBJHANDLE uHandle; -} SHCLCLIENTTRANSFEROBJCTX; -/** Pointer to a Shared Clipboard transfer object context. */ -typedef SHCLCLIENTTRANSFEROBJCTX *PSHCLCLIENTTRANSFEROBJCTX; - typedef struct _SHCLTRANSFEROBJSTATE { /** How many bytes were processed (read / write) so far. */ @@ -625,7 +612,7 @@ typedef struct _SHCLTRANSFERSTATE SHCLTRANSFERGEN uGeneration; /** The transfer's current status. */ SHCLTRANSFERSTATUS enmStatus; - /** The transfer's direction, seen from the perspective who created the transfer. */ + /** The transfer's absolute guest/host direction. */ SHCLTRANSFERDIR enmDir; /** The transfer's source, seen from the perspective who created the transfer. */ SHCLSOURCE enmSource; @@ -837,16 +824,18 @@ typedef struct _SHCLTRANSFERCALLBACKS /** * Called when the transfer gets initialized. * - * @return VBox status code. On error the intialization will will be treated as failed. + * @return VBox status code. On error the initialization is treated as failed. * @param pCbCtx Pointer to callback context to use. */ DECLCALLBACKMEMBER(int, pfnOnInitialize,(PSHCLTRANSFERCALLBACKCTX pCbCtx)); /** - * Called after the transfer got initialized. + * Called after the transfer entered the initialized state. * + * @return VBox status code propagated by ShClTransferInit(). On failure + * the lifecycle owner must abort or destroy the initialized transfer. * @param pCbCtx Pointer to callback context to use. */ - DECLCALLBACKMEMBER(void, pfnOnInitialized,(PSHCLTRANSFERCALLBACKCTX pCbCtx)); + DECLCALLBACKMEMBER(int, pfnOnInitialized,(PSHCLTRANSFERCALLBACKCTX pCbCtx)); /** * Called before the transfer gets destroyed. * @@ -931,6 +920,10 @@ typedef struct SHCLTRANSFER RTCRITSECT CritSect; /** Number of references to this transfer. */ uint32_t cRefs; + /** Event signalled after the last transfer reference has been released. */ + RTSEMEVENTMULTI hNoRefsEvent; + /** Owning transfer context until the unregistration callback has completed. */ + PSHCLTRANSFERCTX pOwnerCtx; /** The transfer's state (for SSM, later). */ SHCLTRANSFERSTATE State; /** Absolute path to root entries. */ @@ -1021,13 +1014,13 @@ typedef struct _SHCLHTTPSERVER { /** Critical section for serializing access. */ RTCRITSECT CritSect; - /** Status event for callers to wait for. - * Updates \a enmStatus. */ - RTSEMEVENT StatusEvent; /** Initialized indicator. */ bool fInitialized; /** Running indicator. */ bool fRunning; + /** Stop operation in progress. Prevents a new server from being started + * while callbacks of the old server are being drained. */ + bool fStopping; /** Current status. */ SHCLHTTPSERVERSTATUS enmStatus; /** Handle of the HTTP server instance. */ @@ -1062,20 +1055,6 @@ typedef SHCLHTTPCONTEXT *PSHCLHTTPCONTEXT; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ -/** - * Structure for keeping a single transfer context event. - */ -typedef struct _SHCLTRANSFERCTXEVENT -{ - /** Transfer bound to this event. - * Can be NULL if not being used. */ - PSHCLTRANSFER pTransfer; - /** Whether a transfer was registered or not. */ - bool fRegistered; -} SHCLTRANSFERCTXEVENT; -/** Pointer to Shared Clipboard transfer context event. */ -typedef SHCLTRANSFERCTXEVENT *PSHCLTRANSFERCTXEVENT; - /** * Structure for keeping Shared Clipboard transfer context around. * @@ -1085,10 +1064,6 @@ typedef struct _SHCLTRANSFERCTX { /** Critical section for serializing access. */ RTCRITSECT CritSect; - /** Event used for waiting. for transfer context changes. */ - RTSEMEVENT ChangedEvent; - /** Event data for \a ChangedEvent. */ - SHCLTRANSFERCTXEVENT ChangedEventData; /** List of transfers. */ RTLISTANCHOR List; /** Transfer ID allocation bitmap; clear bits are free, set bits are busy. */ @@ -1120,20 +1095,14 @@ PSHCLTXPROVIDERIFACE ShClTransferProviderLocalQueryInterface(PSHCLTXPROVIDER pPr /** @name Shared Clipboard transfer object API. * @{ */ -int ShClTransferObjCtxInit(PSHCLCLIENTTRANSFEROBJCTX pObjCtx); -void ShClTransferObjCtxDestroy(PSHCLCLIENTTRANSFEROBJCTX pObjCtx); -bool ShClTransferObjCtxIsValid(PSHCLCLIENTTRANSFEROBJCTX pObjCtx); - int ShClTransferObjInit(PSHCLTRANSFEROBJ pObj); void ShClTransferObjDestroy(PSHCLTRANSFEROBJ pObj); int ShClTransferObjOpenParmsInit(PSHCLOBJOPENCREATEPARMS pParms); -int ShClTransferObjOpenParmsCopy(PSHCLOBJOPENCREATEPARMS pParmsDst, PSHCLOBJOPENCREATEPARMS pParmsSrc); void ShClTransferObjOpenParmsDestroy(PSHCLOBJOPENCREATEPARMS pParms); int ShClTransferObjOpen(PSHCLTRANSFER pTransfer, PSHCLOBJOPENCREATEPARMS pOpenCreateParms, PSHCLOBJHANDLE phObj); int ShClTransferObjClose(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj); -bool ShClTransferObjIsComplete(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj); int ShClTransferObjRead(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj, void *pvBuf, uint32_t cbBuf, uint32_t fFlags, uint32_t *pcbRead); int ShClTransferObjWrite(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj, void *pvBuf, uint32_t cbBuf, uint32_t fFlags, uint32_t *pcbWritten); PSHCLTRANSFEROBJ ShClTransferObjGet(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj); @@ -1146,13 +1115,68 @@ void ShClTransferObjDataChunkFree(PSHCLOBJDATACHUNK pDataChunk); /** @name Shared Clipboard transfer API. * @{ */ +/** + * Checks whether a transfer ID is in the assignable context-local range. + * + * @returns true if the ID can be used by a transfer context, false otherwise. + * @param idTransfer Transfer ID to check before narrowing to SHCLTRANSFERID. + */ +bool ShClTransferIdIsValid(uint32_t idTransfer); + +/** + * Checks whether a transfer key is usable for lifecycle tracking. + * + * @returns true if the key identifies a non-nil service transfer, false otherwise. + * @param idSession Service session ID. + * @param idTransfer Service transfer ID, before narrowing to SHCLTRANSFERID. + * @param uGeneration Service transfer generation. + */ +bool ShClTransferKeyIsValid(SHCLSESSIONID idSession, uint32_t idTransfer, SHCLTRANSFERGEN uGeneration); + +/** + * Checks whether a transfer status is part of the Shared Clipboard protocol. + * + * @returns true if the status is valid, false otherwise. + * @param enmStatus Transfer status to validate. + */ +bool ShClTransferStatusIsValid(SHCLTRANSFERSTATUS enmStatus); + +/** + * Checks whether a transfer status ends the transfer lifecycle. + * + * @returns true if the status is terminal, false otherwise. + * @param enmStatus Transfer status to classify. + */ +bool ShClTransferStatusIsTerminal(SHCLTRANSFERSTATUS enmStatus); + +/** + * Checks whether a transfer status and result form a valid service reply. + * + * @returns true if the status is valid and the result matches it, false otherwise. + * @param enmStatus Transfer status to validate. + * @param rcTransfer Transfer result associated with the status. + */ +bool ShClTransferStatusResultIsValid(SHCLTRANSFERSTATUS enmStatus, int rcTransfer); + +/** + * Checks whether a service-reported transfer status may follow the previous + * service-reported status. + * + * This describes lifecycle records, not the lower-level transfer state + * mutation sequence. + * + * @returns true if both statuses are valid and the transition is monotonic, false otherwise. + * @param enmOldStatus Current transfer status. + * @param enmNewStatus Incoming transfer status. + */ +bool ShClTransferStatusTransitionIsValid(SHCLTRANSFERSTATUS enmOldStatus, SHCLTRANSFERSTATUS enmNewStatus); + int ShClTransferCreateEx(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, uint32_t cbMaxChunkSize, uint32_t cMaxListHandles, uint32_t cMaxObjHandles, PSHCLTRANSFER *ppTransfer); int ShClTransferCreate(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, PSHCLTRANSFER *ppTransfer); int ShClTransferInit(PSHCLTRANSFER pTransfer); int ShClTransferDestroy(PSHCLTRANSFER pTransfer); void ShClTransferReset(PSHCLTRANSFER pTransfer); -bool ShClTransferIsRunning(PSHCLTRANSFER pTransfer); bool ShClTransferIsComplete(PSHCLTRANSFER pTransfer); bool ShClTransferIsAborted(PSHCLTRANSFER pTransfer); @@ -1171,20 +1195,16 @@ SHCLTRANSFERID ShClTransferGetID(PSHCLTRANSFER pTransfer); SHCLSESSIONID ShClTransferGetSessionId(PSHCLTRANSFER pTransfer); SHCLTRANSFERGEN ShClTransferGetGeneration(PSHCLTRANSFER pTransfer); SHCLTRANSFERDIR ShClTransferGetDir(PSHCLTRANSFER pTransfer); -int ShClTransferGetRootPathAbs(PSHCLTRANSFER pTransfer, char *pszPath, size_t cbPath); SHCLSOURCE ShClTransferGetSource(PSHCLTRANSFER pTransfer); SHCLTRANSFERSTATUS ShClTransferGetStatus(PSHCLTRANSFER pTransfer); int ShClTransferWaitForStatus(PSHCLTRANSFER pTransfer, RTMSINTERVAL msTimeout, SHCLTRANSFERSTATUS enmStatus); -int ShClTransferWaitForStatusChange(PSHCLTRANSFER pTransfer, RTMSINTERVAL msTimeout, SHCLTRANSFERSTATUS *penmStatus); int ShClTransferListOpen(PSHCLTRANSFER pTransfer, PSHCLLISTOPENPARMS pOpenParms, PSHCLLISTHANDLE phList); int ShClTransferListClose(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList); int ShClTransferListGetHeader(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, PSHCLLISTHDR pHdr); PSHCLLISTHANDLEINFO ShClTransferListGetByHandle(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList); -PSHCLTRANSFEROBJ ShClTransferListGetObj(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, uint64_t uIdx); int ShClTransferListRead(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, PSHCLLISTENTRY pEntry); int ShClTransferListWrite(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, PSHCLLISTENTRY pEntry); -bool ShClTransferListHandleIsValid(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList); PSHCLLIST ShClTransferListAlloc(void); void ShClTransferListFree(PSHCLLIST pList); @@ -1195,8 +1215,6 @@ int ShClTransferListAddEntry(PSHCLLIST pList, PSHCLLISTENTRY pEntry, bool fAppen int ShClTransferListHandleInfoInit(PSHCLLISTHANDLEINFO pInfo); void ShClTransferListHandleInfoDestroy(PSHCLLISTHANDLEINFO pInfo); -int ShClTransferListHdrAlloc(PSHCLLISTHDR *ppListHdr); -void ShClTransferListHdrFree(PSHCLLISTHDR pListHdr); PSHCLLISTHDR ShClTransferListHdrDup(PSHCLLISTHDR pListHdr); int ShClTransferListHdrInit(PSHCLLISTHDR pListHdr); void ShClTransferListHdrDestroy(PSHCLLISTHDR pListHdr); @@ -1204,7 +1222,6 @@ void ShClTransferListHdrReset(PSHCLLISTHDR pListHdr); bool ShClTransferListHdrIsValid(PSHCLLISTHDR pListHdr); int ShClTransferListOpenParmsCopy(PSHCLLISTOPENPARMS pDst, PSHCLLISTOPENPARMS pSrc); -PSHCLLISTOPENPARMS ShClTransferListOpenParmsDup(PSHCLLISTOPENPARMS pParms); int ShClTransferListOpenParmsInit(PSHCLLISTOPENPARMS pParms); void ShClTransferListOpenParmsDestroy(PSHCLLISTOPENPARMS pParms); @@ -1221,7 +1238,6 @@ int ShClTransferSetProvider(PSHCLTRANSFER pTransfer, PSHCLTXPROVIDER pProvider); int ShClTransferRootsSetFromStringListEx(PSHCLTRANSFER pTransfer, const char *pszRoots, size_t cbRoots, const char *pszSep); int ShClTransferRootsSetFromStringList(PSHCLTRANSFER pTransfer, const char *pszRoots, size_t cbRoots); -int ShClTransferRootsSetFromStringListUnicode(PSHCLTRANSFER pTransfer, PRTUTF16 pwszRoots, size_t cbRoots); int ShClTransferRootsSetFromPath(PSHCLTRANSFER pTransfer, const char *pszPath); uint64_t ShClTransferRootsCount(PSHCLTRANSFER pTransfer); PCSHCLLISTENTRY ShClTransferRootsEntryGet(PSHCLTRANSFER pTransfer, uint64_t uIndex); @@ -1235,81 +1251,43 @@ int ShClTransferCtxInit(PSHCLTRANSFERCTX pTransferCtx); void ShClTransferCtxDestroy(PSHCLTRANSFERCTX pTransferCtx); void ShClTransferCtxReset(PSHCLTRANSFERCTX pTransferCtx); int ShClTransferCtxBeginSession(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idSession); +/** @name Borrowed transfer lookups + * Returned pointers must not outlive external context-lifetime serialization. + * Use a retained lookup across concurrent unregistration or teardown. + * @{ */ PSHCLTRANSFER ShClTransferCtxGetTransferById(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID); PSHCLTRANSFER ShClTransferCtxGetTransferByKey(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration); PSHCLTRANSFER ShClTransferCtxGetTransferByIndex(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx); PSHCLTRANSFER ShClTransferCtxGetTransferLast(PSHCLTRANSFERCTX pTransferCtx); -uint32_t ShClTransferCtxGetRunningTransfers(PSHCLTRANSFERCTX pTransferCtx); +/** @} */ +/** @name Retained transfer lookups + * Returned pointers must be released with ShClTransferRelease(). + * @{ */ +PSHCLTRANSFER ShClTransferCtxGetTransferByIdRetained(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID); +PSHCLTRANSFER ShClTransferCtxGetTransferByKeyRetained(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration); +/** @} */ uint32_t ShClTransferCtxGetTotalTransfers(PSHCLTRANSFERCTX pTransferCtx); -void ShClTransferCtxCleanup(PSHCLTRANSFERCTX pTransferCtx); bool ShClTransferCtxIsMaximumReached(PSHCLTRANSFERCTX pTransferCtx); -int ShClTransferCtxCreateId(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFERID pidTransfer); int ShClTransferCtxRegister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer, PSHCLTRANSFERID pidTransfer); int ShClTransferCtxRegisterById(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer, SHCLTRANSFERID idTransfer); int ShClTransferCtxUnregisterById(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID idTransfer); -int ShClTransferCtxWait(PSHCLTRANSFERCTX pTransferCtx, RTMSINTERVAL msTimeout, bool fRegister, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer); /** @} */ -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP -/** Namespace used as a prefix for HTTP(S) transfer URLs. */ -#define SHCL_HTTPT_URL_NAMESPACE "vbcl" - -/** @name Shared Clipboard HTTP context API. - * @{ - */ -int ShClTransferHttpServerMaybeStart(PSHCLHTTPCONTEXT pCtx); -int ShClTransferHttpServerMaybeStop(PSHCLHTTPCONTEXT pCtx); -/** @} */ - -/** @name Shared Clipboard HTTP server API. - * @{ - */ -int ShClTransferHttpServerInit(PSHCLHTTPSERVER pSrv); -int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv); -int ShClTransferHttpServerStart(PSHCLHTTPSERVER pSrv, unsigned cMaxAttempts, uint16_t *puPort); -int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort); -int ShClTransferHttpServerStop(PSHCLHTTPSERVER pSrv); -int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); -int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); -PSHCLTRANSFER ShClTransferHttpServerGetTransferFirst(PSHCLHTTPSERVER pSrv); -PSHCLTRANSFER ShClTransferHttpServerGetTransferLast(PSHCLHTTPSERVER pSrv); -bool ShClTransferHttpServerGetTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer); -uint16_t ShClTransferHttpServerGetPort(PSHCLHTTPSERVER pSrv); -uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv); -char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv); -char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer, uint64_t idxEntry); -int ShClTransferHttpConvertToStringList(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer, char **ppszData, size_t *pcbData); -bool ShClTransferHttpServerIsInitialized(PSHCLHTTPSERVER pSrv); -bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv); -int ShClTransferHttpServerWaitForStatusChange(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS fStatus, RTMSINTERVAL msTimeout); -/** @} */ -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ - /** @name Shared Clipboard transfers utility functions. * @{ */ int ShClPathSanitizeFilename(char *pszPath, size_t cbPath); int ShClPathSanitize(char *pszPath, size_t cbPath); -bool ShClPathIsSymlink(const char *pszPath); -bool ShClPathIsDirectory(const char *pszPath); -int ShClDirectoryCreate(const char *pszPath); -int ShClHlpTransferPathToHostPath(const char *pszDestination, const char *pszTransferPath, - char *pszHostPath, size_t cbHostPath); const char *ShClTransferStatusToStr(SHCLTRANSFERSTATUS enmStatus); int ShClTransferTransformPath(char *pszPath, size_t cbPath); int ShClTransferValidatePath(const char *pcszPath, bool fMustExist); +int ShClTransferValidatePathEx(const char *pcszPath, size_t cbPath, bool fMustExist); int ShClTransferResolvePathAbs(PSHCLTRANSFER pTransfer, const char *pszPath, uint32_t fFlags, char **ppszResolved); int ShClTransferConvertFileCreateFlags(uint32_t fShClFlags, uint64_t *pfOpen); int ShClFsObjInfoQueryLocal(const char *pszPath, PSHCLFSOBJINFO pObjInfo); int ShClFsObjInfoFromIPRT(PSHCLFSOBJINFO pDst, PCRTFSOBJINFO pSrc); /** @} */ -/** @name Shared Clipboard MIME functions. - * @{ - */ -bool ShClMIMEHasFileURLs(const char *pcszFormat, size_t cchFormatMax); -bool ShClMIMENeedsCache(const char *pcszFormat, size_t cchFormatMax); -/** @} */ - #endif /* !VBOX_INCLUDED_GuestHost_SharedClipboard_transfers_h */ diff --git a/include/VBox/GuestHost/SharedClipboard-win.h b/include/VBox/GuestHost/SharedClipboard-win.h index c3eba2d61c1f..6029a8827c5b 100644 --- a/include/VBox/GuestHost/SharedClipboard-win.h +++ b/include/VBox/GuestHost/SharedClipboard-win.h @@ -113,6 +113,8 @@ typedef struct _SHCLWINAPIOLD #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** Forward declaration for the Windows data object. */ class ShClWinDataObject; +/** Forward declaration for the Windows stream object. */ +class ShClWinStreamImpl; #endif /** @@ -137,7 +139,8 @@ typedef struct _SHCLWINCTX /** Structure for maintaining the old clipboard API. */ SHCLWINAPIOLD oldAPI; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /** The "in-flight" data object for file transfers. + /** The "in-flight" data object for file transfers. This context owns one + * COM reference to the object while the pointer is non-NULL. * This is the current data object which has been created and sent to the Windows clipboard. * That way Windows knows that a potential file transfer is available, but the actual transfer * hasn't been started yet. @@ -155,6 +158,17 @@ int ShClWinClear(void); int ShClWinCtxInit(PSHCLWINCTX pWinCtx); void ShClWinCtxDestroy(PSHCLWINCTX pWinCtx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Disables callbacks on the current in-flight data object. + * + * New callbacks are disabled permanently. The function also waits for an + * active callback running on another thread to return. + * + * @param pWinCtx Windows clipboard context. + */ +void ShClWinCtxDisableDataObjectCallbacks(PSHCLWINCTX pWinCtx); +#endif int ShClWinCheckAndInitNewAPI(PSHCLWINAPINEW pAPI); bool ShClWinIsNewAPI(PSHCLWINAPINEW pAPI); @@ -231,6 +245,10 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili }; /** Pointer to a Shared Clipboard Windows data object callback table. */ typedef CALLBACKS *PCALLBACKS; + /** Transfer-begin callback type. */ + typedef decltype(CALLBACKS::pfnTransferBegin) PFNTRANSFERBEGIN; + /** Transfer-end callback type. */ + typedef decltype(CALLBACKS::pfnTransferEnd) PFNTRANSFEREND; enum Status { @@ -292,6 +310,7 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili int SetTransfer(PSHCLTRANSFER pTransfer); int SetStatus(Status enmStatus, int rcSts = VINF_SUCCESS); + void DisableCallbacks(void); public: @@ -319,8 +338,10 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili bool lookupFormatEtc(LPFORMATETC pFormatEtc, ULONG *puIndex); void registerFormat(LPFORMATETC pFormatEtc, CLIPFORMAT clipFormat, TYMED tyMed = TYMED_HGLOBAL, LONG lindex = -1, DWORD dwAspect = DVASPECT_CONTENT, DVTARGETDEVICE *pTargetDevice = NULL); - int setTransferLocked(PSHCLTRANSFER pTransfer); + int setTransferLocked(PSHCLTRANSFER pTransfer, ShClWinDataObject **ppObjToRelease = NULL); int setStatusLocked(Status enmStatus, int rc = VINF_SUCCESS); + void registerStreamLocked(ShClWinStreamImpl *pStream); + void invalidateStreams(void); protected: @@ -337,9 +358,9 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili /** Vector containing file system objects with its (cached) objection information. */ typedef std::vector FsObjEntryList; + /** List of streams published by this data object. */ + typedef std::vector StreamList; - /** Shared Clipboard context to use. */ - PSHCLCONTEXT m_pCtx; /** The object's current status. */ Status m_enmStatus; /** Last (IPRT-style) error set in conjunction with the status. */ @@ -356,15 +377,25 @@ class ShClWinDataObject : public IDataObject //, public IDataObjectAsyncCapabili LPSTGMEDIUM m_pStgMedium; /** Pointer to the associated transfer object being handled. */ PSHCLTRANSFER m_pTransfer; - /** Current stream object being used. */ - IStream *m_pStream; + /** Published streams. Each entry owns one COM reference. */ + StreamList m_lstStreams; /** Current object index being handled by the data object. * This is needed to create the next IStream object for e.g. the next upcoming file/dir/++ in the transfer. */ ULONG m_uObjIdx; /** List of (cached) file system objects. */ FsObjEntryList m_lstEntries; + /** Whether the critical section has been initialized. */ + bool m_fCritSectInitialized; + /** Whether new backend callbacks may be started. */ + bool m_fCallbacksEnabled; + /** Number of backend callbacks currently executing. */ + uint32_t m_cCallbacks; + /** Native thread executing the sole admitted backend callback. */ + RTNATIVETHREAD m_hCallbackThread; /** Critical section to serialize access. */ RTCRITSECT m_CritSect; + /** Signalled when no backend callback is executing. */ + RTSEMEVENTMULTI m_EventCallbacksDrained; /** Event being triggered when reading the transfer list been completed. */ RTSEMEVENT m_EventListComplete; /** Event being triggered when the object status has been changed. */ @@ -455,6 +486,7 @@ class ShClWinStreamImpl : public IStream static HRESULT Create(ShClWinDataObject *pParent, PSHCLTRANSFER pTransfer, const Utf8Str &strPath, PSHCLFSOBJINFO pObjInfo, IStream **ppStream); + void Invalidate(void); private: /** Pointer to the parent data object. */ @@ -463,6 +495,10 @@ class ShClWinStreamImpl : public IStream LONG m_lRefCount; /** Pointer to the associated Shared Clipboard transfer. */ PSHCLTRANSFER m_pTransfer; + /** Whether the critical section has been initialized. */ + bool m_fCritSectInitialized; + /** Critical section serializing reads and invalidation. */ + RTCRITSECT m_CritSect; /** The object handle to use. */ SHCLOBJHANDLE m_hObj; /** Object path. */ @@ -487,16 +523,27 @@ class ShClWinTransferCtx virtual ~ShClWinTransferCtx() { } - /** Pointer to data object to use for this transfer. Not owned. - * Can be NULL if not being used. */ - ShClWinDataObject *pDataObj; + /** Critical section protecting @a pDataObj. */ + RTCRITSECT CritSect; + /** Pointer to data object to use for this transfer. Owns one COM + * reference while non-NULL. */ + ShClWinDataObject *pDataObj; }; -int ShClWinTransferGetRoots(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); int ShClWinTransferDropFilesToStringList(DROPFILES *pDropFiles, char **papszList, uint32_t *pcbList); int ShClWinTransferGetRootsFromClipboard(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); int ShClWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); +/** + * Unregisters the data object associated with a Windows transfer. + * + * This disables backend callbacks and releases the data object's long-lived + * transfer reference while keeping the per-transfer context valid for + * temporary users. + * + * @param pTransfer Shared Clipboard transfer to unregister. + */ +void ShClWinTransferUnregister(PSHCLTRANSFER pTransfer); void ShClWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, PSHCLCONTEXT pCtx, ShClWinDataObject::PCALLBACKS pCallbacks); @@ -504,4 +551,3 @@ int ShClWinTransferInitialize(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); int ShClWinTransferStart(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer); # endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ #endif /* !VBOX_INCLUDED_GuestHost_SharedClipboard_win_h */ - diff --git a/include/VBox/GuestHost/SharedClipboard-x11.h b/include/VBox/GuestHost/SharedClipboard-x11.h index caa72366bf45..f58d5d2c9187 100644 --- a/include/VBox/GuestHost/SharedClipboard-x11.h +++ b/include/VBox/GuestHost/SharedClipboard-x11.h @@ -106,6 +106,39 @@ typedef struct SHCLX11FMTTABLE /** Defines an index of the X11 clipboard format table. */ typedef unsigned SHCLX11FMTIDX; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +/** + * Backend-neutral state for asynchronously preparing and publishing an X11 + * HTTP-backed file-transfer offer. + * + * The backend which embeds this structure supplies the synchronization. The + * offer generation rejects work completed for an old clipboard offer, while + * the transfer ID and generation identify the exact transfer associated with + * the preparation or currently advertised URI-list data. + */ +typedef struct SHCLX11TRANSFERSTATE +{ + /** Most recently reported source formats. */ + SHCLFORMATS fFormats; + /** Generation of the most recently reported source clipboard offer. */ + uint64_t uOfferGeneration; + /** Offer generation for which a transfer is currently being prepared. */ + uint64_t uPreparingOfferGeneration; + /** ID of the transfer bound to the current preparation request. */ + SHCLTRANSFERID idTransfer; + /** Generation of the transfer bound to the current request. */ + SHCLTRANSFERGEN uTransferGeneration; + /** ID of the transfer backing the currently advertised URI-list data. */ + SHCLTRANSFERID idPublishedTransfer; + /** Generation of the transfer backing the advertised URI-list data. */ + SHCLTRANSFERGEN uPublishedTransferGeneration; + /** Whether a transfer preparation request is outstanding. */ + bool fPreparing; +} SHCLX11TRANSFERSTATE; +/** Pointer to X11 transfer state. */ +typedef SHCLX11TRANSFERSTATE *PSHCLX11TRANSFERSTATE; +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + /** * Structure for maintaining a Shared Clipboard context on X11 platforms. */ @@ -115,14 +148,6 @@ typedef struct SHCLX11CTX PSHCLCONTEXT pFrontend; /** Our callback table to use. */ SHCLCALLBACKS Callbacks; - /** - * Are we running in headless mode? - * - * This is a special situation for running on UNIX-y environments, where an - * X server could not be available when running on a server without any - * desktop environment available, for example. - */ - bool fHeadless; /** The X Toolkit application context structure. */ XtAppContext pAppContext; /** We have a separate thread to wait for window and clipboard events. */ @@ -149,13 +174,15 @@ typedef struct SHCLX11CTX #endif /** What kind of formats does VBox have to offer? */ SHCLFORMATS vboxFormats; + /** Formats which must be served exclusively from the cache. */ + SHCLFORMATS fCacheOnlyFormats; /** Internal cache of VBox clipboard formats. */ SHCLCACHE Cache; /** When we wish the clipboard to exit, we have to wake up the event * loop. We do this by writing into a pipe. This end of the pipe is - * the end that another thread can write to. */ + * the end that another thread can write to. -1 if not created. */ int wakeupPipeWrite; - /** The reader end of the pipe. */ + /** The reader end of the pipe. -1 if not created. */ int wakeupPipeRead; /** A pointer to the XFixesSelectSelectionInput function. */ void (*fixesSelectInput)(Display *, Window, Atom, unsigned long); @@ -207,6 +234,13 @@ typedef struct SHCLX11REQUEST { /** VBox formats to announce. */ SHCLFORMATS fFormats; + /** Optional format whose data should be cached before announcing + * the formats. VBOX_SHCL_FMT_NONE if no data was supplied. */ + SHCLFORMAT uFmtCache; + /** Optional cache data owned by the request. */ + void *pvCache; + /** Size of the optional cache data in bytes. */ + uint32_t cbCache; } Formats; /** Read request. */ struct @@ -264,18 +298,20 @@ typedef SHCLX11RESPONSE *PSHCLX11RESPONSE; /** @name Shared Clipboard APIs for X11. * @{ */ -int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent, bool fHeadless); +int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent); int ShClX11Term(PSHCLX11CTX pCtx); int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool grab); int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab); int ShClX11ThreadStop(PSHCLX11CTX pCtx); -int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS vboxFormats); +int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS fFormats); +/** Reports formats after atomically seeding one X11 clipboard cache entry. */ +int ShClX11ReportFormatsToX11AsyncEx(PSHCLX11CTX pCtx, SHCLFORMATS fFormats, SHCLFORMAT uFmtCache, + const void *pvCache, uint32_t cbCache); int ShClX11ReadDataFromX11Async(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, uint32_t cbMax, PSHCLEVENT pEvent); int ShClX11ReadDataFromX11Ex(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, void **ppvBuf, uint32_t *pcbBuf); int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, void *pvBuf, uint32_t cbBuf, uint32_t *pcbBuf); int ShClX11WriteDataToX11Async(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, const void *pvBuf, uint32_t cbBuf, PSHCLEVENT pEvent); int ShClX11WriteDataToX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMSINTERVAL msTimeout, SHCLFORMAT uFmt, const void *pvBuf, uint32_t cbBuf, uint32_t *pcbWritten); -void ShClX11SetCallbacks(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS int ShClX11TransferConvertToX11(const char *pszSrc, size_t cbSrc, SHCLX11FMT enmFmtX11, void **ppvDst, size_t *pcbDst); int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppszList, size_t *pcbList); @@ -283,4 +319,3 @@ int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppsz /** @} */ #endif /* !VBOX_INCLUDED_GuestHost_SharedClipboard_x11_h */ - diff --git a/include/VBox/GuestHost/SharedClipboard.h b/include/VBox/GuestHost/SharedClipboard.h index eec9c9037743..438da6b2cb14 100644 --- a/include/VBox/GuestHost/SharedClipboard.h +++ b/include/VBox/GuestHost/SharedClipboard.h @@ -92,6 +92,22 @@ typedef uint32_t SHCLFORMATS; /** Pointer to a bit map of Shared Clipboard formats (VBOX_SHCL_FMT_XXX). */ typedef SHCLFORMATS *PSHCLFORMATS; +/** + * Checks whether a value names exactly one Shared Clipboard format. + * + * @returns true if @a uFmt is a single valid VBOX_SHCL_FMT_XXX bit, false otherwise. + * @param uFmt Format value to validate. + */ +VBGH_DECL(bool) ShClFormatIsValid(SHCLFORMAT uFmt); + +/** + * Checks whether a Shared Clipboard format mask contains only known format bits. + * + * @returns true if @a fFormats only contains VBOX_SHCL_FMT_XXX bits, false otherwise. + * @param fFormats Format mask to validate. VBOX_SHCL_FMT_NONE is valid. + */ +VBGH_DECL(bool) ShClFormatsAreValid(SHCLFORMATS fFormats); + /** Main API Shared Clipboard client/session identifier. */ typedef uint32_t SHCLMAINCLIENTID; /** Pointer to a Main API Shared Clipboard client/session identifier. */ @@ -147,36 +163,31 @@ enum /** * Shared Clipboard transfer direction. + * + * The direction is absolute and therefore has the same meaning on the host and + * guest. The explicit values are part of the HGCM protocol and saved state. */ typedef enum SHCLTRANSFERDIR { - /** Unknown transfer directory. */ + /** Unknown transfer direction. */ SHCLTRANSFERDIR_UNKNOWN = 0, - /** Read transfer (from source). */ - SHCLTRANSFERDIR_FROM_REMOTE, - /** Write transfer (to target). */ - SHCLTRANSFERDIR_TO_REMOTE, + /** Guest-to-host transfer. */ + SHCLTRANSFERDIR_GUEST_TO_HOST = 1, + /** Host-to-guest transfer. */ + SHCLTRANSFERDIR_HOST_TO_GUEST = 2, /** The usual 32-bit hack. */ SHCLTRANSFERDIR_32BIT_HACK = 0x7fffffff } SHCLTRANSFERDIR; /** Pointer to a shared clipboard transfer direction. */ typedef SHCLTRANSFERDIR *PSHCLTRANSFERDIR; - /** - * Shared Clipboard data read request. + * Checks whether a Shared Clipboard transfer direction is valid. + * + * @returns true if @a enmDir is valid, false otherwise. + * @param enmDir Transfer direction to validate. */ -typedef struct SHCLDATAREQ -{ - /** In which format the data needs to be sent. */ - SHCLFORMAT uFmt; - /** Read flags; currently unused. */ - uint32_t fFlags; - /** Maximum data (in byte) can be sent. */ - uint32_t cbSize; -} SHCLDATAREQ; -/** Pointer to a shared clipboard data request. */ -typedef SHCLDATAREQ *PSHCLDATAREQ; +VBGH_DECL(bool) ShClTransferDirIsValid(SHCLTRANSFERDIR enmDir); /** * Shared Clipboard event payload (optional). @@ -314,6 +325,14 @@ typedef enum SHCLSOURCE SHCLSOURCE_32BIT_HACK = 0x7fffffff } SHCLSOURCE; +/** + * Checks whether a Shared Clipboard source is valid. + * + * @returns true if @a enmSource is valid, false otherwise. + * @param enmSource Source to validate. + */ +VBGH_DECL(bool) ShClSourceIsValid(SHCLSOURCE enmSource); + /** @name Shared Clipboard caching. * @{ */ @@ -355,7 +374,6 @@ typedef SHCLCACHE *PSHCLCACHE; VBGH_DECL(void) ShClCacheInit(PSHCLCACHE pCache); VBGH_DECL(void) ShClCacheTerm(PSHCLCACHE pCache); VBGH_DECL(void) ShClCacheInvalidate(PSHCLCACHE pCache); -VBGH_DECL(void) ShClCacheInvalidateEntry(PSHCLCACHE pCache, SHCLFORMAT uFmt); VBGH_DECL(PSHCLCACHEENTRY) ShClCacheGet(PSHCLCACHE pCache, SHCLFORMAT uFmt); VBGH_DECL(int) ShClCachePrep(PSHCLCACHE pCache, SHCLFORMAT uFmt, size_t cbData, void **ppvData); VBGH_DECL(int) ShClCacheSet(PSHCLCACHE pCache, SHCLFORMAT uFmt, const void *pvData, size_t cbData); @@ -474,4 +492,3 @@ typedef struct SHCLCALLBACKS typedef SHCLCALLBACKS *PSHCLCALLBACKS; #endif /* !VBOX_INCLUDED_GuestHost_SharedClipboard_h */ - diff --git a/include/VBox/GuestHost/clipboard-helper.h b/include/VBox/GuestHost/clipboard-helper.h index d097c6599bcf..146338df4d72 100644 --- a/include/VBox/GuestHost/clipboard-helper.h +++ b/include/VBox/GuestHost/clipboard-helper.h @@ -1,4 +1,4 @@ -/* $Id: clipboard-helper.h 114830 2026-07-31 10:02:47Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-helper.h 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Helper functions. */ @@ -114,21 +114,6 @@ int ShClHlpUtf16LenUtf8(PCRTUTF16 pcwszSrc, size_t cwcSrc, size_t *pcbLenSansTer */ int ShClHlpUtf8ValidateExact(const char *pchSrc, size_t cbSrc, size_t *pcchText); -/** - * Validates and duplicates an exact bounded UTF-16 payload. - * - * The input may omit its final terminator. If a terminator is present, it - * must be the final code unit; embedded terminators and trailing data are - * rejected. The returned copy is always terminated. - * - * @returns VBox status code. - * @param pwszSrc UTF-16 payload to duplicate. - * @param cwcSrc Exact payload size in UTF-16 code units. - * @param ppwszDst Where to return the allocated terminated copy. - * Free with RTUtf16Free(). - */ -int ShClHlpUtf16DupExact(PCRTUTF16 pwszSrc, size_t cwcSrc, PRTUTF16 *ppwszDst); - /** * Converts an UTF-16 string with LF EOL to an UTF-16 string with CRLF EOL. * @@ -284,14 +269,6 @@ const char *ShClHlpModeToString(uint32_t uMode); */ const char *ShClHlpTransferStateToString(uint32_t uState); -/** - * Converts a Main API clipboard event type value to a printable string. - * - * @returns Printable event type name. - * @param uEventType Main API VBoxEventType value. - */ -const char *ShClHlpVBoxEventTypeToString(uint32_t uEventType); - /** * Parses a clipboard sharing mode value. * @@ -364,29 +341,6 @@ int ShClDbgDumpHtml(const char *pszSrc, size_t cbSrc); void ShClDbgDumpData(const void *pv, size_t cb, SHCLFORMAT u32Format); #endif /* LOG_ENABLED */ -/** - * Translates a Shared Clipboard host function number to a string. - * - * @returns Function ID string name. - * @param uFn The function to translate. - */ -const char *ShClHostFunctionToStr(uint32_t uFn); - -/** - * Translates a Shared Clipboard host message enum to a string. - * - * @returns Message ID string name. - * @param uMsg The message to translate. - */ -const char *ShClHostMsgToStr(uint32_t uMsg); - -/** - * Translates a Shared Clipboard guest message enum to a string. - * - * @returns Message ID string name. - * @param uMsg The message to translate. - */ -const char *ShClGuestMsgToStr(uint32_t uMsg); char *ShClFormatsToStrA(SHCLFORMATS fFormats); diff --git a/include/VBox/GuestHost/clipboard-transfers-http.h b/include/VBox/GuestHost/clipboard-transfers-http.h new file mode 100644 index 000000000000..f40e82139cda --- /dev/null +++ b/include/VBox/GuestHost/clipboard-transfers-http.h @@ -0,0 +1,76 @@ +/* $Id: clipboard-transfers-http.h 115048 2026-08-17 15:07:54Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard - HTTP transfer functions. + */ + +/* + * Copyright (C) 2020-2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_GuestHost_clipboard_transfers_http_h +#define VBOX_INCLUDED_GuestHost_clipboard_transfers_http_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +/** Namespace used as a prefix for HTTP(S) transfer URLs. */ +# define SHCL_HTTPT_URL_NAMESPACE "vbcl" + +/** @name Shared Clipboard HTTP context API. + * @{ + */ +int ShClTransferHttpServerMaybeStart(PSHCLHTTPCONTEXT pCtx); +int ShClTransferHttpServerMaybeStop(PSHCLHTTPCONTEXT pCtx); +/** @} */ + +/** @name Shared Clipboard HTTP server API. + * @{ + */ +int ShClTransferHttpServerInit(PSHCLHTTPSERVER pSrv); +int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv); +int ShClTransferHttpServerStart(PSHCLHTTPSERVER pSrv, unsigned cMaxAttempts, uint16_t *puPort); +int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort); +int ShClTransferHttpServerStop(PSHCLHTTPSERVER pSrv); +int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); +int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer); +bool ShClTransferHttpServerGetTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer); +uint32_t ShClTransferHttpServerGetTransferCount(PSHCLHTTPSERVER pSrv); +char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv); +char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer, uint64_t idxEntry); +int ShClTransferHttpConvertToStringList(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer, char **ppszData, size_t *pcbData); +bool ShClTransferHttpServerIsInitialized(PSHCLHTTPSERVER pSrv); +bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv); +/** @} */ +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + +#endif /* !VBOX_INCLUDED_GuestHost_clipboard_transfers_http_h */ diff --git a/include/VBox/HostServices/VBoxClipboardExt.h b/include/VBox/HostServices/VBoxClipboardExt.h index 13f2728c73f3..7947f42f9680 100644 --- a/include/VBox/HostServices/VBoxClipboardExt.h +++ b/include/VBox/HostServices/VBoxClipboardExt.h @@ -41,16 +41,132 @@ #include #include -#include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include #endif -/** Sets (or unsets) a clipboard extension callback. */ +/** Opaque declaration of an HGCM Shared Clipboard client. */ +typedef struct _SHCLCLIENT SHCLCLIENT, *PSHCLCLIENT; +/** Opaque declaration of a Shared Clipboard client command context. */ +typedef struct _SHCLCLIENTCMDCTX SHCLCLIENTCMDCTX, *PSHCLCLIENTCMDCTX; +/** Opaque declaration of a Shared Clipboard transfer. */ +typedef struct SHCLTRANSFER *PSHCLTRANSFER; +/** Opaque declaration of a Shared Clipboard reply. */ +typedef struct _SHCLREPLY *PSHCLREPLY; + +/** Opaque identity of a client owned exclusively by the HGCM service. */ +typedef struct SHCLCLIENTOPAQUE *SHCLCLIENTHANDLE; +/** Opaque retained guest-data reply owned by the HGCM service. */ +typedef struct SHCLGUESTDATATOKENOPAQUE *SHCLGUESTDATATOKEN; +/** Pointer to an opaque guest-data reply token. */ +typedef SHCLGUESTDATATOKEN *PSHCLGUESTDATATOKEN; + +struct SHCLTRANSPORT; +typedef struct SHCLTRANSPORT SHCLTRANSPORT; +typedef SHCLTRANSPORT *PSHCLTRANSPORT; +typedef SHCLTRANSPORT const *PCSHCLTRANSPORT; + +/** Operations implemented by the HGCM service for an opaque client. */ +typedef struct SHCLSVCOPS +{ + /** Size of this operation table. */ + uint32_t cbStruct; + /** Applies service transfer policy and compatibility rules to a format mask. */ + DECLCALLBACKMEMBER(int, pfnFilterFormats, (SHCLCLIENTHANDLE hClient, bool fHostToGuest, + SHCLFORMATS fFormats, SHCLFORMATS *pfFiltered)); + /** Queues a format announcement for the guest, or returns VINF_NO_CHANGE if policy suppresses it. */ + DECLCALLBACKMEMBER(int, pfnReportFormatsToGuest, (SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + SHCLFORMATS *pfReported)); + /** Queues guest data reads without waiting. */ + DECLCALLBACKMEMBER(int, pfnReadDataFromGuestAsync, (SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + PSHCLEVENT *ppEvent)); + /** Reads and waits for one guest clipboard format. */ + DECLCALLBACKMEMBER(int, pfnReadDataFromGuest, (SHCLCLIENTHANDLE hClient, SHCLFORMAT uFormat, + void **ppvData, uint32_t *pcbData)); + /** Validates and retains a guest reply before it is forwarded. */ + DECLCALLBACKMEMBER(int, pfnGuestDataBegin, (SHCLCLIENTHANDLE hClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken)); + /** Signals and releases a retained guest reply token. */ + DECLCALLBACKMEMBER(int, pfnGuestDataComplete, (SHCLCLIENTHANDLE hClient, SHCLGUESTDATATOKEN hToken, + void const *pvData, uint32_t cbData)); + /** Releases a retained guest reply token without signalling it. */ + DECLCALLBACKMEMBER(void, pfnGuestDataCancel, (SHCLCLIENTHANDLE hClient, SHCLGUESTDATATOKEN hToken)); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Retains a transfer selected by ID. */ + DECLCALLBACKMEMBER(PSHCLTRANSFER, pfnTransferGetByIdRetained, (SHCLCLIENTHANDLE hClient, + SHCLTRANSFERID idTransfer)); + /** Retains a transfer selected by its full generation key. */ + DECLCALLBACKMEMBER(PSHCLTRANSFER, pfnTransferGetByKeyRetained, (SHCLCLIENTHANDLE hClient, + SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration)); + /** Creates and retains a service-owned transfer. */ + DECLCALLBACKMEMBER(int, pfnTransferCreate, (SHCLCLIENTHANDLE hClient, SHCLTRANSFERDIR enmDir, + SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, + SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer)); + /** Initializes a service-owned transfer. */ + DECLCALLBACKMEMBER(int, pfnTransferInit, (SHCLCLIENTHANDLE hClient, PSHCLTRANSFER pTransfer)); + /** Destroys a transfer selected by ID. */ + DECLCALLBACKMEMBER(void, pfnTransferDestroyById, (SHCLCLIENTHANDLE hClient, SHCLTRANSFERID idTransfer)); + /** Destroys all transfers for a disconnecting client. */ + DECLCALLBACKMEMBER(void, pfnTransferDestroyAll, (SHCLCLIENTHANDLE hClient)); + /** Initializes a guest-facing provider without exposing the service client. */ + DECLCALLBACKMEMBER(int, pfnTransferProviderInitGuest, (SHCLCLIENTHANDLE hClient, PSHCLTXPROVIDER pProvider)); +#endif +} SHCLSVCOPS; +/** Pointer to a const service operation table. */ +typedef SHCLSVCOPS const *PCSHCLSVCOPS; + +/** + * Non-owning transport value passed to Main instead of the service client. + * + * The service owns both referenced values from the successful backend-connect + * callback through the matching backend-disconnect callback. Operations are + * synchronous and must not retain either value beyond the call. + */ +struct SHCLTRANSPORT +{ + /** Opaque service-owned client identity. */ + SHCLCLIENTHANDLE hClient; + /** Immutable service operation table. */ + PCSHCLSVCOPS pOps; +}; + +/** + * Checks whether a transport references a service client and operation table. + * + * @returns true if @a pTransport is structurally valid, false otherwise. + * @param pTransport Transport to validate. May be NULL. + */ +DECLINLINE(bool) ShClTransportIsValid(PCSHCLTRANSPORT pTransport) +{ + return pTransport != NULL + && pTransport->hClient != NULL + && pTransport->pOps != NULL + && pTransport->pOps->cbStruct == sizeof(*pTransport->pOps); +} + +/** + * Checks whether two transport values identify the same service endpoint. + * + * @returns true if both transports are valid and identify the same client and + * operation table, false otherwise. + * @param pLeft First transport to compare. + * @param pRight Second transport to compare. + */ +DECLINLINE(bool) ShClTransportIsEqual(PCSHCLTRANSPORT pLeft, PCSHCLTRANSPORT pRight) +{ + return ShClTransportIsValid(pLeft) + && ShClTransportIsValid(pRight) + && pLeft->hClient == pRight->hClient + && pLeft->pOps == pRight->pOps; +} + +/** Reserved. Formerly installed a reverse callback into the HGCM service. */ #define VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK (0) /** The guest reports clipboard formats to the extension. */ #define VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST (1) -/** The clipboard service wants to report formats to the guest. */ +/** Reserved. Formerly bounced remote format reports back through the service. */ #define VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST (2) /** The clipboard service requests clipboard data from the extension. */ #define VBOX_CLIPBOARD_EXT_FN_DATA_READ (3) @@ -68,17 +184,19 @@ #define VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT (9) /** The clipboard service syncs with the backend. */ #define VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC (10) -/** The clipboard service requests clipboard data from the extension. */ +/** Reserved. Formerly bounced VRDE guest-data reads back through the service. */ #define VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE (11) /** The clipboard service initiates the transfer of a file from the guest. */ #define VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER (12) +/** Reserved. */ +#define VBOX_CLIPBOARD_EXT_FN_RESERVED_13 (13) +/** The clipboard service requests the native transfer callback table. */ +#define VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS (14) typedef DECLCALLBACKTYPE(int, FNSHCLEXTCALLBACK,(uint32_t u32Function, uint32_t u32Format, void *pvData, uint32_t cbData)); typedef FNSHCLEXTCALLBACK *PFNSHCLEXTCALLBACK; -/** - * Structure for holding Shared Clipboard service extension parameters. - */ +/** Structure for holding Shared Clipboard service extension parameters. */ typedef struct _SHCLEXTPARMS { union @@ -98,28 +216,24 @@ typedef struct _SHCLEXTPARMS uint32_t cbData; uint32_t cbActual; PSHCLCLIENT pClient; - PSHCLBACKEND pBackend; - VBOXHGCMSVCFNTABLE *pTable; + void *pvReserved0; + void *pvReserved1; PSHCLCLIENTCMDCTX pCmdCtx; bool fHeadless; } ReadWriteData; /** Sets a read / write callback. */ struct { - PFNSHCLEXTCALLBACK - pfnCallback; + PFNSHCLEXTCALLBACK pfnCallback; } SetCallback; /** Reports a clipboard error. */ struct { - /** Clipboard ID. Optional and can be NULL. */ char *pszId; - /** User friendly error message. */ char *pszMsg; - /** IPRT-style error code. */ int rc; } Error; - /** Sends / receives clipboard files */ + /** Sends / receives clipboard files. */ struct { PSHCLCLIENT pClient; @@ -127,9 +241,45 @@ typedef struct _SHCLEXTPARMS SHCLSOURCE enmShClSource; PSHCLREPLY pReply; } FileTransferData; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Queries Main for callbacks to attach to a new transfer. */ + struct + { + PSHCLCLIENT pClient; + PSHCLTRANSFERCALLBACKS pCallbacks; + } TransferCallbacks; +#endif } u; } SHCLEXTPARMS; /** Pointer to Shared Clipboard service extension parameters. */ typedef SHCLEXTPARMS *PSHCLEXTPARMS; +/** Pointer to const Shared Clipboard service extension parameters. */ +typedef SHCLEXTPARMS const *PCSHCLEXTPARMS; + +/** + * Stores an opaque service transport in the reserved extension parameter slots. + * + * @param pParms Extension parameter block to update. + * @param pTransport Transport value to store. + */ +DECLINLINE(void) ShClSvcExtSetTransport(PSHCLEXTPARMS pParms, PCSHCLTRANSPORT pTransport) +{ + pParms->u.ReadWriteData.pvReserved0 = (void *)pTransport->hClient; + pParms->u.ReadWriteData.pvReserved1 = (void *)pTransport->pOps; +} + +/** + * Gets the opaque service transport stored in the reserved parameter slots. + * + * @returns Stored transport value. Use ShClTransportIsValid() before use. + * @param pParms Extension parameter block containing the transport. + */ +DECLINLINE(SHCLTRANSPORT) ShClSvcExtGetTransport(PCSHCLEXTPARMS pParms) +{ + SHCLTRANSPORT Transport; + Transport.hClient = (SHCLCLIENTHANDLE)pParms->u.ReadWriteData.pvReserved0; + Transport.pOps = (PCSHCLSVCOPS)pParms->u.ReadWriteData.pvReserved1; + return Transport; +} #endif /* !VBOX_INCLUDED_HostServices_VBoxClipboardExt_h */ diff --git a/include/VBox/HostServices/VBoxClipboardSvc.h b/include/VBox/HostServices/VBoxClipboardSvc.h index 2bd26cd905b7..a2acba67fe73 100644 --- a/include/VBox/HostServices/VBoxClipboardSvc.h +++ b/include/VBox/HostServices/VBoxClipboardSvc.h @@ -94,8 +94,9 @@ * Operates on the VBOX_SHCL_TRANSFERS_XXX defines. * @since 6.1 */ #define VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE 2 -/** Deprecated for > 7.2: Run headless on the host, i.e. do not touch the host clipboard. - * Kept for compatibility with older Guest Additions and legacy service clients. */ +/** Legacy request to suppress host clipboard access. + * @deprecated No longer implemented; retained for protocol and source + * compatibility. Function number 3 must not be reused. */ #define VBOX_SHCL_HOST_FN_SET_HEADLESS 3 /** Reports cancellation of the current operation to the guest. @@ -260,6 +261,7 @@ * @retval VERR_INVALID_CLIENT_ID * @retval VERR_WRONG_PARAMETER_COUNT * @retval VERR_WRONG_PARAMETER_TYPE + * @retval VERR_INVALID_FLAGS if the format mask contains unknown bits. * @retval VERR_NOT_SUPPORTED if all the formats are unsupported, host * clipboard will be empty. * @retval VERR_ACCESS_DENIED if the clipboard mode is not bi-directional or @@ -691,6 +693,104 @@ /** @} */ +/** + * Translates a Shared Clipboard host function number to a string. + * + * @returns Function ID string name. + * @param uFn The function to translate. + */ +DECLINLINE(const char *) ShClSvcHostFunctionToStr(uint32_t uFn) +{ + switch (uFn) + { + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_MODE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_HEADLESS); + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_CANCEL); + RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_ERROR); + } + return "Unknown"; +} + +/** + * Translates a Shared Clipboard host message enum to a string. + * + * @returns Message ID string name. + * @param uMsg The message to translate. + */ +DECLINLINE(const char *) ShClSvcHostMsgToStr(uint32_t uMsg) +{ + switch (uMsg) + { + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_QUIT); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_CANCELED); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA_CID); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_CANCEL); + RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ERROR); + } + return "Unknown"; +} + +/** + * Translates a Shared Clipboard guest message enum to a string. + * + * @returns Message ID string name. + * @param uMsg The message to translate. + */ +DECLINLINE(const char *) ShClSvcGuestMsgToStr(uint32_t uMsg) +{ + switch (uMsg) + { + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FORMATS); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_CONNECT); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FEATURES); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_QUERY_FEATURES); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_GET); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_CANCEL); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPLY); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_OPEN); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_CLOSE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_OPEN); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_CLOSE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_READ); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_WRITE); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ERROR); + RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE); + } + return "Unknown"; +} + + /* * HGCM parameter structures. */ @@ -784,7 +884,7 @@ typedef struct _VBoxShClTransferStatusMsg /** uint64_t, out: Context ID. */ HGCMFunctionParameter uContext; - /** uint32_t, out: Direction of transfer; of type SHCLTRANSFERDIR_. */ + /** uint32_t, out: Absolute guest/host transfer direction; of type SHCLTRANSFERDIR_. */ HGCMFunctionParameter enmDir; /** uint32_t, out: Status to report; of type SHCLTRANSFERSTATUS_. */ HGCMFunctionParameter enmStatus; @@ -1249,4 +1349,3 @@ typedef struct _VBoxShClParmNegotiateChunkSize #pragma pack() #endif /* !VBOX_INCLUDED_HostServices_VBoxClipboardSvc_h */ - diff --git a/include/VBox/HostServices/VBoxSharedClipboardSvc.h b/include/VBox/HostServices/VBoxSharedClipboardSvc.h index c18a2ac47b2a..91b4bdbac8a2 100644 --- a/include/VBox/HostServices/VBoxSharedClipboardSvc.h +++ b/include/VBox/HostServices/VBoxSharedClipboardSvc.h @@ -1,7 +1,6 @@ -/* $Id: VBoxSharedClipboardSvc.h 114831 2026-07-31 10:10:53Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file - * Shared Clipboard Service - header file for shared clipboard data transfer - * interfaces and platform-dependent backend functionality. + * Shared Clipboard Service - HGCM protocol state and data transfer interfaces. */ /* @@ -41,12 +40,7 @@ # pragma once #endif -#include -#include -#include - #include -#include /* For RTCList. */ #include #include @@ -88,7 +82,7 @@ typedef struct SHCLCLIENTTRANSFERSTATE /** Shared Clipboard (file) transfer mode. */ uint32_t uTransferMode; -} SHCLCLIENTTRANSFERSTATE, *PSHCLCLIENTTRANSFERSTATE; +} SHCLCLIENTTRANSFERSTATE; /** * Structure for holding a single POD (plain old data) transfer. @@ -107,17 +101,13 @@ typedef struct SHCLCLIENTPODSTATE uint64_t cbReadWritten; /** Timestamp (in ms) of Last read/write operation. */ uint64_t tsLastReadWrittenMs; -} SHCLCLIENTPODSTATE, *PSHCLCLIENTPODSTATE; +} SHCLCLIENTPODSTATE; /** @name SHCLCLIENTSTATE_FLAGS_XXX * @note Part of saved state! * @{ */ /** No Shared Clipboard client flags defined. */ #define SHCLCLIENTSTATE_FLAGS_NONE 0 -/** Client has a guest read operation active. Currently unused. */ -#define SHCLCLIENTSTATE_FLAGS_READ_ACTIVE RT_BIT(0) -/** Client has a guest write operation active. Currently unused. */ -#define SHCLCLIENTSTATE_FLAGS_WRITE_ACTIVE RT_BIT(1) /** @} */ /** @@ -157,19 +147,13 @@ typedef struct SHCLCLIENTLEGACYSTATE */ typedef struct SHCLCLIENTSTATE { - struct SHCLCLIENTSTATE *pNext; - struct SHCLCLIENTSTATE *pPrev; - - /** Backend-dependent opaque context structure. - * This contains data only known to a certain backend implementation. - * Optional and can be NULL. */ - SHCLCONTEXT *pCtx; /** The client's HGCM ID. Not related to the session ID below! */ uint32_t uClientID; /** The client's session ID. */ SHCLSESSIONID uSessionID; /** Guest feature flags, VBOX_SHCL_GF_0_XXX. */ - uint64_t fGuestFeatures0; + RT_ALIGNAS_MEMB(8) uint64_t + fGuestFeatures0; /** Guest feature flags, VBOX_SHCL_GF_1_XXX. */ uint64_t fGuestFeatures1; /** Chunk size to use for data transfers. */ @@ -199,25 +183,16 @@ typedef struct _SHCLIENTTRANSFERS { /** Transfer context. */ SHCLTRANSFERCTX Ctx; - /** Backends-specific transfers callbacks to use. */ - SHCLTRANSFERCALLBACKS Callbacks; - /** Backends-specific transfers provider to use. */ - SHCLTXPROVIDER Provider; -} SHCLIENTTRANSFERS, *PSHCLIENTTRANSFERS; +} SHCLIENTTRANSFERS; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ -/** Prototypes for the Shared Clipboard backend. */ -struct SHCLBACKEND; -typedef SHCLBACKEND *PSHCLBACKEND; - /** * Structure for keeping data per (connected) HGCM client. */ typedef struct _SHCLCLIENT { - /** Pointer to associated backend, if any. - * Might be NULL if not being used. */ - PSHCLBACKEND pBackend; + /** HGCM service helpers used to complete deferred guest calls. */ + PVBOXHGCMSVCHELPERS pHelpers; /** General client state data. */ SHCLCLIENTSTATE State; /** The critical section protecting the queue, event source and whatnot. */ @@ -252,6 +227,9 @@ typedef struct _SHCLCLIENT } Pending; } SHCLCLIENT, *PSHCLCLIENT; +AssertCompileMemberAlignment(SHCLCLIENT, State.fGuestFeatures0, 8); +AssertCompileMemberAlignment(SHCLCLIENT, State.fGuestFeatures1, 8); + /** * Returns a client's cached Shared Clipboard mode atomically. * @@ -302,55 +280,6 @@ DECLINLINE(uint32_t) ShClSvcClientGetTransferMode(PSHCLCLIENT pClient) } #endif -/** - * Structure for keeping a single event source map entry. - * Currently empty. - */ -typedef struct _SHCLEVENTSOURCEMAPENTRY -{ -} SHCLEVENTSOURCEMAPENTRY; - -/** Map holding information about connected HGCM clients. Key is the (unique) HGCM client ID. - * The value is a weak pointer to PSHCLCLIENT, which is owned by HGCM. */ -typedef std::map ClipboardClientMap; - -/** Map holding information about event sources. Key is the (unique) event source ID. */ -typedef std::map ClipboardEventSourceMap; - -/** Simple queue (list) which holds deferred (waiting) clients. */ -typedef std::list ClipboardClientQueue; - -/** - * Structure for keeping the Shared Clipboard service extension state. - * - * A service extension is optional, and can be installed by a host component - * to communicate with the Shared Clipboard host service. - */ -typedef struct _SHCLEXTSTATE -{ - /** Pointer to the actual service extension handle. - * - * Must return VERR_NOT_SUPPORTED if the extension didn't handle the requested function. - * This will invoke the regular backend then. - */ - PFNHGCMSVCEXT pfnExtension; - /** Opaque pointer to extension-provided data. Don't touch. */ - void *pvExtension; - /** The HGCM client ID currently assigned to this service extension. - * At the moment only one HGCM client can be assigned per extension. */ - uint32_t uClientID; - /** Whether the host service is reading clipboard data currently. */ - bool fReadingData; - /** Whether the service extension has sent the clipboard formats while - * the the host service is reading clipboard data from it. */ - bool fDelayedAnnouncement; - /** The actual clipboard formats announced while the host service - * is reading clipboard data from the extension. */ - uint32_t fDelayedFormats; -} SHCLEXTSTATE, *PSHCLEXTSTATE; - -extern SHCLEXTSTATE g_ExtState; - /** @name Service client functions. * @{ */ @@ -370,180 +299,18 @@ void shClSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, u int shClSvcMsgSetOldWaitReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms); SHCLFORMATS shClSvcHandleFormats(bool fHostToGuest, PSHCLCLIENT pClient, SHCLFORMATS fFormats); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +bool shClSvcClientTransfersAreAllowed(PSHCLCLIENT pClient); +#endif /** @} */ -/** @name Service functions, accessible by the backends. +/** @name Service functions shared with Main's clipboard backends. * Locking is between the (host) service thread and the platform-dependent (window) thread. * @{ */ -int ShClSvcReadDataFromGuestAsync(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENT *ppEvent); -int ShClSvcReadDataFromGuest(PSHCLCLIENT pClient, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb); -int ShClSvcGuestDataRetainValidatedEvent(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, - SHCLFORMAT uFormat, PSHCLEVENT *ppEvent); -int ShClSvcGuestDataSignalEvent(PSHCLEVENT pEvent, SHCLEVENTID idEvent, void *pvData, uint32_t cbData); -int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); -int ShClSvcReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats); -PSHCLBACKEND ShClSvcGetBackend(void); uint32_t ShClSvcGetMode(void); -bool ShClSvcGetHeadless(void); -bool ShClSvcLock(void); -void ShClSvcUnlock(void); /** @} */ -/** @name Platform-dependent implementations for the Shared Clipboard host service ("backends"), - * called *only* by the host service. - * @{ - */ -/** - * Structure for keeping Shared Clipboard backend instance data. - */ -typedef struct SHCLBACKEND -{ - /** Callback table to use. - * Some callbacks might be optional and therefore NULL -- see the table for more details. */ - SHCLCALLBACKS Callbacks; - - /** Pointer to the helper functions in the VBOXHGCMSVCFNTABLE for use by the backend. */ - PVBOXHGCMSVCHELPERS pHelpers; -} SHCLBACKEND; -/** Pointer to a Shared Clipboard backend. */ -typedef SHCLBACKEND *PSHCLBACKEND; - -/** - * Called on initialization. - * - * @param pBackend Shared Clipboard backend to initialize. - * @param pTable The HGCM service call and parameter table. Mainly for - * adjusting the limits. - */ -int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable); - -/** - * Called on destruction. - * - * @param pBackend Shared Clipboard backend to destroy. - */ -void ShClBackendDestroy(PSHCLBACKEND pBackend); - -/** - * Called when a new HGCM client connects. - * - * @param pBackend Shared Clipboard backend to set callbacks for. - * @param pCallbacks Backend callbacks to use. - * When NULL is specified, the backend's default callbacks are being used. - */ -void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks); - -/** - * Called when a new HGCM client connects. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to connect to. - * @param pClient Shared Clipboard client context. - * @param fHeadless Whether this is a headless connection or not. - */ -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless); - -/** - * Called when a HGCM client disconnects. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to disconnect from. - * @param pClient Shared Clipboard client context. - */ -int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); - -/** - * Called when the guest reports available clipboard formats to the host OS. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to announce formats to. - * @param pClient Shared Clipboard client context. - * @param fFormats The announced formats from the guest, - * VBOX_SHCL_FMT_XXX. - */ -int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats); - -/** - * Called when the host reports available clipboard formats to the guest. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to announce formats to. - * @param pClient Shared Clipboard client context. - * @param fFormats The announced formats from the host, - * VBOX_SHCL_FMT_XXX. - */ -int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats); - -/** - * Called when the guest wants to read host clipboard data. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to read data from. - * @param pClient Shared Clipboard client context. - * @param pCmdCtx Shared Clipboard command context. - * @param uFormat Clipboard format to read. - * @param pvData Where to return the read clipboard data. - * @param cbData Size (in bytes) of buffer where to return the clipboard data. - * @param pcbActual Where to return the amount of bytes read. - * - * @todo Document: Can return VINF_HGCM_ASYNC_EXECUTE to defer returning read - * data - */ -int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, - void *pvData, uint32_t cbData, uint32_t *pcbActual); - -/** - * Called when the guest writes clipboard data to the host. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to write data to. - * @param pClient Shared Clipboard client context. - * @param pCmdCtx Shared Clipboard command context. - * @param uFormat Clipboard format to write. - * @param pvData Clipboard data to write. - * @param cbData Size (in bytes) of buffer clipboard data to write. - */ -int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); - -/** - * Called when synchronization of the clipboard contents of the host clipboard with the guest is needed. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to synchronize. - * @param pClient Shared Clipboard client context. - */ -int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient); -/** @} */ - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** @name Host implementations for Shared Clipboard transfers. - * @{ - */ -/** - * Called before a transfer gets destroyed. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to use. - * @param pClient Shared Clipboard client context. - * @param pTransfer Shared Clipboard transfer to destroy. - */ -int ShClBackendTransferDestroy(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); -/** - * Called after a transfer status got processed. - * - * @returns VBox status code. - * @param pBackend Shared Clipboard backend to use. - * @param pClient Shared Clipboard client context. - * @param pTransfer Shared Clipboard transfer to process status for. - * @param enmSource Transfer source which issues the reply. - * @param enmStatus Transfer status. - * @param rcStatus Status code (IPRT-style). Depends on \a enmStatus set. - */ -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus); -/** @} */ -#endif - #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** @name Shared Clipboard transfer interface implementations for guest -> host transfers. * @{ @@ -565,8 +332,4 @@ int shClSvcTransferSendStatusAsync(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /* Host unit testing interface */ -#ifdef UNIT_TEST -uint32_t TestClipSvcGetMode(void); -#endif - #endif /* !VBOX_INCLUDED_HostServices_VBoxSharedClipboardSvc_h */ diff --git a/include/VBox/err.h b/include/VBox/err.h index 069dd188ab8b..b5d0bbadf086 100644 --- a/include/VBox/err.h +++ b/include/VBox/err.h @@ -3143,6 +3143,10 @@ #define VERR_APIC_IPE_1 (-6706) /** APIC internal error \#2. */ #define VERR_APIC_IPE_2 (-6707) +/** Update the APIC state in R3 after an unaccelerated write (AVIC/APICv). */ +#define VINF_APIC_R3_UPDATE_STATE 6708 +/** Pending interrupt deferred for delivery by hardware (AVIC/APICv). */ +#define VERR_APIC_INTR_DEFER (-6709) /** @} */ /** @name NEM Status Codes diff --git a/include/VBox/intnetr3ipc.h b/include/VBox/intnetr3ipc.h new file mode 100644 index 000000000000..500b7185c0ef --- /dev/null +++ b/include/VBox/intnetr3ipc.h @@ -0,0 +1,109 @@ +/* $Id: intnetr3ipc.h 114959 2026-08-10 14:41:23Z andreas.loeffler@oracle.com $ */ +/** @file + * Internal networking Ring-3 service IPC protocol. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + +#ifndef VBOX_INCLUDED_intnetr3ipc_h +#define VBOX_INCLUDED_intnetr3ipc_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +RT_C_DECLS_BEGIN + +/** The local IPC service identifier. */ +#ifndef INTNET_R3_SVC_NAME +# define INTNET_R3_SVC_NAME "org.virtualbox.intnet" +#endif +/** Maximum generated per-user local IPC service name length, including the terminator. */ +#define INTNET_R3_IPC_MAX_SERVICE_NAME 64 +/** Protocol version. */ +#define INTNET_R3_IPC_VERSION UINT16_C(1) +/** Maximum size of an IntNet request payload. */ +#define INTNET_R3_IPC_MAX_REQ UINT32_C(65536) +/** Maximum shared-memory object name length, including the terminator. */ +#define INTNET_R3_IPC_MAX_SHMEM_NAME 256 +/** Maximum time allowed to complete a started local IPC frame. */ +#define INTNET_R3_IPC_FRAME_TIMEOUT_MS UINT32_C(30000) + +/** Local IPC request header magic. */ +#define INTNET_R3_IPC_REQ_MAGIC RT_MAKE_U32_FROM_U8('I', 'N', 'R', 'Q') +/** Local IPC synchronous reply header magic. */ +#define INTNET_R3_IPC_REPLY_MAGIC RT_MAKE_U32_FROM_U8('I', 'N', 'R', 'P') +/** Local IPC receive-available notification header magic. */ +#define INTNET_R3_IPC_POKE_MAGIC RT_MAKE_U32_FROM_U8('I', 'N', 'R', 'K') + +/** Header preceding an IntNet service request payload. */ +typedef struct INTNETR3IPCREQHDR +{ + /** INTNET_R3_IPC_REQ_MAGIC. */ + uint32_t u32Magic; + /** INTNET_R3_IPC_VERSION. */ + uint16_t u16Version; + /** Size of this header in bytes. */ + uint16_t cbHdr; + /** Size of the request payload following this header. */ + uint32_t cbReq; + /** VMMR0_DO_INTNET_* operation to perform. */ + uint32_t uOperation; +} INTNETR3IPCREQHDR; +/** Pointer to an IntNet service request header. */ +typedef INTNETR3IPCREQHDR *PINTNETR3IPCREQHDR; + +/** Header preceding an IntNet service reply or receive-available notification. */ +typedef struct INTNETR3IPCREPLYHDR +{ + /** INTNET_R3_IPC_REPLY_MAGIC or INTNET_R3_IPC_POKE_MAGIC. */ + uint32_t u32Magic; + /** INTNET_R3_IPC_VERSION. */ + uint16_t u16Version; + /** Size of this header in bytes. */ + uint16_t cbHdr; + /** VBox status code for the request or notification. */ + int32_t rc; + /** Size of the reply payload following this header. */ + uint32_t cbReply; + /** Size of the optional terminated shared-memory name following the payload. */ + uint32_t cbShMemName; + /** Size of the shared-memory object in bytes. */ + uint64_t cbShMem; +} INTNETR3IPCREPLYHDR; +/** Pointer to a const IntNet service reply header. */ +typedef INTNETR3IPCREPLYHDR const *PCINTNETR3IPCREPLYHDR; + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_intnetr3ipc_h */ diff --git a/include/VBox/settings.h b/include/VBox/settings.h index fe96a2fe5f3f..1de93ad0dcc2 100644 --- a/include/VBox/settings.h +++ b/include/VBox/settings.h @@ -1585,7 +1585,13 @@ class MachineConfigFile : public ConfigFileBase void buildMachineXML(xml::ElementNode &elmMachine, uint32_t fl, std::list *pllElementsWithUuidAttributes); + /** + * sanitizeXXXX functions are possibly called during apliance import to make sure + * security wise risky settings are cleaned up. + */ void sanitizeImportedSerialPorts(); + void sanitizeSharedFolderSettings(); + void sanitizeImportedNvramSettings(); static bool isAudioDriverAllowedOnThisHost(AudioDriverType_T enmDrvType); static AudioDriverType_T getHostDefaultAudioDriver(); diff --git a/include/VBox/sup.h b/include/VBox/sup.h index a8e70447dcac..15fe0866f183 100644 --- a/include/VBox/sup.h +++ b/include/VBox/sup.h @@ -1356,10 +1356,9 @@ DECLHIDDEN(int) SUPR3HardenedMain(const char *pszProgName, uint32_t fFlags, int * the VBox support driver is unavailable. */ #define SUPSECMAIN_FLAGS_DRIVERLESS_IEM_ALLOWED RT_BIT_32(9) #ifdef VBOX_WITH_DRIVERLESS_NEM_FALLBACK -/** Driverless NEM is a fallback posibility, so don't fail fatally just +/** Driverless NEM is a fallback possibility, so don't fail fatally just * because the VBox support driver is unavailable. - * This may imply checking NEM requirements, depending on the host. - * @note Not supported on Windows. */ + * This may imply checking NEM requirements, depending on the host. */ # define SUPSECMAIN_FLAGS_DRIVERLESS_NEM_FALLBACK RT_BIT_32(10) #endif @@ -3012,4 +3011,3 @@ extern const unsigned g_cbSUPBuildCert; RT_C_DECLS_END #endif /* !VBOX_INCLUDED_sup_h */ - diff --git a/include/VBox/vmm/cpum.h b/include/VBox/vmm/cpum.h index 923e8d05c8f4..0c72c9d31bf9 100644 --- a/include/VBox/vmm/cpum.h +++ b/include/VBox/vmm/cpum.h @@ -486,6 +486,8 @@ typedef struct CPUMFEATURESX86 uint32_t fClFlushOpt : 1; /** Supports IA32_PRED_CMD.IBPB. */ uint32_t fIbpb : 1; + /** IA32_PRED_CMD.IBPB doest not clear return target predictions. */ + uint32_t fIbpbNoRet : 1; /** Supports the IA32_SPEC_CTRL MSR (summary of the next). */ uint32_t fSpecCtrlMsr : 1; /** Supports IA32_SPEC_CTRL.IBRS. */ @@ -662,7 +664,7 @@ typedef struct CPUMFEATURESX86 /** @} */ /** Alignment padding / reserved for future use. */ - uint32_t fPadding0 : 17; + uint32_t fPadding0 : 16; uint32_t auPadding[3]; /** @name SVM diff --git a/include/VBox/vmm/hm.h b/include/VBox/vmm/hm.h index 6bd25b889b29..36a546a83c07 100644 --- a/include/VBox/vmm/hm.h +++ b/include/VBox/vmm/hm.h @@ -300,6 +300,7 @@ VMMR3DECL(bool) HMR3IsEnabled(PUVM pUVM); VMMR3DECL(bool) HMR3IsNestedPagingActive(PUVM pUVM); VMMR3DECL(bool) HMR3AreVirtApicRegsEnabled(PUVM pUVM); VMMR3DECL(bool) HMR3IsPostedIntrsEnabled(PUVM pUVM); +VMMR3DECL(bool) HMR3IsAvicEnabled(PUVM pUVM); VMMR3DECL(bool) HMR3IsVpidActive(PUVM pUVM); VMMR3DECL(bool) HMR3IsUXActive(PUVM pUVM); VMMR3DECL(bool) HMR3IsSvmEnabled(PUVM pUVM); diff --git a/include/VBox/vmm/hm_svm.h b/include/VBox/vmm/hm_svm.h index 95d5bf59689e..e7a0c404425c 100644 --- a/include/VBox/vmm/hm_svm.h +++ b/include/VBox/vmm/hm_svm.h @@ -65,6 +65,8 @@ #define SVM_MSRPM_PAGES 2 /** Number of pages required for the IO permission bitmap. */ #define SVM_IOPM_PAGES 3 +/** Number of pages required for the AVIC per-VM (APIC access, logical, physical tables). */ +#define SVM_AVIC_PAGES 3 /** @} */ /* @@ -337,6 +339,20 @@ #endif /* !IN_REM_R3*/ +/** @name SVMVMCB.u64ExitInfo2 for AVIC Incomplete IPI. + * @{ + */ +#define SVM_EXIT2_INC_IPI_INDEX_MASK UINT64_C(0xfff) +#define SVM_EXIT2_INC_IPI_ID_SHIFT 32 +#define SVM_EXIT2_INC_IPI_INDEX_INVALID_INTR_TYPE 0 +#define SVM_EXIT2_INC_IPI_INDEX_TARGET_NOT_RUNNING 1 +#define SVM_EXIT2_INC_IPI_INDEX_INVALID_TARGET 2 +#define SVM_EXIT2_INC_IPI_INDEX_INVALID_PTR 3 +#define SVM_EXIT2_INC_IPI_INDEX_INVALID_IPI_VECTOR 4 +#define SVM_EXIT2_INC_IPI_INDEX_UNACCEL_IPI 5 +/** @} */ + + /** @name SVMVMCB.u64ExitInfo2 for task switches * @{ */ @@ -627,7 +643,8 @@ typedef union uint32_t u3Reserved : 3; uint32_t u1VIntrMasking : 1; /* V_INTR_MASKING */ uint32_t u1VGifEnable : 1; /* VGIF enable */ - uint32_t u5Reserved : 5; + uint32_t u4Reserved : 4; + uint32_t u1X2AvicEnable : 1; /* X2AVIC enable */ uint32_t u1AvicEnable : 1; /* AVIC enable */ uint32_t u8VIntrVector : 8; /* V_INTR_VECTOR */ uint32_t u24Reserved : 24; diff --git a/include/VBox/vmm/pdmapic.h b/include/VBox/vmm/pdmapic.h index feacab90e3b0..f1995e957dbd 100644 --- a/include/VBox/vmm/pdmapic.h +++ b/include/VBox/vmm/pdmapic.h @@ -362,6 +362,27 @@ typedef struct PDMAPICBACKENDR3 */ DECLR3CALLBACKMEMBER(VBOXSTRICTRC, pfnExportState, (PVMCPUCC pVCpu)); + /** + * Updates the APIC state after a write to the APIC page by hardware. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param offApicReg The APIC register offset in the backing page which got updated. + * + * @note This is a helper for AVIC/APICv when used on AMD or Intel. + */ + DECLR3CALLBACKMEMBER(VBOXSTRICTRC, pfnUpdateStateAfterWrite, (PVMCPUCC pVCpu, uint16_t offApicReg)); + + /** + * Sets the End-Of-Interrupt (EOI) register when the vector corresponding + * to the EOI is given. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector for the attempted EOI. + */ + DECLR3CALLBACKMEMBER(VBOXSTRICTRC, pfnSetEoiFast, (PVMCPUCC pVCpu, uint8_t uVector)); + /** @name Reserved for future (MBZ). * @{ */ DECLR3CALLBACKMEMBER(int, pfnReserved0, (void)); @@ -370,8 +391,6 @@ typedef struct PDMAPICBACKENDR3 DECLR3CALLBACKMEMBER(int, pfnReserved3, (void)); DECLR3CALLBACKMEMBER(int, pfnReserved4, (void)); DECLR3CALLBACKMEMBER(int, pfnReserved5, (void)); - DECLR3CALLBACKMEMBER(int, pfnReserved6, (void)); - DECLR3CALLBACKMEMBER(int, pfnReserved7, (void)); /** @} */ } PDMAPICBACKENDR3; /** Pointer to ring-3 APIC backend. */ @@ -624,10 +643,32 @@ typedef struct PDMAPICBACKENDR0 * Exports the APIC state. * * @returns Strict VBox status code. - * @param pVCpu The cross context virtual CPU structure. + * @param pVCpu The cross context virtual CPU structure. + * @param offApicReg The APIC register offset which was updated. */ DECLR0CALLBACKMEMBER(VBOXSTRICTRC, pfnExportState, (PVMCPUCC pVCpu)); + /** + * Updates the APIC state after a write to the APIC page by hardware. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param offApicReg The APIC register offset in the backing page which got updated. + * + * @note This is a helper for AVIC/APICv when used on AMD or Intel. + */ + DECLR0CALLBACKMEMBER(VBOXSTRICTRC, pfnUpdateStateAfterWrite, (PVMCPUCC pVCpu, uint16_t offApicReg)); + + /** + * Sets the End-Of-Interrupt (EOI) register when the vector corresponding + * to the EOI is given. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector for the attempted EOI. + */ + DECLR0CALLBACKMEMBER(VBOXSTRICTRC, pfnSetEoiFast, (PVMCPUCC pVCpu, uint8_t uVector)); + /** @name Reserved for future (MBZ). * @{ */ DECLR0CALLBACKMEMBER(int, pfnReserved0, (void)); @@ -636,8 +677,6 @@ typedef struct PDMAPICBACKENDR0 DECLR0CALLBACKMEMBER(int, pfnReserved3, (void)); DECLR0CALLBACKMEMBER(int, pfnReserved4, (void)); DECLR0CALLBACKMEMBER(int, pfnReserved5, (void)); - DECLR0CALLBACKMEMBER(int, pfnReserved6, (void)); - DECLR0CALLBACKMEMBER(int, pfnReserved7, (void)); /** @} */ } PDMAPICBACKENDR0; /** Pointer to ring-0 APIC backend. */ @@ -946,6 +985,8 @@ VMM_INT_DECL(int) PDMApicSetBaseMsr(PVMCPUCC pVCpu, uint64_t u64BaseMs VMM_INT_DECL(int) PDMApicGetInterrupt(PVMCPUCC pVCpu, uint8_t *pu8Vector, uint32_t *puSrcTag); VMM_INT_DECL(int) PDMApicBusDeliver(PVMCC pVM, uint8_t uDest, uint8_t uDestMode, uint8_t uDeliveryMode, uint8_t uVector, uint8_t uPolarity, uint8_t uTriggerMode, uint8_t uIoApicPin, uint32_t uTagSrc); +VMM_INT_DECL(VBOXSTRICTRC) PDMApicUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg); +VMM_INT_DECL(VBOXSTRICTRC) PDMApicSetEoiFast(PVMCPUCC pVCpu, uint8_t uIsrVector); #ifdef IN_RING0 VMM_INT_DECL(int) PDMR0ApicGetApicPageForCpu(PCVMCPUCC pVCpu, PRTHCPHYS pHCPhys, PRTR0PTR pR0Ptr, PRTR3PTR pR3Ptr); #endif diff --git a/include/iprt/asmdefs.mac b/include/iprt/asmdefs.mac index 3785ba4feb11..49b0b1d69f0e 100644 --- a/include/iprt/asmdefs.mac +++ b/include/iprt/asmdefs.mac @@ -638,7 +638,8 @@ SAFE_LABEL NAME(%1) ; Global exported marker which is DECLASM() compatible. %macro EXPORTEDNAME 1 %ifdef ASM_FORMAT_PE - export %1=NAME(%1) + ; HACK ALERT! export difference: nasm: passthru; yasm: word after '=', ignore the rest. (was: export %1=NAME(%1)) + export NAME(%1) %endif %ifdef __NASM__ %ifdef ASM_FORMAT_OMF @@ -739,7 +740,8 @@ GLOBALNAME_RAW NAME(%1), %2, %3, %4 ; %macro EXPORTEDNAME_RAW 3-4 %ifdef ASM_FORMAT_PE - export %2=%1 + ; HACK ALERT! export difference: nasm: passthru; yasm: word after '=', ignore the rest. (was: export %2=%1) + export %1 %endif %ifdef __NASM__ %ifdef ASM_FORMAT_OMF @@ -812,7 +814,8 @@ BEGINPROC_RAW NAME(%1), %2, CALC_PROC_SIZE(%1) %endif %ifdef RT_ASM_WITH_SEH64 %ifdef ASM_FORMAT_PE -export %2=%1 + ; HACK ALERT! export difference: nasm: passthru; yasm: word after '=', ignore the rest. (was: export %2=%1) +export %1 %endif global %1:function proc_frame %1 @@ -1406,6 +1409,33 @@ BEGINPROC_EXPORTED RT_NOCRT(%1), 0 %endmacro ; RT_NOCRT_BEGINPROC +;; @def MARK_OBJECT_RETPOLINE_SAFE +; Marks the object as retpoline safe for the Visual C++ linker. +; +; Repoline support in the Visual C++ toolchain is undocumented, but compiling +; with /guardretpoline or /d2guardretpoline and linking /guard:retpoline will +; add tables annotating the indirect jump and call sites. +; +; The .retplne section starts with a magic 'RetpolineV1\0' and is followed by +; zero or more variable length markup tables for code sections containing +; indirect jumps & calls. The first two dword are the table length and section +; number. This is then followed by dword pairs of type and offset. +; Type 9: Control-flow guarded indirect call. offset of the call instruction. +; +; For the time being we only emit and empty table, as getting the section +; number is probably not supported by the assemblers yet. +; +%macro MARK_OBJECT_RETPOLINE_SAFE 0 +%ifdef RT_ARCH_AMD64 + %ifdef ASM_FORMAT_PE + %ifdef IN_RING0 +section .retplne info align=1 ; flags=100200 + db 'RetpolineV1', 0 + %endif + %endif +%endif +%endmacro ; MARK_OBJECT_RETPOLINE_SAFE + ;; @def xCB ; The stack unit size / The register unit size. diff --git a/include/iprt/crypto/x509.h b/include/iprt/crypto/x509.h index 2673885d4ec5..93d8224fa9bd 100644 --- a/include/iprt/crypto/x509.h +++ b/include/iprt/crypto/x509.h @@ -1223,7 +1223,7 @@ RTDECL(PCRTCRX509CERTIFICATE) RTCrX509CertPathsGetPathNodeCert(RTCRX509CERTPATHS * valid for (starting now). * @param fKeyUsage Key usage mask: RTCRX509CERT_KEY_USAGE_F_XXX. * @param fExtKeyUsage Extended key usage mask: RTCRX509CERT_EKU_F_XXX. - * @param pvSubject Subject name. + * @param pszSubject Subject name. * @param pszCertFile Where to store the certificate (PEM formatting). * @param pszPrivateKeyFile Where to store the unencrypted private key (PEM * formatting). @@ -1231,7 +1231,7 @@ RTDECL(PCRTCRX509CERTIFICATE) RTCrX509CertPathsGetPathNodeCert(RTCRX509CERTPATHS * Optional. */ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType, uint32_t cBits, uint32_t cSecsValidFor, - uint32_t fKeyUsage, uint64_t fExtKeyUsage, const char *pvSubject, + uint32_t fKeyUsage, uint64_t fExtKeyUsage, const char *pszSubject, const char *pszCertFile, const char *pszPrivateKeyFile, PRTERRINFO pErrInfo); RT_C_DECLS_END diff --git a/include/iprt/localipc.h b/include/iprt/localipc.h index 16583484c6b0..f58baaea6e50 100644 --- a/include/iprt/localipc.h +++ b/include/iprt/localipc.h @@ -82,6 +82,15 @@ typedef RTLOCALIPCSESSION *PRTLOCALIPCSESSION; * any special chars or slashes. It will be morphed into a * unique platform specific identifier. * @param fFlags Flags, see RTLOCALIPC_FLAGS_*. + * + * @remarks For portable names, RTLOCALIPC_FLAGS_RESTRICT_TO_USER places the + * endpoint in a protected per-user or per-login-session namespace. + * The client must use RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER as well. + * On Windows, the portable pipe name includes the token session ID + * and logon LUID, while pipe access is limited to LocalSystem and the + * creating token's logon SID. Restricted connections also verify + * the peer session and account. Without the flag, the legacy global + * namespace and security behavior are used. */ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszName, uint32_t fFlags); @@ -89,8 +98,13 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa * @{ */ /** Native name, as apposed to a portable one. */ #define RTLOCALIPC_FLAGS_NATIVE_NAME RT_BIT_32(0) +/** Restrict the portable namespace and access to the login session creating the server. + * + * On Windows, the portable name is tagged with the token session ID and logon + * LUID, and the pipe DACL grants access to the token logon SID and LocalSystem. */ +#define RTLOCALIPC_FLAGS_RESTRICT_TO_USER RT_BIT_32(1) /** The mask of valid flags. */ -#define RTLOCALIPC_FLAGS_VALID_MASK UINT32_C(0x00000001) +#define RTLOCALIPC_FLAGS_VALID_MASK UINT32_C(0x00000003) /** @} */ /** @@ -108,6 +122,8 @@ RTDECL(int) RTLocalIpcServerDestroy(RTLOCALIPCSERVER hServer); * Grant the specified group access to the local IPC server socket. * * @returns IPRT status code. + * @retval VERR_NOT_SUPPORTED if this is not implemented on the host platform, + * including Windows. * @param hServer The server handle. * @param gid Group ID. */ @@ -128,6 +144,8 @@ RTDECL(int) RTLocalIpcServerSetAccessMode(RTLOCALIPCSERVER hServer, RTFMODE fMod * @returns IPRT status code. * @retval VINF_SUCCESS on success and *phClientSession containing the session handle. * @retval VERR_CANCELLED if the listening was interrupted by RTLocalIpcServerCancel(). + * @retval VERR_TRY_AGAIN if a restricted Windows peer was rejected before a + * session could be returned. The server remains usable. * * @param hServer The server handle. * @param phClientSession Where to store the client session handle on success. @@ -162,8 +180,18 @@ RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *p * @{ */ /** Native name, as apposed to a portable one. */ #define RTLOCALIPC_C_FLAGS_NATIVE_NAME RT_BIT_32(0) +/** Allow the server to identify the client. + * + * On Windows this selects the SECURITY_IDENTIFICATION quality-of-service + * level instead of the default SECURITY_ANONYMOUS level. */ +#define RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION RT_BIT_32(1) +/** Resolve a portable name in the protected per-user or per-login-session + * namespace. On Windows, this uses the current token session ID and logon + * LUID and verifies the server's session and account. The server must use + * RTLOCALIPC_FLAGS_RESTRICT_TO_USER. */ +#define RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER RT_BIT_32(2) /** The mask of valid flags. */ -#define RTLOCALIPC_C_FLAGS_VALID_MASK UINT32_C(0x00000001) +#define RTLOCALIPC_C_FLAGS_VALID_MASK UINT32_C(0x00000007) /** @} */ /** @@ -315,6 +343,25 @@ RTDECL(int) RTLocalIpcSessionCancel(RTLOCALIPCSESSION hSession); */ RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS pProcess); +/** + * Verifies that the other party belongs to the user running this process. + * + * @returns IPRT status code. + * @retval VINF_SUCCESS if the peer belongs to the same user. + * @retval VERR_ACCESS_DENIED if the peer belongs to another user or cannot + * present an identity accepted by this session. + * @retval VERR_CANCELLED if the operation was cancelled by RTLocalIpcSessionCancel. + * @retval VERR_NOT_SUPPORTED if this is not implemented on the host platform. + * + * @param hSession The session handle. + * + * @remarks On Windows, the server must first read data sent by the client on + * the session, and the client must connect with + * RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION, for the server-side check + * to succeed. The client-side check verifies the named-pipe owner. + */ +RTDECL(int) RTLocalIpcSessionVerifySameUser(RTLOCALIPCSESSION hSession); + /** * Query the user ID of the other party. * @@ -351,4 +398,3 @@ RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGi RT_C_DECLS_END #endif /* !IPRT_INCLUDED_localipc_h */ - diff --git a/include/iprt/mangling.h b/include/iprt/mangling.h index b6e6d6dddab8..bd0ecec4bd78 100644 --- a/include/iprt/mangling.h +++ b/include/iprt/mangling.h @@ -1487,7 +1487,7 @@ # define RTLocalIpcServerCreate RT_MANGLER(RTLocalIpcServerCreate) # define RTLocalIpcServerDestroy RT_MANGLER(RTLocalIpcServerDestroy) # define RTLocalIpcServerGrantGroupAccess RT_MANGLER(RTLocalIpcServerGrantGroupAccess) -# define RTLocalIpcServerSetAccessMode RT_MANGLER(RTLocalIpcServerSetAccessMode); +# define RTLocalIpcServerSetAccessMode RT_MANGLER(RTLocalIpcServerSetAccessMode) # define RTLocalIpcServerCancel RT_MANGLER(RTLocalIpcServerCancel) # define RTLocalIpcServerListen RT_MANGLER(RTLocalIpcServerListen) # define RTLocalIpcSessionConnect RT_MANGLER(RTLocalIpcSessionConnect) @@ -1501,6 +1501,7 @@ # define RTLocalIpcSessionFlush RT_MANGLER(RTLocalIpcSessionFlush) # define RTLocalIpcSessionWaitForData RT_MANGLER(RTLocalIpcSessionWaitForData) # define RTLocalIpcSessionQueryProcess RT_MANGLER(RTLocalIpcSessionQueryProcess) +# define RTLocalIpcSessionVerifySameUser RT_MANGLER(RTLocalIpcSessionVerifySameUser) # define RTLocalIpcSessionQueryUserId RT_MANGLER(RTLocalIpcSessionQueryUserId) # define RTLocalIpcSessionQueryGroupId RT_MANGLER(RTLocalIpcSessionQueryGroupId) # define RTLocaleQueryLocaleName RT_MANGLER(RTLocaleQueryLocaleName) diff --git a/include/iprt/x86.h b/include/iprt/x86.h index 07885368277d..8f657106d280 100644 --- a/include/iprt/x86.h +++ b/include/iprt/x86.h @@ -2395,6 +2395,8 @@ typedef const X86MTRRVAR *PCX86MTRRVAR; /** SVM - VM_HSAVE_PA - Physical address for saving and restoring * host state during world switch. */ #define MSR_K8_VM_HSAVE_PA UINT32_C(0xc0010117) +/** SVM - AVIC doorbell register. */ +#define MSR_AMD_AVIC_DOORBELL UINT32_C(0xc001011b) /** Virtualized speculation control for AMD processors. * diff --git a/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk b/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk index 3caf8efef5db..d0319c16cf78 100644 --- a/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk +++ b/src/VBox/Additions/common/VBoxGuest/lib/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114575 2026-06-30 15:58:06Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the common guest addition code library. # @@ -164,8 +164,7 @@ ifdef VBOX_WITH_SHARED_CLIPBOARD VBoxGuestR3LibClipboard.cpp ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS VBoxGuestR3Lib_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-mime.cpp + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp endif endif ifdef VBOX_WITH_SHARED_FOLDERS diff --git a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp index 2cf64083f875..0c2ca029af9f 100644 --- a/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp +++ b/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxGuestR3LibClipboard.cpp 114839 2026-07-31 13:09:02Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxGuestR3LibClipboard.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * VBoxGuestR3Lib - Ring-3 Support Library for VirtualBox guest additions, Shared Clipboard. */ @@ -1027,7 +1027,9 @@ static int vbglR3ClipboardTransferSendStatusEx(PVBGLR3SHCLCMDCTX pCtx, uint64_t * * @returns VBox status code. * @param pCtx Shared Clipboard command context to use for the connection. - * @param pTransfer Transfer of report to reply to. + * @param pTransfer Transfer to report status for. Optional when + * replying to a context for which no local + * transfer exists. * @param uStatus Tranfer status to reply. * @param rcTransfer Result code (rc) to reply. */ @@ -1035,9 +1037,31 @@ VBGLR3DECL(int) VbglR3ClipboardTransferSendStatus(PVBGLR3SHCLCMDCTX pCtx, PSHCLT SHCLTRANSFERSTATUS uStatus, int rcTransfer) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); - RT_NOREF(pTransfer); /* Currently not used (yet). */ - int rc = vbglR3ClipboardTransferSendStatusEx(pCtx, pCtx->idContext, uStatus, rcTransfer); + uint64_t idContext = pCtx->idContext; + if (pTransfer) + { + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + if (!ShClTransferIdIsValid(idTransfer)) + { + LogRelMax(16, ("Shared Clipboard: Cannot send status %s for invalid transfer ID %RU16\n", + ShClTransferStatusToStr(uStatus), idTransfer)); + return VERR_INVALID_PARAMETER; + } + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + if (idSession == 0 || idSession == NIL_SHCLSESSIONID) + { + LogRelMax(16, ("Shared Clipboard: Cannot send status %s for transfer %RU16 without a valid service session\n", + ShClTransferStatusToStr(uStatus), idTransfer)); + return VERR_INVALID_PARAMETER; + } + SHCLEVENTID const idEvent = VBOX_SHCL_CONTEXTID_GET_SESSION(idContext) == idSession + && VBOX_SHCL_CONTEXTID_GET_TRANSFER(idContext) == idTransfer + ? VBOX_SHCL_CONTEXTID_GET_EVENT(idContext) : 0; + idContext = VBOX_SHCL_CONTEXTID_MAKE(idSession, idTransfer, idEvent); + } + + int rc = vbglR3ClipboardTransferSendStatusEx(pCtx, idContext, uStatus, rcTransfer); LogFlowFuncLeaveRC(rc); return rc; @@ -2083,6 +2107,82 @@ static DECLCALLBACK(int) vbglR3ClipboardTransferIfaceHGObjRead(PSHCLTXPROVIDERCT return rc; } +/** Retires every transfer belonging to the current host service session. */ +static int vbglR3ClipboardTransferCtxRetireAll(PSHCLTRANSFERCTX pTransferCtx) +{ + for (;;) + { + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferLast(pTransferCtx); + if (!pTransfer) + return VINF_SUCCESS; + + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + int rc = ShClTransferCtxUnregisterById(pTransferCtx, idTransfer); + if (RT_SUCCESS(rc)) + rc = ShClTransferDestroy(pTransfer); + if (RT_FAILURE(rc)) + { + LogRel(("Shared Clipboard: Retiring stale transfer %RU16 failed with %Rrc\n", idTransfer, rc)); + return rc; + } + } +} + +/** + * Binds a transfer context to the service session carried by an incoming + * transfer-status context ID. + * + * Note! The service session is not part of feature negotiation, so the first + * transfer-status message is the earliest authoritative source. A changed + * session invalidates every transfer created by the previous host service + * incarnation (for example after restoring a VM). + */ +static int vbglR3ClipboardTransferCtxEnsureSession(PSHCLTRANSFERCTX pTransferCtx, uint64_t idContext) +{ + SHCLSESSIONID const idSession = VBOX_SHCL_CONTEXTID_GET_SESSION(idContext); + if ( idSession == 0 + || idSession == NIL_SHCLSESSIONID) + return VERR_INVALID_CONTEXT; + + if (pTransferCtx->idSession == idSession) + return VINF_SUCCESS; + + SHCLSESSIONID const idOldSession = pTransferCtx->idSession; + int rc = VINF_SUCCESS; + uint32_t const cTransfers = ShClTransferCtxGetTotalTransfers(pTransferCtx); + if (cTransfers) + { + LogRel(("Shared Clipboard: Service session changed from %RU16 to %RU16; retiring %RU32 stale transfer(s)\n", + idOldSession, idSession, cTransfers)); + rc = vbglR3ClipboardTransferCtxRetireAll(pTransferCtx); + } + else if (idOldSession != NIL_SHCLSESSIONID) + LogRel2(("Shared Clipboard: Service session changed from %RU16 to %RU16\n", idOldSession, idSession)); + + if (RT_SUCCESS(rc)) + rc = ShClTransferCtxBeginSession(pTransferCtx, idSession); + if (RT_SUCCESS(rc)) + LogRel2(("Shared Clipboard: Bound guest transfers to service session %RU16\n", idSession)); + + return rc; +} + +/** Validates that an incoming transfer command belongs to the active service session. */ +static int vbglR3ClipboardTransferCtxCheckSession(PSHCLTRANSFERCTX pTransferCtx, uint64_t idContext) +{ + SHCLSESSIONID const idSession = VBOX_SHCL_CONTEXTID_GET_SESSION(idContext); + if ( idSession == 0 + || idSession == NIL_SHCLSESSIONID + || pTransferCtx->idSession != idSession) + { + LogRel(("Shared Clipboard: Rejecting transfer command for stale service session %RU16 (active %RU16)\n", + idSession, pTransferCtx->idSession)); + return VERR_INVALID_CONTEXT; + } + + return VINF_SUCCESS; +} + /** * Creates (and registers) a transfer on the guest side. * @@ -2140,9 +2240,8 @@ static int vbglR3ClipboardTransferInit(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFER /* Assign local provider first and overwrite interface methods below if needed. */ ShClTransferProviderLocalQueryInterface(&Provider); - /* If this is a read transfer (reading data from host), set the interface to use - * our VbglR3 routines here. */ - if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) /* Host -> Guest */ + /* Host-to-guest transfers use the remote HGCM provider. */ + if (enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST) { Provider.Interface.pfnRootListRead = vbglR3ClipboardTransferIfaceHGRootListRead; @@ -2155,7 +2254,7 @@ static int vbglR3ClipboardTransferInit(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFER Provider.Interface.pfnObjClose = vbglR3ClipboardTransferIfaceHGObjClose; Provider.Interface.pfnObjRead = vbglR3ClipboardTransferIfaceHGObjRead; } - else if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) /* Guest -> Host */ + else if (enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST) { /* Uses the local provider assigned above. */ } @@ -2175,7 +2274,7 @@ static int vbglR3ClipboardTransferInit(PVBGLR3SHCLCMDCTX pCmdCtx, PSHCLTRANSFER if (RT_SUCCESS(rc)) { LogRel2(("Shared Clipboard: Transfer %RU32 (%s) successfully initialized\n", - idTransfer, enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "host -> guest" : "guest -> host")); + idTransfer, enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST ? "host -> guest" : "guest -> host")); } else LogRel(("Shared Clipboard: Unable to initialize transfer %RU16, rc=%Rrc\n", idTransfer, rc)); @@ -2290,7 +2389,7 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); AssertPtrReturn(pEvent, VERR_INVALID_POINTER); - LogFunc(("Handling idMsg=%RU32 (%s), cParms=%RU32\n", idMsg, ShClHostMsgToStr(idMsg), cParms)); + LogFunc(("Handling idMsg=%RU32 (%s), cParms=%RU32\n", idMsg, ShClSvcHostMsgToStr(idMsg), cParms)); int rc; if (!pCmdCtx->fUseLegacyProtocol) @@ -2304,12 +2403,20 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, SHCLTRANSFERDIR enmDir; SHCLTRANSFERREPORT transferReport; rc = VbglR3ClipboarTransferStatusRecv(pCmdCtx, &enmDir, &transferReport); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxEnsureSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { const SHCLTRANSFERID idTransfer = VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->idContext); - LogRel2(("Shared Clipboard: Received status %s (%Rrc) for transfer %RU16\n", - ShClTransferStatusToStr(transferReport.uStatus), transferReport.rc, idTransfer)); + if (transferReport.uStatus == SHCLTRANSFERSTATUS_ERROR) + LogRelMax(16, ("Shared Clipboard: Received error status %Rrc for transfer %RU16 in session %RU16\n", + transferReport.rc, idTransfer, + VBOX_SHCL_CONTEXTID_GET_SESSION(pCmdCtx->idContext))); + else + LogRel2(("Shared Clipboard: Received status %s (%Rrc) for transfer %RU16 in session %RU16\n", + ShClTransferStatusToStr(transferReport.uStatus), transferReport.rc, idTransfer, + VBOX_SHCL_CONTEXTID_GET_SESSION(pCmdCtx->idContext))); SHCLSOURCE enmSource = SHCLSOURCE_INVALID; @@ -2317,7 +2424,7 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, { case SHCLTRANSFERSTATUS_REQUESTED: /* Only used for H->G transfers. */ { - enmDir = SHCLTRANSFERDIR_FROM_REMOTE; + enmDir = SHCLTRANSFERDIR_HOST_TO_GUEST; enmSource = SHCLSOURCE_REMOTE; /* The host acknowledged our request to create a new transfer. @@ -2341,21 +2448,15 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, case SHCLTRANSFERSTATUS_INITIALIZED: { - /* The host announces the transfer direction from its point of view, so inverse the direction here. */ - if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) /* H -> G */ - { - enmDir = SHCLTRANSFERDIR_FROM_REMOTE; + /* Directions are absolute on the wire; only the source is endpoint-relative. */ + if (enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST) enmSource = SHCLSOURCE_REMOTE; - } - else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) /* G -> H */ - { - enmDir = SHCLTRANSFERDIR_TO_REMOTE; + else if (enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST) enmSource = SHCLSOURCE_LOCAL; - } else AssertFailedBreakStmt(rc = VERR_INVALID_PARAMETER); - if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */ + if (enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST) { /* The host reported INITIALIZED for the transfer. * So init our local transfer as well now. */ @@ -2371,12 +2472,14 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, int rc2 = VbglR3ClipboardTransferSendStatus(pCmdCtx, pTransfer, SHCLTRANSFERSTATUS_ERROR, rc); AssertRC(rc2); + if (RT_SUCCESS(rc2)) + fErrorSent = true; } } else rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; } - else if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) /* G->H */ + else if (enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST) { /* The host reported the INITIALIZED status together with the transfer ID. * So create a local transfer here with that ID. */ @@ -2444,6 +2547,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, /** @todo Validate / handle fRoots. */ + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2467,6 +2572,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, uint64_t uIndex; uint32_t fInfo; rc = VbglR3ClipboardTransferRootListEntryReadReq(pCmdCtx, &uIndex, &fInfo); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2491,6 +2598,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, if (RT_SUCCESS(rc)) { rc = VbglR3ClipboardTransferListOpenRecv(pCmdCtx, &openParmsList); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2517,6 +2626,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, { SHCLLISTHANDLE hList; rc = VbglR3ClipboardTransferListCloseRecv(pCmdCtx, &hList); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2540,6 +2651,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; uint32_t fFlags = 0; rc = VbglR3ClipboardTransferListHdrReadRecvReq(pCmdCtx, &hList, &fFlags); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2566,6 +2679,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, SHCLLISTHANDLE hList; uint32_t fInfo; rc = VbglR3ClipboardTransferListEntryReadRecvReq(pCmdCtx, &hList, &fInfo); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2603,6 +2718,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, if (RT_SUCCESS(rc)) { rc = VbglR3ClipboardTransferObjOpenRecv(pCmdCtx, &openParms); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2627,6 +2744,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, { SHCLOBJHANDLE hObj; rc = VbglR3ClipboardTransferObjCloseRecv(pCmdCtx, &hObj); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2649,6 +2768,8 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, uint32_t cbBuf; uint32_t fFlags; rc = VbglR3ClipboardTransferObjReadRecv(pCmdCtx, &hObj, &cbBuf, &fFlags); + if (RT_SUCCESS(rc)) + rc = vbglR3ClipboardTransferCtxCheckSession(pTransferCtx, pCmdCtx->idContext); if (RT_SUCCESS(rc)) { PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(pTransferCtx, @@ -2688,9 +2809,15 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNextEx(uint32_t idMsg, uint32_t cParms, } } + /* A stale context must fail closed locally. Echoing it in an error + * reply would address a transfer owned by another service session. */ if ( !fErrorSent - && RT_FAILURE(rc)) + && RT_FAILURE(rc) + && rc != VERR_INVALID_CONTEXT) { + LogRelMax(16, ("Shared Clipboard: Handling host message %s for context %#RX64 failed with %Rrc\n", + ShClSvcHostMsgToStr(idMsg), pCmdCtx->idContext, rc)); + /* Report transfer-specific error back to the host. */ int rc2 = vbglR3ClipboardTransferSendStatusEx(pCmdCtx, pCmdCtx->idContext, SHCLTRANSFERSTATUS_ERROR, rc); AssertRC(rc2); @@ -2742,7 +2869,7 @@ VBGLR3DECL(int) VbglR3ClipboardEventGetNext(uint32_t idMsg, uint32_t cParms, PVB int rc; if (!pCtx->fUseLegacyProtocol) { - LogFunc(("Handling idMsg=%RU32 (%s)\n", idMsg, ShClHostMsgToStr(idMsg))); + LogFunc(("Handling idMsg=%RU32 (%s)\n", idMsg, ShClSvcHostMsgToStr(idMsg))); switch (idMsg) { case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: diff --git a/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk b/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk index 083749184b28..2d9f1aab5a99 100644 --- a/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk +++ b/src/VBox/Additions/darwin/VBoxClient/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114381 2026-06-16 06:43:15Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the VirtualBox Guest Addition Darwin Client. # @@ -49,6 +49,12 @@ ifdef VBOX_WITH_SHARED_CLIPBOARD VBoxClientClipboardGuestToHost.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp + ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + VBoxClient_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS VBOX_WITH_SHARED_CLIPBOARD_GUEST + VBoxClient_SOURCES += \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp + endif endif VBoxClient_LDFLAGS = -framework IOKit -framework ApplicationServices diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.cpp b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.cpp index fc6fcdfdfaf4..dbb3388ae92b 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.cpp +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxDX.cpp 114634 2026-07-07 15:34:54Z vitali.pelenjow@oracle.com $ */ +/* $Id: VBoxDX.cpp 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox D3D user mode driver. */ @@ -704,16 +704,20 @@ void vboxDXStorePatchLocation(PVBOXDX_DEVICE pDevice, void *pvPatch, PVBOXDXKMRE pKMResource->LastReferencedFenceValue = pDevice->ContextMonitoring.CurrentFenceValue; } - D3DDDI_PATCHLOCATIONLIST *pPatchLocation = &pDevice->pPatchLocationList[pDevice->cPatchLocations]; - pPatchLocation->AllocationIndex = idxAllocation; - pPatchLocation->Value = 0; - pPatchLocation->DriverId = DriverId == 0 - ? pKMResource->AllocationDesc.enmAllocationType - : DriverId; - pPatchLocation->AllocationOffset = offAllocation; - pPatchLocation->PatchOffset = (uintptr_t)pvPatch - (uintptr_t)pDevice->pCommandBuffer; - pPatchLocation->SplitOffset = pDevice->cbCommandBuffer; - ++pDevice->cPatchLocations; + /* Add an optional patch location. */ + if (pvPatch) + { + D3DDDI_PATCHLOCATIONLIST *pPatchLocation = &pDevice->pPatchLocationList[pDevice->cPatchLocations]; + pPatchLocation->AllocationIndex = idxAllocation; + pPatchLocation->Value = 0; + pPatchLocation->DriverId = DriverId == 0 + ? pKMResource->AllocationDesc.enmAllocationType + : DriverId; + pPatchLocation->AllocationOffset = offAllocation; + pPatchLocation->PatchOffset = (uintptr_t)pvPatch - (uintptr_t)pDevice->pCommandBuffer; + pPatchLocation->SplitOffset = pDevice->cbCommandBuffer; + ++pDevice->cPatchLocations; + } /* Move the KM resource to the head of the resource list. */ RTListNodeRemove(&pKMResource->nodeResource); @@ -3538,7 +3542,8 @@ void vboxDXCreateShaderResourceView(PVBOXDX_DEVICE pDevice, PVBOXDXSHADERRESOURC void vboxDXGenMips(PVBOXDX_DEVICE pDevice, PVBOXDXSHADERRESOURCEVIEW pShaderResourceView) { - vgpu10GenMips(pDevice, pShaderResourceView->uShaderResourceViewId); + vgpu10GenMips(pDevice, pShaderResourceView->uShaderResourceViewId, + vboxDXGetKMResource(pShaderResourceView->pResource)); } @@ -3546,7 +3551,8 @@ void vboxDXDestroyShaderResourceView(PVBOXDX_DEVICE pDevice, PVBOXDXSHADERRESOUR { RTListNodeRemove(&pShaderResourceView->nodeView); - vgpu10DestroyShaderResourceView(pDevice, pShaderResourceView->uShaderResourceViewId); + vgpu10DestroyShaderResourceView(pDevice, pShaderResourceView->uShaderResourceViewId, + vboxDXGetKMResource(pShaderResourceView->pResource)); RTHandleTableFree(pDevice->hHTShaderResourceView, pShaderResourceView->uShaderResourceViewId); } @@ -3603,13 +3609,15 @@ void vboxDXCreateRenderTargetView(PVBOXDX_DEVICE pDevice, PVBOXDXRENDERTARGETVIE void vboxDXClearRenderTargetView(PVBOXDX_DEVICE pDevice, PVBOXDXRENDERTARGETVIEW pRenderTargetView, const FLOAT ColorRGBA[4]) { - vgpu10ClearRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId, ColorRGBA); + vgpu10ClearRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId, + vboxDXGetKMResource(pRenderTargetView->pResource), ColorRGBA); } void vboxDXClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, PVBOXDXRENDERTARGETVIEW pRenderTargetView, const FLOAT Color[4], const D3D10_DDI_RECT *pRect, UINT NumRects) { - vgpu10ClearRenderTargetViewRegion(pDevice, pRenderTargetView->uRenderTargetViewId, Color, pRect, NumRects); + vgpu10ClearRenderTargetViewRegion(pDevice, pRenderTargetView->uRenderTargetViewId, + vboxDXGetKMResource(pRenderTargetView->pResource), Color, pRect, NumRects); } @@ -3625,7 +3633,8 @@ void vboxDXDestroyRenderTargetView(PVBOXDX_DEVICE pDevice, PVBOXDXRENDERTARGETVI RTListNodeRemove(&pRenderTargetView->nodeView); - vgpu10DestroyRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId); + vgpu10DestroyRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId, + vboxDXGetKMResource(pRenderTargetView->pResource)); RTHandleTableFree(pDevice->hHTRenderTargetView, pRenderTargetView->uRenderTargetViewId); } @@ -3679,7 +3688,8 @@ void vboxDXClearDepthStencilView(PVBOXDX_DEVICE pDevice, PVBOXDXDEPTHSTENCILVIEW svgaFlags |= SVGA3D_CLEAR_DEPTH; if (Flags & D3D10_DDI_CLEAR_STENCIL) svgaFlags |= SVGA3D_CLEAR_STENCIL; - vgpu10ClearDepthStencilView(pDevice, svgaFlags, Stencil, pDepthStencilView->uDepthStencilViewId, Depth); + vgpu10ClearDepthStencilView(pDevice, svgaFlags, Stencil, pDepthStencilView->uDepthStencilViewId, + vboxDXGetKMResource(pDepthStencilView->pResource), Depth); } @@ -3692,7 +3702,8 @@ void vboxDXDestroyDepthStencilView(PVBOXDX_DEVICE pDevice, PVBOXDXDEPTHSTENCILVI RTListNodeRemove(&pDepthStencilView->nodeView); - vgpu10DestroyDepthStencilView(pDevice, pDepthStencilView->uDepthStencilViewId); + vgpu10DestroyDepthStencilView(pDevice, pDepthStencilView->uDepthStencilViewId, + vboxDXGetKMResource(pDepthStencilView->pResource)); RTHandleTableFree(pDevice->hHTDepthStencilView, pDepthStencilView->uDepthStencilViewId); } @@ -3724,17 +3735,21 @@ void vboxDXSetRenderTargets(PVBOXDX_DEVICE pDevice, PVBOXDXDEPTHSTENCILVIEW pDep pDevice->pipeline.pDepthStencilView = pDepthStencilView; - /* Fetch view ids.*/ + /* Fetch view ids and kernel mode resources. */ uint32_t aRenderTargetViewIds[SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS]; + PVBOXDXKMRESOURCE aViewKMResources[SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS]; for (unsigned i = 0; i < NumRTVs; ++i) { PVBOXDXRENDERTARGETVIEW pRenderTargetView = papRenderTargetViews[i]; aRenderTargetViewIds[i] = pRenderTargetView ? pRenderTargetView->uRenderTargetViewId : SVGA3D_INVALID_ID; + aViewKMResources[i] = pRenderTargetView ? vboxDXGetKMResource(pRenderTargetView->pResource) : NULL; } uint32_t DepthStencilViewId = pDepthStencilView ? pDepthStencilView->uDepthStencilViewId : SVGA3D_INVALID_ID; - vgpu10SetRenderTargets(pDevice, DepthStencilViewId, NumRTVs, ClearSlots, aRenderTargetViewIds); + vgpu10SetRenderTargets(pDevice, DepthStencilViewId, pDepthStencilView ? vboxDXGetKMResource(pDepthStencilView->pResource) : NULL, + NumRTVs, ClearSlots, aRenderTargetViewIds, + aViewKMResources); } @@ -3759,15 +3774,17 @@ void vboxDXSetShaderResourceViews(PVBOXDX_DEVICE pDevice, SVGA3dShaderType enmSh } pSRVS->cShaderResourceView = cSRV; - /* Fetch View ids. */ + /* Fetch View ids and kernel mode resources. */ uint32_t aViewIds[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; + PVBOXDXKMRESOURCE aViewKMResources[D3D11_COMMONSHADER_INPUT_RESOURCE_SLOT_COUNT]; for (unsigned i = 0; i < NumViews; ++i) { VBOXDXSHADERRESOURCEVIEW *pView = papViews[i]; aViewIds[i] = pView ? pView->uShaderResourceViewId : SVGA3D_INVALID_ID; + aViewKMResources[i] = pView ? vboxDXGetKMResource(pView->pResource) : NULL; } - vgpu10SetShaderResources(pDevice, enmShaderType, StartSlot, NumViews, aViewIds); + vgpu10SetShaderResources(pDevice, enmShaderType, StartSlot, NumViews, aViewIds, aViewKMResources); } @@ -3878,7 +3895,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pShaderResourceView->fDefined) { - vgpu10DestroyShaderResourceView(pDevice, pShaderResourceView->uShaderResourceViewId); + vgpu10DestroyShaderResourceView(pDevice, pShaderResourceView->uShaderResourceViewId, + vboxDXGetKMResource(pShaderResourceView->pResource)); pShaderResourceView->fDefined = false; } } @@ -3888,7 +3906,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pRenderTargetView->fDefined) { - vgpu10DestroyRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId); + vgpu10DestroyRenderTargetView(pDevice, pRenderTargetView->uRenderTargetViewId, + vboxDXGetKMResource(pRenderTargetView->pResource)); pRenderTargetView->fDefined = false; } } @@ -3898,7 +3917,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pDepthStencilView->fDefined) { - vgpu10DestroyDepthStencilView(pDevice, pDepthStencilView->uDepthStencilViewId); + vgpu10DestroyDepthStencilView(pDevice, pDepthStencilView->uDepthStencilViewId, + vboxDXGetKMResource(pDepthStencilView->pResource)); pDepthStencilView->fDefined = false; } } @@ -3908,7 +3928,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pUnorderedAccessView->fDefined) { - vgpu10DestroyUAView(pDevice, pUnorderedAccessView->uUnorderedAccessViewId); + vgpu10DestroyUAView(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, + vboxDXGetKMResource(pUnorderedAccessView->pResource)); pUnorderedAccessView->fDefined = false; } } @@ -3918,7 +3939,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pVDOV->fDefined) { - vgpu10DestroyVideoDecoderOutputView(pDevice, pVDOV->uVideoDecoderOutputViewId); + vgpu10DestroyVideoDecoderOutputView(pDevice, pVDOV->uVideoDecoderOutputViewId, + vboxDXGetKMResource(pVDOV->pResource)); pVDOV->fDefined = false; } } @@ -3928,7 +3950,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pVPIV->fDefined) { - vgpu10DestroyVideoProcessorInputView(pDevice, pVPIV->uVideoProcessorInputViewId); + vgpu10DestroyVideoProcessorInputView(pDevice, pVPIV->uVideoProcessorInputViewId, + vboxDXGetKMResource(pVPIV->pResource)); pVPIV->fDefined = false; } } @@ -3939,7 +3962,8 @@ static void vboxDXUndefineResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE { if (pVPOV->fDefined) { - vgpu10DestroyVideoProcessorOutputView(pDevice, pVPOV->uVideoProcessorOutputViewId); + vgpu10DestroyVideoProcessorOutputView(pDevice, pVPOV->uVideoProcessorOutputViewId, + vboxDXGetKMResource(pVPOV->pResource)); pVPOV->fDefined = false; } } @@ -4051,7 +4075,7 @@ static void vboxdxUnbindResourceViews(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE p if (pSRVS->apShaderResourceView[i] == pShaderResourceView) { uint32_t id = SVGA3D_INVALID_ID; - vgpu10SetShaderResources(pDevice, enmShaderType, i, 1, &id); + vgpu10SetShaderResources(pDevice, enmShaderType, i, 1, &id, NULL); } } } @@ -4146,7 +4170,7 @@ HRESULT vboxDXRotateResourceIdentities(PVBOXDX_DEVICE pDevice, UINT cResources, if (fBound) { - vgpu10SetRenderTargets(pDevice, SVGA3D_INVALID_ID, 0, SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS, NULL); + vgpu10SetRenderTargets(pDevice, SVGA3D_INVALID_ID, NULL, 0, SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS, NULL, NULL); break; } } @@ -4385,44 +4409,68 @@ void vboxDXDestroyUnorderedAccessView(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDAC { RTListNodeRemove(&pUnorderedAccessView->nodeView); - vgpu10DestroyUAView(pDevice, pUnorderedAccessView->uUnorderedAccessViewId); + vgpu10DestroyUAView(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, + vboxDXGetKMResource(pUnorderedAccessView->pResource)); RTHandleTableFree(pDevice->hHTUnorderedAccessView, pUnorderedAccessView->uUnorderedAccessViewId); } void vboxDXClearUnorderedAccessViewUint(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView, const UINT Values[4]) { - vgpu10ClearUAViewUint(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, Values); + vgpu10ClearUAViewUint(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, + vboxDXGetKMResource(pUnorderedAccessView->pResource), Values); } void vboxDXClearUnorderedAccessViewFloat(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView, const FLOAT Values[4]) { - vgpu10ClearUAViewFloat(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, Values); + vgpu10ClearUAViewFloat(pDevice, pUnorderedAccessView->uUnorderedAccessViewId, + vboxDXGetKMResource(pUnorderedAccessView->pResource), Values); } -void vboxDXCsSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const uint32_t *paViewIds, const UINT* pUAVInitialCounts) +void vboxDXCsSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const PVBOXDXUNORDEREDACCESSVIEW *papViews, const UINT* pUAVInitialCounts) { - for (unsigned i = 0; i < NumViews; ++i) + AssertReturnVoidStmt( NumViews <= SVGA3D_DX11_1_MAX_UAVIEWS + && StartSlot < SVGA3D_DX11_1_MAX_UAVIEWS + && NumViews + StartSlot <= SVGA3D_DX11_1_MAX_UAVIEWS, + vboxDXDeviceSetError(pDevice, E_INVALIDARG)); + + /* Fetch View ids and kernel mode resources. */ + uint32_t aViewIds[SVGA3D_DX11_1_MAX_UAVIEWS]; + PVBOXDXKMRESOURCE aViewKMResources[SVGA3D_DX11_1_MAX_UAVIEWS]; + for (UINT i = 0; i < NumViews; ++i) { - if (paViewIds[i] != SVGA3D_INVALID_ID) - vgpu10SetStructureCount(pDevice, paViewIds[i], pUAVInitialCounts[i]); + VBOXDXUNORDEREDACCESSVIEW *pView = papViews[i]; + aViewIds[i] = pView ? pView->uUnorderedAccessViewId : SVGA3D_INVALID_ID; + aViewKMResources[i] = pView ? vboxDXGetKMResource(pView->pResource) : NULL; } - vgpu10SetCSUAViews(pDevice, StartSlot, NumViews, paViewIds); + for (UINT i = 0; i < NumViews; ++i) + { + if (aViewIds[i] != SVGA3D_INVALID_ID) + vgpu10SetStructureCount(pDevice, aViewIds[i], aViewKMResources[i], pUAVInitialCounts[i]); + } + + vgpu10SetCSUAViews(pDevice, StartSlot, NumViews, aViewIds, aViewKMResources); } void vboxDXSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const PVBOXDXUNORDEREDACCESSVIEW *papViews, const UINT *pUAVInitialCounts) { - /* Fetch view ids.*/ + AssertReturnVoidStmt( NumViews <= D3D11_1_UAV_SLOT_COUNT + && StartSlot <= SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS, + vboxDXDeviceSetError(pDevice, E_INVALIDARG)); + + /* Fetch View ids and kernel mode resources. */ uint32_t aViewIds[D3D11_1_UAV_SLOT_COUNT]; - for (unsigned i = 0; i < NumViews; ++i) + PVBOXDXKMRESOURCE aViewKMResources[SVGA3D_DX11_1_MAX_UAVIEWS]; + for (UINT i = 0; i < NumViews; ++i) { - PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView = papViews[i]; - aViewIds[i] = pUnorderedAccessView ? pUnorderedAccessView->uUnorderedAccessViewId : SVGA3D_INVALID_ID; + PVBOXDXUNORDEREDACCESSVIEW pView = papViews[i]; + aViewIds[i] = pView ? pView->uUnorderedAccessViewId : SVGA3D_INVALID_ID; + aViewKMResources[i] = pView ? vboxDXGetKMResource(pView->pResource) : NULL; } UINT NumViewsToSet; @@ -4439,13 +4487,13 @@ void vboxDXSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT pDevice->pipeline.cUnorderedAccessViews = NumViews; - for (unsigned i = 0; i < NumViews; ++i) + for (UINT i = 0; i < NumViews; ++i) { if (aViewIds[i] != SVGA3D_INVALID_ID) - vgpu10SetStructureCount(pDevice, aViewIds[i], pUAVInitialCounts[i]); + vgpu10SetStructureCount(pDevice, aViewIds[i], aViewKMResources[i], pUAVInitialCounts[i]); } - vgpu10SetUAViews(pDevice, StartSlot, NumViewsToSet, aViewIds); + vgpu10SetUAViews(pDevice, StartSlot, NumViewsToSet, aViewIds, aViewKMResources); } @@ -4465,7 +4513,8 @@ void vboxDXDispatchIndirect(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pResource, void vboxDXCopyStructureCount(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pDstBuffer, UINT DstAlignedByteOffset, PVBOXDXUNORDEREDACCESSVIEW pSrcView) { - vgpu10CopyStructureCount(pDevice, pSrcView->uUnorderedAccessViewId, vboxDXGetKMResource(pDstBuffer), DstAlignedByteOffset); + vgpu10CopyStructureCount(pDevice, pSrcView->uUnorderedAccessViewId, vboxDXGetKMResource(pSrcView->pResource), + vboxDXGetKMResource(pDstBuffer), DstAlignedByteOffset); } @@ -4496,30 +4545,65 @@ HRESULT vboxDXBlt(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pDstResource, UINT Ds } -void vboxDXClearView(PVBOXDX_DEVICE pDevice, D3D11DDI_HANDLETYPE ViewType, uint32_t ViewId, FLOAT const Color[4], D3D10_DDI_RECT const *pRect, UINT NumRects) +void vboxDXClearView(PVBOXDX_DEVICE pDevice, D3D11DDI_HANDLETYPE ViewType, void *pView, FLOAT const Color[4], D3D10_DDI_RECT const *pRect, UINT NumRects) { - SVGAFifo3dCmdId enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_RTV; + SVGAFifo3dCmdId enmCmdId; + uint32_t ViewId; + PVBOXDXKMRESOURCE pViewKMResource; + + /* "Possible types are the following. + * D3D10DDI_HT_RENDERTARGETVIEW + * D3D11DDI_HT_UNORDEREDACCESSVIEW + * Any D3D11_1DDI_HT_VIDEOXXX type" + */ + switch (ViewType) { case D3D10DDI_HT_RENDERTARGETVIEW: + { + PVBOXDXRENDERTARGETVIEW pRenderTargetView = (PVBOXDXRENDERTARGETVIEW)pView; + enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_RTV; + ViewId = pRenderTargetView->uRenderTargetViewId; + pViewKMResource = vboxDXGetKMResource(pRenderTargetView->pResource); break; + } case D3D11DDI_HT_UNORDEREDACCESSVIEW: + { + PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView = (PVBOXDXUNORDEREDACCESSVIEW)pView; enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_UAV; + ViewId = pUnorderedAccessView->uUnorderedAccessViewId; + pViewKMResource = vboxDXGetKMResource(pUnorderedAccessView->pResource); break; + } case D3D11_1DDI_HT_VIDEODECODEROUTPUTVIEW: + { + PVBOXDXVIDEODECODEROUTPUTVIEW pVideoDecoderOutputView = (PVBOXDXVIDEODECODEROUTPUTVIEW)pView; enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_VDOV; + ViewId = pVideoDecoderOutputView->uVideoDecoderOutputViewId; + pViewKMResource = vboxDXGetKMResource(pVideoDecoderOutputView->pResource); break; + } case D3D11_1DDI_HT_VIDEOPROCESSORINPUTVIEW: + { + PVBOXDXVIDEOPROCESSORINPUTVIEW pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)pView; enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_VPIV; + ViewId = pVideoProcessorInputView->uVideoProcessorInputViewId; + pViewKMResource = vboxDXGetKMResource(pVideoProcessorInputView->pResource); break; + } case D3D11_1DDI_HT_VIDEOPROCESSOROUTPUTVIEW: + { + PVBOXDXVIDEOPROCESSOROUTPUTVIEW pVideoProcessorOutputView = (PVBOXDXVIDEOPROCESSOROUTPUTVIEW)pView; enmCmdId = VBSVGA_3D_CMD_DX_CLEAR_VPOV; + ViewId = pVideoProcessorOutputView->uVideoProcessorOutputViewId; + pViewKMResource = vboxDXGetKMResource(pVideoProcessorOutputView->pResource); break; + } default: AssertFailedReturnVoid(); } - vgpu10ClearView(pDevice, enmCmdId, ViewId, Color, pRect, NumRects); + vgpu10ClearView(pDevice, enmCmdId, ViewId, pViewKMResource, Color, pRect, NumRects); } diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.h b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.h index dba7246b941e..d670d18adcb3 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.h +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDX.h @@ -1,4 +1,4 @@ -/* $Id: VBoxDX.h 114508 2026-06-24 14:34:30Z vitali.pelenjow@oracle.com $ */ +/* $Id: VBoxDX.h 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VBoxVideo Display D3D User mode dll */ @@ -885,13 +885,13 @@ void vboxDXDestroyUnorderedAccessView(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDAC void vboxDXClearUnorderedAccessViewUint(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView, const UINT Values[4]); void vboxDXClearUnorderedAccessViewFloat(PVBOXDX_DEVICE pDevice, PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView, const FLOAT Values[4]); void vboxDXSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const PVBOXDXUNORDEREDACCESSVIEW *papViews, const UINT *pUAVInitialCounts); -void vboxDXCsSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const uint32_t *paViewIds, const UINT* pUAVInitialCounts); +void vboxDXCsSetUnorderedAccessViews(PVBOXDX_DEVICE pDevice, UINT StartSlot, UINT NumViews, const PVBOXDXUNORDEREDACCESSVIEW *papViews, const UINT *pUAVInitialCounts); void vboxDXDispatch(PVBOXDX_DEVICE pDevice, UINT ThreadGroupCountX, UINT ThreadGroupCountY, UINT ThreadGroupCountZ); void vboxDXDispatchIndirect(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pResource, UINT AlignedByteOffsetForArgs); void vboxDXDrawIndexedInstancedIndirect(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pResource, UINT AlignedByteOffsetForArgs); void vboxDXDrawInstancedIndirect(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pResource, UINT AlignedByteOffsetForArgs); void vboxDXCopyStructureCount(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pDstBuffer, UINT DstAlignedByteOffset, PVBOXDXUNORDEREDACCESSVIEW pSrcView); -void vboxDXClearView(PVBOXDX_DEVICE pDevice, D3D11DDI_HANDLETYPE ViewType, uint32_t ViewId, FLOAT const Color[4], D3D10_DDI_RECT const *pRect, UINT NumRects); +void vboxDXClearView(PVBOXDX_DEVICE pDevice, D3D11DDI_HANDLETYPE ViewType, void *pView, FLOAT const Color[4], D3D10_DDI_RECT const *pRect, UINT NumRects); HRESULT vboxDXBlt(PVBOXDX_DEVICE pDevice, PVBOXDX_RESOURCE pDstResource, UINT DstSubresource, PVBOXDX_RESOURCE pSrcResource, UINT SrcSubresource, UINT DstLeft, UINT DstTop, UINT DstRight, UINT DstBottom, diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.cpp b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.cpp index a787d9569470..b47174774992 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.cpp +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxDXCmd.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxDXCmd.cpp 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox D3D user mode driver utilities. */ @@ -859,15 +859,18 @@ int vgpu10DefineShaderResourceView(PVBOXDX_DEVICE pDevice, int vgpu10GenMips(PVBOXDX_DEVICE pDevice, - SVGA3dShaderResourceViewId shaderResourceViewId) + SVGA3dShaderResourceViewId shaderResourceViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_GENMIPS, - sizeof(SVGA3dCmdDXGenMips)); + sizeof(SVGA3dCmdDXGenMips), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXGenMips *cmd = (SVGA3dCmdDXGenMips *)pvCmd; SET_CMD_FIELD(shaderResourceViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -875,15 +878,18 @@ int vgpu10GenMips(PVBOXDX_DEVICE pDevice, int vgpu10DestroyShaderResourceView(PVBOXDX_DEVICE pDevice, - SVGA3dShaderResourceViewId shaderResourceViewId) + SVGA3dShaderResourceViewId shaderResourceViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_DESTROY_SHADERRESOURCE_VIEW, - sizeof(SVGA3dCmdDXDestroyShaderResourceView)); + sizeof(SVGA3dCmdDXDestroyShaderResourceView), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXDestroyShaderResourceView *cmd = (SVGA3dCmdDXDestroyShaderResourceView *)pvCmd; SET_CMD_FIELD(shaderResourceViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -918,10 +924,11 @@ int vgpu10DefineRenderTargetView(PVBOXDX_DEVICE pDevice, int vgpu10ClearRenderTargetView(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId renderTargetViewId, + PVBOXDXKMRESOURCE pViewKMResource, const float rgba[4]) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_CLEAR_RENDERTARGET_VIEW, - sizeof(SVGA3dCmdDXClearRenderTargetView)); + sizeof(SVGA3dCmdDXClearRenderTargetView), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -931,6 +938,8 @@ int vgpu10ClearRenderTargetView(PVBOXDX_DEVICE pDevice, cmd->rgba.value[1] = rgba[1]; cmd->rgba.value[2] = rgba[2]; cmd->rgba.value[3] = rgba[3]; + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -939,12 +948,13 @@ int vgpu10ClearRenderTargetView(PVBOXDX_DEVICE pDevice, int vgpu10ClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId viewId, + PVBOXDXKMRESOURCE pViewKMResource, const float color[4], const D3D10_DDI_RECT *paRects, uint32_t cRects) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_VB_DX_CLEAR_RENDERTARGET_VIEW_REGION, - sizeof(SVGA3dCmdVBDXClearRenderTargetViewRegion) + cRects * sizeof(SVGASignedRect)); + sizeof(SVGA3dCmdVBDXClearRenderTargetViewRegion) + cRects * sizeof(SVGASignedRect), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -966,6 +976,8 @@ int vgpu10ClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, d->right = s->right; d->bottom = s->bottom; } + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -975,12 +987,13 @@ int vgpu10ClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, int vgpu10ClearView(PVBOXDX_DEVICE pDevice, SVGAFifo3dCmdId cmdId, uint32_t viewId, + PVBOXDXKMRESOURCE pViewKMResource, const float color[4], const D3D10_DDI_RECT *paRects, uint32_t cRects) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, cmdId, - sizeof(VBSVGA3dCmdDXClearView) + cRects * sizeof(SVGASignedRect)); + sizeof(VBSVGA3dCmdDXClearView) + cRects * sizeof(SVGASignedRect), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -1001,6 +1014,8 @@ int vgpu10ClearView(PVBOXDX_DEVICE pDevice, d->right = s->right; d->bottom = s->bottom; } + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1008,15 +1023,18 @@ int vgpu10ClearView(PVBOXDX_DEVICE pDevice, int vgpu10DestroyRenderTargetView(PVBOXDX_DEVICE pDevice, - SVGA3dRenderTargetViewId renderTargetViewId) + SVGA3dRenderTargetViewId renderTargetViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_DESTROY_RENDERTARGET_VIEW, - sizeof(SVGA3dCmdDXDestroyRenderTargetView)); + sizeof(SVGA3dCmdDXDestroyRenderTargetView), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXDestroyRenderTargetView *cmd = (SVGA3dCmdDXDestroyRenderTargetView *)pvCmd; SET_CMD_FIELD(renderTargetViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1059,10 +1077,11 @@ int vgpu10ClearDepthStencilView(PVBOXDX_DEVICE pDevice, uint16 flags, uint16 stencil, SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource, float depth) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_CLEAR_DEPTHSTENCIL_VIEW, - sizeof(SVGA3dCmdDXClearDepthStencilView)); + sizeof(SVGA3dCmdDXClearDepthStencilView), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -1071,6 +1090,8 @@ int vgpu10ClearDepthStencilView(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(stencil); SET_CMD_FIELD(depthStencilViewId); SET_CMD_FIELD(depth); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1078,15 +1099,18 @@ int vgpu10ClearDepthStencilView(PVBOXDX_DEVICE pDevice, int vgpu10DestroyDepthStencilView(PVBOXDX_DEVICE pDevice, - SVGA3dDepthStencilViewId depthStencilViewId) + SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_DESTROY_DEPTHSTENCIL_VIEW, - sizeof(SVGA3dCmdDXDestroyDepthStencilView)); + sizeof(SVGA3dCmdDXDestroyDepthStencilView), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXDestroyDepthStencilView *cmd = (SVGA3dCmdDXDestroyDepthStencilView *)pvCmd; SET_CMD_FIELD(depthStencilViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1095,13 +1119,15 @@ int vgpu10DestroyDepthStencilView(PVBOXDX_DEVICE pDevice, int vgpu10SetRenderTargets(PVBOXDX_DEVICE pDevice, SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32_t numRTVs, uint32_t numClearSlots, - uint32_t *paRenderTargetViewIds) + uint32_t *paRenderTargetViewIds, + PVBOXDXKMRESOURCE *papViewKMResources) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_RENDERTARGETS, sizeof(SVGA3dCmdDXSetRenderTargets) - + (numRTVs + numClearSlots) * sizeof(SVGA3dRenderTargetViewId)); + + (numRTVs + numClearSlots) * sizeof(SVGA3dRenderTargetViewId), 1 + numRTVs); if (!pvCmd) return VERR_NO_MEMORY; @@ -1115,6 +1141,16 @@ int vgpu10SetRenderTargets(PVBOXDX_DEVICE pDevice, for (unsigned i = 0; i < numClearSlots; ++i) *dst++ = SVGA3D_INVALID_ID; + if (depthStencilViewId != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); + for (uint32_t i = 0; i < numRTVs; ++i) + { + if (paRenderTargetViewIds[i] != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, papViewKMResources[i], + 0, false); + } + vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; } @@ -1124,11 +1160,12 @@ int vgpu10SetShaderResources(PVBOXDX_DEVICE pDevice, SVGA3dShaderType type, uint32 startView, uint32_t numViews, - uint32_t *paViewIds) + uint32_t *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_SHADER_RESOURCES, sizeof(SVGA3dCmdDXSetShaderResources) - + numViews * sizeof(SVGA3dShaderResourceViewId)); + + numViews * sizeof(SVGA3dShaderResourceViewId), numViews); if (!pvCmd) return VERR_NO_MEMORY; @@ -1137,6 +1174,13 @@ int vgpu10SetShaderResources(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(type); memcpy(&cmd[1], paViewIds, numViews * sizeof(SVGA3dShaderResourceViewId)); + for (uint32_t i = 0; i < numViews; ++i) + { + if (paViewIds[i] != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, papViewKMResources[i], + 0, false); + } + vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; } @@ -1529,15 +1573,18 @@ int vgpu10DefineUAView(PVBOXDX_DEVICE pDevice, int vgpu10DestroyUAView(PVBOXDX_DEVICE pDevice, - SVGA3dUAViewId uaViewId) + SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_DESTROY_UA_VIEW, - sizeof(SVGA3dCmdDXDestroyUAView), 0); + sizeof(SVGA3dCmdDXDestroyUAView), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXDestroyUAView *cmd = (SVGA3dCmdDXDestroyUAView *)pvCmd; SET_CMD_FIELD(uaViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1546,10 +1593,11 @@ int vgpu10DestroyUAView(PVBOXDX_DEVICE pDevice, int vgpu10ClearUAViewUint(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, const uint32 value[4]) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_CLEAR_UA_VIEW_UINT, - sizeof(SVGA3dCmdDXClearUAViewUint), 0); + sizeof(SVGA3dCmdDXClearUAViewUint), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -1559,6 +1607,8 @@ int vgpu10ClearUAViewUint(PVBOXDX_DEVICE pDevice, cmd->value.value[1] = value[1]; cmd->value.value[2] = value[2]; cmd->value.value[3] = value[3]; + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1567,10 +1617,11 @@ int vgpu10ClearUAViewUint(PVBOXDX_DEVICE pDevice, int vgpu10ClearUAViewFloat(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, const float value[4]) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_CLEAR_UA_VIEW_FLOAT, - sizeof(SVGA3dCmdDXClearUAViewFloat), 0); + sizeof(SVGA3dCmdDXClearUAViewFloat), 1); if (!pvCmd) return VERR_NO_MEMORY; @@ -1580,6 +1631,8 @@ int vgpu10ClearUAViewFloat(PVBOXDX_DEVICE pDevice, cmd->value.value[1] = value[1]; cmd->value.value[2] = value[2]; cmd->value.value[3] = value[3]; + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1589,11 +1642,12 @@ int vgpu10ClearUAViewFloat(PVBOXDX_DEVICE pDevice, int vgpu10SetCSUAViews(PVBOXDX_DEVICE pDevice, uint32 startIndex, uint32 numViews, - const SVGA3dUAViewId *paViewIds) + const SVGA3dUAViewId *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_CS_UA_VIEWS, sizeof(SVGA3dCmdDXSetCSUAViews) - + numViews * sizeof(SVGA3dUAViewId)); + + numViews * sizeof(SVGA3dUAViewId), numViews); if (!pvCmd) return VERR_NO_MEMORY; @@ -1601,6 +1655,13 @@ int vgpu10SetCSUAViews(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(startIndex); memcpy(&cmd[1], paViewIds, numViews * sizeof(SVGA3dUAViewId)); + for (uint32 i = 0; i < numViews; ++ i) + { + if (paViewIds[i] != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, papViewKMResources[i], + 0, false); + } + vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; } @@ -1609,11 +1670,12 @@ int vgpu10SetCSUAViews(PVBOXDX_DEVICE pDevice, int vgpu10SetUAViews(PVBOXDX_DEVICE pDevice, uint32 uavSpliceIndex, uint32 numViews, - const SVGA3dUAViewId *paViewIds) + const SVGA3dUAViewId *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_UA_VIEWS, sizeof(SVGA3dCmdDXSetUAViews) - + numViews * sizeof(SVGA3dUAViewId)); + + numViews * sizeof(SVGA3dUAViewId), numViews); if (!pvCmd) return VERR_NO_MEMORY; @@ -1621,6 +1683,13 @@ int vgpu10SetUAViews(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(uavSpliceIndex); memcpy(&cmd[1], paViewIds, numViews * sizeof(SVGA3dUAViewId)); + for (uint32 i = 0; i < numViews; ++ i) + { + if (paViewIds[i] != SVGA3D_INVALID_ID) + vboxDXStorePatchLocation(pDevice, NULL, papViewKMResources[i], + 0, false); + } + vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; } @@ -1628,16 +1697,19 @@ int vgpu10SetUAViews(PVBOXDX_DEVICE pDevice, int vgpu10SetStructureCount(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32 structureCount) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_SET_STRUCTURE_COUNT, - sizeof(SVGA3dCmdDXSetStructureCount), 0); + sizeof(SVGA3dCmdDXSetStructureCount), 1); if (!pvCmd) return VERR_NO_MEMORY; SVGA3dCmdDXSetStructureCount *cmd = (SVGA3dCmdDXSetStructureCount *)pvCmd; SET_CMD_FIELD(uaViewId); SET_CMD_FIELD(structureCount); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1729,11 +1801,12 @@ int vgpu10DrawInstancedIndirect(PVBOXDX_DEVICE pDevice, int vgpu10CopyStructureCount(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId srcUAViewId, + PVBOXDXKMRESOURCE pViewKMResource, PVBOXDXKMRESOURCE pDstKMResource, uint32 destByteOffset) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, SVGA_3D_CMD_DX_COPY_STRUCTURE_COUNT, - sizeof(SVGA3dCmdDXCopyStructureCount), 1); + sizeof(SVGA3dCmdDXCopyStructureCount), 2); if (!pvCmd) return VERR_NO_MEMORY; @@ -1742,6 +1815,8 @@ int vgpu10CopyStructureCount(PVBOXDX_DEVICE pDevice, cmd->destSid = SVGA3D_INVALID_ID; SET_CMD_FIELD(destByteOffset); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXStorePatchLocation(pDevice, &cmd->destSid, pDstKMResource, 0, true); @@ -1845,16 +1920,19 @@ int vgpu10DefineVideoDecoder(PVBOXDX_DEVICE pDevice, int vgpu10VideoDecoderBeginFrame(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderId videoDecoderId, - VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId) + VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_VIDEO_DECODER_BEGIN_FRAME, - sizeof(VBSVGA3dCmdDXVideoDecoderBeginFrame), 0); + sizeof(VBSVGA3dCmdDXVideoDecoderBeginFrame), 1); if (!pvCmd) return VERR_NO_MEMORY; VBSVGA3dCmdDXVideoDecoderBeginFrame *cmd = (VBSVGA3dCmdDXVideoDecoderBeginFrame *)pvCmd; SET_CMD_FIELD(videoDecoderId); SET_CMD_FIELD(videoDecoderOutputViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1957,13 +2035,16 @@ int vgpu10DefineVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, int vgpu10VideoProcessorBlt(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoProcessorId videoProcessorId, VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32 outputFrame, uint32 streamCount, uint32 cbVideoProcessorStreams, - VBSVGA3dVideoProcessorStream *pVideoProcessorStreams) + VBSVGA3dVideoProcessorStream *pVideoProcessorStreams, + uint32_t cVPIViewKMResource, + PVBOXDXKMRESOURCE *papVPIViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_VIDEO_PROCESSOR_BLT, - sizeof(VBSVGA3dCmdDXVideoProcessorBlt) + cbVideoProcessorStreams, 0); + sizeof(VBSVGA3dCmdDXVideoProcessorBlt) + cbVideoProcessorStreams, 1 + cVPIViewKMResource); if (!pvCmd) return VERR_NO_MEMORY; @@ -1973,6 +2054,13 @@ int vgpu10VideoProcessorBlt(PVBOXDX_DEVICE pDevice, SET_CMD_FIELD(outputFrame); SET_CMD_FIELD(streamCount); memcpy(&cmd[1], pVideoProcessorStreams, cbVideoProcessorStreams); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); + for (uint32_t i = 0; i < cVPIViewKMResource; ++i) + { + vboxDXStorePatchLocation(pDevice, NULL, papVPIViewKMResource[i], + 0, false); + } vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -1996,15 +2084,18 @@ int vgpu10DestroyVideoDecoder(PVBOXDX_DEVICE pDevice, int vgpu10DestroyVideoDecoderOutputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId) + VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_DESTROY_VIDEO_DECODER_OUTPUT_VIEW, - sizeof(VBSVGA3dCmdDXDestroyVideoDecoderOutputView), 0); + sizeof(VBSVGA3dCmdDXDestroyVideoDecoderOutputView), 1); if (!pvCmd) return VERR_NO_MEMORY; VBSVGA3dCmdDXDestroyVideoDecoderOutputView *cmd = (VBSVGA3dCmdDXDestroyVideoDecoderOutputView *)pvCmd; SET_CMD_FIELD(videoDecoderOutputViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -2028,15 +2119,18 @@ int vgpu10DestroyVideoProcessor(PVBOXDX_DEVICE pDevice, int vgpu10DestroyVideoProcessorInputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId) + VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_DESTROY_VIDEO_PROCESSOR_INPUT_VIEW, - sizeof(VBSVGA3dCmdDXDestroyVideoProcessorInputView), 0); + sizeof(VBSVGA3dCmdDXDestroyVideoProcessorInputView), 1); if (!pvCmd) return VERR_NO_MEMORY; VBSVGA3dCmdDXDestroyVideoProcessorInputView *cmd = (VBSVGA3dCmdDXDestroyVideoProcessorInputView *)pvCmd; SET_CMD_FIELD(videoProcessorInputViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; @@ -2044,15 +2138,18 @@ int vgpu10DestroyVideoProcessorInputView(PVBOXDX_DEVICE pDevice, int vgpu10DestroyVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId) + VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource) { void *pvCmd = vboxDXCommandBufferReserve(pDevice, VBSVGA_3D_CMD_DX_DESTROY_VIDEO_PROCESSOR_OUTPUT_VIEW, - sizeof(VBSVGA3dCmdDXDestroyVideoProcessorOutputView), 0); + sizeof(VBSVGA3dCmdDXDestroyVideoProcessorOutputView), 1); if (!pvCmd) return VERR_NO_MEMORY; VBSVGA3dCmdDXDestroyVideoProcessorOutputView *cmd = (VBSVGA3dCmdDXDestroyVideoProcessorOutputView *)pvCmd; SET_CMD_FIELD(videoProcessorOutputViewId); + vboxDXStorePatchLocation(pDevice, NULL, pViewKMResource, + 0, false); vboxDXCommandBufferCommit(pDevice); return VINF_SUCCESS; diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.h b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.h index 08ec4006e8b6..da218f6ba5ac 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.h +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXCmd.h @@ -1,4 +1,4 @@ -/* $Id: VBoxDXCmd.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxDXCmd.h 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VBoxVideo Display D3D User mode dll */ @@ -206,9 +206,11 @@ int vgpu10DefineShaderResourceView(PVBOXDX_DEVICE pDevice, SVGA3dResourceType resourceDimension, SVGA3dShaderResourceViewDesc const *pDesc); int vgpu10GenMips(PVBOXDX_DEVICE pDevice, - SVGA3dShaderResourceViewId shaderResourceViewId); + SVGA3dShaderResourceViewId shaderResourceViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DestroyShaderResourceView(PVBOXDX_DEVICE pDevice, - SVGA3dShaderResourceViewId shaderResourceViewId); + SVGA3dShaderResourceViewId shaderResourceViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DefineRenderTargetView(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId renderTargetViewId, PVBOXDXKMRESOURCE pKMResource, @@ -217,9 +219,11 @@ int vgpu10DefineRenderTargetView(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewDesc const *pDesc); int vgpu10ClearRenderTargetView(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId renderTargetViewId, + PVBOXDXKMRESOURCE pViewKMResource, const float rgba[4]); int vgpu10DestroyRenderTargetView(PVBOXDX_DEVICE pDevice, - SVGA3dRenderTargetViewId renderTargetViewId); + SVGA3dRenderTargetViewId renderTargetViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DefineDepthStencilView(PVBOXDX_DEVICE pDevice, SVGA3dDepthStencilViewId depthStencilViewId, PVBOXDXKMRESOURCE pKMResource, @@ -233,19 +237,24 @@ int vgpu10ClearDepthStencilView(PVBOXDX_DEVICE pDevice, uint16 flags, uint16 stencil, SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource, float depth); int vgpu10DestroyDepthStencilView(PVBOXDX_DEVICE pDevice, - SVGA3dDepthStencilViewId depthStencilViewId); + SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10SetRenderTargets(PVBOXDX_DEVICE pDevice, SVGA3dDepthStencilViewId depthStencilViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32_t numRTVs, uint32_t numClearSlots, - uint32_t *paRenderTargetViewIds); + uint32_t *paRenderTargetViewIds, + PVBOXDXKMRESOURCE *papViewKMResources); int vgpu10SetShaderResources(PVBOXDX_DEVICE pDevice, SVGA3dShaderType type, uint32 startView, uint32_t numViews, - uint32_t *paViewIds); + uint32_t *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources); int vgpu10SetSingleConstantBuffer(PVBOXDX_DEVICE pDevice, uint32 slot, SVGA3dShaderType type, @@ -317,23 +326,29 @@ int vgpu10DefineUAView(PVBOXDX_DEVICE pDevice, SVGA3dResourceType resourceDimension, const SVGA3dUAViewDesc &desc); int vgpu10DestroyUAView(PVBOXDX_DEVICE pDevice, - SVGA3dUAViewId uaViewId); + SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10ClearUAViewUint(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, const uint32 value[4]); int vgpu10ClearUAViewFloat(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, const float value[4]); int vgpu10SetCSUAViews(PVBOXDX_DEVICE pDevice, uint32 startIndex, uint32 numViews, - const SVGA3dUAViewId *paViewIds); + const SVGA3dUAViewId *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources); int vgpu10SetUAViews(PVBOXDX_DEVICE pDevice, uint32 uavSpliceIndex, uint32 numViews, - const SVGA3dUAViewId *paViewIds); + const SVGA3dUAViewId *paViewIds, + PVBOXDXKMRESOURCE *papViewKMResources); int vgpu10SetStructureCount(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId uaViewId, + PVBOXDXKMRESOURCE pViewKMResource, uint32 structureCount); int vgpu10Dispatch(PVBOXDX_DEVICE pDevice, uint32 threadGroupCountX, @@ -350,10 +365,12 @@ int vgpu10DrawInstancedIndirect(PVBOXDX_DEVICE pDevice, uint32 byteOffsetForArgs); int vgpu10CopyStructureCount(PVBOXDX_DEVICE pDevice, SVGA3dUAViewId srcUAViewId, + PVBOXDXKMRESOURCE pViewKMResource, PVBOXDXKMRESOURCE pDstKMResource, uint32 destByteOffset); int vgpu10ClearRenderTargetViewRegion(PVBOXDX_DEVICE pDevice, SVGA3dRenderTargetViewId viewId, + PVBOXDXKMRESOURCE pViewKMResource, const float color[4], const D3D10_DDI_RECT *paRects, uint32_t cRects); @@ -379,7 +396,8 @@ int vgpu10DefineVideoDecoder(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderConfig const &config); int vgpu10VideoDecoderBeginFrame(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderId videoDecoderId, - VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId); + VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10VideoDecoderSubmitBuffers(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderId videoDecoderId, uint32 bufferCount, @@ -400,20 +418,26 @@ int vgpu10DefineVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, int vgpu10VideoProcessorBlt(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoProcessorId videoProcessorId, VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, - uint32 OutputFrame, - uint32 StreamCount, + PVBOXDXKMRESOURCE pViewKMResource, + uint32 outputFrame, + uint32 streamCount, uint32 cbVideoProcessorStreams, - VBSVGA3dVideoProcessorStream *paVideoProcessorStreams); + VBSVGA3dVideoProcessorStream *pVideoProcessorStreams, + uint32_t cVPIViewKMResource, + PVBOXDXKMRESOURCE *papVPIViewKMResource); int vgpu10DestroyVideoDecoder(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoDecoderId videoDecoderId); int vgpu10DestroyVideoDecoderOutputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId); + VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DestroyVideoProcessor(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoProcessorId videoProcessorId); int vgpu10DestroyVideoProcessorInputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId); + VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10DestroyVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, - VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId); + VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, + PVBOXDXKMRESOURCE pViewKMResource); int vgpu10VideoProcessorSetOutputTargetRect(PVBOXDX_DEVICE pDevice, VBSVGA3dVideoProcessorId videoProcessorId, BOOL enable, @@ -515,6 +539,7 @@ int vgpu10GetVideoCapability(PVBOXDX_DEVICE pDevice, int vgpu10ClearView(PVBOXDX_DEVICE pDevice, SVGAFifo3dCmdId cmdId, uint32_t viewId, + PVBOXDXKMRESOURCE pViewKMResource, const float color[4], const D3D10_DDI_RECT *paRects, uint32_t cRects); diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXDDI.cpp b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXDDI.cpp index 5666d9008170..9107c678771e 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXDDI.cpp +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXDDI.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxDXDDI.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxDXDDI.cpp 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox D3D11 user mode DDI interface. */ @@ -895,10 +895,6 @@ static void APIENTRY ddi11SetRenderTargets( vboxDXSetRenderTargets(pDevice, pDepthStencilView, NumRTVs, ClearSlots, (PVBOXDXRENDERTARGETVIEW *)phRenderTargetView); - AssertReturnVoidStmt( NumUAVs <= D3D11_1_UAV_SLOT_COUNT - && UAVStartSlot <= SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS, - vboxDXDeviceSetError(pDevice, E_INVALIDARG)); - vboxDXSetUnorderedAccessViews(pDevice, UAVStartSlot, NumUAVs, (PVBOXDXUNORDEREDACCESSVIEW *)phUnorderedAccessView, pUAVInitialCounts); RT_NOREF(UAVRangeStart, UAVRangeSize); /* These are hints and not used by the driver. */ @@ -3870,20 +3866,8 @@ static void APIENTRY ddi11CsSetUnorderedAccessViews( PVBOXDX_DEVICE pDevice = (PVBOXDX_DEVICE)hDevice.pDrvPrivate; LogFlowFunc(("pDevice = %p, StartSlot = %u, NumViews = %u\n", pDevice, StartSlot, NumViews)); - AssertReturnVoidStmt( NumViews <= SVGA3D_DX11_1_MAX_UAVIEWS - && StartSlot < SVGA3D_DX11_1_MAX_UAVIEWS - && NumViews + StartSlot <= SVGA3D_DX11_1_MAX_UAVIEWS, - vboxDXDeviceSetError(pDevice, E_INVALIDARG)); - - /* Fetch View ids. */ - uint32_t aViewIds[SVGA3D_DX11_1_MAX_UAVIEWS]; - for (unsigned i = 0; i < NumViews; ++i) - { - VBOXDXUNORDEREDACCESSVIEW *pView = (PVBOXDXUNORDEREDACCESSVIEW)phUnorderedAccessView[i].pDrvPrivate; - aViewIds[i] = pView ? pView->uUnorderedAccessViewId : SVGA3D_INVALID_ID; - } - - vboxDXCsSetUnorderedAccessViews(pDevice, StartSlot, NumViews, aViewIds, pUAVInitialCounts); + vboxDXCsSetUnorderedAccessViews(pDevice, StartSlot, NumViews, + (PVBOXDXUNORDEREDACCESSVIEW *)phUnorderedAccessView, pUAVInitialCounts); } static void APIENTRY ddi11Dispatch( @@ -4031,53 +4015,7 @@ static void APIENTRY ddi11_1ClearView( if (pDevice->pAdapter->fVBoxCaps & VBSVGA3D_CAP_VIDEO) { - uint32_t ViewId = SVGA3D_INVALID_ID; - - /* "Possible types are the following. - * D3D10DDI_HT_RENDERTARGETVIEW - * D3D11DDI_HT_UNORDEREDACCESSVIEW - * Any D3D11_1DDI_HT_VIDEOXXX type" - */ - switch (ViewType) - { - case D3D10DDI_HT_RENDERTARGETVIEW: - { - PVBOXDXRENDERTARGETVIEW pRenderTargetView = (PVBOXDXRENDERTARGETVIEW)hView; - ViewId = pRenderTargetView->uRenderTargetViewId; - break; - } - case D3D11DDI_HT_UNORDEREDACCESSVIEW: - { - PVBOXDXUNORDEREDACCESSVIEW pUnorderedAccessView = (PVBOXDXUNORDEREDACCESSVIEW)hView; - ViewId = pUnorderedAccessView->uUnorderedAccessViewId; - break; - } - case D3D11_1DDI_HT_VIDEODECODEROUTPUTVIEW: - { - PVBOXDXVIDEODECODEROUTPUTVIEW pVideoDecoderOutputView = (PVBOXDXVIDEODECODEROUTPUTVIEW)hView; - ViewId = pVideoDecoderOutputView->uVideoDecoderOutputViewId; - break; - } - case D3D11_1DDI_HT_VIDEOPROCESSORINPUTVIEW: - { - PVBOXDXVIDEOPROCESSORINPUTVIEW pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)hView; - ViewId = pVideoProcessorInputView->uVideoProcessorInputViewId; - break; - } - case D3D11_1DDI_HT_VIDEOPROCESSOROUTPUTVIEW: - { - PVBOXDXVIDEOPROCESSOROUTPUTVIEW pVideoProcessorOutputView = (PVBOXDXVIDEOPROCESSOROUTPUTVIEW)hView; - ViewId = pVideoProcessorOutputView->uVideoProcessorOutputViewId; - break; - } - default: - { - DEBUG_BREAKPOINT_TEST(); - break; - } - } - if (ViewId != SVGA3D_INVALID_ID) - vboxDXClearView(pDevice, ViewType, ViewId, Color, pRect, NumRects); + vboxDXClearView(pDevice, ViewType, hView, Color, pRect, NumRects); return; } diff --git a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXVideo.cpp b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXVideo.cpp index 9d057f022e37..70408e9d938f 100644 --- a/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXVideo.cpp +++ b/src/VBox/Additions/win/Graphics/Video/disp/wddm/dx/VBoxDXVideo.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxDXVideo.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxDXVideo.cpp 114855 2026-08-04 19:16:41Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox D3D user mode driver. */ @@ -513,7 +513,8 @@ HRESULT vboxDXVideoDecoderBeginFrame(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEODECODER RT_NOREF(pContentKey, ContentKeySize); /** @todo vgpu10VideoDecoderBeginFrame2 */ vgpu10VideoDecoderBeginFrame(pDevice, pVideoDecoder->uVideoDecoderId, - pVideoDecoderOutputView->uVideoDecoderOutputViewId); + pVideoDecoderOutputView->uVideoDecoderOutputViewId, + vboxDXGetKMResource(pVideoDecoderOutputView->pResource)); return S_OK; } @@ -648,25 +649,31 @@ HRESULT vboxDXCreateVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, PVBOXDXVIDE HRESULT vboxDXVideoProcessorBlt(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOPROCESSOR pVideoProcessor, PVBOXDXVIDEOPROCESSOROUTPUTVIEW pVideoProcessorOutputView, UINT OutputFrame, UINT StreamCount, D3D11_1DDI_VIDEO_PROCESSOR_STREAM const *paStream) { + uint32_t cVPIVIds = 0; uint32_t cbVideoProcessorStreams = StreamCount * sizeof(VBSVGA3dVideoProcessorStream); for (UINT i = 0; i < StreamCount; ++i) { D3D11_1DDI_VIDEO_PROCESSOR_STREAM const *s = &paStream[i]; - uint32_t cbIds = (s->PastFrames + 1 + s->FutureFrames) * sizeof(VBSVGA3dVideoProcessorInputViewId); + uint32_t cIds = s->PastFrames + 1 + s->FutureFrames; if (pVideoProcessor->aStreams[i].FrameFormat == D3D11_1DDI_VIDEO_PROCESSOR_STEREO_FORMAT_SEPARATE) - cbIds *= 2; - cbVideoProcessorStreams += cbIds; + cIds *= 2; + cVPIVIds += cIds; + cbVideoProcessorStreams += cIds * sizeof(VBSVGA3dVideoProcessorInputViewId); } - void *pvTmpBuffer = RTMemTmpAlloc(cbVideoProcessorStreams); + size_t const cbAlloc = cbVideoProcessorStreams + cVPIVIds * sizeof(PVBOXDXKMRESOURCE); + void *pvTmpBuffer = RTMemTmpAlloc(cbAlloc); if (!pvTmpBuffer) return E_OUTOFMEMORY; + PVBOXDXKMRESOURCE *papVPIViewKMResource = (PVBOXDXKMRESOURCE *)((uint8_t *)pvTmpBuffer + cbVideoProcessorStreams); + uint32_t idxVPIViewKMResource = 0; + VBSVGA3dVideoProcessorStream *paVideoProcessorStreams = (VBSVGA3dVideoProcessorStream *)pvTmpBuffer; + VBSVGA3dVideoProcessorStream *d = &paVideoProcessorStreams[0]; for (UINT i = 0; i < StreamCount; ++i) { D3D11_1DDI_VIDEO_PROCESSOR_STREAM const *s = &paStream[i]; - VBSVGA3dVideoProcessorStream *d = &paVideoProcessorStreams[i]; d->Enable = s->Enable; d->StereoFormatSeparate = pVideoProcessor->aStreams[i].StereoFormat.Format == D3D11_1DDI_VIDEO_PROCESSOR_STEREO_FORMAT_SEPARATE; @@ -683,15 +690,18 @@ HRESULT vboxDXVideoProcessorBlt(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOPROCESSOR pV { pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->pPastSurfaces[j].pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); } pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->hInputSurface.pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); for (UINT j = 0; j < s->FutureFrames; ++j) { pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->pFutureSurfaces[j].pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); } if (d->StereoFormatSeparate) @@ -700,21 +710,31 @@ HRESULT vboxDXVideoProcessorBlt(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOPROCESSOR pV { pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->pPastSurfacesRight[j].pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); } pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->hInputSurfaceRight.pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); for (UINT j = 0; j < s->FutureFrames; ++j) { pVideoProcessorInputView = (PVBOXDXVIDEOPROCESSORINPUTVIEW)s->pFutureSurfacesRight[j].pDrvPrivate; *pVPIVId++ = pVideoProcessorInputView->uVideoProcessorInputViewId; + papVPIViewKMResource[idxVPIViewKMResource++] = vboxDXGetKMResource(pVideoProcessorInputView->pResource); } } + + d = (VBSVGA3dVideoProcessorStream *)pVPIVId; } - vgpu10VideoProcessorBlt(pDevice, pVideoProcessor->uVideoProcessorId, pVideoProcessorOutputView->uVideoProcessorOutputViewId, - OutputFrame, StreamCount, cbVideoProcessorStreams, paVideoProcessorStreams); + Assert(idxVPIViewKMResource == cVPIVIds); + + vgpu10VideoProcessorBlt(pDevice, pVideoProcessor->uVideoProcessorId, + pVideoProcessorOutputView->uVideoProcessorOutputViewId, + vboxDXGetKMResource(pVideoProcessorOutputView->pResource), + OutputFrame, StreamCount, cbVideoProcessorStreams, paVideoProcessorStreams, + idxVPIViewKMResource, papVPIViewKMResource); RTMemTmpFree(pvTmpBuffer); return S_OK; } @@ -731,7 +751,8 @@ void vboxDXDestroyVideoDecoderOutputView(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEODEC { RTListNodeRemove(&pVideoDecoderOutputView->nodeView); - vgpu10DestroyVideoDecoderOutputView(pDevice, pVideoDecoderOutputView->uVideoDecoderOutputViewId); + vgpu10DestroyVideoDecoderOutputView(pDevice, pVideoDecoderOutputView->uVideoDecoderOutputViewId, + vboxDXGetKMResource(pVideoDecoderOutputView->pResource)); RTHandleTableFree(pDevice->hHTVideoDecoderOutputView, pVideoDecoderOutputView->uVideoDecoderOutputViewId); } @@ -747,7 +768,8 @@ void vboxDXDestroyVideoProcessorInputView(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOPR { RTListNodeRemove(&pVideoProcessorInputView->nodeView); - vgpu10DestroyVideoProcessorInputView(pDevice, pVideoProcessorInputView->uVideoProcessorInputViewId); + vgpu10DestroyVideoProcessorInputView(pDevice, pVideoProcessorInputView->uVideoProcessorInputViewId, + vboxDXGetKMResource(pVideoProcessorInputView->pResource)); RTHandleTableFree(pDevice->hHTVideoProcessorInputView, pVideoProcessorInputView->uVideoProcessorInputViewId); } @@ -756,7 +778,8 @@ void vboxDXDestroyVideoProcessorOutputView(PVBOXDX_DEVICE pDevice, PVBOXDXVIDEOP { RTListNodeRemove(&pVideoProcessorOutputView->nodeView); - vgpu10DestroyVideoProcessorOutputView(pDevice, pVideoProcessorOutputView->uVideoProcessorOutputViewId); + vgpu10DestroyVideoProcessorOutputView(pDevice, pVideoProcessorOutputView->uVideoProcessorOutputViewId, + vboxDXGetKMResource(pVideoProcessorOutputView->pResource)); RTHandleTableFree(pDevice->hHTVideoProcessorOutputView, pVideoProcessorOutputView->uVideoProcessorOutputViewId); } diff --git a/src/VBox/Additions/win/Graphics/Video/mp/Makefile.kmk b/src/VBox/Additions/win/Graphics/Video/mp/Makefile.kmk index 26e100e45fa4..b8552d2e36f5 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/Makefile.kmk +++ b/src/VBox/Additions/win/Graphics/Video/mp/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 113305 2026-03-10 15:20:18Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 115021 2026-08-12 23:45:19Z knut.osmundsen@oracle.com $ ## @file # Makefile for the Windows guest miniport driver. # @@ -106,7 +106,7 @@ ifdef VBOX_WITH_WDDM $(VBOX_GRAPHICS_INCS) VBoxWddm_LDFLAGS.x86 += /Entry:DriverEntry@8 - VBoxWddm_LDFLAGS.amd64 += /Entry:DriverEntry + VBoxWddm_LDFLAGS.amd64 += /Entry:DriverEntry $(VBOX_VCC_LD_GUARD_RETPOLINE_IGNORE) VBoxWddm_LDFLAGS.arm64 += /Entry:DriverEntry VBoxWddm_SOURCES = \ diff --git a/src/VBox/Additions/win/Graphics/Video/mp/wddm/VBoxMPTypes.h b/src/VBox/Additions/win/Graphics/Video/mp/wddm/VBoxMPTypes.h index cf577784810e..3f0a476ce9be 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/wddm/VBoxMPTypes.h +++ b/src/VBox/Additions/win/Graphics/Video/mp/wddm/VBoxMPTypes.h @@ -1,4 +1,4 @@ -/* $Id: VBoxMPTypes.h 114359 2026-06-15 14:38:33Z vitali.pelenjow@oracle.com $ */ +/* $Id: VBoxMPTypes.h 114994 2026-08-11 15:32:50Z vitali.pelenjow@oracle.com $ */ /** @file * VBox WDDM Miniport driver */ @@ -217,6 +217,12 @@ typedef struct VBOXWDDM_ALLOCATION uint32_t sid; /* For surfaces. */ uint32_t SegmentId; /* Segment of the allocation. */ AVLU32TREE treeInstances; /* DX_ALLOCATION_INSTANCE */ + struct + { + uint32_t fReadbackCompleted : 1; + uint32_t fReserved : 31; + } flags; + uint64_t u64LastReferencedCommandFence; } dx; #endif /* VBOX_WITH_VMSVGA3D_DX */ } VBOXWDDM_ALLOCATION, *PVBOXWDDM_ALLOCATION; diff --git a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/SvgaFifo.cpp b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/SvgaFifo.cpp index cd26f2c6644f..b2b3da74f57f 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/SvgaFifo.cpp +++ b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/SvgaFifo.cpp @@ -1,4 +1,4 @@ -/* $Id: SvgaFifo.cpp 114840 2026-07-31 22:12:25Z vitali.pelenjow@oracle.com $ */ +/* $Id: SvgaFifo.cpp 114850 2026-08-04 16:28:13Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox Windows Guest Mesa3D - VMSVGA FIFO. */ @@ -546,6 +546,11 @@ static NTSTATUS svgaCBSubmitLocked(PVBOXWDDM_EXT_VMSVGA pSvga, PVMSVGACB pCB, PV PVMSVGACBSTATE pCBState = pSvga->pCBState; PVMSVGACBHEADERS pCBHeaders = cbStateHeaders(pCBState); + /* Allocate a header for the buffer. */ + Assert(pCBCtx->cHeaders <= RT_ELEMENTS(pCBHeaders->aContext0CBHeaders)); + if (pCBCtx->cHeaders == 0) + return STATUS_PENDING; + #ifdef DEBUG Assert(!pCB->fSubmitted); if (pCB->fSubmitted) @@ -553,11 +558,6 @@ static NTSTATUS svgaCBSubmitLocked(PVBOXWDDM_EXT_VMSVGA pSvga, PVMSVGACB pCB, PV pCB->fSubmitted = true; #endif - /* Allocate a header for the buffer. */ - Assert(pCBCtx->cHeaders <= RT_ELEMENTS(pCBHeaders->aContext0CBHeaders)); - if (pCBCtx->cHeaders == 0) - return STATUS_PENDING; - SVGACBHeader *pCBHeader = &pCBHeaders->aContext0CBHeaders[pCBCtx->idxNextHeader]; pCBCtx->idxNextHeader = (pCBCtx->idxNextHeader + 1) % RT_ELEMENTS(pCBHeaders->aContext0CBHeaders); --pCBCtx->cHeaders; diff --git a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPDX.cpp b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPDX.cpp index b4e700456794..e578104076a0 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPDX.cpp +++ b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPDX.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxMPDX.cpp 114469 2026-06-22 09:48:44Z vitali.pelenjow@oracle.com $ */ +/* $Id: VBoxMPDX.cpp 114994 2026-08-11 15:32:50Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox Windows Guest Graphics Driver - Direct3D (DX) driver function. */ @@ -757,6 +757,330 @@ static NTSTATUS svgaPTVRAM2SysMem(PVBOXMP_DEVEXT pDevExt, PVBOXWDDM_ALLOCATION p } +NTSTATUS SvgaCommandFence(VBOXWDDM_EXT_VMSVGA *pSvga, + uint64_t u64FenceValue, + void *pvCmd, + uint32_t cbReserved, + uint32_t *pcbCmd) +{ + uint32_t cbRequired = sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdDXMobFence64); + + *pcbCmd += cbRequired; + if (cbReserved < cbRequired) + return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + + /* Generate commands. */ + uint8_t *pu8Cmd = (uint8_t *)pvCmd; + SVGA3dCmdHeader *pHdr; + + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_DX_MOB_FENCE_64; + pHdr->size = sizeof(SVGA3dCmdDXMobFence64); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdDXMobFence64 *pCmd = (SVGA3dCmdDXMobFence64 *)pu8Cmd; + pCmd->value = u64FenceValue; + pCmd->mobId = pSvga->mobidMiniport; + pCmd->mobOffset = RT_OFFSETOF(VMSVGAMINIPORTMOB, u64MobFence); + pu8Cmd += sizeof(*pCmd); + } + + Assert((uintptr_t)pu8Cmd - (uintptr_t)pvCmd == cbRequired); + + return STATUS_SUCCESS; +} + + +DECLINLINE(int) SvgaFenceCmp64(uint64_t u64FenceA, uint64_t u64FenceB) +{ + if ( u64FenceA < u64FenceB + || u64FenceA - u64FenceB > UINT64_MAX / 2) + return -1; /* FenceA is newer than FenceB. */ + + return u64FenceA > u64FenceB; +} + + +static NTSTATUS svgaPTHost2SysMem(PVBOXMP_DEVEXT pDevExt, PVBOXWDDM_ALLOCATION pAllocation, + DXGKARG_BUILDPAGINGBUFFER *pBuildPagingBuffer, uint32_t *pcbCommands) +{ + GALOG(("PTHost2SysMem: MDL: offset %u, size %u, 0x%p/%u, stage %u\n", + pBuildPagingBuffer->Transfer.Destination.pMdl->ByteOffset, + pBuildPagingBuffer->Transfer.Destination.pMdl->ByteCount, + pBuildPagingBuffer->Transfer.Destination.pMdl, + pBuildPagingBuffer->Transfer.MdlOffset, + pBuildPagingBuffer->MultipassOffset)); + + /* + * Steps that take partial transfers into account. + * If MultipassOffset is 1, then wait for the MOB fence that was submitted for deleting of MOB. + * - if surface was not yet read back, that is mobid != SVGA3D_INVALID_ID: + * - readback surface; + * - unbind and delete current mob (pGbo still exists in the guest memory); + * - set mobid to SVGA3D_INVALID_ID; + * - set MultipassOffset to 1 and return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER. + * + * - memcpy from dx.pGbo to the MDL. + */ + + VBOXWDDM_EXT_VMSVGA *pSvga = pDevExt->pGa->hw.pSvga; + + /* Segment 3 allocations have one instance. */ + DX_ALLOCATION_INSTANCE *pInstance = svgaGetAllocationInstance(pAllocation, 0); + AssertReturn(pAllocation->dx.SegmentId == 3 && pInstance, STATUS_INVALID_PARAMETER); + + if (!pAllocation->dx.flags.fReadbackCompleted) + { + if (pBuildPagingBuffer->MultipassOffset == 0) + { + /* Start paging transfer for this surface by reading back its content. */ + /* MOB should be bound at this point. */ + Assert(pInstance->mobid != SVGA3D_INVALID_ID); + + /* Read back, unbind and destroy mob. */ + + /* + * How many bytes are required in the DMA buffer + */ + uint32_t cbRequired = 0; + SvgaMobDestroy(pSvga, SVGA3D_INVALID_ID, NULL, 0, &cbRequired); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdReadbackGBSurface); + SvgaCommandFence(pSvga, 0, NULL, 0, &cbRequired); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdBindGBSurface); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdInvalidateGBSurface); + + if (pBuildPagingBuffer->DmaSize < cbRequired) + return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + + uint8_t *pu8Cmd = (uint8_t *)pBuildPagingBuffer->pDmaBuffer; + SVGA3dCmdHeader *pHdr; + + /* + * Readback. + */ + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_READBACK_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdReadbackGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdReadbackGBSurface *pCmd = (SVGA3dCmdReadbackGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pu8Cmd += sizeof(*pCmd); + } + + /* + * Fence that indicates that readback has been completed. + */ + pAllocation->dx.u64LastReferencedCommandFence = ASMAtomicIncU64(&pSvga->u64MobFence); + + uint32_t cbCmd = 0; + NTSTATUS Status = SvgaCommandFence(pSvga, pAllocation->dx.u64LastReferencedCommandFence, pu8Cmd, + cbRequired - ((uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer), + &cbCmd); + AssertReturn(NT_SUCCESS(Status), Status); + pu8Cmd += cbCmd; + + /* + * Unbind the mob. + */ + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_BIND_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdBindGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdBindGBSurface *pCmd = (SVGA3dCmdBindGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pCmd->mobid = SVGA3D_INVALID_ID; + pu8Cmd += sizeof(*pCmd); + } + + /* + * Destroy the mob. + */ + cbCmd = 0; + Status = SvgaMobDestroy(pSvga, pInstance->mobid, pu8Cmd, + cbRequired - ((uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer), + &cbCmd); + AssertReturn(NT_SUCCESS(Status), Status); + pu8Cmd += cbCmd; + + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_INVALIDATE_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdInvalidateGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdInvalidateGBSurface *pCmd = (SVGA3dCmdInvalidateGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pu8Cmd += sizeof(*pCmd); + } + + *pcbCommands = (uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer; + Assert(*pcbCommands == cbRequired); + + pInstance->mobid = SVGA3D_INVALID_ID; + + /* Continue with the next stage. */ + pBuildPagingBuffer->MultipassOffset = 1; + + /* Return this to submit the current commands and get to the next stage of this transfer. */ + return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + } + + if (pBuildPagingBuffer->MultipassOffset == 1) + { + /* Wait until submitted commands are processed by waiting for the fence. */ + uint64_t const u64CurrentCommandFence = ASMAtomicReadU64(&pSvga->pMiniportMobData->u64MobFence); + if (SvgaFenceCmp64(pAllocation->dx.u64LastReferencedCommandFence, u64CurrentCommandFence) > 0) + return STATUS_GRAPHICS_ALLOCATION_BUSY; + + pAllocation->dx.flags.fReadbackCompleted = true; + } + } + + /* Surface has been already read back to its GBO. */ + Assert(pAllocation->dx.flags.fReadbackCompleted); + Assert(pInstance->mobid == SVGA3D_INVALID_ID); + + /* Copy data from GBO to MDL. */ + Assert(!pInstance->pGbo->flags.fMdl); /* The GBO must be allocated by the driver. */ + + /* Check that transfer is within GBO memory. */ + AssertReturn( pBuildPagingBuffer->Transfer.TransferOffset < pInstance->pGbo->cbGbo + && pBuildPagingBuffer->Transfer.TransferSize <= pInstance->pGbo->cbGbo - pBuildPagingBuffer->Transfer.TransferOffset, + STATUS_INVALID_PARAMETER); + + /* Src is the GBO. */ + uint8_t const *pu8Src = (uint8_t *)RTR0MemObjAddress(pInstance->pGbo->hMemObj); + AssertReturn(pu8Src, STATUS_UNSUCCESSFUL); + pu8Src += pBuildPagingBuffer->Transfer.TransferOffset; + + /* Dst is the MDL. */ + uint8_t *pu8Dst = (uint8_t *)MmGetSystemAddressForMdlSafe( + pBuildPagingBuffer->Transfer.Destination.pMdl, NormalPagePriority); + AssertReturn(pu8Dst, STATUS_UNSUCCESSFUL); + pu8Dst += pBuildPagingBuffer->Transfer.MdlOffset * PAGE_SIZE; + + memcpy(pu8Dst, pu8Src, pBuildPagingBuffer->Transfer.TransferSize); + + /** @todo 'pInstance->pGbo' can be deallocated on 'pBuildPagingBuffer->Transfer.Flags.TransferEnd' + * and reallocated in svgaPTSysMem2Host. + */ + + return STATUS_SUCCESS; +} + + +static NTSTATUS svgaPTSysMem2Host(PVBOXMP_DEVEXT pDevExt, PVBOXWDDM_ALLOCATION pAllocation, + DXGKARG_BUILDPAGINGBUFFER *pBuildPagingBuffer, uint32_t *pcbCommands) +{ + GALOG(("PTSysMem2Host: MDL: offset %u, size %u, 0x%p/%u, stage %u\n", + pBuildPagingBuffer->Transfer.Source.pMdl->ByteOffset, + pBuildPagingBuffer->Transfer.Source.pMdl->ByteCount, + pBuildPagingBuffer->Transfer.Source.pMdl, + pBuildPagingBuffer->Transfer.MdlOffset, + pBuildPagingBuffer->MultipassOffset)); + + /* Copy data from MDL to the GBO until end of transfer (pBuildPagingBuffer->Transfer.Flags.TransferEnd). + * Create a mob, bind the surface and update it on TransferEnd. + */ + + VBOXWDDM_EXT_VMSVGA *pSvga = pDevExt->pGa->hw.pSvga; + + /* Segment 3 allocations have one instance. */ + DX_ALLOCATION_INSTANCE *pInstance = svgaGetAllocationInstance(pAllocation, 0); + AssertReturn(pAllocation->dx.SegmentId == 3 && pInstance, STATUS_INVALID_PARAMETER); + + Assert(pAllocation->dx.flags.fReadbackCompleted); + Assert(pInstance->mobid == SVGA3D_INVALID_ID); + + /* Copy data from MDL to GBO. */ + Assert(!pInstance->pGbo->flags.fMdl); /* The GBO must be allocated by the driver. */ + + /* Check that transfer is within GBO memory. */ + AssertReturn( pBuildPagingBuffer->Transfer.TransferOffset < pInstance->pGbo->cbGbo + && pBuildPagingBuffer->Transfer.TransferSize <= pInstance->pGbo->cbGbo - pBuildPagingBuffer->Transfer.TransferOffset, + STATUS_INVALID_PARAMETER); + + /* Src is the MDL. */ + uint8_t const *pu8Src = (uint8_t *)MmGetSystemAddressForMdlSafe( + pBuildPagingBuffer->Transfer.Source.pMdl, NormalPagePriority); + AssertReturn(pu8Src, STATUS_UNSUCCESSFUL); + pu8Src += pBuildPagingBuffer->Transfer.MdlOffset * PAGE_SIZE; + + /* Dst is the GBO. */ + uint8_t *pu8Dst = (uint8_t *)RTR0MemObjAddress(pInstance->pGbo->hMemObj); + AssertReturn(pu8Dst, STATUS_UNSUCCESSFUL); + pu8Dst += pBuildPagingBuffer->Transfer.TransferOffset; + + memcpy(pu8Dst, pu8Src, pBuildPagingBuffer->Transfer.TransferSize); + + if (pBuildPagingBuffer->Transfer.Flags.TransferEnd) + { + /* + * Create a mob, bind the surface and update it. + */ + uint32_t cbRequired = 0; + SvgaMobDefine(pSvga, SVGA3D_INVALID_ID, NULL, 0, &cbRequired); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdBindGBSurface); + cbRequired += sizeof(SVGA3dCmdHeader) + sizeof(SVGA3dCmdUpdateGBSurface); + + if (pBuildPagingBuffer->DmaSize < cbRequired) + return STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + + uint8_t *pu8Cmd = (uint8_t *)pBuildPagingBuffer->pDmaBuffer; + SVGA3dCmdHeader *pHdr; + + /* Create a new mob */ + /// @todo svgaDefineMobForAllocation? + NTSTATUS Status = SvgaMobAlloc(pSvga, &pInstance->mobid, pInstance->pGbo); + AssertReturn(NT_SUCCESS(Status), Status); + + uint32_t cbCmd = 0; + Status = SvgaMobDefine(pSvga, pInstance->mobid, pu8Cmd, + cbRequired - ((uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer), + &cbCmd); + AssertReturnStmt(NT_SUCCESS(Status), SvgaMobFree(pSvga, &pInstance->mobid), Status); + pu8Cmd += cbCmd; + + /* Bind the surface */ + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_BIND_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdBindGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdBindGBSurface *pCmd = (SVGA3dCmdBindGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pCmd->mobid = pInstance->mobid; + pu8Cmd += sizeof(*pCmd); + } + + /* Update the surface */ + pHdr = (SVGA3dCmdHeader *)pu8Cmd; + pHdr->id = SVGA_3D_CMD_UPDATE_GB_SURFACE; + pHdr->size = sizeof(SVGA3dCmdUpdateGBSurface); + pu8Cmd += sizeof(*pHdr); + + { + SVGA3dCmdUpdateGBSurface *pCmd = (SVGA3dCmdUpdateGBSurface *)pu8Cmd; + pCmd->sid = pAllocation->dx.sid; + pu8Cmd += sizeof(*pCmd); + } + + *pcbCommands = (uintptr_t)pu8Cmd - (uintptr_t)pBuildPagingBuffer->pDmaBuffer; + Assert(*pcbCommands == cbRequired); + + pAllocation->dx.flags.fReadbackCompleted = false; + } + + return STATUS_SUCCESS; +} + + static NTSTATUS svgaPagingTransfer(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGINGBUFFER *pBuildPagingBuffer, uint32_t *pcbCommands) { VBOXWDDM_EXT_VMSVGA *pSvga = pDevExt->pGa->hw.pSvga; @@ -773,9 +1097,19 @@ static NTSTATUS svgaPagingTransfer(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGINGBU && pBuildPagingBuffer->Transfer.TransferSize <= cbAllocation - pBuildPagingBuffer->Transfer.TransferOffset, STATUS_INVALID_PARAMETER); + GALOG(("%u -> %u, offset %u, size %u, cbAllocation %u\n", + pBuildPagingBuffer->Transfer.Source.SegmentId, + pBuildPagingBuffer->Transfer.Destination.SegmentId, + pBuildPagingBuffer->Transfer.TransferOffset, + pBuildPagingBuffer->Transfer.TransferSize, + cbAllocation)); + if (pBuildPagingBuffer->Transfer.TransferSize == 0) return STATUS_SUCCESS; + if (!pBuildPagingBuffer->Transfer.Flags.AllocationIsIdle) + return STATUS_GRAPHICS_ALLOCATION_BUSY; + NTSTATUS Status = STATUS_SUCCESS; RT_NOREF(pcbCommands); @@ -795,6 +1129,8 @@ static NTSTATUS svgaPagingTransfer(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGINGBU Status = svgaPTSysMem2VRAM(pDevExt, pAllocation, pBuildPagingBuffer); } } + else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 3) /* To Host */ + Status = svgaPTSysMem2Host(pDevExt, pAllocation, pBuildPagingBuffer, pcbCommands); else DEBUG_BREAKPOINT_TEST(); break; @@ -817,6 +1153,15 @@ static NTSTATUS svgaPagingTransfer(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGINGBU break; } + case 3: /* From a host resource. */ + { + if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) /* To system memory */ + Status = svgaPTHost2SysMem(pDevExt, pAllocation, pBuildPagingBuffer, pcbCommands); + else + DEBUG_BREAKPOINT_TEST(); + break; + } + default: DEBUG_BREAKPOINT_TEST(); } diff --git a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPGaWddm.cpp b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPGaWddm.cpp index b67386f40b27..1a9a92436e6f 100644 --- a/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPGaWddm.cpp +++ b/src/VBox/Additions/win/Graphics/Video/mp/wddm/gallium/VBoxMPGaWddm.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxMPGaWddm.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxMPGaWddm.cpp 115051 2026-08-17 15:26:27Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox Windows Guest Mesa3D - Gallium driver interface for WDDM kernel mode driver. */ @@ -521,10 +521,6 @@ static void gaReportFence(PVBOXMP_DEVEXT pDevExt) } } -/* If there are no commands but we need to trigger fence submission anyway, then submit a buffer of this size. */ -#define GA_DMA_MIN_SUBMIT_SIZE 4 -AssertCompile(GA_DMA_MIN_SUBMIT_SIZE < sizeof(SVGA3dCmdHeader)); - DECLINLINE(PVBOXWDDM_ALLOCATION) getAllocationFromAllocationListEntry(DXGK_ALLOCATIONLIST *pAllocationListEntry) { PVBOXWDDM_OPENALLOCATION pOA = (PVBOXWDDM_OPENALLOCATION)pAllocationListEntry->hDeviceSpecificAllocation; @@ -1135,15 +1131,8 @@ static NTSTATUS gaRenderGA3D(PVBOXWDDM_CONTEXT pContext, DXGKARG_RENDER *pRender void *pvTarget = pRender->pDmaBuffer; uint32_t const cbTarget = pRender->DmaSize; GAHWRENDERDATA *pHwRenderData = NULL; - if (cbTarget > GA_DMA_MIN_SUBMIT_SIZE) - { - Status = SvgaRenderCommands(pGaDevExt->hw.pSvga, pContext->pSvgaContext, pvTarget, cbTarget, pvSource, cbSource, - &u32TargetLength, &u32ProcessedLength, &pHwRenderData); - } - else - { - Status = STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; - } + Status = SvgaRenderCommands(pGaDevExt->hw.pSvga, pContext->pSvgaContext, pvTarget, cbTarget, pvSource, cbSource, + &u32TargetLength, &u32ProcessedLength, &pHwRenderData); GAFENCEOBJECT *pFO = NULL; if (Status == STATUS_SUCCESS) @@ -1267,125 +1256,119 @@ static NTSTATUS gaBuildPagingBufferOld(PVBOXMP_DEVEXT pDevExt, DXGKARG_BUILDPAGI if (pBuildPagingBuffer->DmaBufferPrivateDataSize >= sizeof(GARENDERDATA)) { // void *pvTarget = pBuildPagingBuffer->pDmaBuffer; - const uint32_t cbTarget = pBuildPagingBuffer->DmaSize; - if (cbTarget > GA_DMA_MIN_SUBMIT_SIZE) + // const uint32_t cbTarget = pBuildPagingBuffer->DmaSize; + + switch (pBuildPagingBuffer->Operation) { - switch (pBuildPagingBuffer->Operation) + case DXGK_OPERATION_TRANSFER: { - case DXGK_OPERATION_TRANSFER: + GALOG(("DXGK_OPERATION_TRANSFER: %p: @0x%x, cb 0x%x; src: %d:%p; dst: %d:%p; flags 0x%x, off 0x%x\n", + pBuildPagingBuffer->Transfer.hAllocation, + pBuildPagingBuffer->Transfer.TransferOffset, + pBuildPagingBuffer->Transfer.TransferSize, + pBuildPagingBuffer->Transfer.Source.SegmentId, + pBuildPagingBuffer->Transfer.Source.pMdl, + pBuildPagingBuffer->Transfer.Destination.SegmentId, + pBuildPagingBuffer->Transfer.Destination.pMdl, + pBuildPagingBuffer->Transfer.Flags.Value, + pBuildPagingBuffer->Transfer.MdlOffset)); + if (pBuildPagingBuffer->Transfer.Source.SegmentId == 0) { - GALOG(("DXGK_OPERATION_TRANSFER: %p: @0x%x, cb 0x%x; src: %d:%p; dst: %d:%p; flags 0x%x, off 0x%x\n", - pBuildPagingBuffer->Transfer.hAllocation, - pBuildPagingBuffer->Transfer.TransferOffset, - pBuildPagingBuffer->Transfer.TransferSize, - pBuildPagingBuffer->Transfer.Source.SegmentId, - pBuildPagingBuffer->Transfer.Source.pMdl, - pBuildPagingBuffer->Transfer.Destination.SegmentId, - pBuildPagingBuffer->Transfer.Destination.pMdl, - pBuildPagingBuffer->Transfer.Flags.Value, - pBuildPagingBuffer->Transfer.MdlOffset)); - if (pBuildPagingBuffer->Transfer.Source.SegmentId == 0) + /* SysMem source. */ + if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 1) { - /* SysMem source. */ - if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 1) - { - /* SysMem -> VRAM. */ - Status = gaSoftwarePagingTransfer(pDevExt, pBuildPagingBuffer); - if (Status == STATUS_SUCCESS) - { - /* Generate a NOP. */ - Status = STATUS_NOT_SUPPORTED; - } - } - else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) + /* SysMem -> VRAM. */ + Status = gaSoftwarePagingTransfer(pDevExt, pBuildPagingBuffer); + if (Status == STATUS_SUCCESS) { - /* SysMem -> SysMem, should not happen, bugcheck. */ - AssertFailed(); - Status = STATUS_INVALID_PARAMETER; - } - else - { - /* SysMem -> GPU surface. Our driver probably does not need it. - * SVGA_3D_CMD_SURFACE_DMA(GMR -> Surface)? - */ - AssertFailed(); + /* Generate a NOP. */ Status = STATUS_NOT_SUPPORTED; } } - else if (pBuildPagingBuffer->Transfer.Source.SegmentId == 1) + else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) { - /* VRAM source. */ - if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) - { - /* VRAM -> SysMem. */ - Status = gaSoftwarePagingTransfer(pDevExt, pBuildPagingBuffer); - if (Status == STATUS_SUCCESS) - { - /* Generate a NOP. */ - Status = STATUS_NOT_SUPPORTED; - } - } - else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 1) - { - /* VRAM -> VRAM, should not happen, bugcheck. */ - AssertFailed(); - Status = STATUS_INVALID_PARAMETER; - } - else + /* SysMem -> SysMem, should not happen, bugcheck. */ + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + } + else + { + /* SysMem -> GPU surface. Our driver probably does not need it. + * SVGA_3D_CMD_SURFACE_DMA(GMR -> Surface)? + */ + AssertFailed(); + Status = STATUS_NOT_SUPPORTED; + } + } + else if (pBuildPagingBuffer->Transfer.Source.SegmentId == 1) + { + /* VRAM source. */ + if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 0) + { + /* VRAM -> SysMem. */ + Status = gaSoftwarePagingTransfer(pDevExt, pBuildPagingBuffer); + if (Status == STATUS_SUCCESS) { - /* VRAM -> GPU surface. Our driver probably does not need it. - * SVGA_3D_CMD_SURFACE_DMA(SVGA_GMR_FRAMEBUFFER -> Surface)? - */ - AssertFailed(); + /* Generate a NOP. */ Status = STATUS_NOT_SUPPORTED; } } + else if (pBuildPagingBuffer->Transfer.Destination.SegmentId == 1) + { + /* VRAM -> VRAM, should not happen, bugcheck. */ + AssertFailed(); + Status = STATUS_INVALID_PARAMETER; + } else { - /* GPU surface. Our driver probably does not need it. - * SVGA_3D_CMD_SURFACE_DMA(Surface -> GMR)? + /* VRAM -> GPU surface. Our driver probably does not need it. + * SVGA_3D_CMD_SURFACE_DMA(SVGA_GMR_FRAMEBUFFER -> Surface)? */ AssertFailed(); Status = STATUS_NOT_SUPPORTED; } - - /** @todo Ignore for now. */ - if (Status == STATUS_NOT_SUPPORTED) - { - /* NOP */ - Status = STATUS_SUCCESS; - } - } break; - - case DXGK_OPERATION_FILL: + } + else { - GALOG(("DXGK_OPERATION_FILL: %p: cb 0x%x, pattern 0x%x, %d:0x%08X\n", - pBuildPagingBuffer->Fill.hAllocation, - pBuildPagingBuffer->Fill.FillSize, - pBuildPagingBuffer->Fill.FillPattern, - pBuildPagingBuffer->Fill.Destination.SegmentId, - pBuildPagingBuffer->Fill.Destination.SegmentAddress.LowPart)); - /* NOP */ - } break; + /* GPU surface. Our driver probably does not need it. + * SVGA_3D_CMD_SURFACE_DMA(Surface -> GMR)? + */ + AssertFailed(); + Status = STATUS_NOT_SUPPORTED; + } - case DXGK_OPERATION_DISCARD_CONTENT: + /** @todo Ignore for now. */ + if (Status == STATUS_NOT_SUPPORTED) { - GALOG(("DXGK_OPERATION_DISCARD_CONTENT: %p: flags 0x%x, %d:0x%08X\n", - pBuildPagingBuffer->DiscardContent.hAllocation, - pBuildPagingBuffer->DiscardContent.Flags, - pBuildPagingBuffer->DiscardContent.SegmentId, - pBuildPagingBuffer->DiscardContent.SegmentAddress.LowPart)); /* NOP */ - } break; + Status = STATUS_SUCCESS; + } + } break; - default: - AssertFailed(); - break; - } - } - else - { - Status = STATUS_GRAPHICS_INSUFFICIENT_DMA_BUFFER; + case DXGK_OPERATION_FILL: + { + GALOG(("DXGK_OPERATION_FILL: %p: cb 0x%x, pattern 0x%x, %d:0x%08X\n", + pBuildPagingBuffer->Fill.hAllocation, + pBuildPagingBuffer->Fill.FillSize, + pBuildPagingBuffer->Fill.FillPattern, + pBuildPagingBuffer->Fill.Destination.SegmentId, + pBuildPagingBuffer->Fill.Destination.SegmentAddress.LowPart)); + /* NOP */ + } break; + + case DXGK_OPERATION_DISCARD_CONTENT: + { + GALOG(("DXGK_OPERATION_DISCARD_CONTENT: %p: flags 0x%x, %d:0x%08X\n", + pBuildPagingBuffer->DiscardContent.hAllocation, + pBuildPagingBuffer->DiscardContent.Flags, + pBuildPagingBuffer->DiscardContent.SegmentId, + pBuildPagingBuffer->DiscardContent.SegmentAddress.LowPart)); + /* NOP */ + } break; + + default: + AssertFailed(); + break; } /* Fill RenderData description in any case, it will be ignored if the above code failed. */ diff --git a/src/VBox/Additions/win/SharedFolders/driver/Makefile.kmk b/src/VBox/Additions/win/SharedFolders/driver/Makefile.kmk index 8cec9bb68463..50a9e5b27dac 100644 --- a/src/VBox/Additions/win/SharedFolders/driver/Makefile.kmk +++ b/src/VBox/Additions/win/SharedFolders/driver/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 113299 2026-03-10 08:08:39Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 115021 2026-08-12 23:45:19Z knut.osmundsen@oracle.com $ ## @file # Sub-Makefile for the VirtualBox Windows Guest Shared Folders FSD. # @@ -97,7 +97,7 @@ endif # Hack: The rdbsslib.lib we're using wasn't compiled -guard:ehcont. Unfortunately # we cannot individually disable the -guard options, so we have to disable # everything and re-enable all but ehcont. -VBoxSF_LDFLAGS.win.amd64 = -Guard:No $(VBOX_VCC_LD_GUARD_CF) +VBoxSF_LDFLAGS.win.amd64 = -Guard:No $(VBOX_VCC_LD_GUARD_RETPOLINE_WITH_IGNORE) $(VBOX_VCC_LD_GUARD_CF) VBoxSF_USES.win += vboximportchecker VBoxSF_VBOX_IMPORT_CHECKER.win.x86 = w8/r0 diff --git a/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp b/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp index 7d8adc4f9693..12a8ea603e08 100644 --- a/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp +++ b/src/VBox/Additions/win/VBoxTray/VBoxClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxClipboard.cpp 114414 2026-06-17 21:44:21Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxClipboard.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * VBoxClipboard - Shared clipboard, Windows Guest Implementation. */ @@ -94,8 +94,10 @@ static char s_szClipWndClassName[] = SHCL_WIN_WNDCLASS_NAME; *********************************************************************************************************************************/ #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS static DECLCALLBACK(void) vbtrShClTransferCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); +static DECLCALLBACK(void) vbtrShClTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, + PSHCLTRANSFERCTX pTransferCtx); static DECLCALLBACK(void) vbtrShClTransferDestroyCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); -static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); +static DECLCALLBACK(int) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx); static DECLCALLBACK(void) vbtrShClTransferErrorCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rc); #endif @@ -142,6 +144,25 @@ static DECLCALLBACK(void) vbtrShClTransferCreatedCallback(PSHCLTRANSFERCALLBACKC LogRelFlowFuncLeaveRC(rc); } +/** + * @copydoc SHCLTRANSFERCALLBACKS::pfnOnUnregistered + * + * Disables the IDataObject and drops its long-lived transfer reference before + * consuming teardown waits for temporary transfer users. + * + * @thread Clipboard main thread. + */ +static DECLCALLBACK(void) vbtrShClTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, + PSHCLTRANSFERCTX pTransferCtx) +{ + RT_NOREF(pTransferCtx); + + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtr(pTransfer); + + ShClWinTransferUnregister(pTransfer); +} + /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnDestroy * @@ -184,13 +205,13 @@ static DECLCALLBACK(int) vbtrShClTransferInitializeCallback(PSHCLTRANSFERCALLBAC switch(ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { rc = ShClWinTransferInitialize(&pCtx->Win, pTransfer); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { rc = ShClWinTransferGetRootsFromClipboard(&pCtx->Win, pTransfer); break; @@ -213,7 +234,7 @@ static DECLCALLBACK(int) vbtrShClTransferInitializeCallback(PSHCLTRANSFERCALLBAC * * @thread Clipboard main thread. */ -static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +static DECLCALLBACK(int) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { LogFlowFuncEnter(); @@ -227,13 +248,13 @@ static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLB switch(ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { rc = ShClWinTransferStart(&pCtx->Win, pTransfer); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: break; default: @@ -241,6 +262,7 @@ static DECLCALLBACK(void) vbtrShClTransferInitializedCallback(PSHCLTRANSFERCALLB } LogFlowFuncLeaveRC(rc); + return rc; } /** @@ -303,11 +325,11 @@ static DECLCALLBACK(void) vbtrShClTransferStartedCallback(PSHCLTRANSFERCALLBACKC int rc = VINF_SUCCESS; /* The guest wants to transfer data to the host. */ - if (enmDir == SHCLTRANSFERDIR_TO_REMOTE) /* G->H */ + if (enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST) { rc = ShClWinTransferGetRootsFromClipboard(&pCtx->Win, pTransfer); } - else if (enmDir == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */ + else if (enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST) { /* Nothing to do here. */ } @@ -1032,13 +1054,14 @@ DECLCALLBACK(int) vbtrShClWorker(void *pvInstance, bool volatile *pfShutdown) pCtx->CmdCtx.Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */ pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); - pCtx->CmdCtx.Transfers.Callbacks.pfnOnCreated = vbtrShClTransferCreatedCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnDestroy = vbtrShClTransferDestroyCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialize = vbtrShClTransferInitializeCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbtrShClTransferInitializedCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnStarted = vbtrShClTransferStartedCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbtrShClTransferCompletedCallback; - pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbtrShClTransferErrorCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnCreated = vbtrShClTransferCreatedCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnUnregistered = vbtrShClTransferUnregisteredCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnDestroy = vbtrShClTransferDestroyCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialize = vbtrShClTransferInitializeCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbtrShClTransferInitializedCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnStarted = vbtrShClTransferStartedCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbtrShClTransferCompletedCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnError = vbtrShClTransferErrorCallback; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ int rc; diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp index dcf649447bfb..599b301ff125 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard-x11.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-x11.cpp 114744 2026-07-21 18:37:21Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard-x11.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard implementation. */ @@ -43,6 +43,9 @@ #include #include #include +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +# include +#endif #include "VBoxClient.h" #include "clipboard.h" @@ -55,6 +58,225 @@ #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +static void vbclX11TransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer); + +/** Resets the transfer key bound to the current asynchronous preparation. */ +static void vbclX11TransferStateResetKey(PSHCLX11TRANSFERSTATE pX11TransferState) +{ + pX11TransferState->idTransfer = NIL_SHCLTRANSFERID; + pX11TransferState->uTransferGeneration = NIL_SHCLTRANSFERGEN; +} + +/** Resets the key of the transfer backing the advertised URI-list data. */ +static void vbclX11TransferPublishedResetKey(PSHCLX11TRANSFERSTATE pX11TransferState) +{ + pX11TransferState->idPublishedTransfer = NIL_SHCLTRANSFERID; + pX11TransferState->uPublishedTransferGeneration = NIL_SHCLTRANSFERGEN; +} + +/** + * Checks whether a transfer is the exact transfer bound to the current + * asynchronous preparation. + */ +static bool vbclX11TransferStateMatches(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + return pX11TransferState->fPreparing + && pX11TransferState->idTransfer != NIL_SHCLTRANSFERID + && pX11TransferState->uTransferGeneration != NIL_SHCLTRANSFERGEN + && pX11TransferState->idTransfer == ShClTransferGetID(pTransfer) + && pX11TransferState->uTransferGeneration == ShClTransferGetGeneration(pTransfer); +} + +/** Checks whether a transfer backs the currently advertised URI-list data. */ +static bool vbclX11TransferPublishedMatches(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + return pX11TransferState->idPublishedTransfer != NIL_SHCLTRANSFERID + && pX11TransferState->uPublishedTransferGeneration != NIL_SHCLTRANSFERGEN + && pX11TransferState->idPublishedTransfer == ShClTransferGetID(pTransfer) + && pX11TransferState->uPublishedTransferGeneration == ShClTransferGetGeneration(pTransfer); +} + +/** Cancels the transfer backing URI-list data superseded by a new clipboard offer. */ +static void vbclX11TransferPublishedCancel(PSHCLCONTEXT pCtx) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + if (pX11TransferState->idPublishedTransfer == NIL_SHCLTRANSFERID) + return; + + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferById(&pCtx->TransferCtx, + pX11TransferState->idPublishedTransfer); + if ( pTransfer + && vbclX11TransferPublishedMatches(pCtx, pTransfer)) + { + vbclX11TransferUnregister(pCtx, pTransfer); + if (ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_INITIALIZED) + { + int rc = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pTransfer, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Canceling superseded transfer %RU16/%RU64 failed with %Rrc\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer), rc)); + } + } + else + LogRel2(("Shared Clipboard: Published transfer %RU16/%RU64 was already gone or replaced\n", + pX11TransferState->idPublishedTransfer, pX11TransferState->uPublishedTransferGeneration)); + + vbclX11TransferPublishedResetKey(pX11TransferState); +} + +/** + * Starts preparing HTTP-backed URI-list data for the current host clipboard offer. + * + * The request is issued by the clipboard service worker. Its transfer is + * created and initialized later while that worker processes transfer-status + * messages, so the X11 event thread never waits for this operation. + */ +static int vbclX11TransferStateStart(PSHCLCONTEXT pCtx) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + AssertReturn(!pX11TransferState->fPreparing, VERR_WRONG_ORDER); + AssertReturn(pX11TransferState->fFormats & VBOX_SHCL_FMT_URI_LIST, VERR_INVALID_PARAMETER); + + pX11TransferState->fPreparing = true; + pX11TransferState->uPreparingOfferGeneration = pX11TransferState->uOfferGeneration; + vbclX11TransferStateResetKey(pX11TransferState); + + /* Preserve the existing protocol sequence: consume the URI-list clipboard + * data response before requesting the HTTP transfer. The actual X11 data + * will be the URI list of HTTP URLs produced after transfer initialization. */ + void *pvData = NULL; + uint32_t cbData = 0; + int rc = VbglR3ClipboardReadDataEx(&pCtx->CmdCtx, VBOX_SHCL_FMT_URI_LIST, &pvData, &cbData); + RTMemFree(pvData); + if (RT_SUCCESS(rc)) + rc = VbglR3ClipboardTransferRequest(&pCtx->CmdCtx); + + if (RT_FAILURE(rc)) + { + pX11TransferState->fPreparing = false; + vbclX11TransferStateResetKey(pX11TransferState); + LogRel(("Shared Clipboard: Starting asynchronous X11 transfer preparation failed with %Rrc\n", rc)); + } + else + LogRel2(("Shared Clipboard: Preparing X11 URI list for clipboard offer generation %RU64\n", + pX11TransferState->uPreparingOfferGeneration)); + + return rc; +} + +/** + * Completes asynchronous preparation for an exact transfer ID and generation. + * + * A result which no longer matches the current clipboard offer is discarded. + * If the host clipboard changed while a transfer was being initialized, a new + * serialized request is started for the latest offer after the old transfer + * has been canceled. + * + * @returns VBox status code for preparing and publishing the URI list. + */ +static int vbclX11TransferStateComplete(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, + const char *pszUriList, size_t cbUriList, int rcPreparation) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + if (!vbclX11TransferStateMatches(pCtx, pTransfer)) + { + LogRel2(("Shared Clipboard: Ignoring URI-list preparation completion for unbound transfer %RU16/%RU64\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); + return VINF_SUCCESS; + } + + uint64_t const uPreparingOfferGeneration = pX11TransferState->uPreparingOfferGeneration; + bool const fCurrentOffer = uPreparingOfferGeneration == pX11TransferState->uOfferGeneration + && (pX11TransferState->fFormats & VBOX_SHCL_FMT_URI_LIST); + + pX11TransferState->fPreparing = false; + vbclX11TransferStateResetKey(pX11TransferState); + + bool fPublished = false; + if (RT_SUCCESS(rcPreparation) && fCurrentOffer) + { + if (!pszUriList || !cbUriList) + rcPreparation = VERR_SHCLPB_NO_DATA; + else if (cbUriList > UINT32_MAX) + rcPreparation = VERR_BUFFER_OVERFLOW; + else + { + rcPreparation = ShClX11ReportFormatsToX11AsyncEx(&pCtx->X11, pX11TransferState->fFormats, + VBOX_SHCL_FMT_URI_LIST, pszUriList, + (uint32_t)cbUriList); + if (RT_SUCCESS(rcPreparation)) + { + fPublished = true; + pX11TransferState->idPublishedTransfer = ShClTransferGetID(pTransfer); + pX11TransferState->uPublishedTransferGeneration = ShClTransferGetGeneration(pTransfer); + LogRel2(("Shared Clipboard: Advertised cached X11 URI list for transfer %RU16/%RU64, " + "clipboard offer generation %RU64\n", ShClTransferGetID(pTransfer), + ShClTransferGetGeneration(pTransfer), uPreparingOfferGeneration)); + } + } + } + + if (!fPublished) + { + if (RT_FAILURE(rcPreparation)) + LogRel(("Shared Clipboard: Preparing X11 URI list for transfer %RU16/%RU64 failed with %Rrc\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer), rcPreparation)); + else + LogRel2(("Shared Clipboard: Discarding stale X11 URI list for transfer %RU16/%RU64\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); + + vbclX11TransferUnregister(pCtx, pTransfer); + if ( RT_SUCCESS(rcPreparation) + && ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_INITIALIZED) + { + int rc2 = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pTransfer, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + if (RT_FAILURE(rc2)) + LogRel(("Shared Clipboard: Canceling unused transfer %RU16/%RU64 failed with %Rrc\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer), rc2)); + } + } + + if ( uPreparingOfferGeneration != pX11TransferState->uOfferGeneration + && (pX11TransferState->fFormats & VBOX_SHCL_FMT_URI_LIST)) + { + int rc2 = vbclX11TransferStateStart(pCtx); + if (RT_FAILURE(rc2)) + LogRel(("Shared Clipboard: Restarting X11 transfer preparation failed with %Rrc\n", rc2)); + } + + return rcPreparation; +} + +/** + * Handles a new host clipboard offer without exposing an unprepared URI target. + */ +static int vbclX11ReportHostFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) +{ + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + vbclX11TransferPublishedCancel(pCtx); + pX11TransferState->fFormats = fFormats; + pX11TransferState->uOfferGeneration++; + if (!pX11TransferState->uOfferGeneration) + pX11TransferState->uOfferGeneration = 1; + + /* Non-transfer formats can be advertised immediately. The URI-list bit is + * added by the completion callback together with its pre-seeded cache. */ + int rc = ShClX11ReportFormatsToX11Async(&pCtx->X11, fFormats & ~VBOX_SHCL_FMT_URI_LIST); + if ( (fFormats & VBOX_SHCL_FMT_URI_LIST) + && !pX11TransferState->fPreparing) + { + int rc2 = vbclX11TransferStateStart(pCtx); + if (RT_SUCCESS(rc)) + rc = rc2; + } + + return rc; +} + /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialize * @@ -76,7 +298,7 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA * will start reading those as soon as we report the INITIALIZED status. */ switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { void *pvData; uint32_t cbData; @@ -84,28 +306,14 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA &pvData, &cbData); if (RT_SUCCESS(rc)) { - rc = ShClTransferRootsSetFromStringListEx(pTransfer, (const char *)pvData, cbData, - "\n" /* X11-based Desktop environments separate entries with "\n" */); + rc = ShClTransferRootsSetFromStringList(pTransfer, (const char *)pvData, cbData); RTMemFree(pvData); } break; } - case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ - { - /* Retrieve the root entries as a first action, so that the transfer is ready to go - * once it gets registered to HTTP server. */ - rc = ShClTransferRootListRead(pTransfer); - if (RT_SUCCESS(rc)) - { - if (ShClTransferRootsCount(pTransfer)) - /* As soon as we register the transfer with the HTTP server, the transfer needs to have its roots set. */ - rc = ShClTransferHttpServerRegisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer); - else - rc = VERR_SHCLPB_NO_DATA; - } + case SHCLTRANSFERDIR_HOST_TO_GUEST: break; - } default: break; @@ -115,10 +323,73 @@ static DECLCALLBACK(int) vbclX11OnTransferInitializeCallback(PSHCLTRANSFERCALLBA return rc; } +/** + * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialized + * + * Reads the root list, then builds and publishes URI-list data only for the + * transfer ID and generation captured when the current asynchronous request + * was registered. + * + * @thread Clipboard main thread. + */ +static DECLCALLBACK(int) vbclX11OnTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + + int rc = VINF_SUCCESS; + + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST) + { + if (!vbclX11TransferStateMatches(pCtx, pTransfer)) + { + LogRelMax(16, ("Shared Clipboard: Rejecting unbound initialized transfer %RU16/%RU64\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer))); + int rc2 = VbglR3ClipboardTransferSendStatus(&pCtx->CmdCtx, pTransfer, + SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + if (RT_FAILURE(rc2)) + LogRel(("Shared Clipboard: Canceling unbound transfer %RU16/%RU64 failed with %Rrc\n", + ShClTransferGetID(pTransfer), ShClTransferGetGeneration(pTransfer), rc2)); + return VINF_SUCCESS; + } + + /* The remote provider rejects root-list reads until ShClTransferInit() + * has changed the transfer state to INITIALIZED. Registering the HTTP + * transfer from pfnOnInitialize therefore races ahead of that state + * transition and leaves URI-list conversion waiting forever. */ + rc = ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx); + if (RT_SUCCESS(rc)) + rc = ShClTransferRootListRead(pTransfer); + if (RT_SUCCESS(rc)) + { + if (ShClTransferRootsCount(pTransfer)) + rc = ShClTransferHttpServerRegisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer); + else + rc = VERR_SHCLPB_NO_DATA; + } + + char *pszUriList = NULL; + size_t cbUriList = 0; + if (RT_SUCCESS(rc)) + rc = ShClTransferHttpConvertToStringList(&pCtx->X11.HttpCtx.HttpServer, pTransfer, + &pszUriList, &cbUriList); + + rc = vbclX11TransferStateComplete(pCtx, pTransfer, pszUriList, cbUriList, rc); + RTStrFree(pszUriList); + } + + return rc; +} + /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnRegistered * - * This starts the HTTP server if not done yet and registers the transfer with it. + * This binds pending transfer preparation to the newly registered transfer's + * exact ID and generation. The HTTP server is started and the transfer is + * added after its roots have been read by the initialized callback. * * @thread Clipboard main thread. */ @@ -135,11 +406,27 @@ static DECLCALLBACK(void) vbclX11OnTransferRegisteredCallback(PSHCLTRANSFERCALLB AssertPtr(pTransfer); /* We only need to start the HTTP server when we actually receive data from the remote (host). */ - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) /* H->G */ + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST) { - int rc2 = ShClTransferHttpServerMaybeStart(&pCtx->X11.HttpCtx); - if (RT_FAILURE(rc2)) - LogRel(("Shared Clipboard: Registering HTTP transfer failed: %Rrc\n", rc2)); + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + /* H->G requests are serialized by fPreparing. The first H->G + * registration is therefore the protocol response to the outstanding + * request. Capture its immutable key once; later registration + * callbacks must not redirect this preparation to another transfer. */ + if ( pX11TransferState->fPreparing + && pX11TransferState->idTransfer == NIL_SHCLTRANSFERID) + { + pX11TransferState->idTransfer = ShClTransferGetID(pTransfer); + pX11TransferState->uTransferGeneration = ShClTransferGetGeneration(pTransfer); + LogRel2(("Shared Clipboard: Bound X11 transfer preparation to transfer %RU16/%RU64\n", + pX11TransferState->idTransfer, pX11TransferState->uTransferGeneration)); + } + else if (pX11TransferState->fPreparing) + LogRel2(("Shared Clipboard: Keeping X11 transfer preparation bound to transfer %RU16/%RU64; " + "ignoring registration of %RU16/%RU64\n", pX11TransferState->idTransfer, + pX11TransferState->uTransferGeneration, ShClTransferGetID(pTransfer), + ShClTransferGetGeneration(pTransfer))); + } LogFlowFuncLeave(); @@ -157,7 +444,7 @@ static DECLCALLBACK(void) vbclX11OnTransferRegisteredCallback(PSHCLTRANSFERCALLB */ static void vbclX11TransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer) { - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST) { if (ShClTransferHttpServerIsInitialized(&pCtx->X11.HttpCtx.HttpServer)) { @@ -177,7 +464,13 @@ static void vbclX11TransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer static DECLCALLBACK(void) vbclX11OnTransferUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx) { RT_NOREF(pTransferCtx); - vbclX11TransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer); + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; + if (vbclX11TransferStateMatches(pCtx, pCbCtx->pTransfer)) + vbclX11TransferStateComplete(pCtx, pCbCtx->pTransfer, NULL, 0, VERR_CANCELLED); + if (vbclX11TransferPublishedMatches(pCtx, pCbCtx->pTransfer)) + vbclX11TransferPublishedResetKey(&pCtx->X11TransferState); + vbclX11TransferUnregister(pCtx, pCbCtx->pTransfer); } /** @@ -189,8 +482,13 @@ static DECLCALLBACK(void) vbclX11OnTransferUnregisteredCallback(PSHCLTRANSFERCAL */ static DECLCALLBACK(void) vbclX11OnTransferCompletedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, int rc) { - RT_NOREF(rc); - vbclX11TransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer); + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; + if (vbclX11TransferStateMatches(pCtx, pCbCtx->pTransfer)) + vbclX11TransferStateComplete(pCtx, pCbCtx->pTransfer, NULL, 0, + RT_SUCCESS(rc) ? VERR_SHCLPB_NO_DATA : rc); + if (vbclX11TransferPublishedMatches(pCtx, pCbCtx->pTransfer)) + vbclX11TransferPublishedResetKey(&pCtx->X11TransferState); + vbclX11TransferUnregister(pCtx, pCbCtx->pTransfer); } /** @copydoc SHCLTRANSFERCALLBACKS::pfnOnError @@ -245,48 +543,12 @@ static DECLCALLBACK(int) vbclX11OnRequestDataFromSourceCallback(PSHCLCONTEXT pCt #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP if (uFmt == VBOX_SHCL_FMT_URI_LIST) { - rc = vbclX11ReadDataWorker(pCtx, uFmt, ppv, pcb, pvUser); - if (RT_SUCCESS(rc)) - { - /* Request a new H->G transfer from the host. - * This is needed in order to get a transfer ID from the host we can initialize our own local transfer with. - * Transfer creation and set up will be done in VbglR3. */ - rc = VbglR3ClipboardTransferRequest(&pCtx->CmdCtx); - if (RT_SUCCESS(rc)) - { - PSHCLHTTPSERVER pSrv = &pCtx->X11.HttpCtx.HttpServer; - - /* Wait until the HTTP server got the transfer registered, so that we have something to work with. */ - rc = ShClTransferHttpServerWaitForStatusChange(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED, SHCL_TIMEOUT_DEFAULT_MS); - if (RT_SUCCESS(rc)) - { - PSHCLTRANSFER pTransfer = ShClTransferHttpServerGetTransferLast(pSrv); - if (pTransfer) - { - rc = ShClTransferWaitForStatus(pTransfer, SHCL_TIMEOUT_DEFAULT_MS, SHCLTRANSFERSTATUS_INITIALIZED); - if (RT_SUCCESS(rc)) - { - char *pszData; - size_t cbData; - rc = ShClTransferHttpConvertToStringList(pSrv, pTransfer, &pszData, &cbData); - if (RT_SUCCESS(rc)) - { - *ppv = pszData; - *pcb = cbData; - /* ppv has ownership of pszData now. */ - } - } - } - else - { - AssertMsgFailed(("No registered transfer found for HTTP server\n")); - rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; - } - } - else - LogRel(("Shared Clipboard: Could not start transfer, as no new HTTP transfer was registered in time\n")); - } - } + /* URI targets are marked cache-only when advertised, so the common + * X11 code normally handles a miss without invoking this callback. + * Refuse it here as well: never start or wait for a transfer from the + * X11 event thread. */ + LogRelMax(16, ("Shared Clipboard: X11 URI-list conversion missed its prepared URI-list data cache\n")); + rc = VERR_SHCLPB_NO_DATA; } else /* Anything else */ #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ @@ -317,7 +579,7 @@ static DECLCALLBACK(int) vbclX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_ #if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - LogRel2(("Shared Clipboard: X11 guest requires HTTP transfer support for URI-list offers, masking format\n")); + LogRelMax(16, ("Shared Clipboard: X11 guest requires HTTP transfer support for URI-list offers, masking format\n")); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; } #endif @@ -337,6 +599,12 @@ int VBClX11ClipboardInit(void) { LogFlowFuncEnter(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + RT_ZERO(g_Ctx.X11TransferState); + vbclX11TransferStateResetKey(&g_Ctx.X11TransferState); + vbclX11TransferPublishedResetKey(&g_Ctx.X11TransferState); +#endif + int rc = ShClEventSourceInit(&g_Ctx.EventSrc, 0 /* uID */); AssertRCReturn(rc, rc); @@ -345,7 +613,7 @@ int VBClX11ClipboardInit(void) Callbacks.pfnReportFormats = vbclX11ReportFormatsCallback; Callbacks.pfnOnRequestDataFromSource = vbclX11OnRequestDataFromSourceCallback; - rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx, false /* fHeadless */); + rc = ShClX11Init(&g_Ctx.X11, &Callbacks, &g_Ctx); if (RT_SUCCESS(rc)) { rc = ShClX11ThreadStart(&g_Ctx.X11, false /* grab */); @@ -406,6 +674,7 @@ int VBClX11ClipboardMain(void) pCtx->CmdCtx.Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialize = vbclX11OnTransferInitializeCallback; + pCtx->CmdCtx.Transfers.Callbacks.pfnOnInitialized = vbclX11OnTransferInitializedCallback; pCtx->CmdCtx.Transfers.Callbacks.pfnOnRegistered = vbclX11OnTransferRegisteredCallback; pCtx->CmdCtx.Transfers.Callbacks.pfnOnUnregistered = vbclX11OnTransferUnregisteredCallback; pCtx->CmdCtx.Transfers.Callbacks.pfnOnCompleted = vbclX11OnTransferCompletedCallback; @@ -459,7 +728,11 @@ int VBClX11ClipboardMain(void) { case VBGLR3CLIPBOARDEVENTTYPE_REPORT_FORMATS: { - ShClX11ReportFormatsToX11Async(&g_Ctx.X11, pEvent->u.fReportedFormats); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + rc = vbclX11ReportHostFormats(pCtx, pEvent->u.fReportedFormats); +#else + rc = ShClX11ReportFormatsToX11Async(&g_Ctx.X11, pEvent->u.fReportedFormats); +#endif break; } diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.cpp b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp index 2d2ee57bd7b1..078887d6f125 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard.cpp +++ b/src/VBox/Additions/x11/VBoxClient/clipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard.cpp 114748 2026-07-21 20:16:49Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard.cpp 115077 2026-08-19 10:14:49Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - Common Shared Clipboard wrapper service. */ @@ -107,7 +107,7 @@ static DECLCALLBACK(int) vbclShClWorker(bool volatile *pfShutdown) { RT_NOREF(pfShutdown); - int rc; + int rc = VINF_SUCCESS; g_fVBClWayland = false; VBGHDISPLAYSERVERTYPE const enmDispType = VBClGetDisplayServerTypeResolveAuto(); if (VBClClipboardShouldUseWayland(enmDispType)) diff --git a/src/VBox/Additions/x11/VBoxClient/clipboard.h b/src/VBox/Additions/x11/VBoxClient/clipboard.h index 39c79769aca7..1d94f19de1f2 100644 --- a/src/VBox/Additions/x11/VBoxClient/clipboard.h +++ b/src/VBox/Additions/x11/VBoxClient/clipboard.h @@ -1,4 +1,4 @@ -/* $Id: clipboard.h 114748 2026-07-21 20:16:49Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard.h 114867 2026-08-06 15:19:51Z andreas.loeffler@oracle.com $ */ /** @file * Guest Additions - X11 Shared Clipboard - Main header. */ @@ -72,6 +72,10 @@ struct SHCLCONTEXT #endif /** Event source for waiting for X11 request responses in the VbglR3 clipboard event loop. */ SHCLEVENTSOURCE EventSrc; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + /** Guest-side asynchronous X11 HTTP file-transfer state. */ + SHCLX11TRANSFERSTATE X11TransferState; +#endif union { /** X11 clipboard context. */ diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp index e63c96ed1c77..4f63ab9ff609 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA-cmd.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA-cmd.cpp 114811 2026-07-28 14:08:48Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA-cmd.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * VMware SVGA device - implementation of VMSVGA commands. */ @@ -1936,7 +1936,8 @@ static void vmsvga3dCmdDefineSurface(PVGASTATECC pThisCC, SVGA3dCmdDefineSurface SVGA3dMSQualityLevel const qualityLevel = pCmd->multisampleCount > 1 ? SVGA3D_MS_QUALITY_FULL : SVGA3D_MS_QUALITY_NONE; vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, multisamplePattern, qualityLevel, pCmd->autogenFilter, - pCmd->face[0].numMipLevels, &paMipLevelSizes[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ true); + pCmd->face[0].numMipLevels, &paMipLevelSizes[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, + VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS); } @@ -2024,7 +2025,8 @@ static void vmsvga3dCmdDefineGBSurface(PVGASTATECC pThisCC, SVGA3dCmdDefineGBSur SVGA3dMSQualityLevel const qualityLevel = pCmd->multisampleCount > 1 ? SVGA3D_MS_QUALITY_FULL : SVGA3D_MS_QUALITY_NONE; vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, multisamplePattern, qualityLevel, pCmd->autogenFilter, - pCmd->numMipLevels, &pCmd->size, /* arraySize = */ 0, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ false); + pCmd->numMipLevels, &pCmd->size, /* arraySize = */ 0, /* bufferByteStride = */ 0, + VMSVGA3D_SURFACE_DEFINE_F_GB); } } @@ -2624,7 +2626,7 @@ static void vmsvga3dCmdDestroyGBScreenTarget(PVGASTATE pThis, PVGASTATECC pThisC /* SVGA_3D_CMD_BIND_GB_SCREENTARGET 1126 */ -static void vmsvga3dCmdBindGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdBindGBScreenTarget const *pCmd) +static void vmsvga3dCmdBindGBScreenTarget(PVGASTATE pThis, PVGASTATECC pThisCC, SVGA3dCmdBindGBScreenTarget const *pCmd) { //DEBUG_BREAKPOINT_TEST(); PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; @@ -2673,7 +2675,7 @@ static void vmsvga3dCmdBindGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdBindGBSc rect.y = 0; rect.w = entry.width; rect.h = entry.height; - vmsvga3dScreenUpdateFromScreenTarget(pThisCC, pScreen, rect, entry.image); + vmsvga3dScreenUpdateFromScreenTarget(pThis, pThisCC, pScreen, rect, entry.image); } } } @@ -2682,7 +2684,7 @@ static void vmsvga3dCmdBindGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdBindGBSc /* SVGA_3D_CMD_UPDATE_GB_SCREENTARGET 1127 */ -static void vmsvga3dCmdUpdateGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdUpdateGBScreenTarget const *pCmd) +static void vmsvga3dCmdUpdateGBScreenTarget(PVGASTATE pThis, PVGASTATECC pThisCC, SVGA3dCmdUpdateGBScreenTarget const *pCmd) { //DEBUG_BREAKPOINT_TEST(); PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; @@ -2713,7 +2715,7 @@ static void vmsvga3dCmdUpdateGBScreenTarget(PVGASTATECC pThisCC, SVGA3dCmdUpdate RT_UNTRUSTED_VALIDATED_FENCE(); VMSVGASCREENOBJECT *pScreen = &pSvgaR3State->aScreens[pCmd->stid]; - vmsvga3dScreenUpdateFromScreenTarget(pThisCC, pScreen, pCmd->rect, entryScreenTarget.image); + vmsvga3dScreenUpdateFromScreenTarget(pThis, pThisCC, pScreen, pCmd->rect, entryScreenTarget.image); } } } @@ -2754,7 +2756,8 @@ static void vmsvga3dCmdDefineGBSurface_v2(PVGASTATECC pThisCC, SVGA3dCmdDefineGB SVGA3dMSQualityLevel const qualityLevel = pCmd->multisampleCount > 1 ? SVGA3D_MS_QUALITY_FULL : SVGA3D_MS_QUALITY_NONE; vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, multisamplePattern, qualityLevel, pCmd->autogenFilter, - pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ false); + pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, /* bufferByteStride = */ 0, + VMSVGA3D_SURFACE_DEFINE_F_GB); } } @@ -4489,7 +4492,8 @@ static int vmsvga3dCmdDefineGBSurface_v3(PVGASTATECC pThisCC, SVGA3dCmdDefineGBS /* Create the host surface. */ vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, pCmd->multisamplePattern, pCmd->qualityLevel, pCmd->autogenFilter, - pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ false); + pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, /* bufferByteStride = */ 0, + VMSVGA3D_SURFACE_DEFINE_F_GB); } return rc; #else @@ -4896,7 +4900,8 @@ static int vmsvga3dCmdDefineGBSurface_v4(PVGASTATECC pThisCC, SVGA3dCmdDefineGBS /* Create the host surface. */ vmsvga3dSurfaceDefine(pThisCC, pCmd->sid, pCmd->surfaceFlags, pCmd->format, pCmd->multisampleCount, pCmd->multisamplePattern, pCmd->qualityLevel, pCmd->autogenFilter, - pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, pCmd->bufferByteStride, /* fAllocMipLevels = */ false); + pCmd->numMipLevels, &pCmd->size, pCmd->arraySize, pCmd->bufferByteStride, + VMSVGA3D_SURFACE_DEFINE_F_GB); } return rc; #else @@ -6348,7 +6353,7 @@ int vmsvgaR3Process3dCmd(PVGASTATE pThis, PVGASTATECC pThisCC, uint32_t idDXCont { SVGA3dCmdBindGBScreenTarget *pCmd = (SVGA3dCmdBindGBScreenTarget *)pvCmd; VMSVGAFIFO_CHECK_3D_CMD_MIN_SIZE_BREAK(sizeof(*pCmd)); - vmsvga3dCmdBindGBScreenTarget(pThisCC, pCmd); + vmsvga3dCmdBindGBScreenTarget(pThis, pThisCC, pCmd); break; } @@ -6356,7 +6361,7 @@ int vmsvgaR3Process3dCmd(PVGASTATE pThis, PVGASTATECC pThisCC, uint32_t idDXCont { SVGA3dCmdUpdateGBScreenTarget *pCmd = (SVGA3dCmdUpdateGBScreenTarget *)pvCmd; VMSVGAFIFO_CHECK_3D_CMD_MIN_SIZE_BREAK(sizeof(*pCmd)); - vmsvga3dCmdUpdateGBScreenTarget(pThisCC, pCmd); + vmsvga3dCmdUpdateGBScreenTarget(pThis, pThisCC, pCmd); break; } diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp index a3d3d752b65a..d6aa33330d44 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA.cpp 114796 2026-07-27 16:22:58Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * VMware SVGA device. * @@ -1689,9 +1689,9 @@ int vmsvgaR3ChangeMode(PVGASTATE pThis, PVGASTATECC pThisCC) VMSVGASCREENOBJECT *pScreen = &pSVGAState->aScreens[0]; Assert(pScreen->idScreen == 0); - if ( pScreen->cWidth == VMSVGA_VAL_UNINITIALIZED - || pScreen->cHeight == VMSVGA_VAL_UNINITIALIZED - || pScreen->cBpp == VMSVGA_VAL_UNINITIALIZED) + if ( pThis->svga.uWidth == VMSVGA_VAL_UNINITIALIZED + || pThis->svga.uHeight == VMSVGA_VAL_UNINITIALIZED + || pThis->svga.uBpp == VMSVGA_VAL_UNINITIALIZED) { /* Do not apply the change if the guest has not finished updating registers. * This is necessary in order to make a full mode change. @@ -1699,13 +1699,22 @@ int vmsvgaR3ChangeMode(PVGASTATE pThis, PVGASTATECC pThisCC) return VINF_SUCCESS; } + ASSERT_GUEST_RETURN(pThis->svga.uWidth > 0 && pThis->svga.uWidth <= pThis->svga.u32MaxWidth, VERR_INVALID_STATE); + ASSERT_GUEST_RETURN(pThis->svga.uHeight > 0 && pThis->svga.uHeight <= pThis->svga.u32MaxHeight, VERR_INVALID_STATE); + + /* Height can't exceed the available VRAM. */ + uint32_t const cbPitch = pThis->svga.cbScanline + ? pThis->svga.cbScanline + : (uint32_t)pThis->svga.uWidth * (RT_ALIGN(pThis->svga.uBpp, 8) / 8); + ASSERT_GUEST_RETURN(pThis->svga.uHeight <= pThis->vram_size / cbPitch, VERR_INVALID_STATE); + pScreen->fDefined = true; pScreen->fModified = true; pScreen->fuScreen = SVGA_SCREEN_MUST_BE_SET | SVGA_SCREEN_IS_PRIMARY; pScreen->xOrigin = 0; pScreen->yOrigin = 0; pScreen->offVRAM = 0; - pScreen->cbPitch = pThis->svga.cbScanline; + pScreen->cbPitch = cbPitch; pScreen->cWidth = pThis->svga.uWidth; pScreen->cHeight = pThis->svga.uHeight; pScreen->cBpp = pThis->svga.uBpp; @@ -3604,13 +3613,15 @@ static SVGACBStatus vmsvgaR3CmdBufDCPreempt(PPDMDEVINS pDevIns, PVMSVGAR3STATE p RT_UNTRUSTED_VALIDATED_FENCE(); PVMSVGACMDBUFCTX const pCmdBufCtx = pSvgaR3State->apCmdBufCtxs[pCmd->context]; + if (!pCmdBufCtx) + return SVGA_CB_STATUS_COMMAND_ERROR; + RTLISTANCHOR listPreempted; + RTListInit(&listPreempted); int rc = RTCritSectEnter(&pSvgaR3State->CritSectCmdBuf); AssertRC(rc); - RTListInit(&listPreempted); - PVMSVGACMDBUF pIter, pNext; RTListForEachSafe(&pCmdBufCtx->listSubmitted, pIter, pNext, VMSVGACMDBUF, nodeBuffer) { @@ -5395,7 +5406,22 @@ static void vmsvgaR3FifoHandleExtCmd(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGAST # ifdef VBOX_WITH_VMSVGA3D if (pThis->svga.f3DEnabled || pThis->svga.fVMSVGA2dGBO) { - if (vmsvga3dIsLegacyBackend(pThisCC)) + bool fUseLegacyLoadExec; + if (pThis->svga.fVMSVGA2dGBO && pLoadState->uVersion < VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF) + { + /* 'vmsvga3dIsLegacyBackend' was true in 'fVMSVGA2dGBO' mode, because 'vmsvga3dIsLegacyBackend' + * checked pFuncsDX but 'fVMSVGA2dGBO' did not create this interface. + * So 'fVMSVGA2dGBO' used the legacy saved state function even though the DX backend was used. + * Now, with VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF, 'vmsvga3dIsLegacyBackend' checks for pFuncsGBO, + * which is the essential interface of the VPGU10 capable DX backend, and 'vmsvga3dIsLegacyBackend' + * works correctly for 'fVMSVGA2dGBO' too. + */ + fUseLegacyLoadExec = true; + } + else + fUseLegacyLoadExec = vmsvga3dIsLegacyBackend(pThisCC); + + if (fUseLegacyLoadExec) vmsvga3dLoadExec(pDevIns, pThis, pThisCC, pLoadState->pSSM, pLoadState->uVersion, pLoadState->uPass); # ifdef VMSVGA3D_DX else @@ -7273,13 +7299,16 @@ static int vmsvgaR3LoadExecFifo(PCPDMDEVHLPR3 pHlp, PVGASTATE pThis, PVGASTATECC /* Try to setup at least the first screen. */ VMSVGASCREENOBJECT *pScreen = &pSVGAState->aScreens[0]; Assert(pScreen->idScreen == 0); + uint32_t const cbPitch = pThis->svga.cbScanline + ? pThis->svga.cbScanline + : (uint32_t)pThis->svga.uWidth * (RT_ALIGN(pThis->svga.uBpp, 8) / 8); pScreen->fDefined = true; pScreen->fModified = true; pScreen->fuScreen = SVGA_SCREEN_MUST_BE_SET | SVGA_SCREEN_IS_PRIMARY; pScreen->xOrigin = 0; pScreen->yOrigin = 0; pScreen->offVRAM = pThis->svga.uScreenOffset; - pScreen->cbPitch = pThis->svga.cbScanline; + pScreen->cbPitch = cbPitch; pScreen->cWidth = pThis->svga.uWidth; pScreen->cHeight = pThis->svga.uHeight; pScreen->cBpp = pThis->svga.uBpp; diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp index d1be0ce7eb88..94c0b62b298c 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-dx11.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 114814 2026-07-28 15:06:29Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-dx11.cpp 115066 2026-08-18 13:31:58Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -313,21 +313,18 @@ typedef struct DXSTREAMOUTPUT typedef struct DXBOUNDVERTEXBUFFER { ID3D11Buffer *pBuffer; - uint32_t stride; - uint32_t offset; + PVMSVGA3DSURFACE pSurface; + //uint32_t stride; + //uint32_t offset; } DXBOUNDVERTEXBUFFER; typedef struct DXBOUNDINDEXBUFFER { ID3D11Buffer *pBuffer; - DXGI_FORMAT indexBufferFormat; - uint32_t indexBufferOffset; + //DXGI_FORMAT indexBufferFormat; + //uint32_t indexBufferOffset; } DXBOUNDINDEXBUFFER; -/** @todo Temporary development define. */ -#define DX_CB - -#ifdef DX_CB /* Constant buffer management: * - allocate a large dynamic buffer * - update the buffer with constant data in SetSingleConstantBuffer using MAP_NO_OVERWRITE @@ -348,17 +345,17 @@ typedef struct DXCONSTANTBUFFERSTATE /* 8 * maximum constant buffer size (4096 * 16) */ #define DX_CONSTANT_UPLOAD_BUFFER_SIZE _512K -#endif /* DX_CB */ typedef struct DXBOUNDRESOURCES /* Currently bound resources. Mirror SVGADXContextMobFormat structure. */ { struct { -#ifndef DX_CB - ID3D11Buffer *constantBuffers[SVGA3D_DX_MAX_CONSTBUFFERS]; -#else + DXBOUNDVERTEXBUFFER vertexBuffers[SVGA3D_DX_MAX_VERTEXBUFFERS]; + DXBOUNDINDEXBUFFER indexBuffer; + } inputAssembly; + struct + { DXCONSTANTBUFFERSTATE constantBuffers; -#endif /* DX_CB */ } shaderState[SVGA3D_NUM_SHADERTYPE]; } DXBOUNDRESOURCES; @@ -404,9 +401,7 @@ typedef struct VMSVGA3DBACKENDDXCONTEXT uint32_t cSOTarget; /* How many SO targets are currently set (SetSOTargets) */ -#ifdef DX_CB DXUPLOADBUFFERMANAGER constantBufferManager; -#endif DXBOUNDRESOURCES resources; } VMSVGA3DBACKENDDXCONTEXT; @@ -427,9 +422,11 @@ typedef struct VMSVGA3DBACKEND UINT VendorId; UINT DeviceId; +#ifndef DX_STATE_TRACKER SVGADXContextMobFormat svgaDXContext; /* Current state of pipeline. */ DXBOUNDRESOURCES resources; /* What is currently applied to the pipeline. */ +#endif struct { @@ -1634,9 +1631,12 @@ static int dxViewDestroy(DXVIEW *pDXView) pDXView->cid, pDXView->sid, pDXView->viewId, pDXView->enmViewType)); if (pDXView->u.pView) { + pDXView->cid = SVGA3D_INVALID_ID; + pDXView->sid = SVGA3D_INVALID_ID; + pDXView->viewId = SVGA3D_INVALID_ID; + pDXView->enmViewType = VMSVGA3D_VIEWTYPE_NONE; D3D_RELEASE(pDXView->u.pView); RTListNodeRemove(&pDXView->nodeSurfaceView); - RT_ZERO(*pDXView); } return VINF_SUCCESS; @@ -1655,17 +1655,30 @@ static int dxViewInit(DXVIEW *pDXView, PVMSVGA3DSURFACE pSurface, VMSVGA3DDXCONT LogFunc(("cid = %u, sid = %u, viewId = %u, type = %u\n", pDXView->cid, pDXView->sid, pDXView->viewId, pDXView->enmViewType)); -DXVIEW *pIter, *pNext; -RTListForEachSafe(&pSurface->pBackendSurface->listView, pIter, pNext, DXVIEW, nodeSurfaceView) -{ - AssertPtr(pNext); - LogFunc(("pIter=%p, pNext=%p\n", pIter, pNext)); -} +#ifdef LOG_ENABLED + DXVIEW *pIter, *pNext; + RTListForEachSafe(&pSurface->pBackendSurface->listView, pIter, pNext, DXVIEW, nodeSurfaceView) + { + AssertPtr(pNext); + LogFunc(("pIter=%p, pNext=%p\n", pIter, pNext)); + } +#endif return VINF_SUCCESS; } +static void dxViewInit(DXVIEW *pDXView) +{ + pDXView->cid = SVGA3D_INVALID_ID; + pDXView->sid = SVGA3D_INVALID_ID; + pDXView->viewId = SVGA3D_INVALID_ID; + pDXView->enmViewType = VMSVGA3D_VIEWTYPE_NONE; + pDXView->u.pView = NULL; + RT_ZERO(pDXView->nodeSurfaceView); +} + + static DXDEVICE *dxDeviceGet(PVMSVGA3DSTATE p3dState) { DXDEVICE *pDXDevice = &p3dState->pBackend->dxDevice; @@ -2519,6 +2532,13 @@ static HRESULT dxDepthStencilViewCreate(PVGASTATECC pThisCC, SVGACOTableDXDSView desc.Texture2DArray.ArraySize = pEntry->arraySize; } break; + case SVGA3D_RESOURCE_TEXTURECUBE: + /* Cube is a 6 elements array. */ + desc.ViewDimension = D3D11_DSV_DIMENSION_TEXTURE2DARRAY; + desc.Texture2DArray.MipSlice = pEntry->mipSlice; + desc.Texture2DArray.FirstArraySlice = pEntry->firstArraySlice; + desc.Texture2DArray.ArraySize = pEntry->arraySize; + break; default: ASSERT_GUEST_FAILED_RETURN(E_INVALIDARG); } @@ -2627,36 +2647,6 @@ static void dxShaderSet(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, SVGA } -#ifndef DX_CB -static void dxConstantBufferSet(DXDEVICE *pDevice, uint32_t slot, SVGA3dShaderType type, ID3D11Buffer *pConstantBuffer) -{ - switch (type) - { - case SVGA3D_SHADERTYPE_VS: - pDevice->pImmediateContext->VSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_PS: - pDevice->pImmediateContext->PSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_GS: - pDevice->pImmediateContext->GSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_HS: - pDevice->pImmediateContext->HSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_DS: - pDevice->pImmediateContext->DSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - case SVGA3D_SHADERTYPE_CS: - pDevice->pImmediateContext->CSSetConstantBuffers(slot, 1, &pConstantBuffer); - break; - default: - ASSERT_GUEST_FAILED_RETURN_VOID(); - } -} -#endif /* !DX_CB */ - - static void dxSamplerSet(DXDEVICE *pDevice, SVGA3dShaderType type, uint32_t startSampler, uint32_t cSampler, ID3D11SamplerState * const *papSampler) { switch (type) @@ -3353,6 +3343,8 @@ static int dxEnsureResource(PVGASTATECC pThisCC, uint32_t sid, rc = vmsvga3dBackSurfaceCreateResource(pThisCC, pSurface); AssertRCReturn(rc, rc); LogFunc(("Created for sid = %u\n", sid)); + + vmsvga3dSurfaceFreeMipLevels(pSurface); } ID3D11Resource *pResource = dxResource(pSurface); @@ -3401,7 +3393,9 @@ static DECLCALLBACK(int) vmsvga3dBackInit(PPDMDEVINS pDevIns, PVGASTATE pThis, P } #endif +#ifndef DX_STATE_TRACKER vmsvga3dDXInitContextMobData(&pBackend->svgaDXContext); +#endif //DEBUG_BREAKPOINT_TEST(); return rc; } @@ -4715,6 +4709,14 @@ static DECLCALLBACK(int) vmsvga3dBackSurfaceUnmap(PVGASTATECC pThisCC, SVGA3dSur } +static DECLCALLBACK(int) vmsvga3dBackEnsureResource(PVGASTATECC pThisCC, uint32_t sid) +{ + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + return dxEnsureResource(pThisCC, sid, &pSurface, &pResource); +} + + static DECLCALLBACK(int) vmsvga3dScreenTargetBind(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, uint32_t sid) { int rc = VINF_SUCCESS; @@ -6235,11 +6237,9 @@ static DECLCALLBACK(int) vmsvga3dBackDXDefineContext(PVGASTATECC pThisCC, PVMSVG AssertPtrReturn(pBackendDXContext, VERR_NO_MEMORY); pDXContext->pBackendDXContext = pBackendDXContext; -#ifdef DX_CB dxUploadBufferManagerInit(&pDXContext->pBackendDXContext->constantBufferManager, 4096 * 16, /* cbMaxData = 4096 constant 16 bytes each */ DX_CONSTANT_UPLOAD_BUFFER_SIZE, D3D11_BIND_CONSTANT_BUFFER); -#endif LogFunc(("cid %d\n", pDXContext->cid)); return VINF_SUCCESS; @@ -6260,15 +6260,7 @@ static DECLCALLBACK(int) vmsvga3dBackDXDestroyContext(PVGASTATECC pThisCC, PVMSV /* Clean up context resources. */ VMSVGA3DBACKENDDXCONTEXT *pBackendDXContext = pDXContext->pBackendDXContext; -#ifndef DX_CB - for (uint32_t idxShaderState = 0; idxShaderState < RT_ELEMENTS(pBackendDXContext->resources.shaderState); ++idxShaderState) - { - ID3D11Buffer **papConstantBuffer = &pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers[0]; - D3D_RELEASE_ARRAY(RT_ELEMENTS(pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers), papConstantBuffer); - } -#else dxUploadBufferManagerUninit(&pDXContext->pBackendDXContext->constantBufferManager); -#endif if (pBackendDXContext->paRenderTargetView) { @@ -6379,6 +6371,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXBindContext(PVGASTATECC pThisCC, PVMSVGA3 static DECLCALLBACK(int) vmsvga3dBackDXSwitchContext(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContextFrom, PVMSVGA3DDXCONTEXT pDXContext) { + for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) + { + DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; + pCb->StartSlot = 0; + pCb->NumBuffers = D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; + } + #ifndef DX_STATE_TRACKER /* The new context state will be applied by the generic DX code. */ RT_NOREF(pThisCC, pDXContextFrom, pDXContext); @@ -6417,12 +6416,12 @@ static DECLCALLBACK(int) vmsvga3dBackDXSwitchContext(PVGASTATECC pThisCC, PVMSVG if (cBoundCSUAV) pDXDevice->pImmediateContext->CSSetUnorderedAccessViews(0, cBoundCSUAV, u.papUnorderedAccessView, NULL); - for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) - { - DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; - pCb->StartSlot = 0; - pCb->NumBuffers = D3D11_COMMONSHADER_CONSTANT_BUFFER_API_SLOT_COUNT; - } + /* Reset vertex buffers. */ + uint32_t const cBoundVB = pDXContextFrom + ? pDXContextFrom->state.ia.vb.cMaxBound + : SVGA3D_DX_MAX_VERTEXBUFFERS; + AssertCompile(SVGA3D_DX_MAX_VERTEXBUFFERS == 32); + pDXContext->state.ia.vb.au32Modified[0] = (UINT32_C(0xFFFFFFFF) >> (32 - cBoundVB)); #endif return VINF_SUCCESS; } @@ -6448,105 +6447,12 @@ static DECLCALLBACK(int) vmsvga3dBackDXInvalidateContext(PVGASTATECC pThisCC, PV static DECLCALLBACK(int) vmsvga3dBackDXSetSingleConstantBuffer(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, uint32_t slot, SVGA3dShaderType type, SVGA3dSurfaceId sid, uint32_t offsetInBytes, uint32_t sizeInBytes) { -#ifndef DX_CB - PVMSVGA3DBACKEND pBackend = pThisCC->svga.p3dState->pBackend; - RT_NOREF(pBackend); - - DXDEVICE *pDevice = dxDeviceGet(pThisCC->svga.p3dState); - AssertReturn(pDevice->pDevice, VERR_INVALID_STATE); - - if (sid == SVGA_ID_INVALID) - { - uint32_t const idxShaderState = type - SVGA3D_SHADERTYPE_MIN; - D3D_RELEASE(pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers[slot]); - return VINF_SUCCESS; - } - - PVMSVGA3DSURFACE pSurface; - int rc = vmsvga3dSurfaceFromSid(pThisCC->svga.p3dState, sid, &pSurface); - AssertRCReturn(rc, rc); - - PVMSVGA3DMIPMAPLEVEL pMipLevel; - rc = vmsvga3dMipmapLevel(pSurface, 0, 0, &pMipLevel); - AssertRCReturn(rc, rc); - - uint32_t const cbSurface = pMipLevel->cbSurface; - ASSERT_GUEST_RETURN( offsetInBytes < cbSurface - && sizeInBytes <= cbSurface - offsetInBytes, VERR_INVALID_PARAMETER); - - /* Constant buffers are created on demand. */ - Assert(pSurface->pBackendSurface == NULL); - - /* Upload the current data, if any. */ - D3D11_SUBRESOURCE_DATA *pInitialData = NULL; - D3D11_SUBRESOURCE_DATA initialData; - if (pMipLevel->pSurfaceData) - { - initialData.pSysMem = (uint8_t *)pMipLevel->pSurfaceData + offsetInBytes; - initialData.SysMemPitch = sizeInBytes; - initialData.SysMemSlicePitch = sizeInBytes; - - pInitialData = &initialData; - -#ifdef LOG_ENABLED - if (LogIs8Enabled()) - { - float *pValuesF = (float *)initialData.pSysMem; - for (unsigned i = 0; i < sizeInBytes / sizeof(float) / 4; ++i) - { - Log8(("ConstF /*%d*/ " FLOAT_FMT_STR ", " FLOAT_FMT_STR ", " FLOAT_FMT_STR ", " FLOAT_FMT_STR ",\n", - i, FLOAT_FMT_ARGS(pValuesF[i*4 + 0]), FLOAT_FMT_ARGS(pValuesF[i*4 + 1]), FLOAT_FMT_ARGS(pValuesF[i*4 + 2]), FLOAT_FMT_ARGS(pValuesF[i*4 + 3]))); - } - } -#endif - } - - uint32_t const idxShaderState = type - SVGA3D_SHADERTYPE_MIN; - ID3D11Buffer **ppCurrentBuffer = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers[slot]; - - LogFunc(("constant buffer: [%u][%u]: sid = %u, %u, %u\n", - idxShaderState, slot, sid, offsetInBytes, sizeInBytes)); - - D3D11_BUFFER_DESC bd; - - if (*ppCurrentBuffer) - { - RT_ZERO(bd); - (*ppCurrentBuffer)->GetDesc(&bd); - if (bd.ByteWidth != sizeInBytes) - { - /* Have to create a new one. */ - D3D_RELEASE(*ppCurrentBuffer); /* This will set the pointer to NULL. */ - } - } - - if (!(*ppCurrentBuffer)) - { - RT_ZERO(bd); - bd.ByteWidth = sizeInBytes; - bd.Usage = D3D11_USAGE_DEFAULT; - bd.BindFlags = D3D11_BIND_CONSTANT_BUFFER; - //bd.CPUAccessFlags = 0; - //bd.MiscFlags = 0; - //bd.StructureByteStride = 0; - - HRESULT hr = pDevice->pDevice->CreateBuffer(&bd, pInitialData, ppCurrentBuffer); - AssertReturn(SUCCEEDED(hr), VERR_NO_MEMORY); - } - else - { - if (pInitialData) - pDevice->pImmediateContext->UpdateSubresource(*ppCurrentBuffer, 0, 0, pInitialData->pSysMem, 0, 0); - } - - return VINF_SUCCESS; -#else DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturn(pDXDevice->pDevice, VERR_INVALID_STATE); uint32_t const idxShaderState = type - SVGA3D_SHADERTYPE_MIN; DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; - + /* Fetch the data from the surface because it is valid only during processing of SET_SINGLE_CONSTANT_BUFFER. */ if (sid == SVGA3D_INVALID_ID) { /* Clear the constant buffer slot. */ @@ -6629,7 +6535,6 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetSingleConstantBuffer(PVGASTATECC pThis } return VINF_SUCCESS; -#endif /* DX_CB */ } @@ -7007,38 +6912,8 @@ static void dxCreateInputLayout(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXConte } -static void dxSetConstantBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) +static void dxSetConstantBuffers(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) { -#ifndef DX_CB -//DEBUG_BREAKPOINT_TEST(); - PVMSVGA3DBACKEND pBackend = pThisCC->svga.p3dState->pBackend; - DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); - VMSVGA3DBACKENDDXCONTEXT *pBackendDXContext = pDXContext->pBackendDXContext; - - AssertCompile(RT_ELEMENTS(pBackendDXContext->resources.shaderState[0].constantBuffers) == SVGA3D_DX_MAX_CONSTBUFFERS); - - for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) - { - SVGA3dShaderType const shaderType = (SVGA3dShaderType)(idxShaderState + SVGA3D_SHADERTYPE_MIN); - for (uint32_t idxSlot = 0; idxSlot < SVGA3D_DX_MAX_CONSTBUFFERS; ++idxSlot) - { - ID3D11Buffer **pBufferContext = &pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers[idxSlot]; - ID3D11Buffer **pBufferPipeline = &pBackend->resources.shaderState[idxShaderState].constantBuffers[idxSlot]; - if (*pBufferContext != *pBufferPipeline) - { - LogFunc(("constant buffer: [%u][%u]: %p -> %p\n", - idxShaderState, idxSlot, *pBufferPipeline, *pBufferContext)); - dxConstantBufferSet(pDXDevice, idxSlot, shaderType, *pBufferContext); - - if (*pBufferContext) - (*pBufferContext)->AddRef(); - D3D_RELEASE(*pBufferPipeline); - *pBufferPipeline = *pBufferContext; - } - } - } -#else - DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) { DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; @@ -7138,9 +7013,10 @@ static void dxSetConstantBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXCont pCb->StartSlot = 0; pCb->NumBuffers = 0; } -#endif } + +#ifndef DX_STATE_TRACKER static void dxSetVertexBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) { PVMSVGA3DBACKEND pBackend = pThisCC->svga.p3dState->pBackend; @@ -7220,7 +7096,70 @@ static void dxSetVertexBuffers(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContex pDXDevice->pImmediateContext->IASetVertexBuffers(idxMinSlot, (idxMaxSlot - idxMinSlot) + 1, &paResources[idxMinSlot], &paStride[idxMinSlot], &paOffset[idxMinSlot]); } +#else +static void dxSetVertexBuffers(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) +{ + ID3D11Buffer *paResources[SVGA3D_DX_MAX_VERTEXBUFFERS]; + UINT paStride[SVGA3D_DX_MAX_VERTEXBUFFERS]; + UINT paOffset[SVGA3D_DX_MAX_VERTEXBUFFERS]; + + UINT StartSlot = 0; + UINT NumBuffers = 0; + for (UINT i = 0; i < pDXContext->state.ia.vb.cMaxBound; ++i) + { + if (ASMBitTest(pDXContext->state.ia.vb.au32Modified, i)) + { + SVGA3dBufferBinding const *pBufferBinding = &pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i]; + if (pBufferBinding->bufferId != SVGA3D_INVALID_ID) + { + DXBOUNDVERTEXBUFFER const *p = &pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i]; + + /* pBuffer is created in ensureResourcesAndViews */ + PVMSVGA3DSURFACE pSurface = p->pSurface; + paResources[i] = p->pBuffer; + + /* DX11 supports stride up to 2048. Ignore large values (> 40000) that Ubuntu guest might send. */ + paStride[i] = pBufferBinding->stride <= 2048 ? pBufferBinding->stride : 0; + + if ( paStride[i] <= pSurface->paMipmapLevels[0].cbSurface + && pBufferBinding->offset <= pSurface->paMipmapLevels[0].cbSurface - paStride[i]) + paOffset[i] = pBufferBinding->offset; + else + { + paStride[i] = 0; + paOffset[i] = 0; + } + } + else + { + paResources[i] = NULL; + paStride[i] = 0; + paOffset[i] = 0; + } + + if (NumBuffers == 0) + StartSlot = i; + ++NumBuffers; + } + else if (NumBuffers > 0) + { + pDXDevice->pImmediateContext->IASetVertexBuffers(StartSlot, NumBuffers, + &paResources[StartSlot], + &paStride[StartSlot], + &paOffset[StartSlot]); + NumBuffers = 0; + } + } + if (NumBuffers > 0) + pDXDevice->pImmediateContext->IASetVertexBuffers(StartSlot, NumBuffers, + &paResources[StartSlot], + &paStride[StartSlot], + &paOffset[StartSlot]); +} +#endif + +#ifndef DX_STATE_TRACKER static void dxSetIndexBuffer(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) { PVMSVGA3DBACKEND pBackend = pThisCC->svga.p3dState->pBackend; @@ -7278,6 +7217,39 @@ static void dxSetIndexBuffer(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); pDXDevice->pImmediateContext->IASetIndexBuffer(pBuffer, enmDxgiFormat, Offset); } +#else +static void dxSetIndexBuffer(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) +{ + ID3D11Buffer *pBuffer; + DXGI_FORMAT enmDxgiFormat; + UINT Offset; + + if (pDXContext->svgaDXContext.inputAssembly.indexBufferSid != SVGA3D_INVALID_ID) + { + /* pBuffer is created in ensureResourcesAndViews */ + enmDxgiFormat = vmsvgaDXSurfaceFormat2Dxgi((SVGA3dSurfaceFormat)pDXContext->svgaDXContext.inputAssembly.indexBufferFormat); + if (enmDxgiFormat == DXGI_FORMAT_R16_UINT || enmDxgiFormat == DXGI_FORMAT_R32_UINT) + { + pBuffer = pDXContext->pBackendDXContext->resources.inputAssembly.indexBuffer.pBuffer; + Offset = pDXContext->svgaDXContext.inputAssembly.indexBufferOffset; + } + else + { + pBuffer = NULL; + enmDxgiFormat = DXGI_FORMAT_UNKNOWN; + Offset = 0; + } + } + else + { + pBuffer = NULL; + enmDxgiFormat = DXGI_FORMAT_UNKNOWN; + Offset = 0; + } + + pDXDevice->pImmediateContext->IASetIndexBuffer(pBuffer, enmDxgiFormat, Offset); +} +#endif #ifdef LOG_ENABLED static void dxDbgLogVertexElement(DXGI_FORMAT Format, void const *pvElementData) @@ -7834,7 +7806,7 @@ static int dxEnsureUnorderedAccessView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT p #ifdef DX_STATE_TRACKER -static void dxEnsureViews(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) +static void dxEnsureResourcesAndViews(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) { /* * Ensure that all views exist. @@ -7851,12 +7823,15 @@ static void dxEnsureViews(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) AssertRC(rc); #ifdef LOG_ENABLED - SVGACOTableDXDSViewEntry const *pDSViewEntry = &pDXContext->cot.paDSView[viewId]; - PVMSVGA3DSURFACE pSurface = NULL; - vmsvga3dSurfaceFromSid(pThisCC->svga.p3dState, pDXView->sid, &pSurface); - LogFunc(("dsv sid = %u, dsvid = %u, format = %s(%d), %dx%d\n", - pDXView->sid, viewId, vmsvgaLookupEnum((int)pDSViewEntry->format, &g_SVGA3dSurfaceFormat2String), pDSViewEntry->format, - pSurface->paMipmapLevels[0].cBlocksX * pSurface->cxBlock, pSurface->paMipmapLevels[0].cBlocksY * pSurface->cyBlock)); + if (RT_SUCCESS(rc)) + { + SVGACOTableDXDSViewEntry const *pDSViewEntry = &pDXContext->cot.paDSView[viewId]; + PVMSVGA3DSURFACE pSurface = NULL; + vmsvga3dSurfaceFromSid(pThisCC->svga.p3dState, pDXView->sid, &pSurface); + LogFunc(("dsv sid = %u, dsvid = %u, format = %s(%d), %dx%d\n", + pDXView->sid, viewId, vmsvgaLookupEnum((int)pDSViewEntry->format, &g_SVGA3dSurfaceFormat2String), pDSViewEntry->format, + pSurface->paMipmapLevels[0].cBlocksX * pSurface->cxBlock, pSurface->paMipmapLevels[0].cBlocksY * pSurface->cyBlock)); + } #endif } @@ -7960,6 +7935,57 @@ static void dxEnsureViews(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) } } } + + /* Vertex buffers. */ + for (uint32_t i = 0; i < pDXContext->state.ia.vb.cMaxBound; ++i) + { + if (pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].bufferId != SVGA3D_INVALID_ID) + { + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + rc = dxEnsureResource(pThisCC, pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].bufferId, &pSurface, &pResource); + AssertContinue(RT_SUCCESS(rc)); + + if (pSurface->pBackendSurface->enmResType == VMSVGA3D_RESTYPE_BUFFER) + { + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pBuffer = (ID3D11Buffer *)pResource; + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pSurface = pSurface; + } + else + { + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pBuffer = NULL; + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pSurface = NULL; + } + + LogFunc(("vb[%u]: sid = %u, stride %u, offset %u%s\n", i, + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].bufferId, + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].stride, + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[i].offset, + pDXContext->pBackendDXContext->resources.inputAssembly.vertexBuffers[i].pBuffer ? "" : " NULL")); + } + } + + /* Index buffer. */ + if (pDXContext->svgaDXContext.inputAssembly.indexBufferSid != SVGA3D_INVALID_ID) + { + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + rc = dxEnsureResource(pThisCC, pDXContext->svgaDXContext.inputAssembly.indexBufferSid, &pSurface, &pResource); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + if (pSurface->pBackendSurface->enmResType == VMSVGA3D_RESTYPE_BUFFER) + pDXContext->pBackendDXContext->resources.inputAssembly.indexBuffer.pBuffer = (ID3D11Buffer *)pResource; + else + pDXContext->pBackendDXContext->resources.inputAssembly.indexBuffer.pBuffer = NULL; + + LogFunc(("ib: sid = %u, offset %u, fmt %u%s\n", + pDXContext->svgaDXContext.inputAssembly.indexBufferSid, + pDXContext->svgaDXContext.inputAssembly.indexBufferOffset, + pDXContext->svgaDXContext.inputAssembly.indexBufferFormat, + pDXContext->pBackendDXContext->resources.inputAssembly.indexBuffer.pBuffer ? "" : " NULL")); + } + } } @@ -8316,7 +8342,12 @@ static void dxCheckState(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) { ID3D11ShaderResourceView *paShaderResourceView[SVGA3D_DX_MAX_SRVIEWS]; } sr; - //ID3D11Buffer *pConstantBuffer; + struct + { + ID3D11Buffer *apConstantBuffer[SVGA3D_DX_MAX_CONSTBUFFERS]; + UINT aFirstConstant[SVGA3D_DX_MAX_CONSTBUFFERS]; + UINT aNumConstants[SVGA3D_DX_MAX_CONSTBUFFERS]; + } cb; //ID3D11VertexShader *pVertexShader; //ID3D11HullShader *pHullShader; //ID3D11DomainShader *pDomainShader; @@ -8485,8 +8516,64 @@ static void dxCheckState(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) D3D_RELEASE_ARRAY(SVGA3D_DX_MAX_SRVIEWS, p.sr.paShaderResourceView); } - //pImmediateContext->VSGetConstantBuffers(0, 1, &p.pConstantBuffer); - //D3D_RELEASE(p.pConstantBuffer); + /* Constant buffers */ + RT_ZERO(p); + for (uint32_t idxShaderState = 0; idxShaderState < SVGA3D_NUM_SHADERTYPE; ++idxShaderState) + { + RT_ZERO(p.cb); + + uint32_t const cMaxBound = pDXContext->state.shader[idxShaderState].constantBuffers.cMaxBound; + if (cMaxBound == 0) + continue; + + AssertCompile(RT_ELEMENTS(p.cb.apConstantBuffer) == SVGA3D_DX_MAX_CONSTBUFFERS); + switch (idxShaderState) + { + case 0: + pImmediateContext->VSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 1: + pImmediateContext->PSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 2: + pImmediateContext->GSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 3: + pImmediateContext->HSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 4: + pImmediateContext->DSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + case 5: + pImmediateContext->CSGetConstantBuffers1(0, cMaxBound, + p.cb.apConstantBuffer, p.cb.aFirstConstant, p.cb.aNumConstants); + break; + default: + break; + } + + DXCONSTANTBUFFERSTATE *pCb = &pDXContext->pBackendDXContext->resources.shaderState[idxShaderState].constantBuffers; + AssertCompile(RT_ELEMENTS(pCb->apConstantBuffer) == SVGA3D_DX_MAX_CONSTBUFFERS); + + for (uint32_t i = 0; i < cMaxBound; ++i) + { + uint32_t sid = pDXContext->svgaDXContext.shaderState[idxShaderState].constantBuffers[i].sid; + if (sid != SVGA3D_INVALID_ID) + { + AssertRelease(pCb->apConstantBuffer[i] == p.cb.apConstantBuffer[i]); + AssertRelease(pCb->aFirstConstant[i] == p.cb.aFirstConstant[i]); + AssertRelease(pCb->aNumConstants[i] == p.cb.aNumConstants[i]); + } + else + AssertRelease(pCb->apConstantBuffer[i] == NULL); + } + D3D_RELEASE_ARRAY(cMaxBound, p.cb.apConstantBuffer); + } //pImmediateContext->VSGetShader(&p.pVertexShader, NULL, NULL); //D3D_RELEASE(p.pVertexShader); @@ -8538,7 +8625,7 @@ static void dxSetupPipeline(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturnVoid(pDXDevice->pDevice); - dxEnsureViews(pThisCC, pDXContext); + dxEnsureResourcesAndViews(pThisCC, pDXContext); /* * State objects @@ -8635,9 +8722,23 @@ static void dxSetupPipeline(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) } #endif - dxSetConstantBuffers(pThisCC, pDXContext); + dxSetConstantBuffers(pDXDevice, pDXContext); +#ifndef DX_STATE_TRACKER dxSetVertexBuffers(pThisCC, pDXContext); dxSetIndexBuffer(pThisCC, pDXContext); +#else + if (pDXContext->u64ContextFlags & DX_CTX_F_STATE_VERTEXBUFFER) + { + pDXContext->u64ContextFlags &= ~DX_CTX_F_STATE_VERTEXBUFFER; + dxSetVertexBuffers(pDXDevice, pDXContext); + } + + if (pDXContext->u64ContextFlags & DX_CTX_F_STATE_INDEXBUFFER) + { + pDXContext->u64ContextFlags &= ~DX_CTX_F_STATE_INDEXBUFFER; + dxSetIndexBuffer(pDXDevice, pDXContext); + } +#endif /* * Shader resources @@ -9015,11 +9116,7 @@ static void dxSetupPipeline(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext) static void dxPostDraw(DXDEVICE *pDXDevice, PVMSVGA3DDXCONTEXT pDXContext) { -#ifndef DX_CB - RT_NOREF(pDXDevice, pDXContext); -#else dxUploadBufferManagerProcessFull(&pDXContext->pBackendDXContext->constantBufferManager, pDXDevice->pImmediateContext); -#endif } @@ -11158,11 +11255,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D sizeof(pBackendDXContext->paRenderTargetView[0]), pDXContext->cot.cRTView, cValidEntries); AssertRCBreak(rc); - for (uint32_t i = 0; i < cValidEntries; ++i) + for (uint32_t i = 0; i < pBackendDXContext->cRenderTargetView; ++i) { DXVIEW *pDXView = &pBackendDXContext->paRenderTargetView[i]; - if (pDXView->u.pView) + if (i < cValidEntries && pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); } #endif break; @@ -11217,11 +11316,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D sizeof(pBackendDXContext->paDepthStencilView[0]), pDXContext->cot.cDSView, cValidEntries); AssertRCBreak(rc); - for (uint32_t i = 0; i < cValidEntries; ++i) + for (uint32_t i = 0; i < pBackendDXContext->cDepthStencilView; ++i) { DXVIEW *pDXView = &pBackendDXContext->paDepthStencilView[i]; - if (pDXView->u.pView) + if (i < cValidEntries && pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); } #endif break; @@ -11276,11 +11377,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D sizeof(pBackendDXContext->paShaderResourceView[0]), pDXContext->cot.cSRView, cValidEntries); AssertRCBreak(rc); - for (uint32_t i = 0; i < cValidEntries; ++i) + for (uint32_t i = 0; i < pBackendDXContext->cShaderResourceView; ++i) { DXVIEW *pDXView = &pBackendDXContext->paShaderResourceView[i]; - if (pDXView->u.pView) + if (i < cValidEntries && pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); } #endif break; @@ -11614,11 +11717,13 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D sizeof(pBackendDXContext->paUnorderedAccessView[0]), pDXContext->cot.cUAView, cValidEntries); AssertRCBreak(rc); - for (uint32_t i = 0; i < cValidEntries; ++i) + for (uint32_t i = 0; i < pBackendDXContext->cUnorderedAccessView; ++i) { DXVIEW *pDXView = &pBackendDXContext->paUnorderedAccessView[i]; - if (pDXView->u.pView) + if (i < cValidEntries && pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); } #endif break; @@ -11647,6 +11752,7 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D } break; case VBSVGA_COTABLE_VDOV: +#ifndef DX_STATE_TRACKER if (pBackendDXContext->paVideoDecoderOutputView) { /* Destroy the no longer used entries. */ @@ -11674,6 +11780,36 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D if (pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); } +#else + if (!fGrow) + cValidEntries = 0; + + if (pBackendDXContext->paVideoDecoderOutputView) + { + /* Destroy the no longer used entries. */ + for (uint32_t i = 0; i < pBackendDXContext->cVideoDecoderOutputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoDecoderOutputView[i]; + if (i < cValidEntries) + dxViewRemoveFromList(pDXView); /* Remove from list because DXVIEW array will be reallocated. */ + else + dxViewDestroy(pDXView); + } + } + + rc = dxCOTableRealloc((void **)&pBackendDXContext->paVideoDecoderOutputView, &pBackendDXContext->cVideoDecoderOutputView, + sizeof(pBackendDXContext->paVideoDecoderOutputView[0]), pDXContext->cot.cVideoDecoderOutputView, cValidEntries); + AssertRCBreak(rc); + + for (uint32_t i = 0; i < pBackendDXContext->cVideoDecoderOutputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoDecoderOutputView[i]; + if (i < cValidEntries && pDXView->u.pView) + dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); + } +#endif break; case VBSVGA_COTABLE_VIDEODECODER: if (pBackendDXContext->paVideoDecoder) @@ -11699,6 +11835,7 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D } break; case VBSVGA_COTABLE_VPIV: +#ifndef DX_STATE_TRACKER if (pBackendDXContext->paVideoProcessorInputView) { /* Destroy the no longer used entries. */ @@ -11726,8 +11863,39 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D if (pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); } +#else + if (!fGrow) + cValidEntries = 0; + + if (pBackendDXContext->paVideoProcessorInputView) + { + /* Destroy the no longer used entries. */ + for (uint32_t i = 0; i < pBackendDXContext->cVideoProcessorInputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoProcessorInputView[i]; + if (i < cValidEntries) + dxViewRemoveFromList(pDXView); /* Remove from list because DXVIEW array will be reallocated. */ + else + dxViewDestroy(pDXView); + } + } + + rc = dxCOTableRealloc((void **)&pBackendDXContext->paVideoProcessorInputView, &pBackendDXContext->cVideoProcessorInputView, + sizeof(pBackendDXContext->paVideoProcessorInputView[0]), pDXContext->cot.cVideoProcessorInputView, cValidEntries); + AssertRCBreak(rc); + + for (uint32_t i = 0; i < pBackendDXContext->cVideoProcessorInputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoProcessorInputView[i]; + if (i < cValidEntries && pDXView->u.pView) + dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); + } +#endif break; case VBSVGA_COTABLE_VPOV: +#ifndef DX_STATE_TRACKER if (pBackendDXContext->paVideoProcessorOutputView) { /* Destroy the no longer used entries. */ @@ -11755,6 +11923,36 @@ static DECLCALLBACK(int) vmsvga3dBackDXSetCOTable(PVGASTATECC pThisCC, PVMSVGA3D if (pDXView->u.pView) dxViewAddToList(pThisCC, pDXView); } +#else + if (!fGrow) + cValidEntries = 0; + + if (pBackendDXContext->paVideoProcessorOutputView) + { + /* Destroy the no longer used entries. */ + for (uint32_t i = 0; i < pBackendDXContext->cVideoProcessorOutputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoProcessorOutputView[i]; + if (i < cValidEntries) + dxViewRemoveFromList(pDXView); /* Remove from list because DXVIEW array will be reallocated. */ + else + dxViewDestroy(pDXView); + } + } + + rc = dxCOTableRealloc((void **)&pBackendDXContext->paVideoProcessorOutputView, &pBackendDXContext->cVideoProcessorOutputView, + sizeof(pBackendDXContext->paVideoProcessorOutputView[0]), pDXContext->cot.cVideoProcessorOutputView, cValidEntries); + AssertRCBreak(rc); + + for (uint32_t i = 0; i < pBackendDXContext->cVideoProcessorOutputView; ++i) + { + DXVIEW *pDXView = &pBackendDXContext->paVideoProcessorOutputView[i]; + if (i < cValidEntries && pDXView->u.pView) + dxViewAddToList(pThisCC, pDXView); + else + dxViewInit(pDXView); + } +#endif break; case VBSVGA_COTABLE_MAX: break; /* Compiler warning */ #ifndef DEBUG_sunlover @@ -12760,54 +12958,30 @@ static void dxVideoProcessorSetStreamRotation(DXDEVICE *pDXDevice, DXVIDEOPROCES } -static int dxCreateVideoDecoderOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, VBSVGA3dVideoDecoderOutputViewId videoDecoderOutputViewId, VBSVGACOTableDXVideoDecoderOutputViewEntry const *pEntry) +static HRESULT dxCreateVideoDecoderOutputView(PVGASTATECC pThisCC, VBSVGACOTableDXVideoDecoderOutputViewEntry const *pEntry, + ID3D11Resource *pResource, ID3D11VideoDecoderOutputView **ppVDOView) { DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturn(pDXDevice->pVideoDevice, VERR_INVALID_STATE); - PVMSVGA3DSURFACE pSurface; - ID3D11Resource *pResource; - int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); - AssertRCReturn(rc, rc); - - videoDecoderOutputViewId = dxVideoDecoderOutputViewId(pDXContext, videoDecoderOutputViewId); - if (videoDecoderOutputViewId == SVGA3D_INVALID_ID) - return VERR_INVALID_PARAMETER; - - DXVIEW *pView = &pDXContext->pBackendDXContext->paVideoDecoderOutputView[videoDecoderOutputViewId]; - Assert(pView->u.pView == NULL); - D3D11_VIDEO_DECODER_OUTPUT_VIEW_DESC Desc; RT_ZERO(Desc); memcpy(&Desc.DecodeProfile, &pEntry->desc.DecodeProfile, sizeof(GUID)); Desc.ViewDimension = dxVDOVDimension(pEntry->desc.ViewDimension); Desc.Texture2D.ArraySlice = pEntry->desc.Texture2D.ArraySlice; - ID3D11VideoDecoderOutputView *pVDOView; - HRESULT hr = pDXDevice->pVideoDevice->CreateVideoDecoderOutputView(pResource, &Desc, &pVDOView); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); - - return dxViewInit(pView, pSurface, pDXContext, videoDecoderOutputViewId, VMSVGA3D_VIEWTYPE_VIDEODECODEROUTPUT, pVDOView); + HRESULT hr = pDXDevice->pVideoDevice->CreateVideoDecoderOutputView(pResource, &Desc, ppVDOView); + Assert(SUCCEEDED(hr)); + return hr; } -static int dxCreateVideoProcessorInputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, VBSVGA3dVideoProcessorInputViewId videoProcessorInputViewId, VBSVGACOTableDXVideoProcessorInputViewEntry const *pEntry) +static HRESULT dxCreateVideoProcessorInputView(PVGASTATECC pThisCC, VBSVGACOTableDXVideoProcessorInputViewEntry const *pEntry, + ID3D11Resource *pResource, ID3D11VideoProcessorInputView **ppVPIView) { DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturn(pDXDevice->pVideoDevice, VERR_INVALID_STATE); - PVMSVGA3DSURFACE pSurface; - ID3D11Resource *pResource; - int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); - AssertRCReturn(rc, rc); - - videoProcessorInputViewId = dxVideoProcessorInputViewId(pDXContext, videoProcessorInputViewId); - if (videoProcessorInputViewId == SVGA3D_INVALID_ID) - return VERR_INVALID_PARAMETER; - - DXVIEW *pView = &pDXContext->pBackendDXContext->paVideoProcessorInputView[videoProcessorInputViewId]; - Assert(pView->u.pView == NULL); - D3D11_VIDEO_PROCESSOR_CONTENT_DESC ContentDesc; RT_ZERO(ContentDesc); ContentDesc.InputFrameFormat = dxVideoFrameFormat(pEntry->contentDesc.InputFrameFormat); @@ -12823,7 +12997,7 @@ static int dxCreateVideoProcessorInputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTE ID3D11VideoProcessorEnumerator *pEnum; HRESULT hr = pDXDevice->pVideoDevice->CreateVideoProcessorEnumerator(&ContentDesc, &pEnum); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); + AssertReturn(SUCCEEDED(hr), hr); D3D11_VIDEO_PROCESSOR_INPUT_VIEW_DESC Desc; RT_ZERO(Desc); @@ -12832,32 +13006,19 @@ static int dxCreateVideoProcessorInputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTE Desc.Texture2D.MipSlice = pEntry->desc.Texture2D.MipSlice; Desc.Texture2D.ArraySlice = pEntry->desc.Texture2D.ArraySlice; - ID3D11VideoProcessorInputView *pVPIView; - hr = pDXDevice->pVideoDevice->CreateVideoProcessorInputView(pResource, pEnum, &Desc, &pVPIView); + hr = pDXDevice->pVideoDevice->CreateVideoProcessorInputView(pResource, pEnum, &Desc, ppVPIView); D3D_RELEASE(pEnum); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); - - return dxViewInit(pView, pSurface, pDXContext, videoProcessorInputViewId, VMSVGA3D_VIEWTYPE_VIDEOPROCESSORINPUT, pVPIView); + Assert(SUCCEEDED(hr)); + return hr; } -static int dxCreateVideoProcessorOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, VBSVGA3dVideoProcessorOutputViewId videoProcessorOutputViewId, VBSVGACOTableDXVideoProcessorOutputViewEntry const *pEntry) +static HRESULT dxCreateVideoProcessorOutputView(PVGASTATECC pThisCC, VBSVGACOTableDXVideoProcessorOutputViewEntry const *pEntry, + ID3D11Resource *pResource, ID3D11VideoProcessorOutputView **ppVPOView) { DXDEVICE *pDXDevice = dxDeviceGet(pThisCC->svga.p3dState); AssertReturn(pDXDevice->pVideoDevice, VERR_INVALID_STATE); - PVMSVGA3DSURFACE pSurface; - ID3D11Resource *pResource; - int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); - AssertRCReturn(rc, rc); - - videoProcessorOutputViewId = dxVideoProcessorOutputViewId(pDXContext, videoProcessorOutputViewId); - if (videoProcessorOutputViewId == SVGA3D_INVALID_ID) - return VERR_INVALID_PARAMETER; - - DXVIEW *pView = &pDXContext->pBackendDXContext->paVideoProcessorOutputView[videoProcessorOutputViewId]; - Assert(pView->u.pView == NULL); - D3D11_VIDEO_PROCESSOR_CONTENT_DESC ContentDesc; RT_ZERO(ContentDesc); ContentDesc.InputFrameFormat = dxVideoFrameFormat(pEntry->contentDesc.InputFrameFormat); @@ -12873,7 +13034,7 @@ static int dxCreateVideoProcessorOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONT ID3D11VideoProcessorEnumerator *pEnum; HRESULT hr = pDXDevice->pVideoDevice->CreateVideoProcessorEnumerator(&ContentDesc, &pEnum); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); + AssertReturn(SUCCEEDED(hr), hr); D3D11_VIDEO_PROCESSOR_OUTPUT_VIEW_DESC Desc; RT_ZERO(Desc); @@ -12889,31 +13050,40 @@ static int dxCreateVideoProcessorOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONT Desc.Texture2DArray.ArraySize = pEntry->desc.Texture2DArray.ArraySize; } - ID3D11VideoProcessorOutputView *pVPOView; - hr = pDXDevice->pVideoDevice->CreateVideoProcessorOutputView(pResource, pEnum, &Desc, &pVPOView); + hr = pDXDevice->pVideoDevice->CreateVideoProcessorOutputView(pResource, pEnum, &Desc, ppVPOView); D3D_RELEASE(pEnum); - AssertReturn(SUCCEEDED(hr), VERR_NOT_SUPPORTED); - - return dxViewInit(pView, pSurface, pDXContext, videoProcessorOutputViewId, VMSVGA3D_VIEWTYPE_VIDEOPROCESSOROUTPUT, pVPOView); + Assert(SUCCEEDED(hr)); + return hr; } static int dxEnsureVideoDecoderOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, VBSVGA3dVideoDecoderOutputViewId viewId, DXVIEW **ppResult) { - ASSERT_GUEST_RETURN(viewId < pDXContext->cot.cVideoDecoderOutputView, VERR_INVALID_PARAMETER); - viewId = dxVideoDecoderOutputViewId(pDXContext, viewId); viewId = svgaVideoDecoderOutputViewId(pDXContext, viewId); if (viewId == SVGA3D_INVALID_ID) return VERR_INVALID_PARAMETER; + VBSVGACOTableDXVideoDecoderOutputViewEntry const *pEntry = &pDXContext->cot.paVideoDecoderOutputView[viewId]; DXVIEW *pDXView = &pDXContext->pBackendDXContext->paVideoDecoderOutputView[viewId]; + + /* dxEnsureResource must be always called because it makes sure that resource is up to date. */ + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); + AssertRCReturn(rc, rc); + if (!pDXView->u.pView) { - VBSVGACOTableDXVideoDecoderOutputViewEntry const *pEntry = &pDXContext->cot.paVideoDecoderOutputView[viewId]; - int rc = dxCreateVideoDecoderOutputView(pThisCC, pDXContext, viewId, pEntry); + ID3D11VideoDecoderOutputView *pVDOView; + HRESULT hr = dxCreateVideoDecoderOutputView(pThisCC, pEntry, pResource, &pVDOView); + if (SUCCEEDED(hr)) + rc = dxViewInit(pDXView, pSurface, pDXContext, viewId, VMSVGA3D_VIEWTYPE_VIDEODECODEROUTPUT, pVDOView); + else + rc = VERR_INVALID_STATE; AssertRCReturn(rc, rc); } + *ppResult = pDXView; return VINF_SUCCESS; } @@ -12926,13 +13096,26 @@ static int dxEnsureVideoProcessorInputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONTE if (viewId == SVGA3D_INVALID_ID) return VERR_INVALID_PARAMETER; + VBSVGACOTableDXVideoProcessorInputViewEntry const *pEntry = &pDXContext->cot.paVideoProcessorInputView[viewId]; DXVIEW *pDXView = &pDXContext->pBackendDXContext->paVideoProcessorInputView[viewId]; + + /* dxEnsureResource must be always called because it makes sure that resource is up to date. */ + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); + AssertRCReturn(rc, rc); + if (!pDXView->u.pView) { - VBSVGACOTableDXVideoProcessorInputViewEntry const *pEntry = &pDXContext->cot.paVideoProcessorInputView[viewId]; - int rc = dxCreateVideoProcessorInputView(pThisCC, pDXContext, viewId, pEntry); + ID3D11VideoProcessorInputView *pVPIView; + HRESULT hr = dxCreateVideoProcessorInputView(pThisCC, pEntry, pResource, &pVPIView); + if (SUCCEEDED(hr)) + rc = dxViewInit(pDXView, pSurface, pDXContext, viewId, VMSVGA3D_VIEWTYPE_VIDEOPROCESSORINPUT, pVPIView); + else + rc = VERR_INVALID_STATE; AssertRCReturn(rc, rc); } + *ppResult = pDXView; return VINF_SUCCESS; } @@ -12945,13 +13128,26 @@ static int dxEnsureVideoProcessorOutputView(PVGASTATECC pThisCC, PVMSVGA3DDXCONT if (viewId == SVGA3D_INVALID_ID) return VERR_INVALID_PARAMETER; + VBSVGACOTableDXVideoProcessorOutputViewEntry const *pEntry = &pDXContext->cot.paVideoProcessorOutputView[viewId]; DXVIEW *pDXView = &pDXContext->pBackendDXContext->paVideoProcessorOutputView[viewId]; + + /* dxEnsureResource must be always called because it makes sure that resource is up to date. */ + PVMSVGA3DSURFACE pSurface; + ID3D11Resource *pResource; + int rc = dxEnsureResource(pThisCC, pEntry->sid, &pSurface, &pResource); + AssertRCReturn(rc, rc); + if (!pDXView->u.pView) { - VBSVGACOTableDXVideoProcessorOutputViewEntry const *pEntry = &pDXContext->cot.paVideoProcessorOutputView[viewId]; - int rc = dxCreateVideoProcessorOutputView(pThisCC, pDXContext, viewId, pEntry); + ID3D11VideoProcessorOutputView *pVPOView; + HRESULT hr = dxCreateVideoProcessorOutputView(pThisCC, pEntry, pResource, &pVPOView); + if (SUCCEEDED(hr)) + rc = dxViewInit(pDXView, pSurface, pDXContext, viewId, VMSVGA3D_VIEWTYPE_VIDEOPROCESSOROUTPUT, pVPOView); + else + rc = VERR_INVALID_STATE; AssertRCReturn(rc, rc); } + *ppResult = pDXView; return VINF_SUCCESS; } @@ -14417,6 +14613,7 @@ static DECLCALLBACK(int) vmsvga3dBackQueryInterface(PVGASTATECC pThisCC, char co VMSVGA3DBACKENDFUNCSMAP *p = (VMSVGA3DBACKENDFUNCSMAP *)pvInterfaceFuncs; p->pfnSurfaceMap = vmsvga3dBackSurfaceMap; p->pfnSurfaceUnmap = vmsvga3dBackSurfaceUnmap; + p->pfnEnsureResource = vmsvga3dBackEnsureResource; } } else diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-savedstate.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-savedstate.cpp index 83c987acf075..f4096ce73fef 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-savedstate.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx-savedstate.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx-savedstate.cpp 114266 2026-06-08 15:03:54Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx-savedstate.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - DX backend saved state. */ @@ -53,7 +53,7 @@ * Load */ -static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHANDLE pSSM) +static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHANDLE pSSM, uint32_t uVersion) { PVMSVGA3DSTATE p3dState = pThisCC->svga.p3dState; int rc; @@ -65,17 +65,32 @@ static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA if (sid == SVGA3D_INVALID_ID) return VINF_SUCCESS; + bool fGB = true; /* DX backend uses GB surfaces. */ + bool fHW = false; + if (uVersion >= VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF) + { + pHlp->pfnSSMGetBool(pSSM, &fGB); + rc = pHlp->pfnSSMGetBool(pSSM, &fHW); + AssertRCReturn(rc, rc); + AssertReturn(fGB, VERR_INVALID_STATE); + } + /* Define the surface. */ SVGAOTableSurfaceEntry entrySurface; rc = vmsvgaR3OTableReadSurface(pThisCC->svga.pSvgaR3State, sid, &entrySurface); AssertRCReturn(rc, rc); - /** @todo fAllocMipLevels=false and alloc miplevels if there is data to be loaded. */ + /* Alloc miplevels if there is data to be loaded. */ + uint32_t defineFlags = 0; + if (fGB) + defineFlags |= VMSVGA3D_SURFACE_DEFINE_F_GB; + if (p3dState->fVMSVGA2dGBO) + defineFlags |= VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS; rc = vmsvga3dSurfaceDefine(pThisCC, sid, RT_MAKE_U64(entrySurface.surface1Flags, entrySurface.surface2Flags), entrySurface.format, entrySurface.multisampleCount, (SVGA3dMSPattern)entrySurface.multisamplePattern, (SVGA3dMSQualityLevel)entrySurface.qualityLevel, entrySurface.autogenFilter, entrySurface.numMipLevels, &entrySurface.size, entrySurface.arraySize, entrySurface.bufferByteStride, - /* fAllocMipLevels = */ true); + defineFlags); AssertRCReturn(rc, rc); PVMSVGA3DSURFACE pSurface = p3dState->papSurfaces[sid]; @@ -85,13 +100,25 @@ static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA pHlp->pfnSSMGetU32(pSSM, &pSurface->idAssociatedContext); /* Load miplevels data to the surface buffers. */ + bool fAllocated = RT_BOOL(defineFlags & VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS); for (uint32_t j = 0; j < pSurface->cLevels * pSurface->surfaceDesc.numArrayElements; j++) { PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->paMipmapLevels[j]; - /* vmsvga3dSurfaceDefine already allocated the surface data buffer. */ - Assert(pMipmapLevel->cbSurface); - AssertReturn(pMipmapLevel->pSurfaceData, VERR_INTERNAL_ERROR); + if (uVersion >= VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF) + { + bool fUpdated; + rc = pHlp->pfnSSMGetBool(pSSM, &fUpdated); + AssertRCReturn(rc, rc); + pMipmapLevel->fUpdated = fUpdated; + + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.x); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.y); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.z); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.w); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.h); + pHlp->pfnSSMGetU32(pSSM, &pMipmapLevel->boxUpdated.d); + } /* Fetch the data present boolean first. */ bool fDataPresent; @@ -100,6 +127,13 @@ static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA if (fDataPresent) { + if (!fAllocated) + { + rc = vmsvga3dSurfaceAllocMipLevels(pSurface); + AssertRCReturn(rc, rc); + fAllocated = true; + } + rc = pHlp->pfnSSMGetMem(pSSM, pMipmapLevel->pSurfaceData, pMipmapLevel->cbSurface); AssertRCReturn(rc, rc); @@ -110,6 +144,9 @@ static int vmsvga3dDXLoadSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA pMipmapLevel->fDirty = false; } + if (fHW) + vmsvga3dEnsureResource(pThisCC, sid); + return VINF_SUCCESS; } @@ -278,7 +315,7 @@ int vmsvga3dDXLoadExec(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGASTATECC pThisCC, for (uint32_t i = 0; i < p3dState->cSurfaces; ++i) { - rc = vmsvga3dDXLoadSurface(pHlp, pThisCC, pSSM); + rc = vmsvga3dDXLoadSurface(pHlp, pThisCC, pSSM, uVersion); AssertRCReturn(rc, rc); } } @@ -320,15 +357,27 @@ int vmsvga3dDXLoadExec(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGASTATECC pThisCC, static int vmsvga3dDXSaveSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHANDLE pSSM, PVMSVGA3DSURFACE pSurface) { - RT_NOREF(pThisCC); int rc; + /* DX backend uses GB surfaces. If a non GB surface was created (which should not happen), then skip it. */ + if (pSurface->id != SVGA3D_INVALID_ID && !pSurface->fGB) + { + AssertFailed(); + return pHlp->pfnSSMPutU32(pSSM, SVGA3D_INVALID_ID); + } + rc = pHlp->pfnSSMPutU32(pSSM, pSurface->id); AssertRCReturn(rc, rc); if (pSurface->id == SVGA3D_INVALID_ID) return VINF_SUCCESS; + /* VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF begin */ + pHlp->pfnSSMPutBool(pSSM, pSurface->fGB); + rc = pHlp->pfnSSMPutBool(pSSM, VMSVGA3DSURFACE_HAS_HW_SURFACE(pSurface)); + AssertRCReturn(rc, rc); + /* VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF end */ + /* Save the surface fields which are not part of SVGAOTableSurfaceEntry. */ pHlp->pfnSSMPutU32(pSSM, pSurface->idAssociatedContext); @@ -339,6 +388,17 @@ static int vmsvga3dDXSaveSurface(PCPDMDEVHLPR3 pHlp, PVGASTATECC pThisCC, PSSMHA uint32_t idx = iMipmap + iArray * pSurface->cLevels; PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->paMipmapLevels[idx]; + /* VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF begin */ + pHlp->pfnSSMPutBool(pSSM, pMipmapLevel->fUpdated); + + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.x); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.y); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.z); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.w); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.h); + pHlp->pfnSSMPutU32(pSSM, pMipmapLevel->boxUpdated.d); + /* VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF end */ + /* Multisample surface content can't be accessed. */ if (pSurface->surfaceDesc.multisampleCount > 1) { diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp index 8c029c1a9c96..471e86e6a7a2 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-dx.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-dx.cpp 114712 2026-07-15 17:24:33Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-dx.cpp 115047 2026-08-17 15:05:08Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - Common code for DX backend interface. */ @@ -134,8 +134,42 @@ void vmsvga3dDXInitContextMobData(SVGADXContextMobFormat *p) p->csuaViewIds[i] = SVGA3D_INVALID_ID; } + +#ifdef DUMP_BITMAPS +static void vmsvga3dDXDrawDumpRenderTargets(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, const char *pszPrefix = NULL) +{ + for (uint32_t i = 0; i < SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS; ++i) + { + if (pDXContext->svgaDXContext.renderState.renderTargetViewIds[i] != SVGA3D_INVALID_ID) + { + SVGACOTableDXRTViewEntry *pRTViewEntry = &pDXContext->cot.paRTView[pDXContext->svgaDXContext.renderState.renderTargetViewIds[i]]; + Log(("Dump RT[%u] sid = %u rtvid = %u\n", i, pRTViewEntry->sid, pDXContext->svgaDXContext.renderState.renderTargetViewIds[i])); + + SVGA3dSurfaceImageId image; + image.sid = pRTViewEntry->sid; + image.face = 0; + image.mipmap = 0; + VMSVGA3D_MAPPED_SURFACE map; + int rc = vmsvga3dSurfaceMap(pThisCC, &image, NULL, VMSVGA3D_SURFACE_MAP_READ, VMSVGA3D_MAP_F_NONE, &map); + if (RT_SUCCESS(rc)) + { + vmsvga3dMapWriteBmpFile(&map, pszPrefix ? pszPrefix : "rt-"); + vmsvga3dSurfaceUnmap(pThisCC, &image, &map, /* fWritten = */ false); + } + else + Log(("Map failed %Rrc\n", rc)); + } + } +} +#endif + + DECLINLINE(void) dxPostDraw(PVMSVGA3DDXCONTEXT pDXContext) { +#ifdef DUMP_BITMAPS + vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); +#endif + RT_ZERO(pDXContext->state.ia.vb.au32Modified); RT_ZERO(pDXContext->state.shader[0].shaderResources.au64Modified); RT_ZERO(pDXContext->state.shader[1].shaderResources.au64Modified); RT_ZERO(pDXContext->state.shader[2].shaderResources.au64Modified); @@ -552,6 +586,12 @@ int vmsvga3dDXSetSingleConstantBuffer(PVGASTATECC pThisCC, uint32_t idDXContext, pCBB->offsetInBytes = pCmd->offsetInBytes; pCBB->sizeInBytes = pCmd->sizeInBytes; +#ifdef DX_STATE_TRACKER + if (pCBB->sid != SVGA3D_INVALID_ID) + pDXContext->state.shader[idxShaderState].constantBuffers.cMaxBound = + RT_MAX(pCmd->slot + 1, pDXContext->state.shader[idxShaderState].constantBuffers.cMaxBound); +#endif + rc = pSvgaR3State->pFuncsDX->pfnDXSetSingleConstantBuffer(pThisCC, pDXContext, pCmd->slot, pCmd->type, pCmd->sid, pCmd->offsetInBytes, pCmd->sizeInBytes); return rc; } @@ -689,34 +729,6 @@ int vmsvga3dDXSetSamplers(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmdDX } -#ifdef DUMP_BITMAPS -static void vmsvga3dDXDrawDumpRenderTargets(PVGASTATECC pThisCC, PVMSVGA3DDXCONTEXT pDXContext, const char *pszPrefix = NULL) -{ - for (uint32_t i = 0; i < SVGA3D_MAX_SIMULTANEOUS_RENDER_TARGETS; ++i) - { - if (pDXContext->svgaDXContext.renderState.renderTargetViewIds[i] != SVGA3D_INVALID_ID) - { - SVGACOTableDXRTViewEntry *pRTViewEntry = &pDXContext->cot.paRTView[pDXContext->svgaDXContext.renderState.renderTargetViewIds[i]]; - Log(("Dump RT[%u] sid = %u rtvid = %u\n", i, pRTViewEntry->sid, pDXContext->svgaDXContext.renderState.renderTargetViewIds[i])); - - SVGA3dSurfaceImageId image; - image.sid = pRTViewEntry->sid; - image.face = 0; - image.mipmap = 0; - VMSVGA3D_MAPPED_SURFACE map; - int rc = vmsvga3dSurfaceMap(pThisCC, &image, NULL, VMSVGA3D_SURFACE_MAP_READ, VMSVGA3D_MAP_F_NONE, &map); - if (RT_SUCCESS(rc)) - { - vmsvga3dMapWriteBmpFile(&map, pszPrefix ? pszPrefix : "rt-"); - vmsvga3dSurfaceUnmap(pThisCC, &image, &map, /* fWritten = */ false); - } - else - Log(("Map failed %Rrc\n", rc)); - } - } -} -#endif - int vmsvga3dDXDraw(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmdDXDraw const *pCmd) { int rc; @@ -731,9 +743,6 @@ int vmsvga3dDXDraw(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmdDXDraw co rc = pSvgaR3State->pFuncsDX->pfnDXDraw(pThisCC, pDXContext, pCmd->vertexCount, pCmd->startVertexLocation); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -752,9 +761,6 @@ int vmsvga3dDXDrawIndexed(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmdDX rc = pSvgaR3State->pFuncsDX->pfnDXDrawIndexed(pThisCC, pDXContext, pCmd->indexCount, pCmd->startIndexLocation, pCmd->baseVertexLocation); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -774,9 +780,6 @@ int vmsvga3dDXDrawInstanced(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCmd rc = pSvgaR3State->pFuncsDX->pfnDXDrawInstanced(pThisCC, pDXContext, pCmd->vertexCountPerInstance, pCmd->instanceCount, pCmd->startVertexLocation, pCmd->startInstanceLocation); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -796,9 +799,6 @@ int vmsvga3dDXDrawIndexedInstanced(PVGASTATECC pThisCC, uint32_t idDXContext, SV rc = pSvgaR3State->pFuncsDX->pfnDXDrawIndexedInstanced(pThisCC, pDXContext, pCmd->indexCountPerInstance, pCmd->instanceCount, pCmd->startIndexLocation, pCmd->baseVertexLocation, pCmd->startInstanceLocation); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -817,9 +817,6 @@ int vmsvga3dDXDrawAuto(PVGASTATECC pThisCC, uint32_t idDXContext) rc = pSvgaR3State->pFuncsDX->pfnDXDrawAuto(pThisCC, pDXContext); dxPostDraw(pDXContext); -#ifdef DUMP_BITMAPS - vmsvga3dDXDrawDumpRenderTargets(pThisCC, pDXContext); -#endif return rc; } @@ -871,6 +868,7 @@ int vmsvga3dDXSetVertexBuffers(PVGASTATECC pThisCC, uint32_t idDXContext, uint32 ASSERT_GUEST_RETURN(cVertexBuffer <= SVGA3D_DX_MAX_VERTEXBUFFERS - startBuffer, VERR_INVALID_PARAMETER); RT_UNTRUSTED_VALIDATED_FENCE(); +#ifndef DX_STATE_TRACKER for (uint32_t i = 0; i < cVertexBuffer; ++i) { uint32_t const idxVertexBuffer = startBuffer + i; @@ -879,6 +877,35 @@ int vmsvga3dDXSetVertexBuffers(PVGASTATECC pThisCC, uint32_t idDXContext, uint32 pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].stride = paVertexBuffer[i].stride; pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].offset = paVertexBuffer[i].offset; } +#else + bool fModified = false; + uint32_t cMaxBound = 0; + for (uint32_t i = 0; i < cVertexBuffer; ++i) + { + uint32_t const idxVertexBuffer = startBuffer + i; + + if ( pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].bufferId != paVertexBuffer[i].sid + || pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].stride != paVertexBuffer[i].stride + || pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].offset != paVertexBuffer[i].offset) + { + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].bufferId = paVertexBuffer[i].sid; + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].stride = paVertexBuffer[i].stride; + pDXContext->svgaDXContext.inputAssembly.vertexBuffers[idxVertexBuffer].offset = paVertexBuffer[i].offset; + fModified = true; + ASMBitSet(pDXContext->state.ia.vb.au32Modified, idxVertexBuffer); + } + + if (paVertexBuffer[i].sid != SVGA3D_INVALID_ID) + cMaxBound = idxVertexBuffer + 1; + } + + if (fModified) + pDXContext->u64ContextFlags |= DX_CTX_F_STATE_VERTEXBUFFER; + + /* Remember how many slots the context actually uses. */ + if (pDXContext->state.ia.vb.cMaxBound < cMaxBound) + pDXContext->state.ia.vb.cMaxBound = cMaxBound; +#endif rc = pSvgaR3State->pFuncsDX->pfnDXSetVertexBuffers(pThisCC, pDXContext, startBuffer, cVertexBuffer, paVertexBuffer); return rc; @@ -897,9 +924,21 @@ int vmsvga3dDXSetIndexBuffer(PVGASTATECC pThisCC, uint32_t idDXContext, SVGA3dCm rc = vmsvga3dDXContextFromCid(p3dState, idDXContext, &pDXContext); AssertRCReturn(rc, rc); +#ifndef DX_STATE_TRACKER pDXContext->svgaDXContext.inputAssembly.indexBufferSid = pCmd->sid; pDXContext->svgaDXContext.inputAssembly.indexBufferOffset = pCmd->offset; pDXContext->svgaDXContext.inputAssembly.indexBufferFormat = pCmd->format; +#else + if ( pDXContext->svgaDXContext.inputAssembly.indexBufferSid != pCmd->sid + || pDXContext->svgaDXContext.inputAssembly.indexBufferOffset != pCmd->offset + || pDXContext->svgaDXContext.inputAssembly.indexBufferFormat != (uint32)pCmd->format) + { + pDXContext->svgaDXContext.inputAssembly.indexBufferSid = pCmd->sid; + pDXContext->svgaDXContext.inputAssembly.indexBufferOffset = pCmd->offset; + pDXContext->svgaDXContext.inputAssembly.indexBufferFormat = pCmd->format; + pDXContext->u64ContextFlags |= DX_CTX_F_STATE_INDEXBUFFER; + } +#endif rc = pSvgaR3State->pFuncsDX->pfnDXSetIndexBuffer(pThisCC, pDXContext, pCmd->sid, pCmd->format, pCmd->offset); return rc; @@ -2701,6 +2740,14 @@ static int dxSanitizeQueryEntry(PVMSVGA3DDXCONTEXT pDXContext, SVGACOTableDXQuer static int dxSanitizeShaderEntry(PVMSVGA3DDXCONTEXT pDXContext, SVGACOTableDXShaderEntry *pEntry) { RT_NOREF(pDXContext); + if (pEntry->type == SVGA3D_SHADERTYPE_INVALID) + { + ASSERT_GUEST_RETURN(pEntry->sizeInBytes == 0, VERR_INVALID_PARAMETER); + ASSERT_GUEST_RETURN(pEntry->offsetInBytes == 0, VERR_INVALID_PARAMETER); + ASSERT_GUEST_RETURN(pEntry->mobid == SVGA3D_INVALID_ID, VERR_INVALID_PARAMETER); + return VINF_SUCCESS; + } + ASSERT_GUEST_RETURN(pEntry->type >= SVGA3D_SHADERTYPE_MIN && pEntry->type < SVGA3D_SHADERTYPE_MAX, VERR_INVALID_PARAMETER); ASSERT_GUEST_RETURN(pEntry->sizeInBytes >= 8, VERR_INVALID_PARAMETER); /* Version Token + Length Token. */ return VINF_SUCCESS; diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h index 66d1f952d85f..068bed2a8881 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-internal.h @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-internal.h 114455 2026-06-19 11:33:52Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-internal.h 115042 2026-08-15 14:51:14Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device - 3D part, internal header. */ @@ -438,6 +438,10 @@ typedef struct VMSVGA3DMIPMAPLEVEL /** Set if pvSurfaceData contains data not realized in hardware or pushed to the * hardware surface yet. */ bool fDirty; + /* Whether the GB surface (VMSVGA3DSURFACE::fGB == true) has been updated by the guest. */ + bool fUpdated : 1; + /* The updated region of the GB surface (if fUpdated is true). */ + SVGA3dBox boxUpdated; } VMSVGA3DMIPMAPLEVEL; /** Pointer to a mipmap level. */ typedef VMSVGA3DMIPMAPLEVEL *PVMSVGA3DMIPMAPLEVEL; @@ -570,6 +574,7 @@ typedef struct VMSVGA3DSURFACE /** @todo Only numArrayElements field is used currently. The code uses old fields cLevels, etc for anything else. */ VMSVGA3D_SURFACE_DESC surfaceDesc; + bool fGB : 1; /* Whether this is a guest backed surface. */ union { @@ -996,6 +1001,8 @@ static SSMFIELD const g_aVMSVGA3DCONTEXTFields[] = #define DX_CTX_F_STATE_VIEWPORT 0x00000400 #define DX_CTX_F_STATE_SCISSORRECT 0x00000800 #define DX_CTX_F_STATE_RASTERIZERSTATE 0x00001000 +#define DX_CTX_F_STATE_INDEXBUFFER 0x00002000 +#define DX_CTX_F_STATE_VERTEXBUFFER 0x00004000 #define DX_CTX_F_STATE_SAMPLER_VS 0x00010000 /* Sampler bits must be in this order without gaps for '<<'. */ #define DX_CTX_F_STATE_SAMPLER_PS 0x00020000 #define DX_CTX_F_STATE_SAMPLER_GS 0x00040000 @@ -1018,6 +1025,8 @@ static SSMFIELD const g_aVMSVGA3DCONTEXTFields[] = | DX_CTX_F_STATE_VIEWPORT \ | DX_CTX_F_STATE_SCISSORRECT \ | DX_CTX_F_STATE_RASTERIZERSTATE \ + | DX_CTX_F_STATE_INDEXBUFFER \ + | DX_CTX_F_STATE_VERTEXBUFFER \ | DX_CTX_F_STATE_SAMPLER_VS \ | DX_CTX_F_STATE_SAMPLER_PS \ | DX_CTX_F_STATE_SAMPLER_GS \ @@ -1092,6 +1101,15 @@ typedef struct VMSVGA3DDXCONTEXT } cot; struct { + struct + { + struct + { + uint32_t cMaxBound; + AssertCompile(SVGA3D_DX_MAX_VERTEXBUFFERS == 32); + uint32_t au32Modified[(SVGA3D_DX_MAX_VERTEXBUFFERS + 31) / 32]; + } vb; + } ia; struct { struct @@ -1099,6 +1117,10 @@ typedef struct VMSVGA3DDXCONTEXT uint32_t cMaxBound; uint64_t au64Modified[(SVGA3D_DX_MAX_SRVIEWS + 63) / 64]; } shaderResources; + struct + { + uint32_t cMaxBound; + } constantBuffers; } shader[SVGA3D_NUM_SHADERTYPE]; struct { @@ -1510,6 +1532,9 @@ DECLINLINE(uint32_t) vmsvga3dClampedUMul32(uint32_t a, uint32_t b) return UINT32_C(0xFFFFFFFF); } +int vmsvga3dSurfaceAllocMipLevels(PVMSVGA3DSURFACE pSurface); /* For saved state. */ +void vmsvga3dSurfaceFreeMipLevels(PVMSVGA3DSURFACE pSurface); /* For backend. */ + #if defined(VMSVGA3D_DIRECT3D) HRESULT D3D9UpdateTexture(PVMSVGA3DCONTEXT pContext, PVMSVGA3DSURFACE pSurface); diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-savedstate.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-savedstate.cpp index 5f98f4b9012b..551c1e3cc133 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-savedstate.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-savedstate.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-savedstate.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-savedstate.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - Saved state and assocated stuff. */ @@ -685,7 +685,7 @@ int vmsvga3dLoadExec(PPDMDEVINS pDevIns, PVGASTATE pThis, PVGASTATECC pThisCC, P rc = vmsvga3dSurfaceDefine(pThisCC, sid, surface.f.surfaceFlags, surface.format, surface.multiSampleCount, SVGA3D_MS_PATTERN_NONE, SVGA3D_MS_QUALITY_NONE, - surface.autogenFilter, surface.cLevels, &pMipmapLevelSize[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ true); + surface.autogenFilter, surface.cLevels, &pMipmapLevelSize[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS); AssertRCReturn(rc, rc); RTMemFree(pMipmapLevelSize); diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp index bcb34c28de56..f71ec0886bed 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d-win.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d-win.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DevVGA-SVGA3d-win.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device */ @@ -2741,7 +2741,7 @@ static DECLCALLBACK(int) vmsvga3dBackContextDestroy(PVGASTATECC pThisCC, uint32_ AssertRC(rc); rc = vmsvga3dSurfaceDefine(pThisCC, sid, surfaceFlags, format, multisampleCount, SVGA3D_MS_PATTERN_NONE, SVGA3D_MS_QUALITY_NONE, autogenFilter, - cMipLevels, &pMipLevelSize[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, /* fAllocMipLevels = */ true); + cMipLevels, &pMipLevelSize[0], /* arraySize = */ 0, /* bufferByteStride = */ 0, VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS); AssertRC(rc); Assert(!pSurface->u.pSurface); diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp index 52eb0514e045..17559edbc857 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d.cpp 114812 2026-07-28 14:10:11Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d.cpp 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevSVGA3d - VMWare SVGA device, 3D parts - Common core code. */ @@ -140,7 +140,7 @@ static void vmsvgaSurfaceStats(PVGASTATECC pThisCC) } -static int vmsvga3dSurfaceAllocMipLevels(PVMSVGA3DSURFACE pSurface) +int vmsvga3dSurfaceAllocMipLevels(PVMSVGA3DSURFACE pSurface) { /* Allocate buffer to hold the surface data until we can move it into a D3D object */ for (uint32_t i = 0; i < pSurface->cLevels * pSurface->surfaceDesc.numArrayElements; ++i) @@ -154,7 +154,7 @@ static int vmsvga3dSurfaceAllocMipLevels(PVMSVGA3DSURFACE pSurface) } -static void vmsvga3dSurfaceFreeMipLevels(PVMSVGA3DSURFACE pSurface) +void vmsvga3dSurfaceFreeMipLevels(PVMSVGA3DSURFACE pSurface) { for (uint32_t i = 0; i < pSurface->cLevels * pSurface->surfaceDesc.numArrayElements; ++i) { @@ -182,11 +182,11 @@ static void vmsvga3dSurfaceFreeMipLevels(PVMSVGA3DSURFACE pSurface) * @param pMipLevel0Size . * @param arraySize Number of elements in a texture array. * @param bufferByteStride . - * @param fAllocMipLevels . + * @param defineFlags . */ int vmsvga3dSurfaceDefine(PVGASTATECC pThisCC, uint32_t sid, SVGA3dSurfaceAllFlags surfaceFlags, SVGA3dSurfaceFormat format, uint32_t multisampleCount, SVGA3dMSPattern multisamplePattern, SVGA3dMSQualityLevel qualityLevel, SVGA3dTextureFilter autogenFilter, - uint32_t numMipLevels, SVGA3dSize const *pMipLevel0Size, uint32_t arraySize, uint32_t bufferByteStride, bool fAllocMipLevels) + uint32_t numMipLevels, SVGA3dSize const *pMipLevel0Size, uint32_t arraySize, uint32_t bufferByteStride, uint32_t defineFlags) { PVMSVGA3DSURFACE pSurface; PVMSVGA3DSTATE pState = pThisCC->svga.p3dState; @@ -264,6 +264,8 @@ int vmsvga3dSurfaceDefine(PVGASTATECC pThisCC, uint32_t sid, SVGA3dSurfaceAllFla pSurface->surfaceDesc.qualityLevel = RT_CLAMP(qualityLevel, SVGA3D_MS_QUALITY_MIN, SVGA3D_MS_QUALITY_MAX); pSurface->surfaceDesc.bufferByteStride = bufferByteStride; + pSurface->fGB = RT_BOOL(defineFlags & VMSVGA3D_SURFACE_DEFINE_F_GB); + /** @todo This 'switch' and the surfaceFlags tweaks should not be necessary. * The actual surface type will be figured out when the surface is actually used later. * The backends code must be reviewed for unnecessary dependencies on the surfaceFlags value. @@ -498,7 +500,7 @@ int vmsvga3dSurfaceDefine(PVGASTATECC pThisCC, uint32_t sid, SVGA3dSurfaceAllFla Assert(!VMSVGA3DSURFACE_HAS_HW_SURFACE(pSurface)); - if (fAllocMipLevels || pState->fVMSVGA2dGBO) + if (RT_BOOL(defineFlags & VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS) || pState->fVMSVGA2dGBO) { rc = vmsvga3dSurfaceAllocMipLevels(pSurface); AssertRCReturn(rc, rc); @@ -1073,7 +1075,7 @@ int vmsvga3dSurfaceBlitToScreen(PVGASTATE pThis, PVGASTATECC pThisCC, uint32_t i PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; if (pSvgaR3State->pFuncsMap) - return vmsvga3dScreenUpdateFromSurface(pThisCC, pScreen, destRect, src, srcRect, cRects, pRect); + return vmsvga3dScreenUpdateFromSurface(pThis, pThisCC, pScreen, destRect, src, srcRect, cRects, pRect); /* Screens which are associated with screen targets should be handled by vmsvga3dScreenUpdateFromSurface * because the code below updates the guest VRAM and screen targets have a separate memory buffer for the @@ -1152,7 +1154,7 @@ int vmsvga3dSurfaceBlitToScreen(PVGASTATE pThis, PVGASTATECC pThisCC, uint32_t i } -static int vmsvga3dScreenUpdate(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGASignedRect const &dstRect, +static int vmsvga3dScreenUpdate(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGASignedRect const &dstRect, SVGA3dSurfaceImageId const &srcImage, SVGASignedRect const &srcRect, uint32_t cDstClipRects, SVGASignedRect *paDstClipRect) { @@ -1196,6 +1198,9 @@ static int vmsvga3dScreenUpdate(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen if ( dstRect.right <= dstRect.left || dstRect.bottom <= dstRect.top) return VINF_SUCCESS; /* Empty dst rect. */ + + if (pScreen->offVRAM != VMSVGA_VRAM_OFFSET_SCREEN_TARGET) + ASSERT_GUEST_RETURN(pScreen->offVRAM < pThis->vram_size, VERR_INVALID_PARAMETER); /* paranoia, ensured elsewhere. */ RT_UNTRUSTED_VALIDATED_FENCE(); ASSERT_GUEST_RETURN( srcRect.right - srcRect.left == dstRect.right - dstRect.left @@ -1257,12 +1262,19 @@ static int vmsvga3dScreenUpdate(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen uint8_t const *pu8Src = (uint8_t *)srcMap.pvData; - uint32_t const cbDst = pScreen->cHeight * pScreen->cbPitch; + uint32_t cbDst = pScreen->cHeight * pScreen->cbPitch; uint8_t *pu8Dst; if (pScreen->offVRAM == VMSVGA_VRAM_OFFSET_SCREEN_TARGET) + { + cbDst = RT_MIN(cbDst, pScreen->pScreenOutputTarget->desc.cbOutputBuffer); pu8Dst = (uint8_t *)pScreen->pScreenOutputTarget->desc.pvOutputBuffer; + } else + { + uint32_t const cbVRAM = pThis->vram_size - pScreen->offVRAM; + cbDst = RT_MIN(cbDst, cbVRAM); /* paranoia: pScreen->cHeight and pScreen->cbPitch has been verified. */ pu8Dst = (uint8_t *)pThisCC->pbVRam + pScreen->offVRAM; + } SVGASignedRect dstClipRect; if (cDstClipRects == 0) @@ -1348,7 +1360,7 @@ static int vmsvga3dScreenUpdate(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen } -int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGA3dRect const &rect, +int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGA3dRect const &rect, SVGA3dSurfaceImageId const &srcImage) { PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; @@ -1374,11 +1386,11 @@ int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATECC pThisCC, VMSVGASCREENOBJECT r.right = rect.x + rect.w; r.bottom = rect.y + rect.h; - return vmsvga3dScreenUpdate(pThisCC, pScreen, r, srcImage, r, 0, NULL); + return vmsvga3dScreenUpdate(pThis, pThisCC, pScreen, r, srcImage, r, 0, NULL); } -int vmsvga3dScreenUpdateFromSurface(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGASignedRect const &dstRect, +int vmsvga3dScreenUpdateFromSurface(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen, SVGASignedRect const &dstRect, SVGA3dSurfaceImageId const &srcImage, SVGASignedRect const &srcRect, uint32_t cDstClipRects, SVGASignedRect *paDstClipRect) { @@ -1400,7 +1412,7 @@ int vmsvga3dScreenUpdateFromSurface(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pSc } } - return vmsvga3dScreenUpdate(pThisCC, pScreen, dstRect, srcImage, srcRect, cDstClipRects, paDstClipRect); + return vmsvga3dScreenUpdate(pThis, pThisCC, pScreen, dstRect, srcImage, srcRect, cDstClipRects, paDstClipRect); } @@ -1543,6 +1555,8 @@ int vmsvga3dSurfaceInvalidate(PVGASTATECC pThisCC, uint32_t sid, uint32_t face, PVMSVGA3DMIPMAPLEVEL pMipmapLevel = &pSurface->paMipmapLevels[i]; pMipmapLevel->fDirty = true; } + + vmsvga3dSurfaceFreeMipLevels(pSurface); } else { @@ -1894,6 +1908,16 @@ int vmsvga3dSurfaceMap(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, int rc = vmsvga3dSurfaceFromSid(pThisCC->svga.p3dState, pImage->sid, &pSurface); AssertRCReturn(rc, rc); + if (fMapFlags & VMSVGA3D_MAP_F_ENSURE_RESOURCE) + { + PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; + AssertReturn(pSvgaR3State->pFuncsMap, VERR_NOT_IMPLEMENTED); + + /* Ensure that the HW resource exists. */ + rc = pSvgaR3State->pFuncsMap->pfnEnsureResource(pThisCC, pImage->sid); + AssertRCReturn(rc, rc); + } + if (VMSVGA3DSURFACE_HAS_HW_SURFACE(pSurface)) { PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; @@ -1969,6 +1993,19 @@ int vmsvga3dSurfaceUnmap(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage } +int vmsvga3dEnsureResource(PVGASTATECC pThisCC, SVGA3dSurfaceId sid) +{ + PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; + AssertReturn(pSvgaR3State->pFuncsMap, VERR_NOT_IMPLEMENTED); + + /* Ensure that the HW resource exists. */ + int rc = pSvgaR3State->pFuncsMap->pfnEnsureResource(pThisCC, sid); + AssertRCReturn(rc, rc); + + return VINF_SUCCESS; +} + + int vmsvga3dCalcSurfaceMipmapAndFace(PVGASTATECC pThisCC, uint32_t sid, uint32_t iSubresource, uint32_t *piMipmap, uint32_t *piFace) { PVMSVGA3DSURFACE pSurface; @@ -2104,14 +2141,17 @@ int vmsvga3dGetBoxDimensions(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pI /* - * Whether a legacy 3D backend is used. - * The new DX context can be built together with the legacy D3D9 or OpenGL backend. + * Whether a legacy VGPU9 3D backend is used. + * The new DX backend can be built together with the legacy D3D9 or OpenGL backend. * The actual backend is selected at the VM startup. */ bool vmsvga3dIsLegacyBackend(PVGASTATECC pThisCC) { PVMSVGAR3STATE const pSvgaR3State = pThisCC->svga.pSvgaR3State; - return pSvgaR3State->pFuncsDX == NULL; + /* 'pFuncsGBO' is the essential, base interface of the VGPU10 capable backend. + * 'pFuncsGBO' is created and used for both 3D enabled and 'fVMSVGA2dGBO' modes. + */ + return pSvgaR3State->pFuncsGBO == NULL; } diff --git a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h index 214e91e5ac82..7a23716765af 100644 --- a/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h +++ b/src/VBox/Devices/Graphics/DevVGA-SVGA3d.h @@ -1,4 +1,4 @@ -/* $Id: DevVGA-SVGA3d.h 114460 2026-06-19 14:01:13Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA-SVGA3d.h 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevVMWare - VMWare SVGA device - 3D part. */ @@ -89,6 +89,7 @@ typedef enum VMSVGA3D_SURFACE_MAP #define VMSVGA3D_MAP_F_DYNAMIC_INTERMEDIATE 0x00000001 #define VMSVGA3D_MAP_F_STAGING_INTERMEDIATE 0x00000002 #define VMSVGA3D_MAP_F_EXACT_REGION 0x00000004 +#define VMSVGA3D_MAP_F_ENSURE_RESOURCE 0x00000008 typedef struct VMSVGA3D_MAPPED_SURFACE { @@ -124,9 +125,12 @@ int vmsvga3dSaveExec(PPDMDEVINS pDevIns, PVGASTATECC pThisCC, PSSMHANDLE pSSM); void vmsvga3dUpdateHostScreenViewport(PVGASTATECC pThisCC, uint32_t idScreen, VMSVGAVIEWPORT const *pOldViewport); int vmsvga3dQueryCaps(PVGASTATECC pThisCC, SVGA3dDevCapIndex idx3dCaps, uint32_t *pu32Val); +#define VMSVGA3D_SURFACE_DEFINE_F_NONE 0x0 +#define VMSVGA3D_SURFACE_DEFINE_F_ALLOC_MIP_LEVELS 0x1 +#define VMSVGA3D_SURFACE_DEFINE_F_GB 0x2 int vmsvga3dSurfaceDefine(PVGASTATECC pThisCC, uint32_t sid, SVGA3dSurfaceAllFlags surfaceFlags, SVGA3dSurfaceFormat format, uint32_t multisampleCount, SVGA3dMSPattern multisamplePattern, SVGA3dMSQualityLevel qualityLevel, SVGA3dTextureFilter autogenFilter, - uint32_t numMipLevels, SVGA3dSize const *pMipLevel0Size, uint32_t arraySize, uint32_t bufferByteStride, bool fAllocMipLevels); + uint32_t numMipLevels, SVGA3dSize const *pMipLevel0Size, uint32_t arraySize, uint32_t bufferByteStride, uint32_t defineFlags); int vmsvga3dSurfaceDestroy(PVGASTATECC pThisCC, uint32_t sid); int vmsvga3dSurfaceCopy(PVGASTATECC pThisCC, SVGA3dSurfaceImageId dest, SVGA3dSurfaceImageId src, uint32_t cCopyBoxes, SVGA3dCopyBox *pBox); @@ -144,10 +148,10 @@ int vmsvga3dChangeMode(PVGASTATECC pThisCC); int vmsvga3dDefineScreen(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen); int vmsvga3dDestroyScreen(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pScreen); -int vmsvga3dScreenUpdateFromSurface(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pDstScreen, SVGASignedRect const &dstRect, +int vmsvga3dScreenUpdateFromSurface(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pDstScreen, SVGASignedRect const &dstRect, SVGA3dSurfaceImageId const &srcImage, SVGASignedRect const &srcRect, uint32_t cDstClipRects, SVGASignedRect *paDstClipRect); -int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pDstScreen, SVGA3dRect const &rect, +int vmsvga3dScreenUpdateFromScreenTarget(PVGASTATE pThis, PVGASTATECC pThisCC, VMSVGASCREENOBJECT *pDstScreen, SVGA3dRect const &rect, SVGA3dSurfaceImageId const &srcImage); void vmsvga3dProcessPendingTasks(PVGASTATE pThis, PVGASTATECC pThisCC); @@ -185,6 +189,7 @@ int vmsvga3dSurfaceInvalidate(PVGASTATECC pThisCC, uint32_t sid, uint32_t face, int vmsvga3dSurfaceMap(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, SVGA3dBox const *pBox, VMSVGA3D_SURFACE_MAP enmMapType, uint32_t fMapFlags, VMSVGA3D_MAPPED_SURFACE *pMap); int vmsvga3dSurfaceUnmap(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, VMSVGA3D_MAPPED_SURFACE *pMap, bool fWritten); +int vmsvga3dEnsureResource(PVGASTATECC pThisCC, SVGA3dSurfaceId sid); uint32_t vmsvga3dCalcSubresourceOffset(PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage); @@ -478,6 +483,7 @@ typedef struct { DECLCALLBACKMEMBER(int, pfnSurfaceMap, (PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, SVGA3dBox const *pBox, VMSVGA3D_SURFACE_MAP enmMapType, uint32_t fMapFlags, VMSVGA3D_MAPPED_SURFACE *pMap)); DECLCALLBACKMEMBER(int, pfnSurfaceUnmap, (PVGASTATECC pThisCC, SVGA3dSurfaceImageId const *pImage, VMSVGA3D_MAPPED_SURFACE *pMap, bool fWritten)); + DECLCALLBACKMEMBER(int, pfnEnsureResource, (PVGASTATECC pThisCC, uint32_t sid)); } VMSVGA3DBACKENDFUNCSMAP; typedef struct VMSVGA3DSHADER *PVMSVGA3DSHADER; diff --git a/src/VBox/Devices/Graphics/DevVGA.cpp b/src/VBox/Devices/Graphics/DevVGA.cpp index 913c84d4bec1..36fdfc8ef73a 100644 --- a/src/VBox/Devices/Graphics/DevVGA.cpp +++ b/src/VBox/Devices/Graphics/DevVGA.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA.cpp 114719 2026-07-16 15:46:04Z aleksey.ilyushin@oracle.com $ */ +/* $Id: DevVGA.cpp 114932 2026-08-10 12:38:24Z vitali.pelenjow@oracle.com $ */ /** @file * DevVGA - VBox VGA/VESA device. */ @@ -2868,6 +2868,13 @@ static int vgaR3DrawBlank(PVGASTATE pThis, PVGASTATER3 pThisCC, bool full_update d = pDrv->pbData; if (pThis->fRenderVRAM) { + /* If VRAM rendering is enabled, then check that the provided target memory buffer has a correct size. */ + if ( pDrv->cx != pThis->last_scr_width + || pDrv->cy != pThis->last_scr_height) + { + return VINF_SUCCESS; + } + for(i = 0; i < (int)pThis->last_scr_height; i++) { memset(d, val, w); d += cbScanline; @@ -5091,10 +5098,13 @@ static DECLCALLBACK(int) vgaR3PortUpdateDisplay(PPDMIDISPLAYPORT pInterface) # ifndef VBOX_WITH_HGSMI /* This should be called only in non VBVA mode. */ # else - if (VBVAUpdateDisplay(pThis, pThisCC) == VINF_SUCCESS) + if (!pThis->fVMSVGAEnabled) { - PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); - return VINF_SUCCESS; + if (VBVAUpdateDisplay(pThis, pThisCC) == VINF_SUCCESS) + { + PDMDevHlpCritSectLeave(pDevIns, &pThis->CritSect); + return VINF_SUCCESS; + } } # endif /* VBOX_WITH_HGSMI */ diff --git a/src/VBox/Devices/Graphics/DevVGASavedState.h b/src/VBox/Devices/Graphics/DevVGASavedState.h index 64339da9d64b..cf4e1d74d75a 100644 --- a/src/VBox/Devices/Graphics/DevVGASavedState.h +++ b/src/VBox/Devices/Graphics/DevVGASavedState.h @@ -1,4 +1,4 @@ -/* $Id: DevVGASavedState.h 112846 2026-02-05 17:22:12Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGASavedState.h 114945 2026-08-10 13:06:53Z vitali.pelenjow@oracle.com $ */ /** @file * DevVGA - Saved state versions. * @@ -56,7 +56,8 @@ } \ } while (0) -#define VGA_SAVEDSTATE_VERSION 33 +#define VGA_SAVEDSTATE_VERSION 34 +#define VGA_SAVEDSTATE_VERSION_VMSVGA_GB_SURF 34 /* Manage host shadow buffers for GB surfaces. @bugref{10934} */ #define VGA_SAVEDSTATE_VERSION_VMSVGA_HOST_CMDS 33 /* Host commands. See @bugref{11042}. */ #define VGA_SAVEDSTATE_VERSION_VMSVGA_CURSOR_MOB 32 /* Cursor MOB support. See @bugref{11042}. */ #define VGA_SAVEDSTATE_VERSION_VMSVGA_COTABLES 31 /* COTable content. See @bugref{11021}. */ diff --git a/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp b/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp index e64260ae89cd..d62d9ce2f800 100644 --- a/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp +++ b/src/VBox/Devices/Graphics/DevVGA_VBVA.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVGA_VBVA.cpp 113822 2026-04-12 21:10:19Z vitali.pelenjow@oracle.com $ */ +/* $Id: DevVGA_VBVA.cpp 114932 2026-08-10 12:38:24Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox Video Acceleration (VBVA). */ @@ -138,9 +138,8 @@ static bool vbvaFetchBytes(VBVADATA *pVBVAData, uint8_t *pbDst, uint32_t cb) const uint8_t RT_UNTRUSTED_VOLATILE_GUEST *pbSrc = &pVBVAData->guest.pu8Data[pVBVAData->off32Data]; const uint32_t u32BytesTillBoundary = pVBVAData->cbData - pVBVAData->off32Data; - const int32_t i32Diff = cb - u32BytesTillBoundary; - if (i32Diff <= 0) + if (cb <= u32BytesTillBoundary) { /* Chunk will not cross buffer boundary. */ RT_BCOPY_VOLATILE(pbDst, pbSrc, cb); @@ -149,7 +148,7 @@ static bool vbvaFetchBytes(VBVADATA *pVBVAData, uint8_t *pbDst, uint32_t cb) { /* Chunk crosses buffer boundary. */ RT_BCOPY_VOLATILE(pbDst, pbSrc, u32BytesTillBoundary); - RT_BCOPY_VOLATILE(pbDst + u32BytesTillBoundary, &pVBVAData->guest.pu8Data[0], i32Diff); + RT_BCOPY_VOLATILE(pbDst + u32BytesTillBoundary, &pVBVAData->guest.pu8Data[0], cb - u32BytesTillBoundary); } /* Advance data offset and sync with guest. */ @@ -192,12 +191,18 @@ static bool vbvaPartialRead(uint32_t cbRecord, VBVADATA *pVBVAData) Log(("vbvaPartialRead: failed to (re)alocate memory for partial record!!! cbRecord 0x%08X\n", cbRecord)); + RTMemFree(pPartialRecord->pu8); + pPartialRecord->pu8 = NULL; + pPartialRecord->cb = 0; return false; } /* Fetch data from the ring buffer. */ if (!vbvaFetchBytes(pVBVAData, pu8New + pPartialRecord->cb, cbChunk)) { + RTMemFree(pu8New); + pPartialRecord->pu8 = NULL; + pPartialRecord->cb = 0; return false; } @@ -208,8 +213,7 @@ static bool vbvaPartialRead(uint32_t cbRecord, VBVADATA *pVBVAData) } /** - * For contiguous chunks just return the address in the buffer. For crossing - * boundary - allocate a buffer from heap. + * Always allocate a bounce buffer from heap. */ static bool vbvaFetchCmd(VBVADATA *pVBVAData, VBVACMDHDR RT_UNTRUSTED_VOLATILE_GUEST **ppHdr, uint32_t *pcbCmd) { @@ -310,36 +314,18 @@ static bool vbvaFetchCmd(VBVADATA *pVBVAData, VBVACMDHDR RT_UNTRUSTED_VOLATILE_G if (cbRecord) { - /* The size of largest contiguous chunk in the ring buffer. */ - uint32_t u32BytesTillBoundary = pVBVAData->cbData - pVBVAData->off32Data; - - /* The pointer to data in the ring buffer. */ - uint8_t RT_UNTRUSTED_VOLATILE_GUEST *pbSrc = &pVBVAData->guest.pu8Data[pVBVAData->off32Data]; - - /* Fetch or point the data. */ - if (u32BytesTillBoundary >= cbRecord) + /* Make a copy of the data. */ + uint8_t *pbDst = (uint8_t *)RTMemAlloc(cbRecord); + if (!pbDst) { - /* The command does not cross buffer boundary. Return address in the buffer. */ - *ppHdr = (VBVACMDHDR RT_UNTRUSTED_VOLATILE_GUEST *)pbSrc; - - /* The data offset will be updated in vbvaReleaseCmd. */ + LogFlowFunc (("could not allocate %d bytes from heap!!!\n", cbRecord)); + return false; } - else - { - /* The command crosses buffer boundary. Rare case, so not optimized. */ - uint8_t *pbDst = (uint8_t *)RTMemAlloc(cbRecord); - if (!pbDst) - { - LogFlowFunc (("could not allocate %d bytes from heap!!!\n", cbRecord)); - return false; - } - vbvaFetchBytes(pVBVAData, pbDst, cbRecord); + if (!vbvaFetchBytes(pVBVAData, pbDst, cbRecord)) + return false; - *ppHdr = (VBVACMDHDR *)pbDst; - - LOGVBVABUFFER(("Allocated from heap %p\n", pbDst)); - } + *ppHdr = (VBVACMDHDR *)pbDst; } *pcbCmd = cbRecord; @@ -356,38 +342,11 @@ static bool vbvaFetchCmd(VBVADATA *pVBVAData, VBVACMDHDR RT_UNTRUSTED_VOLATILE_G static void vbvaReleaseCmd(VBVADATA *pVBVAData, VBVACMDHDR RT_UNTRUSTED_VOLATILE_GUEST *pHdr, uint32_t cbCmd) { - VBVAPARTIALRECORD *pPartialRecord = &pVBVAData->partialRecord; - const uint8_t RT_UNTRUSTED_VOLATILE_GUEST *pbRingBuffer = pVBVAData->guest.pu8Data; - - if ( (uintptr_t)pHdr >= (uintptr_t)pbRingBuffer - && (uintptr_t)pHdr < (uintptr_t)&pbRingBuffer[pVBVAData->cbData]) - { - /* The pointer is inside ring buffer. Must be continuous chunk. */ - Assert(pVBVAData->cbData - (uint32_t)((uint8_t *)pHdr - pbRingBuffer) >= cbCmd); + RT_NOREF(pVBVAData, cbCmd); - /* Advance data offset and sync with guest. */ - pVBVAData->off32Data = (pVBVAData->off32Data + cbCmd) % pVBVAData->cbData; - pVBVAData->guest.pVBVA->off32Data = pVBVAData->off32Data; - - Assert(!pPartialRecord->pu8 && pPartialRecord->cb == 0); - } - else - { - /* The pointer is outside. It is then an allocated copy. */ - LOGVBVABUFFER(("Free heap %p\n", pHdr)); + LOGVBVABUFFER(("Free heap %p\n", pHdr)); - if ((uint8_t *)pHdr == pPartialRecord->pu8) - { - pPartialRecord->pu8 = NULL; - pPartialRecord->cb = 0; - } - else - { - Assert(!pPartialRecord->pu8 && pPartialRecord->cb == 0); - } - - RTMemFree((void *)pHdr); - } + RTMemFree((void *)pHdr); } static int vbvaFlushProcess(PVGASTATECC pThisCC, VBVADATA *pVBVAData, unsigned uScreenId) @@ -428,9 +387,10 @@ static int vbvaFlushProcess(PVGASTATECC pThisCC, VBVADATA *pVBVAData, unsigned u if (cbCmd < sizeof(VBVACMDHDR)) { - LogFunc(("short command. off32Data = %d, off32Free = %d, cbCmd %d!!!\n", + LogFunc(("command length: off32Data = %d, off32Free = %d, cbCmd %d!!!\n", pVBVAData->off32Data, pVBVAData->guest.pVBVA->off32Free, cbCmd)); + vbvaReleaseCmd(pVBVAData, pHdr, cbCmd); return VERR_NOT_SUPPORTED; } @@ -1591,6 +1551,22 @@ static DECLCALLBACK(int) vbvaChannelHandler(void *pvHandler, uint16_t u16Channel PHGSMIINSTANCE pIns = pThisCC->pHGSMI; VBVACONTEXT *pCtx = (VBVACONTEXT *)HGSMIContext(pIns); + if (pThis->fVMSVGAEnabled) + { + if ( u16ChannelInfo == VBVA_QUERY_CONF32 + || u16ChannelInfo == VBVA_INFO_HEAP + || u16ChannelInfo == VBVA_MOUSE_POINTER_SHAPE + || u16ChannelInfo == VBVA_INFO_CAPS + || u16ChannelInfo == VBVA_CURSOR_POSITION + ) + { /* allowed */ } + else + { + /* NOPs for VMSVGA mode. */ + return VINF_SUCCESS; + } + } + switch (u16ChannelInfo) { #ifdef VBOX_WITH_VDMA diff --git a/src/VBox/Devices/Makefile.kmk b/src/VBox/Devices/Makefile.kmk index 52494f6fa9d1..6ba6a1a47707 100644 --- a/src/VBox/Devices/Makefile.kmk +++ b/src/VBox/Devices/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114503 2026-06-23 15:44:22Z aleksey.ilyushin@oracle.com $ +# $Id: Makefile.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ ## @file # Top-level sub-makefile for the devices, drivers and services. # @@ -175,7 +175,8 @@ if !defined(VBOX_ONLY_EXTPACKS) && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SU VBOX_WITH_DMI_OEMSTRINGS \ $(if $(VBOX_WITH_IOMMU_AMD),VBOX_WITH_IOMMU_AMD,) \ $(if $(VBOX_WITH_IOMMU_INTEL),VBOX_WITH_IOMMU_INTEL,) \ - $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) ifeq ($(KBUILD_TARGET_ARCH),x86) VBoxDD_DEFS.darwin = VBOX_WITH_2X_4GB_ADDR_SPACE endif @@ -226,6 +227,7 @@ if !defined(VBOX_ONLY_EXTPACKS) && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SU Input/DrvKeyboardQueue.cpp \ Input/DrvMouseQueue.cpp \ Network/DrvIntNet.cpp \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),../NetworkServices/NetLib/IntNetIf.cpp,) \ Network/DrvDedicatedNic.cpp \ PC/DrvACPI.cpp \ PC/DrvAcpiCpu.cpp \ @@ -512,7 +514,7 @@ if !defined(VBOX_ONLY_EXTPACKS) && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SU if defined(VBOX_WITH_USB_VIDEO_IMPL) VBoxDD_DEFS += VBOX_WITH_USB_VIDEO_IMPL VBoxDD_INCS += \ - $(VBOX_JPEG_INCS) + $(SDK_VBoxLibJpeg_INCS) VBoxDD_SOURCES += \ Video/UsbWebcam.cpp \ Video/UsbWebcamDesc.cpp \ @@ -527,10 +529,7 @@ if !defined(VBOX_ONLY_EXTPACKS) && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SU Video/HostWebcam-v4l2.cpp VBoxDD_SOURCES.win += \ Video/HostWebcam-win.cpp - VBoxDD_LIBS += \ - $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) + VBoxDD_LIBS += $(SDK_VBoxLibJpeg_LIBS) VBoxDD_LIBS.win += \ strmiids.lib VBoxDD_LDFLAGS.darwin += \ diff --git a/src/VBox/Devices/Network/DrvIntNet.cpp b/src/VBox/Devices/Network/DrvIntNet.cpp index 4db38f7080e9..1b41517aa978 100644 --- a/src/VBox/Devices/Network/DrvIntNet.cpp +++ b/src/VBox/Devices/Network/DrvIntNet.cpp @@ -1,4 +1,4 @@ -/* $Id: DrvIntNet.cpp 114205 2026-05-29 13:56:57Z andreas.loeffler@oracle.com $ */ +/* $Id: DrvIntNet.cpp 114879 2026-08-06 22:04:52Z andreas.loeffler@oracle.com $ */ /** @file * DrvIntNet - Internal network transport driver. */ @@ -30,9 +30,6 @@ * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_DRV_INTNET -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) -# include /* This needs to be here because it drags PVM in and cdefs.h needs to undefine it... */ -#endif #include #include @@ -62,6 +59,9 @@ #endif #include "VBoxDD.h" +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 +# include "../../NetworkServices/NetLib/IntNetIf.h" +#endif /********************************************************************************************************************************* @@ -199,13 +199,11 @@ typedef struct DRVINTNET /** The nano ts of the last receive. */ uint64_t u64LastReceiveTS; #endif -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - /** XPC connection handle to the R3 internal network switch service. */ - xpc_connection_t hXpcCon; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + /** Context for the R3 internal network switch service. */ + INTNETIFCTX hIfCtx; /** Flag whether the R3 internal network service is being used. */ bool fIntNetR3Svc; - /** Size of the communication buffer in bytes. */ - size_t cbBuf; #endif } DRVINTNET; AssertCompileMemberAlignment(DRVINTNET, XmitLock, 8); @@ -241,61 +239,43 @@ typedef DRVINTNETFLAG const *PCDRVINTNETFLAG; */ static int drvR3IntNetCallSvc(PDRVINTNET pThis, uint32_t uOperation, void *pvArg, unsigned cbArg) { -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 if (pThis->fIntNetR3Svc) { - xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); - xpc_dictionary_set_uint64(hObj, "req-id", uOperation); - xpc_dictionary_set_data(hObj, "req", pvArg, cbArg); - xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj); - xpc_release(hObj); - - uint64_t u64Rc = xpc_dictionary_get_uint64(hObjReply, "rc"); - if (INTNET_R3_SVC_IS_VALID_RC(u64Rc)) + switch (uOperation) { - size_t cbReply = 0; - const void *pvData = xpc_dictionary_get_data(hObjReply, "reply", &cbReply); - AssertRelease(cbReply == cbArg); - memcpy(pvArg, pvData, cbArg); - xpc_release(hObjReply); + case VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS: + { + AssertReturn(cbArg == sizeof(INTNETIFSETMACADDRESSREQ), VERR_INVALID_PARAMETER); + PINTNETIFSETMACADDRESSREQ pReq = (PINTNETIFSETMACADDRESSREQ)pvArg; + return IntNetR3IfSetMacAddress(pThis->hIfCtx, &pReq->Mac); + } - return INTNET_R3_SVC_GET_RC(u64Rc); - } + case VMMR0_DO_INTNET_IF_SET_ACTIVE: + { + AssertReturn(cbArg == sizeof(INTNETIFSETACTIVEREQ), VERR_INVALID_PARAMETER); + PINTNETIFSETACTIVEREQ pReq = (PINTNETIFSETACTIVEREQ)pvArg; + return IntNetR3IfSetActive(pThis->hIfCtx, pReq->fActive); + } - xpc_release(hObjReply); - return VERR_INVALID_STATE; - } - else -#endif - return PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, uOperation, pvArg, cbArg); -} + case VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE: + { + AssertReturn(cbArg == sizeof(INTNETIFSETPROMISCUOUSMODEREQ), VERR_INVALID_PARAMETER); + PINTNETIFSETPROMISCUOUSMODEREQ pReq = (PINTNETIFSETPROMISCUOUSMODEREQ)pvArg; + return IntNetR3IfSetPromiscuous(pThis->hIfCtx, pReq->fPromiscuous); + } + case VMMR0_DO_INTNET_IF_SEND: + AssertReturn(cbArg == sizeof(INTNETIFSENDREQ), VERR_INVALID_PARAMETER); + return IntNetR3IfSend(pThis->hIfCtx); -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) -/** - * Calls the internal networking switch service living in either R0 or in another R3 process. - * - * @returns VBox status code. - * @param pThis The internal network driver instance data. - * @param uOperation The operation to execute. - * @param pvArg Pointer to the argument data. - * @param cbArg Size of the argument data in bytes. - */ -static int drvR3IntNetCallSvcAsync(PDRVINTNET pThis, uint32_t uOperation, void *pvArg, unsigned cbArg) -{ - if (pThis->fIntNetR3Svc) - { - xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); - xpc_dictionary_set_uint64(hObj, "req-id", uOperation); - xpc_dictionary_set_data(hObj, "req", pvArg, cbArg); - xpc_connection_send_message(pThis->hXpcCon, hObj); - xpc_release(hObj); - return VINF_SUCCESS; + default: + AssertFailedReturn(VERR_NOT_SUPPORTED); + } } - else - return PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, uOperation, pvArg, cbArg); -} #endif + return PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, uOperation, pvArg, cbArg); +} /** @@ -306,7 +286,10 @@ static int drvR3IntNetCallSvcAsync(PDRVINTNET pThis, uint32_t uOperation, void * */ static int drvR3IntNetMapBufferPointers(PDRVINTNET pThis) { - int rc = VINF_SUCCESS; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (pThis->fIntNetR3Svc) + return IntNetR3IfQueryBufferPtr(pThis->hIfCtx, &pThis->pBufR3); +#endif INTNETIFGETBUFFERPTRSREQ GetBufferPtrsReq; GetBufferPtrsReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; @@ -316,46 +299,15 @@ static int drvR3IntNetMapBufferPointers(PDRVINTNET pThis) GetBufferPtrsReq.pRing3Buf = NULL; GetBufferPtrsReq.pRing0Buf = NIL_RTR0PTR; -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - if (pThis->fIntNetR3Svc) - { - xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); - xpc_dictionary_set_uint64(hObj, "req-id", VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS); - xpc_dictionary_set_data(hObj, "req", &GetBufferPtrsReq, sizeof(GetBufferPtrsReq)); - xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj); - xpc_release(hObj); - - uint64_t u64Rc = xpc_dictionary_get_uint64(hObjReply, "rc"); - if (INTNET_R3_SVC_IS_VALID_RC(u64Rc)) - rc = INTNET_R3_SVC_GET_RC(u64Rc); - else - rc = VERR_INVALID_STATE; - - if (RT_SUCCESS(rc)) - { - /* Get the shared memory object. */ - xpc_object_t hObjShMem = xpc_dictionary_get_value(hObjReply, "buf-ptr"); - size_t cbMem = xpc_shmem_map(hObjShMem, (void **)&pThis->pBufR3); - if (!cbMem) - rc = VERR_NO_MEMORY; - else - pThis->cbBuf = cbMem; - } - - xpc_release(hObjReply); - } - else -#endif + int rc = PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, + &GetBufferPtrsReq, sizeof(GetBufferPtrsReq)); + if (RT_SUCCESS(rc)) { - rc = PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, &GetBufferPtrsReq, sizeof(GetBufferPtrsReq)); - if (RT_SUCCESS(rc)) - { - AssertRelease(RT_VALID_PTR(GetBufferPtrsReq.pRing3Buf)); - pThis->pBufR3 = GetBufferPtrsReq.pRing3Buf; + AssertRelease(RT_VALID_PTR(GetBufferPtrsReq.pRing3Buf)); + pThis->pBufR3 = GetBufferPtrsReq.pRing3Buf; #ifdef VBOX_WITH_DRVINTNET_IN_R0 - pThis->pBufR0 = GetBufferPtrsReq.pRing0Buf; + pThis->pBufR0 = GetBufferPtrsReq.pRing0Buf; #endif - } } return rc; @@ -1005,35 +957,30 @@ static int drvR3IntNetRecvRun(PDRVINTNET pThis) LogFlow(("drvR3IntNetRecvRun: returns VINF_SUCCESS (state changed - #1)\n")); return VERR_STATE_CHANGED; } - INTNETIFWAITREQ WaitReq; - WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; - WaitReq.Hdr.cbReq = sizeof(WaitReq); - WaitReq.pSession = NIL_RTR0PTR; - WaitReq.hIf = pThis->hIf; - WaitReq.cMillies = 30000; /* 30s - don't wait forever, timeout now and then. */ STAM_PROFILE_ADV_STOP(&pThis->StatReceive, a); -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 if (pThis->fIntNetR3Svc) { - /* Send an asynchronous message. */ - int rc = drvR3IntNetCallSvcAsync(pThis, VMMR0_DO_INTNET_IF_WAIT, &WaitReq, sizeof(WaitReq)); - if (RT_SUCCESS(rc)) + int rc = IntNetR3IfWait(pThis->hIfCtx, 30 * RT_MS_1SEC); + if ( RT_FAILURE(rc) + && rc != VERR_TIMEOUT + && rc != VERR_INTERRUPTED + && rc != VERR_SEM_DESTROYED) { - /* Wait on the receive semaphore. */ - rc = RTSemEventWait(pThis->hRecvEvt, 30 * RT_MS_1SEC); - if ( RT_FAILURE(rc) - && rc != VERR_TIMEOUT - && rc != VERR_INTERRUPTED) - { - LogFlow(("drvR3IntNetRecvRun: returns %Rrc\n", rc)); - return rc; - } + LogFlow(("drvR3IntNetRecvRun: returns %Rrc\n", rc)); + return rc; } } else #endif { + INTNETIFWAITREQ WaitReq; + WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + WaitReq.Hdr.cbReq = sizeof(WaitReq); + WaitReq.pSession = NIL_RTR0PTR; + WaitReq.hIf = pThis->hIf; + WaitReq.cMillies = 30000; /* 30s - don't wait forever, timeout now and then. */ int rc = PDMDrvHlpSUPCallVMMR0Ex(pThis->pDrvInsR3, VMMR0_DO_INTNET_IF_WAIT, &WaitReq, sizeof(WaitReq)); if ( RT_FAILURE(rc) && rc != VERR_TIMEOUT @@ -1367,8 +1314,13 @@ static DECLCALLBACK(void) drvR3IntNetDestruct(PPDMDRVINS pDrvIns) if (pThis->hIf != INTNET_HANDLE_INVALID) { -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - if (!pThis->fIntNetR3Svc) /* The R3 service case is handled b the hRecEvt event semaphore. */ +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (pThis->fIntNetR3Svc) + { + int rc = IntNetR3IfWaitAbort(pThis->hIfCtx); + AssertMsg(RT_SUCCESS(rc) || rc == VERR_SEM_DESTROYED, ("%Rrc\n", rc)); RT_NOREF_PV(rc); + } + else #endif { INTNETIFABORTWAITREQ AbortWaitReq; @@ -1434,6 +1386,18 @@ static DECLCALLBACK(void) drvR3IntNetDestruct(PPDMDRVINS pDrvIns) /* * Close the interface */ +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (pThis->fIntNetR3Svc) + { + pThis->hIf = INTNET_HANDLE_INVALID; + pThis->pBufR3 = NULL; + int rc = IntNetR3IfDestroy(pThis->hIfCtx); + AssertRC(rc); + pThis->hIfCtx = NULL; + pThis->fIntNetR3Svc = false; + } + else +#endif if (pThis->hIf != INTNET_HANDLE_INVALID) { INTNETIFCLOSEREQ CloseReq; @@ -1446,17 +1410,6 @@ static DECLCALLBACK(void) drvR3IntNetDestruct(PPDMDRVINS pDrvIns) AssertRC(rc); } -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - if (pThis->fIntNetR3Svc) - { - /* Unmap the shared buffer. */ - munmap(pThis->pBufR3, pThis->cbBuf); - xpc_connection_cancel(pThis->hXpcCon); - pThis->fIntNetR3Svc = false; - pThis->hXpcCon = NULL; - } -#endif - /* * Destroy the semaphores, S/G cache and xmit lock. */ @@ -1575,6 +1528,10 @@ static DECLCALLBACK(int) drvR3IntNetConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg pThis->hSgCache = NIL_RTMEMCACHE; pThis->enmRecvState = RECVSTATE_SUSPENDED; pThis->fActivateEarlyDeactivateLate = false; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + pThis->hIfCtx = NULL; + pThis->fIntNetR3Svc = false; +#endif /* IBase* */ pDrvIns->IBase.pfnQueryInterface = drvR3IntNetIBase_QueryInterface; #ifdef VBOX_WITH_DRVINTNET_IN_R0 @@ -1947,33 +1904,29 @@ static DECLCALLBACK(int) drvR3IntNetConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg /* * Create the interface. */ + OpenReq.hIf = INTNET_HANDLE_INVALID; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 if (SUPR3IsDriverless()) { -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - xpc_connection_t hXpcCon = xpc_connection_create(INTNET_R3_SVC_NAME, NULL); - xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) { - if (xpc_get_type(hObj) == XPC_TYPE_ERROR) - { - /** @todo Error handling - reconnecting. */ - } - else - { - /* Out of band messages should only come when there is something to receive. */ - RTSemEventSignal(pThis->hRecvEvt); - } - }); - - xpc_connection_resume(hXpcCon); - pThis->hXpcCon = hXpcCon; - pThis->fIntNetR3Svc = true; -#else - /** @todo This is probably not good enough for doing fuzz testing, but later... */ - return PDMDrvHlpVMSetError(pDrvIns, VERR_SUP_DRIVERLESS, RT_SRC_POS, - N_("Cannot attach to '%s' in driverless mode"), pThis->szNetwork); + rc = IntNetR3IfCreateExWithRecvEvent(&pThis->hIfCtx, OpenReq.szNetwork, OpenReq.enmTrunkType, + OpenReq.szTrunk, OpenReq.cbSend, OpenReq.cbRecv, OpenReq.fFlags, + pThis->hRecvEvt); + if (RT_SUCCESS(rc)) + { + pThis->fIntNetR3Svc = true; + rc = IntNetR3IfQueryHandle(pThis->hIfCtx, &pThis->hIf); + } + } + else +#endif + { +#ifndef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (SUPR3IsDriverless()) + return PDMDrvHlpVMSetError(pDrvIns, VERR_SUP_DRIVERLESS, RT_SRC_POS, + N_("Cannot attach to '%s' in driverless mode"), pThis->szNetwork); #endif + rc = drvR3IntNetCallSvc(pThis, VMMR0_DO_INTNET_OPEN, &OpenReq, sizeof(OpenReq)); } - OpenReq.hIf = INTNET_HANDLE_INVALID; - rc = drvR3IntNetCallSvc(pThis, VMMR0_DO_INTNET_OPEN, &OpenReq, sizeof(OpenReq)); if (RT_FAILURE(rc)) { if (fIgnoreConnectFailure) @@ -1995,8 +1948,14 @@ static DECLCALLBACK(int) drvR3IntNetConstruct(PPDMDRVINS pDrvIns, PCFGMNODE pCfg N_("Failed to open/create the internal network '%s'"), pThis->szNetwork); } - AssertRelease(OpenReq.hIf != INTNET_HANDLE_INVALID); - pThis->hIf = OpenReq.hIf; +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + if (!pThis->fIntNetR3Svc) +#endif + { + AssertRelease(OpenReq.hIf != INTNET_HANDLE_INVALID); + pThis->hIf = OpenReq.hIf; + } + AssertRelease(pThis->hIf != INTNET_HANDLE_INVALID); Log(("IntNet%d: hIf=%RX32 '%s'\n", pDrvIns->iInstance, pThis->hIf, pThis->szNetwork)); /* @@ -2138,4 +2097,3 @@ const PDMDRVREG g_DrvIntNet = }; #endif /* IN_RING3 */ - diff --git a/src/VBox/Devices/Network/UsbNet.cpp b/src/VBox/Devices/Network/UsbNet.cpp index 45bf2e91e217..172b56ead345 100644 --- a/src/VBox/Devices/Network/UsbNet.cpp +++ b/src/VBox/Devices/Network/UsbNet.cpp @@ -1,4 +1,4 @@ -/* $Id: UsbNet.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: UsbNet.cpp 114919 2026-08-10 12:09:50Z alexander.eichner@oracle.com $ */ /** @file * UsbNet - USB NCM Ethernet Device Emulation. */ @@ -2081,6 +2081,7 @@ static DECLCALLBACK(int) usbNetUsbSetInterface(PPDMUSBINS pUsbIns, uint8_t bInte PUSBNET pThis = PDMINS_2_DATA(pUsbIns, PUSBNET); LogFlowFunc(("/#%u/ bInterfaceNumber=%u bAlternateSetting=%u\n", pUsbIns->iInstance, bInterfaceNumber, bAlternateSetting)); Assert(bAlternateSetting == 0 || bAlternateSetting == 1); + RTCritSectEnter(&pThis->CritSect); if (pThis->bAlternateSetting != bAlternateSetting) { if (bAlternateSetting == 0) @@ -2097,6 +2098,7 @@ static DECLCALLBACK(int) usbNetUsbSetInterface(PPDMUSBINS pUsbIns, uint8_t bInte } pThis->bAlternateSetting = bAlternateSetting; } + RTCritSectLeave(&pThis->CritSect); return VINF_SUCCESS; } @@ -2224,8 +2226,10 @@ static DECLCALLBACK(void) usbNetVMReset(PPDMUSBINS pUsbIns) PUSBNET pThis = PDMINS_2_DATA(pUsbIns, PUSBNET); LogFlowFunc(("/#%u/\n", pUsbIns->iInstance)); + RTCritSectEnter(&pThis->CritSect); int rc = usbNetResetWorker(pThis, NULL, false /*fSetConfig*/); AssertRC(rc); + RTCritSectLeave(&pThis->CritSect); } diff --git a/src/VBox/Devices/Network/testcase/tstVBoxLibssh.cpp b/src/VBox/Devices/Network/testcase/tstVBoxLibssh.cpp index 539d23213352..ef721771f248 100644 --- a/src/VBox/Devices/Network/testcase/tstVBoxLibssh.cpp +++ b/src/VBox/Devices/Network/testcase/tstVBoxLibssh.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVBoxLibssh.cpp 114489 2026-06-22 16:31:04Z aleksey.ilyushin@oracle.com $ */ +/* $Id: tstVBoxLibssh.cpp 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ */ /** @file * tstVBoxLibssh - Testcase for the libssh. Requires sshd with keys configured on some host. */ @@ -73,6 +73,7 @@ static int runSessionAndExec(void) do { + RTTestIPrintf(RTTESTLVL_ALWAYS, "Testing with libssh-" SSH_STRINGIFY(LIBSSH_VERSION) "\n"); RTTestIPrintf(RTTESTLVL_ALWAYS, "Connecting to host=%s user=%s key=%s\n", g_szHost, g_szUser, g_szKeyFile); diff --git a/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp index a74ab92d5fa4..d582e6e969d5 100644 --- a/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp +++ b/src/VBox/Devices/Storage/DevLsiLogicSCSI.cpp @@ -1,4 +1,4 @@ -/* $Id: DevLsiLogicSCSI.cpp 114731 2026-07-20 09:16:25Z michal.necasek@oracle.com $ */ +/* $Id: DevLsiLogicSCSI.cpp 114914 2026-08-10 12:06:34Z alexander.eichner@oracle.com $ */ /** @file * DevLsiLogicSCSI - LsiLogic LSI53c1030 SCSI controller. */ @@ -1195,7 +1195,7 @@ static int lsilogicR3ProcessMessageRequest(PPDMDEVINS pDevIns, PLSILOGICSCSI pTh if (pRegion) { uint32_t offImgHdr = (LSILOGIC_FWIMGHDR_LOAD_ADDRESS - pRegion->u32AddrStart); - if (pRegion->u32AddrEnd - offImgHdr + 1 >= sizeof(FwImageHdr)) /* End address is inclusive. */ + if (pRegion->u32AddrEnd - LSILOGIC_FWIMGHDR_LOAD_ADDRESS + 1 >= sizeof(FwImageHdr)) /* End address is inclusive. */ { PFwImageHdr pFwImgHdr = (PFwImageHdr)&pRegion->au32Data[offImgHdr / 4]; diff --git a/src/VBox/Devices/Storage/DevNVMe.cpp b/src/VBox/Devices/Storage/DevNVMe.cpp index 0f1f0cb2f478..b1446eb4ed76 100644 --- a/src/VBox/Devices/Storage/DevNVMe.cpp +++ b/src/VBox/Devices/Storage/DevNVMe.cpp @@ -1,4 +1,4 @@ -/* $Id: DevNVMe.cpp 114273 2026-06-09 06:54:50Z alexander.eichner@oracle.com $ */ +/* $Id: DevNVMe.cpp 114921 2026-08-10 12:13:36Z alexander.eichner@oracle.com $ */ /** @file * DevNVMe - Non Volatile Memory express (previous name: NVMHCI) */ @@ -4345,7 +4345,21 @@ static int nvmeR3CmdAdminProcessCqDelete(PPDMDEVINS pDevIns, PNVME pThis, PNVMEC uintptr_t idxIoQueueComp = pIoQueueComp - &pThis->aQueuesComp[0]; Assert(idxIoQueueComp < RT_ELEMENTS(pThis->aQueuesComp)); - int rc = RTSemFastMutexDestroy(pThisCC->aQueuesComp[idxIoQueueComp].hMtx); + + PNVMEQUEUECOMPR3 pNvmeQueueR3 = &pThisCC->aQueuesComp[idxIoQueueComp]; + RTSemFastMutexRequest(pNvmeQueueR3->hMtx); + /* Destroy all waiters. */ + PNVMECOMPQUEUEWAITER pWaiter; + PNVMECOMPQUEUEWAITER pWaiterNext; + RTListForEachSafe(&pNvmeQueueR3->LstCompletionsWaiting, pWaiter, pWaiterNext, NVMECOMPQUEUEWAITER, NdLstQueue) + { + RTListNodeRemove(&pWaiter->NdLstQueue); + RTMemFree(pWaiter); + } + Assert(RTListIsEmpty(&pNvmeQueueR3->LstCompletionsWaiting)); + RTSemFastMutexRelease(pNvmeQueueR3->hMtx); + + int rc = RTSemFastMutexDestroy(pNvmeQueueR3->hMtx); AssertRC(rc); pIoQueueComp->Hdr.u16Id = 0; @@ -5107,6 +5121,15 @@ static void nvmeR3IoReqComplete(PPDMDEVINS pDevIns, PNVME pThis, PNVMECC pThisCC uint32_t cActivities = ASMAtomicDecU32(&pThis->cActivities); ASMAtomicDecU32(&pQueueSubm->cReqsActive); + /* Deallocate queue if it was deferred previously. */ + if ( ASMAtomicReadU32(&pQueueSubm->cReqsActive) == 0 + && ASMAtomicUoReadU32((volatile uint32_t *)&pQueueSubm->Hdr.enmState) == NVMEQUEUESTATE_DEALLOCATING) + { + /* The original submission queue for the deallocation request must be the admin queue. */ + PNVMEQUEUESUBM pAdmQueueSubm = &pThis->aQueuesSubm[NVME_ADM_QUEUE_ID]; + nvmeR3QueueSubmDeallocateDeferred(pDevIns, pThis, pThisCC, pAdmQueueSubm, pQueueSubm); + } + if (RT_SUCCESS(rcReq)) rc = nvmeR3CmdCompleteWithSuccess(pDevIns, pThis, pThisCC, pQueueSubm, u16Cid, 0); else if ( rcReq == VERR_PDM_MEDIAEX_IOBUF_OVERFLOW @@ -6859,7 +6882,8 @@ static void nvmeR3SuspendOrPowerOff(PPDMDEVINS pDevIns) * 0 so skip the decrement to avoid any hangs. */ if ( ( pThis->enmState == NVMESTATE_READY - || pThis->enmState == NVMESTATE_PAUSED) + || pThis->enmState == NVMESTATE_PAUSED + || pThis->enmState == NVMESTATE_FAULT) && ASMAtomicReadU32(&pThis->cActivities) > 0) ASMAtomicDecU32(&pThis->cActivities); @@ -7023,7 +7047,8 @@ static DECLCALLBACK(void) nvmeR3Reset(PPDMDEVINS pDevIns) LogFlow(("nvmeR3Reset:\n")); if ( ( pThis->enmState == NVMESTATE_READY - || pThis->enmState == NVMESTATE_PAUSED)) + || pThis->enmState == NVMESTATE_PAUSED + || pThis->enmState == NVMESTATE_FAULT)) ASMAtomicDecU32(&pThis->cActivities); ASMAtomicWriteBool(&pThisCC->fSignalIdle, true); diff --git a/src/VBox/Devices/Storage/DevVirtioSCSI.cpp b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp index fed37ca74c58..24a1f83c34dc 100644 --- a/src/VBox/Devices/Storage/DevVirtioSCSI.cpp +++ b/src/VBox/Devices/Storage/DevVirtioSCSI.cpp @@ -1,4 +1,4 @@ -/* $Id: DevVirtioSCSI.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DevVirtioSCSI.cpp 114947 2026-08-10 13:26:53Z aleksey.ilyushin@oracle.com $ */ /** @file * VBox storage devices - Virtio SCSI Driver * @@ -1084,13 +1084,14 @@ static DECLCALLBACK(int) virtioScsiR3IoReqFinish(PPDMIMEDIAEXPORT pInterface, PD RTSgBufInit(&ReqSgBuf, aReqSegs, RT_ELEMENTS(aReqSegs)); size_t cbReqSgBuf = RTSgBufCalcTotalLength(&ReqSgBuf); - /** @todo r=bird: Returning here looks a little bogus... */ - AssertMsgReturn(cbReqSgBuf <= pReq->pVirtqBuf->cbPhysReturn, - ("Guest expected less req data (space needed: %zu, avail: %u)\n", - cbReqSgBuf, pReq->pVirtqBuf->cbPhysReturn), - VERR_BUFFER_OVERFLOW); - - virtioScsiR3VirtqUsedBufPutAndSync(pDevIns, &pThis->Virtio, pReq->uVirtqNbr, &ReqSgBuf, pReq->pVirtqBuf); + if (cbReqSgBuf <= pReq->pVirtqBuf->cbPhysReturn) + virtioScsiR3VirtqUsedBufPutAndSync(pDevIns, &pThis->Virtio, pReq->uVirtqNbr, &ReqSgBuf, pReq->pVirtqBuf); + else + { + AssertMsgFailed(("Guest expected less req data (space needed: %zu, avail: %u)\n", + cbReqSgBuf, pReq->pVirtqBuf->cbPhysReturn)); + rc = VERR_BUFFER_OVERFLOW; + } Log2(("-----------------------------------------------------------------------------------------\n")); } @@ -1110,7 +1111,7 @@ static DECLCALLBACK(int) virtioScsiR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterfac PVIRTIOSCSITARGET pTarget = RT_FROM_MEMBER(pInterface, VIRTIOSCSITARGET, IMediaExPort); PPDMDEVINS pDevIns = pTarget->pDevIns; PVIRTIOSCSIREQ pReq = (PVIRTIOSCSIREQ)pvIoReqAlloc; - RT_NOREF(hIoReq, cbCopy); + RT_NOREF(hIoReq); if (!pReq->cbDataIn) return VINF_SUCCESS; @@ -1120,8 +1121,7 @@ static DECLCALLBACK(int) virtioScsiR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterfac PVIRTIOSGBUF pSgPhysReturn = pReq->pVirtqBuf->pSgPhysReturn; virtioCoreGCPhysChainAdvance(pSgPhysReturn, offDst); - size_t cbCopied = 0; - size_t cbRemain = pReq->cbDataIn; + size_t cbRemain = RT_MIN(cbCopy, pReq->cbDataIn); /* Skip past the REQ_RESP_HDR_T and sense code if we're at the start of the buffer. */ if (!pSgPhysReturn->idxSeg && pSgPhysReturn->cbSegLeft == pSgPhysReturn->paSegs[0].cbSeg) @@ -1129,12 +1129,17 @@ static DECLCALLBACK(int) virtioScsiR3IoReqCopyFromBuf(PPDMIMEDIAEXPORT pInterfac while (cbRemain) { - cbCopied = RT_MIN(pSgBuf->cbSegLeft, pSgPhysReturn->cbSegLeft); - Assert(cbCopied > 0); - PDMDevHlpPCIPhysWriteUser(pDevIns, pSgPhysReturn->GCPhysCur, pSgBuf->pvSegCur, cbCopied); - RTSgBufAdvance(pSgBuf, cbCopied); - virtioCoreGCPhysChainAdvance(pSgPhysReturn, cbCopied); - cbRemain -= cbCopied; + size_t cbThisCopy = 0; + const void *pvSrc = RTSgBufGetCurrentSegment(pSgBuf, RT_MIN(cbRemain, pSgPhysReturn->cbSegLeft), &cbThisCopy); + + Assert(cbThisCopy); + if (!cbThisCopy) + break; + + PDMDevHlpPCIPhysWriteUser(pDevIns, pSgPhysReturn->GCPhysCur, pvSrc, cbThisCopy); + RTSgBufAdvance(pSgBuf, cbThisCopy); + virtioCoreGCPhysChainAdvance(pSgPhysReturn, cbThisCopy); + cbRemain -= cbThisCopy; } RT_UNTRUSTED_NONVOLATILE_COPY_FENCE(); /* needed? */ @@ -2106,7 +2111,8 @@ static DECLCALLBACK(int) virtioScsiR3LoadExec(PPDMDEVINS pDevIns, PSSMHANDLE pSS */ for (int uVirtqNbr = VIRTQ_REQ_BASE; uVirtqNbr < VIRTIOSCSI_VIRTQ_CNT; uVirtqNbr++) { - if (pThis->afVirtqAttached[uVirtqNbr]) + if ( pThis->afVirtqAttached[uVirtqNbr] + && virtioCoreIsVirtqEnabled(&pThis->Virtio, uVirtqNbr)) { LogFunc(("Waking %s worker.\n", VIRTQNAME(uVirtqNbr))); int rc2 = PDMDevHlpSUPSemEventSignal(pDevIns, pThis->aWorkers[uVirtqNbr].hEvtProcess); diff --git a/src/VBox/Devices/Storage/DrvSCSI.cpp b/src/VBox/Devices/Storage/DrvSCSI.cpp index 3bb53495436e..f28c4eea305e 100644 --- a/src/VBox/Devices/Storage/DrvSCSI.cpp +++ b/src/VBox/Devices/Storage/DrvSCSI.cpp @@ -1,4 +1,4 @@ -/* $Id: DrvSCSI.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DrvSCSI.cpp 114955 2026-08-10 13:52:40Z aleksey.ilyushin@oracle.com $ */ /** @file * VBox storage drivers: Generic SCSI command parser and execution driver */ @@ -146,6 +146,8 @@ typedef struct DRVSCSI /** Indicates whether PDMDrvHlpAsyncNotificationCompleted should be called by * any of the dummy functions. */ bool volatile fDummySignal; + /** Flag whether an eject operation is pending. */ + bool volatile fEjectPending; /** Current I/O depth. */ volatile uint32_t StatIoDepth; /** Errors printed in the release log. */ @@ -317,28 +319,36 @@ static DECLCALLBACK(int) drvscsiEject(VSCSILUN hVScsiLun, void *pvScsiLunUser) RT_NOREF(hVScsiLun); PDRVSCSI pThis = (PDRVSCSI)pvScsiLunUser; int rc = VINF_SUCCESS; - RTSEMEVENT hSemEvt = NIL_RTSEMEVENT; - /* This must be done from EMT. */ - rc = RTSemEventCreate(&hSemEvt); - if (RT_SUCCESS(rc)) + if (!ASMAtomicXchgBool(&pThis->fEjectPending, true)) { - PDRVSCSIEJECTSTATE pEjectState = (PDRVSCSIEJECTSTATE)PDMDrvHlpQueueAlloc(pThis->pDrvIns, pThis->hQueue); - if (pEjectState) + RTSEMEVENT hSemEvt = NIL_RTSEMEVENT; + + /* This must be done from EMT. */ + rc = RTSemEventCreate(&hSemEvt); + if (RT_SUCCESS(rc)) { - pEjectState->hSemEvt = hSemEvt; - PDMDrvHlpQueueInsert(pThis->pDrvIns, pThis->hQueue, &pEjectState->Core); + PDRVSCSIEJECTSTATE pEjectState = (PDRVSCSIEJECTSTATE)PDMDrvHlpQueueAlloc(pThis->pDrvIns, pThis->hQueue); + if (pEjectState) + { + pEjectState->hSemEvt = hSemEvt; + PDMDrvHlpQueueInsert(pThis->pDrvIns, pThis->hQueue, &pEjectState->Core); - /* Wait for completion. */ - rc = RTSemEventWait(pEjectState->hSemEvt, RT_INDEFINITE_WAIT); - if (RT_SUCCESS(rc)) - rc = pEjectState->rcReq; + /* Wait for completion. */ + rc = RTSemEventWait(pEjectState->hSemEvt, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + rc = pEjectState->rcReq; + } + else + rc = VERR_NO_MEMORY; + + RTSemEventDestroy(hSemEvt); } - else - rc = VERR_NO_MEMORY; - RTSemEventDestroy(pEjectState->hSemEvt); + ASMAtomicXchgBool(&pThis->fEjectPending, false); } + else + rc = VERR_ALREADY_EXISTS; return rc; } @@ -943,7 +953,7 @@ static DECLCALLBACK(int) drvscsiIoReqSendScsiCmd(PPDMIMEDIAEX pInterface, PDMMED /* Allocate and sync buffers if a data transfer is indicated. */ if (cbBuf) { - pReq->pvBuf = RTMemAlloc(cbBuf); + pReq->pvBuf = RTMemAllocZ(cbBuf); if (RT_UNLIKELY(!pReq->pvBuf)) rc = VERR_NO_MEMORY; } diff --git a/src/VBox/Devices/USB/DevEHCI.cpp b/src/VBox/Devices/USB/DevEHCI.cpp index 4ee1a96bd28d..1289d8c1b565 100644 --- a/src/VBox/Devices/USB/DevEHCI.cpp +++ b/src/VBox/Devices/USB/DevEHCI.cpp @@ -1,4 +1,4 @@ -/* $Id: DevEHCI.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: DevEHCI.cpp 114925 2026-08-10 12:20:59Z alexander.eichner@oracle.com $ */ /** @file * DevEHCI - Enhanced Host Controller Interface for USB. */ @@ -1270,6 +1270,12 @@ static void ehciR3DoReset(PPDMDEVINS pDevIns, PEHCI pThis, PEHCICC pThisCC, uint LogFunc(("%s reset%s\n", fNewMode == EHCI_USB_RESET ? "hardware" : "software", fResetOnLinux ? " (reset on linux)" : "")); + if (!(pThis->intr_status & EHCI_STATUS_HCHALTED)) + { + LogRel(("EHCI: Ignoring reset while not halted!\n")); + return; + } + /* * Cancel all outstanding URBs. * @@ -5054,6 +5060,7 @@ static DECLCALLBACK(int) ehciR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFG /* * Do a hardware reset. */ + pThis->intr_status = EHCI_STATUS_HCHALTED; ehciR3DoReset(pDevIns, pThis, pThisCC, EHCI_USB_RESET, false /* don't reset devices */); #ifdef VBOX_WITH_STATISTICS diff --git a/src/VBox/Devices/USB/VUSBUrb.cpp b/src/VBox/Devices/USB/VUSBUrb.cpp index 287d5095ad44..4f4dbe2896d5 100644 --- a/src/VBox/Devices/USB/VUSBUrb.cpp +++ b/src/VBox/Devices/USB/VUSBUrb.cpp @@ -1,4 +1,4 @@ -/* $Id: VUSBUrb.cpp 114696 2026-07-14 11:50:33Z michal.necasek@oracle.com $ */ +/* $Id: VUSBUrb.cpp 114928 2026-08-10 12:24:46Z alexander.eichner@oracle.com $ */ /** @file * Virtual USB - URBs. */ @@ -934,7 +934,8 @@ static int vusbUrbSubmitCtrl(PVUSBURB pUrb) break; } - /* vusbMsgSetup() may have reallocated pMsg */ + /* vusbMsgSetup() may have reallocated just pMsg or the entire extra struct. */ + pExtra = pPipe->pCtrl; pSetup = pExtra->pMsg; /* pre-buffer our output if it's device-to-host */ diff --git a/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceR0A.asm b/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceR0A.asm index a82798bdca72..a6283afd340a 100644 --- a/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceR0A.asm +++ b/src/VBox/ExtPacks/VBoxDTrace/VBoxDTraceR0A.asm @@ -1,4 +1,4 @@ -; $Id: VBoxDTraceR0A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VBoxDTraceR0A.asm 115014 2026-08-12 23:38:48Z knut.osmundsen@oracle.com $ ;; @file ; VBoxDTraceR0 - Assembly Hacks. ; @@ -30,3 +30,4 @@ extern NAME(dtrace_probe) GLOBALNAME dtrace_probe6 jmp NAME(dtrace_probe) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Frontends/VBoxIntnetPcap/Makefile.kmk b/src/VBox/Frontends/VBoxIntnetPcap/Makefile.kmk index fc42365bf0e6..ad9eb8b19dd2 100644 --- a/src/VBox/Frontends/VBoxIntnetPcap/Makefile.kmk +++ b/src/VBox/Frontends/VBoxIntnetPcap/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114880 2026-08-07 06:47:30Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for the VBoxIntnetPcap # @@ -31,7 +31,8 @@ include $(KBUILD_PATH)/subheader.kmk PROGRAMS += VBoxIntnetPcap VBoxIntnetPcap_TEMPLATE := VBoxR3Exe VBoxIntnetPcap_DEFS := \ - $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) VBoxIntnetPcap_INCS := \ ../../NetworkServices/NetLib \ ../../Devices/Network diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageClipboard.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageClipboard.cpp index 64b7802fe256..5c538c1808ed 100644 --- a/src/VBox/Frontends/VBoxManage/VBoxManageClipboard.cpp +++ b/src/VBox/Frontends/VBoxManage/VBoxManageClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxManageClipboard.cpp 114646 2026-07-08 08:18:59Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxManageClipboard.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VBoxManage - Implementation of the clipboard command. */ @@ -149,6 +149,20 @@ typedef struct SHCLCLIPBOARDEVENTINFO } SHCLCLIPBOARDEVENTINFO; +typedef struct SHCLHANDLELISTENSTATE +{ + SHCLHANDLELISTENSTATE() + : fHaveSource(false) + , enmSource(ClipboardSource_Custom) + { } + + /** Whether enmSource contains the last source reported by an event. */ + bool fHaveSource; + /** Last clipboard source reported by an event. */ + ClipboardSource_T enmSource; +} SHCLHANDLELISTENSTATE; + + /** Current verbosity level for clipboard command diagnostics. */ static unsigned g_uVerbosity = 0; @@ -281,51 +295,100 @@ static bool shclGetClipboardEventInfo(const ComPtr &ptrEvent, SHCLCLIPBO } -/** - * Prints optional clipboard event identity fields in JSON output. - * - * @param pInfo Event identity fields, optional. - */ -static void shclPrintEventInfoJson(const SHCLCLIPBOARDEVENTINFO *pInfo) -{ - if (!pInfo) - return; - if (pInfo->fHaveRevision) - RTStrmPrintf(g_pStdOut, ",\"revision\":%RI64", (int64_t)pInfo->iRevision); - if (pInfo->fHaveClientId) - RTStrmPrintf(g_pStdOut, ",\"clientId\":%RU32", (uint32_t)pInfo->uClientId); -} +static void shclHandleListenJsonString(const char *pszValue); /** - * Prints optional clipboard event identity fields in machine-readable output. + * Starts a uniformly ordered clipboard-listen event record. * - * @param pInfo Event identity fields, optional. - */ -static void shclPrintEventInfoMachineReadable(const SHCLCLIPBOARDEVENTINFO *pInfo) -{ - if (!pInfo) - return; - if (pInfo->fHaveRevision) - RTPrintf(" revision=\"%RI64\"", (int64_t)pInfo->iRevision); - if (pInfo->fHaveClientId) - RTPrintf(" clientId=\"%RU32\"", (uint32_t)pInfo->uClientId); -} - - -/** - * Prints optional clipboard event identity fields in human-readable output. + * The caller appends event-specific fields and terminates the record. * + * @param enmFormat Output format. * @param pInfo Event identity fields, optional. + * @param pszSource Clipboard source, optional. + * @param pszEvent Event name. + * @param pszAction Clipboard action, optional. */ -static void shclPrintEventInfoHuman(const SHCLCLIPBOARDEVENTINFO *pInfo) +static void shclHandleListenPrintPrefix(CLIPBOARDLISTENFMT enmFormat, + const SHCLCLIPBOARDEVENTINFO *pInfo, + const char *pszSource, + const char *pszEvent, + const char *pszAction) { - if (!pInfo) - return; - if (pInfo->fHaveRevision) - RTPrintf(" revision=%RI64", (int64_t)pInfo->iRevision); - if (pInfo->fHaveClientId) - RTPrintf(" clientId=%RU32", (uint32_t)pInfo->uClientId); + AssertPtrReturnVoid(pszEvent); + + if (enmFormat == CLIPBOARDLISTENFMT_JSON) + { + const char *pszSeparator = ""; + RTStrmPutCh(g_pStdOut, '{'); + if (pInfo && pInfo->fHaveRevision) + { + RTStrmPrintf(g_pStdOut, "\"revision\":%RI64", (int64_t)pInfo->iRevision); + pszSeparator = ","; + } + if (pszSource) + { + RTStrmPrintf(g_pStdOut, "%s\"source\":", pszSeparator); + shclHandleListenJsonString(pszSource); + pszSeparator = ","; + } + if (g_uVerbosity && pInfo && pInfo->fHaveClientId) + { + RTStrmPrintf(g_pStdOut, "%s\"clientId\":%RU32", pszSeparator, (uint32_t)pInfo->uClientId); + pszSeparator = ","; + } + RTStrmPrintf(g_pStdOut, "%s\"event\":", pszSeparator); + shclHandleListenJsonString(pszEvent); + if (pszAction) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"action\":")); + shclHandleListenJsonString(pszAction); + } + } + else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) + { + const char *pszSeparator = ""; + if (pInfo && pInfo->fHaveRevision) + { + RTPrintf("revision=\"%RI64\"", (int64_t)pInfo->iRevision); + pszSeparator = " "; + } + if (pszSource) + { + RTPrintf("%ssource=\"%s\"", pszSeparator, pszSource); + pszSeparator = " "; + } + if (g_uVerbosity && pInfo && pInfo->fHaveClientId) + { + RTPrintf("%sclientId=\"%RU32\"", pszSeparator, (uint32_t)pInfo->uClientId); + pszSeparator = " "; + } + RTPrintf("%sevent=\"%s\"", pszSeparator, pszEvent); + if (pszAction) + RTPrintf(" action=\"%s\"", pszAction); + } + else + { + const char *pszSeparator = ""; + if (pInfo && pInfo->fHaveRevision) + { + RTPrintf("rev=%RI64", (int64_t)pInfo->iRevision); + pszSeparator = " "; + } + if (pszSource) + { + RTPrintf("%ssrc=%s", pszSeparator, pszSource); + pszSeparator = " "; + } + if (g_uVerbosity && pInfo && pInfo->fHaveClientId) + { + RTPrintf("%scid=%RU32", pszSeparator, (uint32_t)pInfo->uClientId); + pszSeparator = " "; + } + RTPrintf("%sevent=%s", pszSeparator, pszEvent); + if (pszAction) + RTPrintf(" action=%s", pszAction); + } } @@ -506,6 +569,36 @@ static HRESULT shclGet(HandlerArg *pArg, const char *pszMachine, ComPtr ptrMachine; + HRESULT hrc = pArg->session->COMGETTER(Machine)(ptrMachine.asOutParam()); + if (FAILED(hrc)) + return hrc; + if (ptrMachine.isNull()) + return E_FAIL; + + ComPtr ptrClipboardSettings; + hrc = ptrMachine->COMGETTER(Clipboard)(ptrClipboardSettings.asOutParam()); + if (FAILED(hrc)) + return hrc; + if (ptrClipboardSettings.isNull()) + return E_FAIL; + + return ptrClipboardSettings->COMGETTER(Mode)(penmMode); +} + + /** * Opens the VM clipboard settings object for modification. * @@ -935,24 +1028,39 @@ static void shclHandleListenJsonStringN(const char *pszValue, size_t cchValue) RTStrmPutCh(g_pStdOut, '"'); if (pszValue) { + size_t offPending = 0; for (size_t i = 0; i < cchValue; i++) { unsigned char const ch = (unsigned char)pszValue[i]; + const char *pszEscape = NULL; + size_t cchEscape = 0; switch (ch) { - case '\\': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\\\")); break; - case '"': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\\"")); break; - case '\n': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\n")); break; - case '\r': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\r")); break; - case '\t': RTStrmWrite(g_pStdOut, RT_STR_TUPLE("\\t")); break; + case '\\': pszEscape = "\\\\"; cchEscape = 2; break; + case '"': pszEscape = "\\\""; cchEscape = 2; break; + case '\n': pszEscape = "\\n"; cchEscape = 2; break; + case '\r': pszEscape = "\\r"; cchEscape = 2; break; + case '\t': pszEscape = "\\t"; cchEscape = 2; break; default: - if (ch >= 0x20) - RTStrmPutCh(g_pStdOut, ch); - else + if (ch < 0x20) + { + if (i > offPending) + RTStrmWrite(g_pStdOut, &pszValue[offPending], i - offPending); RTStrmPrintf(g_pStdOut, "\\u%04x", ch); + offPending = i + 1; + } break; } + if (pszEscape) + { + if (i > offPending) + RTStrmWrite(g_pStdOut, &pszValue[offPending], i - offPending); + RTStrmWrite(g_pStdOut, pszEscape, cchEscape); + offPending = i + 1; + } } + if (offPending < cchValue) + RTStrmWrite(g_pStdOut, &pszValue[offPending], cchValue - offPending); } RTStrmPutCh(g_pStdOut, '"'); } @@ -983,6 +1091,38 @@ static void shclHandleListenQuotedString(const char *pszValue) } +/** + * Prints the startup warning used when Shared Clipboard is disabled. + * + * @param enmFormat Output format. + */ +static void shclHandleListenPrintDisabledWarning(CLIPBOARDLISTENFMT enmFormat) +{ + const char *pszMessage = Clipboard::tr("Shared Clipboard is disabled for this VM."); + + shclHandleListenPrintPrefix(enmFormat, NULL /* pInfo */, NULL /* pszSource */, + "warning", NULL /* pszAction */); + if (enmFormat == CLIPBOARDLISTENFMT_JSON) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"mode\":\"disabled\",\"message\":")); + shclHandleListenJsonString(pszMessage); + RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); + } + else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" mode=\"disabled\" message=")); + shclHandleListenQuotedString(pszMessage); + RTStrmPutCh(g_pStdOut, '\n'); + } + else + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" mode=disabled message=")); + shclHandleListenQuotedString(pszMessage); + RTStrmPutCh(g_pStdOut, '\n'); + } +} + + /** * Converts a bounded UTF-8 or UTF-16 clipboard payload to UTF-8 for verbose event output. * @@ -1130,107 +1270,41 @@ static void shclVerbosePayloadData(const char *pszCommand, const char *pszOperat /** - * Reads and logs current guest data for extra-verbose format-only diagnostics. + * Reads current guest data for an extra-verbose format event. * - * @returns true if data was read and logged, false otherwise. - * @param pszCommand Clipboard subcommand name. - * @param pszOperation Copying operation being logged. - * @param ptrSession Clipboard session to read from. - * @param strExpectedMimeType Expected MIME type selected from the format event. - * @param fLogFailures Whether read misses/errors should be logged. + * @returns true if matching non-empty guest data was returned. + * @param ptrSession Clipboard session to read from. + * @param strExpectedMimeType MIME type selected from the format event. + * @param strReadMimeType Where to return the MIME type read. + * @param aBuffer Where to return the payload read. */ -static bool shclVerboseReadGuestData(const char *pszCommand, const char *pszOperation, - const ComPtr &ptrSession, const Utf8Str &strExpectedMimeType, - bool fLogFailures) +static bool shclHandleListenReadGuestData(const ComPtr &ptrSession, + const Utf8Str &strExpectedMimeType, + Utf8Str &strReadMimeType, + SafeArray &aBuffer) { - if (g_uVerbosity < 2) + if (g_uVerbosity < 2 || ptrSession.isNull()) return false; ClipboardSource_T enmReadSource = ClipboardSource_Custom; Bstr bstrRequestedMimeType(""); Bstr bstrReadMimeType; - SafeArray aBuffer; HRESULT hrc = ptrSession->ReadDataRaw(ClipboardAction_Copy, bstrRequestedMimeType.raw(), &enmReadSource, bstrReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aBuffer)); if (FAILED(hrc)) - { - if (fLogFailures) - shclVerbose("%s: data read failed for format=%s: %Rhrc", pszCommand, - strExpectedMimeType.c_str(), hrc); return false; - } - Utf8Str strReadMimeType(bstrReadMimeType); + strReadMimeType = bstrReadMimeType; if (enmReadSource != ClipboardSource_Guest) - { - if (fLogFailures) - shclVerbose("%s: data read ignored non-guest source=%s format=%s size=%zu", pszCommand, - ShClHlpSourceToString(enmReadSource), strReadMimeType.c_str(), aBuffer.size()); return false; - } if (!shclMimeEquivalent(strExpectedMimeType, strReadMimeType)) - { - if (fLogFailures) - shclVerbose("%s: data read ignored mismatched format=%s expected=%s size=%zu", pszCommand, - strReadMimeType.c_str(), strExpectedMimeType.c_str(), aBuffer.size()); return false; - } if (!aBuffer.size()) - { - if (fLogFailures) - shclVerbose("%s: data read ignored empty guest data format=%s", pszCommand, - strReadMimeType.c_str()); return false; - } - - shclVerbosePayloadData(pszCommand, pszOperation, strReadMimeType, aBuffer.raw(), aBuffer.size()); return true; } -/** - * Reads and logs guest data selected by a format-changed event for extra-verbose diagnostics. - * - * @param pszCommand Clipboard subcommand name. - * @param ptrSession Clipboard session to read from. - * @param ptrFormatEvent Format-changed event to inspect. - */ -static void shclHandleListenVerboseReadFormatEventGuestData(const char *pszCommand, - const ComPtr &ptrSession, - const ComPtr &ptrFormatEvent) -{ - if (g_uVerbosity < 2 || ptrFormatEvent.isNull()) - return; - - ClipboardSource_T enmSource = ClipboardSource_Custom; - SafeIfaceArray aFormats; - HRESULT hrc = ptrFormatEvent->COMGETTER(ClipboardSource)(&enmSource); - if (SUCCEEDED(hrc)) - hrc = ptrFormatEvent->COMGETTER(Formats)(ComSafeArrayAsOutParam(aFormats)); - if (FAILED(hrc)) - { - shclVerbose("%s: format-event inspection failed: %Rhrc", pszCommand, hrc); - return; - } - if (enmSource != ClipboardSource_Guest) - return; - - ComPtr ptrPreferredFormat; - Utf8Str strMimeType; - hrc = shclSelectPreferredFormat(aFormats, ptrPreferredFormat, strMimeType); - if (hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED) - return; - if (FAILED(hrc)) - { - shclVerbose("%s: format selection failed: %Rhrc", pszCommand, hrc); - return; - } - - shclVerboseReadGuestData(pszCommand, "read guest data after format event", ptrSession, strMimeType, - true /* fLogFailures */); -} - - /** * Gets the MIME type of a clipboard format. * @@ -1353,17 +1427,31 @@ static const char *shclEventTypeToString(VBoxEventType_T enmType) * @param enmFormat Output format. * @param pszEvent Event name. * @param ptrItem Clipboard item associated with the event. + * @param pszAction Clipboard action associated with the event, optional. * @param fVerboseData Whether to include payload data. * @param pEventInfo Optional event identity fields. + * @param pListenState Listen state to update and use as a source fallback. */ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const char *pszEvent, - const ComPtr &ptrItem, bool fVerboseData, - const SHCLCLIPBOARDEVENTINFO *pEventInfo) + const ComPtr &ptrItem, const char *pszAction, + bool fVerboseData, const SHCLCLIPBOARDEVENTINFO *pEventInfo, + SHCLHANDLELISTENSTATE *pListenState) { Utf8Str strMimeType; ClipboardSource_T enmSource = ClipboardSource_Host; SafeArray aBuffer; HRESULT hrc = ptrItem.isNotNull() ? shclGetItemData(ptrItem, strMimeType, &enmSource, aBuffer) : E_FAIL; + if (SUCCEEDED(hrc) && pListenState) + { + pListenState->fHaveSource = true; + pListenState->enmSource = enmSource; + } + + const char *pszSource = NULL; + if (SUCCEEDED(hrc)) + pszSource = ShClHlpSourceToString(enmSource); + else if (pListenState && pListenState->fHaveSource) + pszSource = ShClHlpSourceToString(pListenState->enmSource); char *pszVerboseText = NULL; size_t cchVerboseText = 0; @@ -1375,13 +1463,9 @@ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const c if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":")); - shclHandleListenJsonString(pszEvent); - shclPrintEventInfoJson(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, pszEvent, pszAction); if (SUCCEEDED(hrc)) { - RTStrmPrintf(g_pStdOut, ",\"source\":"); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); RTStrmPrintf(g_pStdOut, ",\"format\":"); shclHandleListenJsonString(strMimeType.c_str()); RTStrmPrintf(g_pStdOut, ",\"size\":%zu", aBuffer.size()); @@ -1400,12 +1484,10 @@ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const c } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"%s\"", pszEvent); - shclPrintEventInfoMachineReadable(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, pszEvent, pszAction); if (SUCCEEDED(hrc)) { - RTPrintf(" source=\"%s\" format=\"%s\" size=\"%zu\"", - ShClHlpSourceToString(enmSource), strMimeType.c_str(), aBuffer.size()); + RTPrintf(" format=\"%s\" size=\"%zu\"", strMimeType.c_str(), aBuffer.size()); if (fHaveVerboseData) { RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data=\"")); @@ -1422,11 +1504,12 @@ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const c } else { - RTPrintf("clipboard: %s", pszEvent); - shclPrintEventInfoHuman(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, pszEvent, pszAction); if (SUCCEEDED(hrc)) { - RTPrintf(" source=%s format=%s size=%zu", ShClHlpSourceToString(enmSource), strMimeType.c_str(), aBuffer.size()); + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" format=")); + shclHandleListenQuotedString(strMimeType.c_str()); + RTPrintf(" size=%zu", aBuffer.size()); if (fHaveVerboseData) { RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data=\"")); @@ -1451,14 +1534,24 @@ static void shclHandleListenPrintEventItem(CLIPBOARDLISTENFMT enmFormat, const c * * @param enmFormat Output format. * @param ptrFormatEvent Format-changed event object to print. + * @param ptrSession Clipboard session for extra-verbose guest reads. * @param pEventInfo Optional event identity fields. + * @param pListenState Listen state to update. */ static void shclHandleListenPrintFormatChangedEvent(CLIPBOARDLISTENFMT enmFormat, const ComPtr &ptrFormatEvent, - const SHCLCLIPBOARDEVENTINFO *pEventInfo) + const ComPtr &ptrSession, + const SHCLCLIPBOARDEVENTINFO *pEventInfo, + SHCLHANDLELISTENSTATE *pListenState) { ClipboardSource_T enmSource = ClipboardSource_Host; HRESULT hrc = ptrFormatEvent->COMGETTER(ClipboardSource)(&enmSource); + bool const fHaveSource = SUCCEEDED(hrc); + if (SUCCEEDED(hrc) && pListenState) + { + pListenState->fHaveSource = true; + pListenState->enmSource = enmSource; + } SafeIfaceArray aFormats; if (SUCCEEDED(hrc)) @@ -1477,14 +1570,33 @@ static void shclHandleListenPrintFormatChangedEvent(CLIPBOARDLISTENFMT enmFormat } } + Utf8Str strDataMimeType; + SafeArray aDataBuffer; + bool fHaveData = false; + if (SUCCEEDED(hrc) && enmSource == ClipboardSource_Guest && g_uVerbosity > 1) + { + ComPtr ptrPreferredFormat; + Utf8Str strPreferredMimeType; + HRESULT const hrcSelect = shclSelectPreferredFormat(aFormats, ptrPreferredFormat, strPreferredMimeType); + if (SUCCEEDED(hrcSelect)) + fHaveData = shclHandleListenReadGuestData(ptrSession, strPreferredMimeType, + strDataMimeType, aDataBuffer); + } + + char *pszData = NULL; + size_t cchData = 0; + bool fDataTruncated = false; + bool const fHaveText = fHaveData + && shclGetVerboseText(strDataMimeType, aDataBuffer.raw(), aDataBuffer.size(), + &pszData, &cchData, &fDataTruncated); + if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"format-changed\"")); - shclPrintEventInfoJson(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + fHaveSource ? ShClHlpSourceToString(enmSource) : NULL, + "format-changed", NULL /* pszAction */); if (SUCCEEDED(hrc)) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"source\":")); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"formats\":[")); for (size_t i = 0; i < vecMimeTypes.size(); ++i) { @@ -1493,37 +1605,77 @@ static void shclHandleListenPrintFormatChangedEvent(CLIPBOARDLISTENFMT enmFormat shclHandleListenJsonString(vecMimeTypes[i].c_str()); } RTStrmPutCh(g_pStdOut, ']'); + if (fHaveData) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"data-format\":")); + shclHandleListenJsonString(strDataMimeType.c_str()); + RTStrmPrintf(g_pStdOut, ",\"size\":%zu,\"data\":", aDataBuffer.size()); + if (fHaveText) + shclHandleListenJsonStringN(pszData, cchData); + else + shclHandleListenJsonString(""); + if (fHaveText && fDataTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"data-truncated\":true")); + } } RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"format-changed\""); - shclPrintEventInfoMachineReadable(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + fHaveSource ? ShClHlpSourceToString(enmSource) : NULL, + "format-changed", NULL /* pszAction */); if (SUCCEEDED(hrc)) { - RTPrintf(" source=\"%s\"", ShClHlpSourceToString(enmSource)); for (size_t i = 0; i < vecMimeTypes.size(); ++i) RTPrintf(" format=\"%s\"", vecMimeTypes[i].c_str()); + if (fHaveData) + { + RTPrintf(" data-format=\"%s\" size=\"%zu\" data=\"", strDataMimeType.c_str(), aDataBuffer.size()); + if (fHaveText) + ShClHlpPrintEscapedString(g_pStdOut, pszData, cchData); + else + RTStrmWrite(g_pStdOut, RT_STR_TUPLE("")); + RTStrmPutCh(g_pStdOut, '"'); + if (fHaveText && fDataTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-truncated=\"true\"")); + } } RTPrintf("\n"); } else { - RTPrintf("clipboard: format-changed"); - shclPrintEventInfoHuman(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + fHaveSource ? ShClHlpSourceToString(enmSource) : NULL, + "format-changed", NULL /* pszAction */); if (SUCCEEDED(hrc)) { - RTPrintf(" source=%s formats=", ShClHlpSourceToString(enmSource)); + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" formats=\"")); for (size_t i = 0; i < vecMimeTypes.size(); ++i) { if (i) - RTPrintf(","); - RTPrintf("%s", vecMimeTypes[i].c_str()); + RTStrmPutCh(g_pStdOut, ','); + ShClHlpPrintEscapedString(g_pStdOut, vecMimeTypes[i].c_str(), vecMimeTypes[i].length()); + } + RTStrmPutCh(g_pStdOut, '"'); + if (fHaveData) + { + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-format=")); + shclHandleListenQuotedString(strDataMimeType.c_str()); + RTPrintf(" size=%zu data=\"", aDataBuffer.size()); + if (fHaveText) + ShClHlpPrintEscapedString(g_pStdOut, pszData, cchData); + else + RTStrmWrite(g_pStdOut, RT_STR_TUPLE("")); + RTStrmPutCh(g_pStdOut, '"'); + if (fHaveText && fDataTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-truncated=true")); } } RTPrintf("\n"); } + + RTStrFree(pszData); } @@ -1533,10 +1685,12 @@ static void shclHandleListenPrintFormatChangedEvent(CLIPBOARDLISTENFMT enmFormat * @param enmFormat Output format. * @param ptrRequestEvent Data-requested event object to print. * @param pEventInfo Optional event identity fields. + * @param pListenState Listen state to update. */ static void shclHandleListenPrintDataRequestedEvent(CLIPBOARDLISTENFMT enmFormat, const ComPtr &ptrRequestEvent, - const SHCLCLIPBOARDEVENTINFO *pEventInfo) + const SHCLCLIPBOARDEVENTINFO *pEventInfo, + SHCLHANDLELISTENSTATE *pListenState) { ULONG uRequestId = 0; ClipboardAction_T enmAction = ClipboardAction_Copy; @@ -1548,6 +1702,12 @@ static void shclHandleListenPrintDataRequestedEvent(CLIPBOARDLISTENFMT enmFormat hrc = ptrRequestEvent->COMGETTER(Action)(&enmAction); if (SUCCEEDED(hrc)) hrc = ptrRequestEvent->COMGETTER(ClipboardSource)(&enmSource); + bool const fHaveSource = SUCCEEDED(hrc); + if (fHaveSource && pListenState) + { + pListenState->fHaveSource = true; + pListenState->enmSource = enmSource; + } if (SUCCEEDED(hrc)) hrc = ptrRequestEvent->COMGETTER(Format)(ptrFormat.asOutParam()); @@ -1555,41 +1715,83 @@ static void shclHandleListenPrintDataRequestedEvent(CLIPBOARDLISTENFMT enmFormat if (SUCCEEDED(hrc)) hrc = shclGetFormatMimeType(ptrFormat, strMimeType); + ComPtr ptrItem; + HRESULT hrcItem = ptrRequestEvent->COMGETTER(Item)(ptrItem.asOutParam()); + Utf8Str strItemMimeType; + ClipboardSource_T enmItemSource = ClipboardSource_Custom; + SafeArray aItemBuffer; + if (SUCCEEDED(hrcItem) && ptrItem.isNotNull()) + hrcItem = shclGetItemData(ptrItem, strItemMimeType, &enmItemSource, aItemBuffer); + else + hrcItem = E_FAIL; + + char *pszText = NULL; + size_t cchText = 0; + bool fTextTruncated = false; + bool const fHaveText = g_uVerbosity > 1 + && SUCCEEDED(hrc) + && enmSource == ClipboardSource_Host + && SUCCEEDED(hrcItem) + && enmItemSource == ClipboardSource_Host + && shclMimeEquivalent(strMimeType, strItemMimeType) + && shclGetVerboseText(strItemMimeType, aItemBuffer.raw(), aItemBuffer.size(), + &pszText, &cchText, &fTextTruncated); + + const char *pszSource = fHaveSource ? ShClHlpSourceToString(enmSource) : NULL; + const char *pszAction = SUCCEEDED(hrc) ? shclActionToString(enmAction) : NULL; + if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"data-requested\"")); - shclPrintEventInfoJson(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, "data-requested", pszAction); if (SUCCEEDED(hrc)) { - RTStrmPrintf(g_pStdOut, ",\"request-id\":%RU32,\"action\":", (uint32_t)uRequestId); - shclHandleListenJsonString(shclActionToString(enmAction)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"source\":")); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"format\":")); + RTStrmPrintf(g_pStdOut, ",\"request-id\":%RU32,\"format\":", (uint32_t)uRequestId); shclHandleListenJsonString(strMimeType.c_str()); } + if (fHaveText) + { + RTStrmPrintf(g_pStdOut, ",\"size\":%zu,\"data\":", aItemBuffer.size()); + shclHandleListenJsonStringN(pszText, cchText); + if (fTextTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"data-truncated\":true")); + } RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"data-requested\""); - shclPrintEventInfoMachineReadable(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, "data-requested", pszAction); if (SUCCEEDED(hrc)) - RTPrintf(" request-id=\"%RU32\" action=\"%s\" source=\"%s\" format=\"%s\"", - (uint32_t)uRequestId, shclActionToString(enmAction), - ShClHlpSourceToString(enmSource), strMimeType.c_str()); + RTPrintf(" request-id=\"%RU32\" format=\"%s\"", (uint32_t)uRequestId, strMimeType.c_str()); + if (fHaveText) + { + RTPrintf(" size=\"%zu\" data=\"", aItemBuffer.size()); + ShClHlpPrintEscapedString(g_pStdOut, pszText, cchText); + RTStrmPutCh(g_pStdOut, '"'); + if (fTextTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-truncated=\"true\"")); + } RTPrintf("\n"); } else { - RTPrintf("clipboard: data-requested"); - shclPrintEventInfoHuman(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, "data-requested", pszAction); if (SUCCEEDED(hrc)) - RTPrintf(" request-id=%RU32 action=%s source=%s format=%s", - (uint32_t)uRequestId, shclActionToString(enmAction), - ShClHlpSourceToString(enmSource), strMimeType.c_str()); + { + RTPrintf(" request-id=%RU32 format=", (uint32_t)uRequestId); + shclHandleListenQuotedString(strMimeType.c_str()); + } + if (fHaveText) + { + RTPrintf(" size=%zu data=\"", aItemBuffer.size()); + ShClHlpPrintEscapedString(g_pStdOut, pszText, cchText); + RTStrmPutCh(g_pStdOut, '"'); + if (fTextTruncated) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE(" data-truncated=true")); + } RTPrintf("\n"); } + + RTStrFree(pszText); } @@ -1632,16 +1834,13 @@ static void shclHandleListenPrintTransferEvent(CLIPBOARDLISTENFMT enmFormat, if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"transfer\"")); - shclPrintEventInfoJson(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + SUCCEEDED(hrcTransfer) ? ShClHlpSourceToString(enmSource) : NULL, + "transfer", SUCCEEDED(hrcTransfer) ? shclActionToString(enmAction) : NULL); if (SUCCEEDED(hrcTransfer)) { RTStrmPrintf(g_pStdOut, ",\"id\":%RU32,\"direction\":", (uint32_t)idTransfer); shclHandleListenJsonString(shclTransferDirectionToString(enmDirection)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"source\":")); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"action\":")); - shclHandleListenJsonString(shclActionToString(enmAction)); } if (SUCCEEDED(hrcEvent)) { @@ -1660,12 +1859,12 @@ static void shclHandleListenPrintTransferEvent(CLIPBOARDLISTENFMT enmFormat, } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"transfer\""); - shclPrintEventInfoMachineReadable(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + SUCCEEDED(hrcTransfer) ? ShClHlpSourceToString(enmSource) : NULL, + "transfer", SUCCEEDED(hrcTransfer) ? shclActionToString(enmAction) : NULL); if (SUCCEEDED(hrcTransfer)) - RTPrintf(" id=\"%RU32\" direction=\"%s\" source=\"%s\" action=\"%s\"", - (uint32_t)idTransfer, shclTransferDirectionToString(enmDirection), - ShClHlpSourceToString(enmSource), shclActionToString(enmAction)); + RTPrintf(" id=\"%RU32\" direction=\"%s\"", + (uint32_t)idTransfer, shclTransferDirectionToString(enmDirection)); if (SUCCEEDED(hrcEvent)) { RTPrintf(" state=\"%s\" interaction=\"%s\" path=", @@ -1678,12 +1877,11 @@ static void shclHandleListenPrintTransferEvent(CLIPBOARDLISTENFMT enmFormat, } else { - RTPrintf("clipboard: transfer"); - shclPrintEventInfoHuman(pEventInfo); + shclHandleListenPrintPrefix(enmFormat, pEventInfo, + SUCCEEDED(hrcTransfer) ? ShClHlpSourceToString(enmSource) : NULL, + "transfer", SUCCEEDED(hrcTransfer) ? shclActionToString(enmAction) : NULL); if (SUCCEEDED(hrcTransfer)) - RTPrintf(" id=%RU32 direction=%s source=%s action=%s", - (uint32_t)idTransfer, shclTransferDirectionToString(enmDirection), - ShClHlpSourceToString(enmSource), shclActionToString(enmAction)); + RTPrintf(" id=%RU32 direction=%s", (uint32_t)idTransfer, shclTransferDirectionToString(enmDirection)); if (SUCCEEDED(hrcEvent)) { RTPrintf(" state=%s interaction=%s path=", @@ -1703,29 +1901,19 @@ static void shclHandleListenPrintTransferEvent(CLIPBOARDLISTENFMT enmFormat, * @param enmFormat Output format. * @param pszEvent Event name. * @param pEventInfo Optional event identity fields. + * @param pListenState Listen state providing the current source, optional. */ static void shclHandleListenPrintSimpleEvent(CLIPBOARDLISTENFMT enmFormat, const char *pszEvent, - const SHCLCLIPBOARDEVENTINFO *pEventInfo) + const SHCLCLIPBOARDEVENTINFO *pEventInfo, + const SHCLHANDLELISTENSTATE *pListenState) { + const char *pszSource = pListenState && pListenState->fHaveSource + ? ShClHlpSourceToString(pListenState->enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, pEventInfo, pszSource, pszEvent, NULL /* pszAction */); if (enmFormat == CLIPBOARDLISTENFMT_JSON) - { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":")); - shclHandleListenJsonString(pszEvent); - shclPrintEventInfoJson(pEventInfo); RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); - } - else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) - { - RTPrintf("event=\"%s\"", pszEvent); - shclPrintEventInfoMachineReadable(pEventInfo); - RTPrintf("\n"); - } else - { - RTPrintf("clipboard: %s", pszEvent); - shclPrintEventInfoHuman(pEventInfo); RTPrintf("\n"); - } } @@ -1735,9 +1923,11 @@ static void shclHandleListenPrintSimpleEvent(CLIPBOARDLISTENFMT enmFormat, const * @param enmFormat Output format. * @param ptrEvent Event object to print. * @param ptrSession Clipboard session for extra-verbose diagnostic reads. + * @param pListenState Listen state to update. */ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPtr &ptrEvent, - const ComPtr &ptrSession) + const ComPtr &ptrSession, + SHCLHANDLELISTENSTATE *pListenState) { VBoxEventType_T enmType; HRESULT hrc = ptrEvent->COMGETTER(Type)(&enmType); @@ -1753,34 +1943,24 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt { ComPtr ptrSourceEvent = ptrEvent; ClipboardSource_T enmSource = ClipboardSource_Host; - ptrSourceEvent->COMGETTER(ClipboardSource)(&enmSource); - if (enmFormat == CLIPBOARDLISTENFMT_JSON) + HRESULT const hrcSource = ptrSourceEvent->COMGETTER(ClipboardSource)(&enmSource); + if (SUCCEEDED(hrcSource) && pListenState) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"source-changed\"")); - shclPrintEventInfoJson(&EventInfo); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"source\":")); - shclHandleListenJsonString(ShClHlpSourceToString(enmSource)); - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); - } - else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) - { - RTPrintf("event=\"source-changed\""); - shclPrintEventInfoMachineReadable(&EventInfo); - RTPrintf(" source=\"%s\"\n", ShClHlpSourceToString(enmSource)); + pListenState->fHaveSource = true; + pListenState->enmSource = enmSource; } + const char *pszSource = SUCCEEDED(hrcSource) ? ShClHlpSourceToString(enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, &EventInfo, pszSource, "source-changed", NULL /* pszAction */); + if (enmFormat == CLIPBOARDLISTENFMT_JSON) + RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); else - { - RTPrintf("clipboard: source-changed"); - shclPrintEventInfoHuman(&EventInfo); - RTPrintf(" source=%s\n", ShClHlpSourceToString(enmSource)); - } + RTPrintf("\n"); break; } case VBoxEventType_OnClipboardFormatChanged: { ComPtr ptrFormatEvent = ptrEvent; - shclHandleListenPrintFormatChangedEvent(enmFormat, ptrFormatEvent, &EventInfo); - shclHandleListenVerboseReadFormatEventGuestData("listen", ptrSession, ptrFormatEvent); + shclHandleListenPrintFormatChangedEvent(enmFormat, ptrFormatEvent, ptrSession, &EventInfo, pListenState); break; } case VBoxEventType_OnClipboardDataChanged: @@ -1788,13 +1968,17 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt ComPtr ptrDataEvent = ptrEvent; ComPtr ptrItem; ptrDataEvent->COMGETTER(Item)(ptrItem.asOutParam()); - shclHandleListenPrintEventItem(enmFormat, "data-changed", ptrItem, g_uVerbosity > 1, &EventInfo); + ClipboardAction_T enmAction = ClipboardAction_Copy; + HRESULT const hrcAction = ptrDataEvent->COMGETTER(Action)(&enmAction); + shclHandleListenPrintEventItem(enmFormat, "data-changed", ptrItem, + SUCCEEDED(hrcAction) ? shclActionToString(enmAction) : NULL, + g_uVerbosity > 1, &EventInfo, pListenState); break; } case VBoxEventType_OnClipboardDataRequested: { ComPtr ptrRequestEvent = ptrEvent; - shclHandleListenPrintDataRequestedEvent(enmFormat, ptrRequestEvent, &EventInfo); + shclHandleListenPrintDataRequestedEvent(enmFormat, ptrRequestEvent, &EventInfo, pListenState); break; } case VBoxEventType_OnClipboardTransfer: @@ -1811,25 +1995,26 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt ptrErrorEvent->COMGETTER(Msg)(bstrMsg.asOutParam()); ptrErrorEvent->COMGETTER(RcError)(&rcError); Utf8Str strMsg(bstrMsg); + const char *pszSource = pListenState && pListenState->fHaveSource + ? ShClHlpSourceToString(pListenState->enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, &EventInfo, pszSource, "error", NULL /* pszAction */); if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"error\"")); - shclPrintEventInfoJson(&EventInfo); - RTStrmPrintf(g_pStdOut, ",\"rc\":%ld,\"message\":", rcError); + RTStrmPrintf(g_pStdOut, ",\"rc\":%RI32,\"message\":", (int32_t)rcError); shclHandleListenJsonString(strMsg.c_str()); RTStrmWrite(g_pStdOut, RT_STR_TUPLE("}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) { - RTPrintf("event=\"error\""); - shclPrintEventInfoMachineReadable(&EventInfo); - RTPrintf(" rc=\"%ld\" message=\"%s\"\n", rcError, strMsg.c_str()); + RTPrintf(" rc=\"%RI32\" message=", (int32_t)rcError); + shclHandleListenQuotedString(strMsg.c_str()); + RTPrintf("\n"); } else { - RTPrintf("clipboard: error"); - shclPrintEventInfoHuman(&EventInfo); - RTPrintf(" rc=%ld message=%s\n", rcError, strMsg.c_str()); + RTPrintf(" rc=%RI32 message=", (int32_t)rcError); + shclHandleListenQuotedString(strMsg.c_str()); + RTPrintf("\n"); } break; } @@ -1839,26 +2024,19 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt ClipboardMode_T enmMode = ClipboardMode_Disabled; ptrModeEvent->COMGETTER(ClipboardMode)(&enmMode); const char *pszMode = ShClHlpModeToString(enmMode); + const char *pszSource = pListenState && pListenState->fHaveSource + ? ShClHlpSourceToString(pListenState->enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, &EventInfo, pszSource, "mode-changed", NULL /* pszAction */); if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"mode-changed\"")); - shclPrintEventInfoJson(&EventInfo); RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"mode\":")); shclHandleListenJsonString(pszMode); RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"active\":true}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) - { - RTPrintf("event=\"mode-changed\""); - shclPrintEventInfoMachineReadable(&EventInfo); RTPrintf(" mode=\"%s\" active=\"true\"\n", pszMode); - } else - { - RTPrintf("clipboard: mode-changed"); - shclPrintEventInfoHuman(&EventInfo); - RTPrintf(" mode=%s now-active\n", pszMode); - } + RTPrintf(" mode=%s active=true\n", pszMode); break; } case VBoxEventType_OnClipboardFileTransferModeChanged: @@ -1866,10 +2044,12 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt ComPtr ptrModeEvent = ptrEvent; BOOL fEnabled = FALSE; ptrModeEvent->COMGETTER(Enabled)(&fEnabled); + const char *pszSource = pListenState && pListenState->fHaveSource + ? ShClHlpSourceToString(pListenState->enmSource) : NULL; + shclHandleListenPrintPrefix(enmFormat, &EventInfo, pszSource, + "file-transfer-mode-changed", NULL /* pszAction */); if (enmFormat == CLIPBOARDLISTENFMT_JSON) { - RTStrmWrite(g_pStdOut, RT_STR_TUPLE("{\"event\":\"file-transfer-mode-changed\"")); - shclPrintEventInfoJson(&EventInfo); RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"enabled\":")); if (fEnabled) RTStrmWrite(g_pStdOut, RT_STR_TUPLE("true")); @@ -1878,21 +2058,13 @@ static void shclHandleListenPrintEvent(CLIPBOARDLISTENFMT enmFormat, const ComPt RTStrmWrite(g_pStdOut, RT_STR_TUPLE(",\"active\":true}\n")); } else if (enmFormat == CLIPBOARDLISTENFMT_MACHINE_READABLE) - { - RTPrintf("event=\"file-transfer-mode-changed\""); - shclPrintEventInfoMachineReadable(&EventInfo); RTPrintf(" enabled=\"%s\" active=\"true\"\n", fEnabled ? "true" : "false"); - } else - { - RTPrintf("clipboard: file-transfer-mode-changed"); - shclPrintEventInfoHuman(&EventInfo); - RTPrintf(" enabled=%s now-active\n", fEnabled ? "true" : "false"); - } + RTPrintf(" enabled=%s active=true\n", fEnabled ? "true" : "false"); break; } default: - shclHandleListenPrintSimpleEvent(enmFormat, "unknown", &EventInfo); + shclHandleListenPrintSimpleEvent(enmFormat, "unknown", &EventInfo, pListenState); break; } } @@ -3185,8 +3357,8 @@ static RTEXITCODE shclHandleTransferOffer(HandlerArg *pArg, int argc, char **arg } ComPtr ptrTransfer; - hrc = ptrManager->CreateTransfer(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, - ClipboardAction_Copy, ptrTransfer.asOutParam()); + hrc = ptrManager->Create(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, + ClipboardAction_Copy, ptrTransfer.asOutParam()); if (FAILED(hrc) || ptrTransfer.isNull()) { pArg->session->UnlockMachine(); @@ -3199,22 +3371,19 @@ static RTEXITCODE shclHandleTransferOffer(HandlerArg *pArg, int argc, char **arg hrc = ptrTransfer->SetSourcePaths(ComSafeArrayAsInParam(aSourcePaths)); if (FAILED(hrc)) { + HRESULT const hrcRemove = ptrManager->Remove(ptrTransfer); + if (FAILED(hrcRemove)) + shclVerbose("transfer offer: removing the unconfigured manager transfer failed: %Rhrc", hrcRemove); pArg->session->UnlockMachine(); return RTMsgErrorExit(RTEXITCODE_FAILURE, Clipboard::tr("Configuring clipboard file transfer source paths failed: %Rhrc"), hrc); } - hrc = ptrManager->Add(ptrTransfer); - if (FAILED(hrc)) - { - pArg->session->UnlockMachine(); - return RTMsgErrorExit(RTEXITCODE_FAILURE, Clipboard::tr("Publishing clipboard file transfer failed: %Rhrc"), hrc); - } - ULONG idTransfer = shclGetTransferId(ptrTransfer); if (idTransfer) - shclVerbose("transfer offer: published transfer id=%RU32", (uint32_t)idTransfer); - shclInfo(Clipboard::tr("Published %zu host path(s) as clipboard file transfer."), vecAbsSources.size()); + shclVerbose("transfer offer: configured manager transfer id=%RU32", (uint32_t)idTransfer); + shclInfo(Clipboard::tr("Configured %zu host path(s) on a manager-tracked clipboard file transfer."), + vecAbsSources.size()); RTEXITCODE rcExit = shclWaitForTransferCompletion(ptrClipboardSession, ptrManager, ClipboardTransferDirection_ToGuest, idTransfer, cMsTimeout); @@ -5113,11 +5282,22 @@ static RTEXITCODE shclHandleListen(HandlerArg *pArg, int argc, char **argv) if (rcExit != RTEXITCODE_SUCCESS) return rcExit; + ClipboardMode_T enmMode = ClipboardMode_Disabled; + hrc = shclGetMode(pArg, &enmMode); + if (SUCCEEDED(hrc) && enmMode == ClipboardMode_Disabled) + { + shclHandleListenPrintDisabledWarning(enmOutputFormat); + RTStrmFlush(g_pStdOut); + } + else if (FAILED(hrc)) + shclVerbose("getting the Shared Clipboard mode failed: %Rhrc", hrc); + shclSignalHandlerInstall(); uint64_t const msStart = RTTimeMilliTS(); uint32_t cEvents = 0; bool fTimedOut = false; + SHCLHANDLELISTENSTATE ListenState; while (cEvents < cEventsMax) { if (shclSignalWasCaught()) @@ -5148,7 +5328,7 @@ static RTEXITCODE shclHandleListen(HandlerArg *pArg, int argc, char **argv) continue; shclMarkEventProcessed(ptrEventSource, ptrListener, ptrEvent); - shclHandleListenPrintEvent(enmOutputFormat, ptrEvent, ptrClipboardSession); + shclHandleListenPrintEvent(enmOutputFormat, ptrEvent, ptrClipboardSession, &ListenState); RTStrmFlush(g_pStdOut); cEvents++; } @@ -5156,9 +5336,9 @@ static RTEXITCODE shclHandleListen(HandlerArg *pArg, int argc, char **argv) bool const fInterrupted = shclSignalWasCaught(); shclCleanupListener(ptrEventSource, ptrListener, true /* fSignalHandlerInstalled */); if (fInterrupted) - shclVerbose("listen: interrupted; shutting down"); + shclVerbose("interrupted; shutting down"); else if (fTimedOut) - shclVerbose("listen: timed out after %RU32 ms", cMsTimeout); + shclVerbose("timed out after %RU32 ms", cMsTimeout); return RTEXITCODE_SUCCESS; } @@ -5196,6 +5376,11 @@ RTEXITCODE handleClipboard(HandlerArg *pArg) | HELP_SCOPE_CLIPBOARD_TRANSFER_CANCEL); return shclHandleTransfer(pArg, pArg->argc, pArg->argv); } +#else + if ( !strcmp(pszSubcommand, "set-filetransfers") + || !strcmp(pszSubcommand, "transfer")) + return RTMsgErrorExit(RTEXITCODE_FAILURE, + Clipboard::tr("Clipboard transfer commands are not implemented on this platform.")); #endif if (!strcmp(pszSubcommand, "copy")) { diff --git a/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp b/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp index 96023c1c0fa3..e992dc501026 100644 --- a/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp +++ b/src/VBox/Frontends/VBoxManage/VBoxManageModifyVM.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxManageModifyVM.cpp 114362 2026-06-15 18:31:38Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxManageModifyVM.cpp 114979 2026-08-11 08:57:17Z knut.osmundsen@oracle.com $ */ /** @file * VBoxManage - Implementation of modifyvm command. */ @@ -2285,7 +2285,8 @@ RTEXITCODE handleModifyVM(HandlerArg *a) } break; } - #undef ITERATE_TO_NEXT_TERM +#undef ITERATE_TO_NEXT_TERM + case MODIFYVM_NATALIASMODE: { ComPtr nic; @@ -2548,20 +2549,16 @@ RTEXITCODE handleModifyVM(HandlerArg *a) { bool fEnableUsb = false; if (!RTStrICmp(ValueUnion.psz, "ps2")) - { CHECK_ERROR(sessionMachine, COMSETTER(KeyboardHIDType)(KeyboardHIDType_PS2Keyboard)); - } else if (!RTStrICmp(ValueUnion.psz, "usb")) { CHECK_ERROR(sessionMachine, COMSETTER(KeyboardHIDType)(KeyboardHIDType_USBKeyboard)); - if (SUCCEEDED(hrc)) - fEnableUsb = true; + fEnableUsb = SUCCEEDED(hrc); } else if (!RTStrICmp(ValueUnion.psz, "none")) { CHECK_ERROR(sessionMachine, COMSETTER(KeyboardHIDType)(KeyboardHIDType_None)); - if (SUCCEEDED(hrc)) - fEnableUsb = true; + fEnableUsb = SUCCEEDED(hrc); } else { @@ -2574,7 +2571,8 @@ RTEXITCODE handleModifyVM(HandlerArg *a) ULONG cOhciCtrls = 0; ULONG cXhciCtrls = 0; hrc = sessionMachine->GetUSBControllerCountByType(USBControllerType_OHCI, &cOhciCtrls); - if (SUCCEEDED(hrc)) { + if (SUCCEEDED(hrc)) + { hrc = sessionMachine->GetUSBControllerCountByType(USBControllerType_XHCI, &cXhciCtrls); if ( SUCCEEDED(hrc) && cOhciCtrls + cXhciCtrls == 0) @@ -2597,9 +2595,7 @@ RTEXITCODE handleModifyVM(HandlerArg *a) ASSERT(uart); if (!RTStrICmp(ValueUnion.psz, "disconnected")) - { CHECK_ERROR(uart, COMSETTER(HostMode)(PortMode_Disconnected)); - } else if ( !RTStrICmp(ValueUnion.psz, "server") || !RTStrICmp(ValueUnion.psz, "client") || !RTStrICmp(ValueUnion.psz, "tcpserver") @@ -2656,20 +2652,14 @@ RTEXITCODE handleModifyVM(HandlerArg *a) ASSERT(uart); if (!RTStrICmp(ValueUnion.psz, "16450")) - { CHECK_ERROR(uart, COMSETTER(UartType)(UartType_U16450)); - } else if (!RTStrICmp(ValueUnion.psz, "16550A")) - { CHECK_ERROR(uart, COMSETTER(UartType)(UartType_U16550A)); - } else if (!RTStrICmp(ValueUnion.psz, "16750")) - { CHECK_ERROR(uart, COMSETTER(UartType)(UartType_U16750)); - } else - return errorSyntax(ModifyVM::tr("Invalid argument to '%s'"), - GetOptState.pDef->pszLong); + return errorSyntax(ModifyVM::tr("Invalid argument to '%s': %s"), + GetOptState.pDef->pszLong, ValueUnion.psz); break; } diff --git a/src/VBox/Frontends/VirtualBox/src/manager/UIToolPane.cpp b/src/VBox/Frontends/VirtualBox/src/manager/UIToolPane.cpp index 472de368b909..e21dd18d7405 100644 --- a/src/VBox/Frontends/VirtualBox/src/manager/UIToolPane.cpp +++ b/src/VBox/Frontends/VirtualBox/src/manager/UIToolPane.cpp @@ -1,4 +1,4 @@ -/* $Id: UIToolPane.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: UIToolPane.cpp 114848 2026-08-04 09:49:02Z andreas.loeffler@oracle.com $ */ /** @file * VBox Qt GUI - UIToolPane class implementation. */ @@ -526,8 +526,12 @@ void UIToolPane::setErrorDetails(const QString &strDetails) void UIToolPane::setItems(const QList &items) { - /* Cache passed value: */ - m_items = items; + /* Cache accessible items only. Inaccessible machines expose limited + * information and are handled by the Error pane. */ + m_items.clear(); + foreach (UIVirtualMachineItem *pItem, items) + if (pItem && pItem->accessible()) + m_items << pItem; /* Update details pane if it is open: */ if (isToolOpened(UIToolType_Details)) diff --git a/src/VBox/Frontends/VirtualBox/src/manager/chooser/UIChooserModel.cpp b/src/VBox/Frontends/VirtualBox/src/manager/chooser/UIChooserModel.cpp index dfd78327897a..b978bfd4f40d 100644 --- a/src/VBox/Frontends/VirtualBox/src/manager/chooser/UIChooserModel.cpp +++ b/src/VBox/Frontends/VirtualBox/src/manager/chooser/UIChooserModel.cpp @@ -916,14 +916,30 @@ void UIChooserModel::startOrShowSelectedItems() void UIChooserModel::refreshSelectedMachineItems() { + /* Remember selected items */ + QStringList selectedItemDefinitions; + foreach (UIChooserItem *pSelectedItem, selectedItems()) + { + AssertPtrReturnVoid(pSelectedItem); + selectedItemDefinitions << pSelectedItem->definition(); + } + + /* Remember current item */ + UIChooserItem *pCurItem = currentItem(); + AssertPtrReturnVoid(pCurItem); + QString currentItemDefinition = pCurItem->definition(); + + /* Remember scrolling location */ + const int iScrollLocation = m_pRoot ? m_pRoot->toGroupItem()->scrollingValue() : 0; + /* Gather list of current unique inaccessible machine-items: */ QList inaccessibleMachineItemList; UIChooserItemMachine::enumerateMachineItems(selectedItems(), inaccessibleMachineItemList, UIChooserItemMachineEnumerationFlag_Unique | UIChooserItemMachineEnumerationFlag_Inaccessible); - /* Prepare item to be selected: */ - UIChooserItem *pSelectedItem = 0; + /* Ids of all local machines to refresh */ + QList localMachineItemIds; /* For each machine-item: */ foreach (UIChooserItemMachine *pItem, inaccessibleMachineItemList) @@ -939,15 +955,10 @@ void UIChooserModel::refreshSelectedMachineItems() /* Became accessible? */ if (pItem->accessible()) { - /* Acquire machine ID: */ - const QUuid uId = pItem->id(); - /* Reload this machine: */ - sltReloadMachine(uId); - /* Select first of reloaded items: */ - if (!pSelectedItem) - pSelectedItem = root()->searchForItem(uId.toString(), - UIChooserItemSearchFlag_Machine | - UIChooserItemSearchFlag_ExactId); + /* Call to the sltReloadMachine method have to be delayed because after the first call to this + * method, cache type of remaining items become UIVirtualMachineItemType_Invalid so related + * machines are not refreshed. */ + localMachineItemIds << pItem->id(); } break; @@ -978,12 +989,33 @@ void UIChooserModel::refreshSelectedMachineItems() } } - /* Some item to be selected? */ - if (pSelectedItem) + foreach (QUuid uId, localMachineItemIds) { + sltReloadMachine(uId); + } + + /* Restore selected items */ + QList itemsToSelect; + foreach (const QString &strSelectedItemDefinition, selectedItemDefinitions) { - pSelectedItem->makeSureItsVisible(); - setSelectedItem(pSelectedItem); + UIChooserItem *pItemToSelect = searchItemByDefinition(strSelectedItemDefinition); + if (pItemToSelect) + { + itemsToSelect << pItemToSelect; + } } + setSelectedItems(itemsToSelect); + makeSureAtLeastOneItemSelected(); + + /* Restore current item */ + pCurItem = searchItemByDefinition(currentItemDefinition); + if (!pCurItem || !selectedItems().contains(pCurItem)) + { + pCurItem = firstSelectedItem(); + } + setCurrentItem(pCurItem); + + /* Restore scrolling location: */ + m_pRoot->toGroupItem()->setScrollingValue(iScrollLocation); } void UIChooserModel::sortSelectedGroupItem() diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp index 5c10fc48cdd7..78cbe31514de 100644 --- a/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardDataObjectImpl-win.cpp 114661 2026-07-08 10:39:13Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardDataObjectImpl-win.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * ClipboardDataObjectImpl-win.cpp - Shared Clipboard IDataObject implementation. */ @@ -59,16 +59,19 @@ #endif ShClWinDataObject::ShClWinDataObject(void) - : m_pCtx(NULL) - , m_enmStatus(Uninitialized) + : m_enmStatus(Uninitialized) , m_rcStatus(VERR_IPE_UNINITIALIZED_STATUS) , m_lRefCount(0) , m_cFormats(0) , m_pFormatEtc(NULL) , m_pStgMedium(NULL) , m_pTransfer(NULL) - , m_pStream(NULL) , m_uObjIdx(0) + , m_fCritSectInitialized(false) + , m_fCallbacksEnabled(false) + , m_cCallbacks(0) + , m_hCallbackThread(NIL_RTNATIVETHREAD) + , m_EventCallbacksDrained(NIL_RTSEMEVENTMULTI) , m_EventListComplete(NIL_RTSEMEVENT) , m_EventStatusChanged(NIL_RTSEMEVENT) , m_cfFileDescriptorA(0) @@ -114,8 +117,6 @@ int ShClWinDataObject::Init(PSHCLCONTEXT pCtx, ShClWinDataObject::PCALLBACKS pCa int rc = VINF_SUCCESS; - m_pCtx = pCtx; /* Save opaque context. */ - /* * Set up callback context + table. */ @@ -180,16 +181,23 @@ int ShClWinDataObject::Init(PSHCLCONTEXT pCtx, ShClWinDataObject::PCALLBACKS pCa if (RT_SUCCESS(rc)) { - m_cFormats = cAllFormats; - m_enmStatus = Initialized; - rc = RTCritSectInit(&m_CritSect); if (RT_SUCCESS(rc)) { - rc = RTSemEventCreate(&m_EventListComplete); + m_fCritSectInitialized = true; + rc = RTSemEventMultiCreate(&m_EventCallbacksDrained); + if (RT_SUCCESS(rc)) + rc = RTSemEventCreate(&m_EventListComplete); if (RT_SUCCESS(rc)) rc = RTSemEventCreate(&m_EventStatusChanged); } + + if (RT_SUCCESS(rc)) + { + m_fCallbacksEnabled = true; + m_cFormats = cAllFormats; + m_enmStatus = Initialized; + } } LogFlowFunc(("cAllFormats=%RU32, rc=%Rrc\n", cAllFormats, rc)); @@ -203,6 +211,7 @@ void ShClWinDataObject::uninitInternal(void) { LogFlowFuncEnter(); + Assert(m_fCritSectInitialized); lock(); if (m_enmStatus != Uninitialized) @@ -222,9 +231,13 @@ void ShClWinDataObject::uninitInternal(void) } /* Make sure to release the transfer in any state. */ - setTransferLocked(NULL); + ShClWinDataObject *pObjToRelease = NULL; + setTransferLocked(NULL, &pObjToRelease); unlock(); + + if (pObjToRelease) + pObjToRelease->Release(); } /** @@ -234,7 +247,45 @@ void ShClWinDataObject::Uninit(void) { LogFlowFuncEnter(); - uninitInternal(); + if (m_fCritSectInitialized) + { + DisableCallbacks(); + uninitInternal(); + invalidateStreams(); + } + + /* No callback may retain or use its owner after the object is invalidated. */ + m_CallbackCtx.pvUser = NULL; + m_CallbackCtx.pThis = this; +} + +/** + * Permanently disables new backend callbacks and waits for an active callback + * running on another thread to return. + */ +void ShClWinDataObject::DisableCallbacks(void) +{ + if (!m_fCritSectInitialized) + return; + + for (;;) + { + lock(); + m_fCallbacksEnabled = false; + m_Callbacks.pfnTransferBegin = NULL; + m_Callbacks.pfnTransferEnd = NULL; + bool const fWaitCallbacks = m_cCallbacks != 0; + bool const fCallbackThread = fWaitCallbacks + && m_hCallbackThread == RTThreadNativeSelf(); + unlock(); + + if ( !fWaitCallbacks + || fCallbackThread) + break; + + int rc = RTSemEventMultiWait(m_EventCallbacksDrained, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(rc, ("Draining Windows clipboard data-object callbacks failed with %Rrc\n", rc)); + } } /** @@ -244,31 +295,41 @@ void ShClWinDataObject::Destroy(void) { LogFlowFuncEnter(); - if (m_enmStatus == Uninitialized) /* Crit sect not available anymore. */ - return; + Uninit(); - uninitInternal(); + if (m_fCritSectInitialized) + { + int rc = RTCritSectDelete(&m_CritSect); + AssertRC(rc); + m_fCritSectInitialized = false; + } - int rc = RTCritSectDelete(&m_CritSect); - AssertRC(rc); + m_CallbackCtx.pvUser = NULL; + m_CallbackCtx.pThis = this; + m_cFormats = 0; + m_enmStatus = Uninitialized; + + if (m_EventCallbacksDrained != NIL_RTSEMEVENTMULTI) + { + int rc = RTSemEventMultiDestroy(m_EventCallbacksDrained); + AssertRC(rc); + m_EventCallbacksDrained = NIL_RTSEMEVENTMULTI; + } if (m_EventListComplete != NIL_RTSEMEVENT) { - rc = RTSemEventDestroy(m_EventListComplete); + int rc = RTSemEventDestroy(m_EventListComplete); AssertRC(rc); m_EventListComplete = NIL_RTSEMEVENT; } if (m_EventStatusChanged != NIL_RTSEMEVENT) { - rc = RTSemEventDestroy(m_EventStatusChanged); + int rc = RTSemEventDestroy(m_EventStatusChanged); AssertRC(rc); m_EventStatusChanged = NIL_RTSEMEVENT; } - if (m_pStream) - m_pStream = NULL; - if (m_pFormatEtc) { delete[] m_pFormatEtc; @@ -579,7 +640,12 @@ DECLCALLBACK(int) ShClWinDataObject::readThread(PSHCLTRANSFER pTransfer, void *p if (RT_FAILURE(rc)) break; - switch (pThis->m_enmStatus) + pThis->lock(); + Status const enmStatus = pThis->m_enmStatus; + int const rcStatus = pThis->m_rcStatus; + pThis->unlock(); + + switch (enmStatus) { case Uninitialized: /* Can happen due to transfer erros. */ LogRel2(("Shared Clipboard: Data object was uninitialized\n")); @@ -603,8 +669,8 @@ DECLCALLBACK(int) ShClWinDataObject::readThread(PSHCLTRANSFER pTransfer, void *p break; case Error: - LogRel(("Shared Clipboard: Data object: Transfer error %Rrc occurred\n", pThis->m_rcStatus)); - rc = ShClTransferError(pTransfer, pThis->m_rcStatus); + LogRel(("Shared Clipboard: Data object: Transfer error %Rrc occurred\n", rcStatus)); + rc = ShClTransferError(pTransfer, rcStatus); break; default: @@ -612,11 +678,33 @@ DECLCALLBACK(int) ShClWinDataObject::readThread(PSHCLTRANSFER pTransfer, void *p break; } - if (pThis->m_Callbacks.pfnTransferEnd) + pThis->lock(); + PFNTRANSFEREND const pfnTransferEnd = pThis->m_fCallbacksEnabled + ? pThis->m_Callbacks.pfnTransferEnd : NULL; + CALLBACKCTX CallbackCtx = pThis->m_CallbackCtx; + if (pfnTransferEnd) { - int rc2 = pThis->m_Callbacks.pfnTransferEnd(&pThis->m_CallbackCtx, pTransfer, pThis->m_rcStatus); + Assert(pThis->m_cCallbacks == 0); + pThis->m_cCallbacks = 1; + pThis->m_hCallbackThread = RTThreadNativeSelf(); + int rc2 = RTSemEventMultiReset(pThis->m_EventCallbacksDrained); + AssertFatalMsgRC(rc2, ("Resetting the Windows clipboard callback-drain event failed with %Rrc\n", rc2)); + } + pThis->unlock(); + + if (pfnTransferEnd) + { + int rc2 = pfnTransferEnd(&CallbackCtx, pTransfer, rcStatus); if (RT_SUCCESS(rc)) rc = rc2; + + pThis->lock(); + Assert(pThis->m_cCallbacks == 1); + pThis->m_cCallbacks = 0; + pThis->m_hCallbackThread = NIL_RTNATIVETHREAD; + rc2 = RTSemEventMultiSignal(pThis->m_EventCallbacksDrained); + AssertFatalMsgRC(rc2, ("Signalling the Windows clipboard callback-drain event failed with %Rrc\n", rc2)); + pThis->unlock(); } break; @@ -629,6 +717,7 @@ DECLCALLBACK(int) ShClWinDataObject::readThread(PSHCLTRANSFER pTransfer, void *p LogRel(("Shared Clipboard: Transfer read thread failed with %Rrc\n", rc)); LogFlowFuncLeaveRC(rc); + pThis->Release(); return rc; } @@ -806,7 +895,7 @@ int ShClWinDataObject::createUnicodeTextFromTransferRoots(PSHCLTRANSFER pTransfe uint64_t const cRoots = ShClTransferRootsCount(pTransfer); if (!cRoots) { - LogRelMax2(16, ("Shared Clipboard: Cannot provide CF_UNICODETEXT for file transfer because the transfer has no root entries\n")); + LogRelMax(16, ("Shared Clipboard: Cannot provide CF_UNICODETEXT for file transfer because the transfer has no root entries\n")); return VERR_NOT_FOUND; } @@ -822,7 +911,7 @@ int ShClWinDataObject::createUnicodeTextFromTransferRoots(PSHCLTRANSFER pTransfe rc = RTStrCalcUtf16LenEx(pRootEntry->pszName, RTSTR_MAX, &cwcRoot); if (RT_FAILURE(rc)) { - LogRelMax2(16, ("Shared Clipboard: Cannot convert transfer root '%s' to UTF-16 length for CF_UNICODETEXT, rc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Cannot convert transfer root '%s' to UTF-16 length for CF_UNICODETEXT, rc=%Rrc\n", pRootEntry->pszName, rc)); break; } @@ -929,16 +1018,37 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) { LogRel2(("Shared Clipboard: Requesting data for IDataObject ...\n")); - if (!m_Callbacks.pfnTransferBegin) + if ( !m_fCallbacksEnabled + || !m_Callbacks.pfnTransferBegin) { - LogRelMax2(16, ("Shared Clipboard: Cannot start IDataObject transfer because no transfer-begin callback is installed\n")); + LogRelMax(16, ("Shared Clipboard: Cannot start IDataObject transfer because no transfer-begin callback is installed\n")); return VERR_INVALID_POINTER; } + if (m_cCallbacks != 0) + return VERR_TRY_AGAIN; + + PFNTRANSFERBEGIN const pfnTransferBegin = m_Callbacks.pfnTransferBegin; + CALLBACKCTX CallbackCtx = m_CallbackCtx; + m_cCallbacks = 1; + m_hCallbackThread = RTThreadNativeSelf(); + rc = RTSemEventMultiReset(m_EventCallbacksDrained); + AssertFatalMsgRC(rc, ("Resetting the Windows clipboard callback-drain event failed with %Rrc\n", rc)); /* Leave lock while requesting + waiting. */ unlock(); - rc = m_Callbacks.pfnTransferBegin(&m_CallbackCtx); + rc = pfnTransferBegin(&CallbackCtx); + + lock(); + Assert(m_cCallbacks > 0); + if (--m_cCallbacks == 0) + { + m_hCallbackThread = NIL_RTNATIVETHREAD; + int rc2 = RTSemEventMultiSignal(m_EventCallbacksDrained); + AssertFatalMsgRC(rc2, ("Signalling the Windows clipboard callback-drain event failed with %Rrc\n", rc2)); + } + unlock(); + if (RT_SUCCESS(rc)) { LogRel2(("Shared Clipboard: Waiting for IDataObject started status ...\n")); @@ -955,13 +1065,13 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) if (RT_FAILURE(rc)) { - LogRelMax2(16, ("Shared Clipboard: Waiting for IDataObject transfer to start failed, rc=%Rrc\n", rc)); + LogRelMax(16, ("Shared Clipboard: Waiting for IDataObject transfer to start failed, rc=%Rrc\n", rc)); return rc; } if (m_enmStatus != Running) { - LogRelMax2(16, ("Shared Clipboard: IDataObject transfer did not enter running state (status=%#x)\n", m_enmStatus)); + LogRelMax(16, ("Shared Clipboard: IDataObject transfer did not enter running state (status=%#x)\n", m_enmStatus)); return VERR_WRONG_ORDER; } } @@ -992,14 +1102,20 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) rc = ShClTransferStart(pTransfer); if (RT_SUCCESS(rc)) { + /* The transfer worker receives a raw user pointer, so retain the + * object until readThread() returns. */ + AddRef(); rc = ShClTransferRun(pTransfer, &ShClWinDataObject::readThread, this /* pvUser */); if (RT_SUCCESS(rc)) fNeedListWait = true; else - LogRelMax2(16, ("Shared Clipboard: Starting IDataObject transfer read thread failed with %Rrc\n", rc)); + { + Release(); + LogRelMax(16, ("Shared Clipboard: Starting IDataObject transfer read thread failed with %Rrc\n", rc)); + } } else - LogRelMax2(16, ("Shared Clipboard: Starting IDataObject transfer failed with %Rrc\n", rc)); + LogRelMax(16, ("Shared Clipboard: Starting IDataObject transfer failed with %Rrc\n", rc)); } if ( RT_SUCCESS(rc) @@ -1009,7 +1125,7 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) LogRel2(("Shared Clipboard: Waiting for IDataObject listing to arrive ...\n")); rc = RTSemEventWait(m_EventListComplete, RT_MS_10SEC); if (RT_FAILURE(rc)) - LogRelMax2(16, ("Shared Clipboard: Timed out or failed waiting for IDataObject transfer listing, rc=%Rrc\n", rc)); + LogRelMax(16, ("Shared Clipboard: Timed out or failed waiting for IDataObject transfer listing, rc=%Rrc\n", rc)); } ShClTransferRelease(pTransfer); @@ -1022,7 +1138,7 @@ int ShClWinDataObject::ensureTransferListReadyLocked(void) || m_enmStatus != Running || m_lstEntries.empty())) /* Still in running state and with a listing? */ { - LogRelMax2(16, ("Shared Clipboard: IDataObject transfer listing is unavailable after wait (transfer=%p, status=%#x, entries=%zu)\n", + LogRelMax(16, ("Shared Clipboard: IDataObject transfer listing is unavailable after wait (transfer=%p, status=%#x, entries=%zu)\n", m_pTransfer, m_enmStatus, m_lstEntries.size())); rc = VERR_SHCLPB_NO_DATA; } @@ -1118,14 +1234,17 @@ STDMETHODIMP ShClWinDataObject::GetData(LPFORMATETC pFormatEtc, LPSTGMEDIUM pMed LogRel2(("Shared Clipboard: Receiving object '%s' ...\n", fsObjEntry.pszPath)); /* Hand-in the provider so that our IStream implementation can continue working with it. */ + IStream *pStream = NULL; hr = ShClWinStreamImpl::Create(this /* pParent */, m_pTransfer, - fsObjEntry.pszPath /* File name */, &fsObjEntry.objInfo /* PSHCLFSOBJINFO */, - &m_pStream); + fsObjEntry.pszPath /* File name */, &fsObjEntry.objInfo /* PSHCLFSOBJINFO */, + &pStream); if (SUCCEEDED(hr)) { /* Hand over the stream to the caller. */ pMedium->tymed = TYMED_ISTREAM; - pMedium->pstm = m_pStream; + pMedium->pstm = pStream; + + registerStreamLocked((ShClWinStreamImpl *)pStream); } } } @@ -1320,9 +1439,10 @@ STDMETHODIMP ShClWinDataObject::StartOperation(IBindCtx *pbcReserved) * @param pTransfer Transfer to assign. * When set to NULL, the transfer will be released from the object. */ -int ShClWinDataObject::setTransferLocked(PSHCLTRANSFER pTransfer) +int ShClWinDataObject::setTransferLocked(PSHCLTRANSFER pTransfer, ShClWinDataObject **ppObjToRelease /* = NULL */) { AssertReturn(RTCritSectIsOwned(&m_CritSect), VERR_WRONG_ORDER); + AssertReturn(!ppObjToRelease || !*ppObjToRelease, VERR_INVALID_PARAMETER); LogFunc(("pTransfer=%p\n", pTransfer)); @@ -1334,14 +1454,26 @@ int ShClWinDataObject::setTransferLocked(PSHCLTRANSFER pTransfer) if (m_enmStatus == Initialized) { - m_pTransfer = pTransfer; - ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)pTransfer->pvUser; AssertPtr(pWinURITransferCtx); - pWinURITransferCtx->pDataObj = this; /* Save a backref to this object. */ + rc = RTCritSectEnter(&pWinURITransferCtx->CritSect); + if (RT_SUCCESS(rc)) + { + Assert(pWinURITransferCtx->pDataObj == NULL); + if (!pWinURITransferCtx->pDataObj) + { + AddRef(); + pWinURITransferCtx->pDataObj = this; + m_pTransfer = pTransfer; + ShClTransferAcquire(pTransfer); + } + else + rc = VERR_WRONG_ORDER; - ShClTransferAcquire(pTransfer); + int rc2 = RTCritSectLeave(&pWinURITransferCtx->CritSect); + AssertRC(rc2); + } } else AssertFailedStmt(rc = VERR_WRONG_ORDER); @@ -1353,7 +1485,22 @@ int ShClWinDataObject::setTransferLocked(PSHCLTRANSFER pTransfer) ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)m_pTransfer->pvUser; AssertPtr(pWinURITransferCtx); - pWinURITransferCtx->pDataObj = NULL; /* Release backref to this object. */ + int rc2 = RTCritSectEnter(&pWinURITransferCtx->CritSect); + AssertFatalMsgRC(rc2, ("Taking the Windows transfer-context lock while detaching failed with %Rrc\n", rc2)); + + if (pWinURITransferCtx->pDataObj == this) + { + pWinURITransferCtx->pDataObj = NULL; + if (ppObjToRelease) + *ppObjToRelease = this; + else + AssertFailed(); + } + else + Assert(pWinURITransferCtx->pDataObj == NULL); + + rc2 = RTCritSectLeave(&pWinURITransferCtx->CritSect); + AssertFatalMsgRC(rc2, ("Releasing the Windows transfer-context lock while detaching failed with %Rrc\n", rc2)); ShClTransferRelease(m_pTransfer); m_pTransfer = NULL; @@ -1378,13 +1525,53 @@ int ShClWinDataObject::SetTransfer(PSHCLTRANSFER pTransfer) { lock(); - int rc = setTransferLocked(pTransfer); + ShClWinDataObject *pObjToRelease = NULL; + int rc = setTransferLocked(pTransfer, &pObjToRelease); unlock(); + if (pObjToRelease) + pObjToRelease->Release(); + return rc; } +/** + * Registers a stream while the data-object lock is held. + * + * @param pStream Stream to register. + */ +void ShClWinDataObject::registerStreamLocked(ShClWinStreamImpl *pStream) +{ + Assert(RTCritSectIsOwned(&m_CritSect)); + AssertPtr(pStream); + + m_lstStreams.push_back(pStream); /** @todo Can this throw? */ + pStream->AddRef(); +} + +/** + * Invalidates all streams and drops the references owned by this data object. + */ +void ShClWinDataObject::invalidateStreams(void) +{ + for (;;) + { + lock(); + if (m_lstStreams.empty()) + { + unlock(); + break; + } + ShClWinStreamImpl *pStream = m_lstStreams.back(); + m_lstStreams.pop_back(); + unlock(); + + pStream->Invalidate(); + pStream->Release(); + } +} + /** * Sets a new status to the data object and signals its waiter. * diff --git a/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp index aaa50270cbcc..b6c87aa4d201 100644 --- a/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardStreamImpl-win.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: ClipboardStreamImpl-win.cpp 115068 2026-08-18 14:45:28Z andreas.loeffler@oracle.com $ */ /** @file * ClipboardStreamImpl-win.cpp - Shared Clipboard IStream object implementation (guest and host side). */ @@ -64,14 +64,23 @@ ShClWinStreamImpl::ShClWinStreamImpl(ShClWinDataObject *pParent, PSHCLTRANSFER p : m_pParent(pParent) , m_lRefCount(1) /* Our IDataObjct *always* holds the last reference to this object; needed for the callbacks. */ , m_pTransfer(pTransfer) + , m_fCritSectInitialized(false) , m_hObj(NIL_SHCLOBJHANDLE) , m_strPath(strPath) , m_objInfo(*pObjInfo) , m_cbProcessed(0) , m_fIsComplete(false) { + AssertPtr(m_pParent); AssertPtr(m_pTransfer); + int rc = RTCritSectInit(&m_CritSect); + AssertFatalMsgRC(rc, ("Initializing the Windows clipboard stream lock failed with %Rrc\n", rc)); + m_fCritSectInitialized = true; + + m_pParent->AddRef(); + ShClTransferAcquire(m_pTransfer); + LogFunc(("m_strPath=%s\n", m_strPath.c_str())); #ifdef VBOX_SHARED_CLIPBOARD_DEBUG_OBJECT_COUNTS @@ -84,6 +93,14 @@ ShClWinStreamImpl::~ShClWinStreamImpl(void) { LogFlowThisFuncEnter(); + Invalidate(); + if (m_fCritSectInitialized) + { + int rc = RTCritSectDelete(&m_CritSect); + AssertRC(rc); + m_fCritSectInitialized = false; + } + #ifdef VBOX_SHARED_CLIPBOARD_DEBUG_OBJECT_COUNTS g_cDbgStreamObj--; LogFlowFunc(("g_cDataObj=%d, g_cStreamObj=%d, g_cEnumFmtObj=%d\n", g_cDbgDataObj, g_cDbgStreamObj, g_cDbgEnumFmtObj)); @@ -185,16 +202,24 @@ STDMETHODIMP ShClWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG * { LogFlowThisFunc(("Enter: m_cbProcessed=%RU64\n", m_cbProcessed)); - /** @todo Is there any locking required so that parallel reads aren't possible? */ - if (!pvBuffer) return STG_E_INVALIDPOINTER; + int rcLock = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(rcLock, ("Taking the Windows clipboard stream lock failed with %Rrc\n", rcLock)); + + if (!m_pTransfer) + { + RTCritSectLeave(&m_CritSect); + return STG_E_REVERTED; + } + if ( nBytesToRead == 0 || m_fIsComplete) { if (nBytesRead) *nBytesRead = 0; + RTCritSectLeave(&m_CritSect); return S_OK; } @@ -232,11 +257,25 @@ STDMETHODIMP ShClWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG * { if (cbToRead) { - rc = ShClTransferObjRead(m_pTransfer, m_hObj, pvBuffer, cbToRead, 0 /* fFlags */, &cbRead); - if (RT_SUCCESS(rc)) + /* Windows treats a short IStream read as EOF, so satisfy it using + * as many transfer chunks as necessary. */ + while (cbRead < cbToRead) { - m_cbProcessed += cbRead; + uint32_t const cbToReadChunk = RT_MIN(cbToRead - cbRead, m_pTransfer->cbMaxChunkSize); + uint32_t cbReadChunk = 0; + rc = ShClTransferObjRead(m_pTransfer, m_hObj, (uint8_t *)pvBuffer + cbRead, cbToReadChunk, + 0 /* fFlags */, &cbReadChunk); + if (RT_FAILURE(rc)) + break; + + AssertBreakStmt(cbReadChunk <= cbToReadChunk, rc = VERR_TOO_MUCH_DATA); + + cbRead += cbReadChunk; + m_cbProcessed += cbReadChunk; Assert(m_cbProcessed <= cbSize); + + if (cbReadChunk < cbToReadChunk) + break; } } @@ -246,6 +285,7 @@ STDMETHODIMP ShClWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG * if (m_fIsComplete) { rc = ShClTransferObjClose(m_pTransfer, m_hObj); + m_hObj = NIL_SHCLOBJHANDLE; if (m_pParent) m_pParent->SetStatus(ShClWinDataObject::Completed); @@ -264,6 +304,9 @@ STDMETHODIMP ShClWinStreamImpl::Read(void *pvBuffer, ULONG nBytesToRead, ULONG * if (nBytesRead) *nBytesRead = (ULONG)cbRead; + int rc2 = RTCritSectLeave(&m_CritSect); + AssertRC(rc2); + if (nBytesToRead != cbRead) return S_FALSE; @@ -389,6 +432,7 @@ HRESULT ShClWinStreamImpl::Create(ShClWinDataObject *pParent, PSHCLTRANSFER pTra const Utf8Str &strPath, PSHCLFSOBJINFO pObjInfo, IStream **ppStream) { + AssertPtrReturn(pParent, E_POINTER); AssertPtrReturn(pTransfer, E_POINTER); ShClWinStreamImpl *pStream = new ShClWinStreamImpl(pParent, pTransfer, strPath, pObjInfo); @@ -401,3 +445,36 @@ HRESULT ShClWinStreamImpl::Create(ShClWinDataObject *pParent, PSHCLTRANSFER pTra return E_FAIL; } +/** + * Invalidates this stream and drops its data-object and transfer references. + */ +void ShClWinStreamImpl::Invalidate(void) +{ + if (!m_fCritSectInitialized) + return; + + int rc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(rc, ("Taking the Windows clipboard stream lock during invalidation failed with %Rrc\n", rc)); + + PSHCLTRANSFER pTransfer = m_pTransfer; + ShClWinDataObject *pParent = m_pParent; + SHCLOBJHANDLE const hObj = m_hObj; + m_pTransfer = NULL; + m_pParent = NULL; + m_hObj = NIL_SHCLOBJHANDLE; + + if ( pTransfer + && hObj != NIL_SHCLOBJHANDLE) + { + int rc2 = ShClTransferObjClose(pTransfer, hObj); + AssertRC(rc2); + } + + rc = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(rc, ("Releasing the Windows clipboard stream lock during invalidation failed with %Rrc\n", rc)); + + if (pTransfer) + ShClTransferRelease(pTransfer); + if (pParent) + pParent->Release(); +} diff --git a/src/VBox/GuestHost/SharedClipboard/VbghWaylandClipboard.cpp b/src/VBox/GuestHost/SharedClipboard/VbghWaylandClipboard.cpp index 37060feb95b9..5ca40889ca2c 100644 --- a/src/VBox/GuestHost/SharedClipboard/VbghWaylandClipboard.cpp +++ b/src/VBox/GuestHost/SharedClipboard/VbghWaylandClipboard.cpp @@ -1,4 +1,4 @@ -/* $Id: VbghWaylandClipboard.cpp 114766 2026-07-24 18:01:54Z knut.osmundsen@oracle.com $ */ +/* $Id: VbghWaylandClipboard.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Guest / Host common code - Wayland Clipboard. */ @@ -386,14 +386,14 @@ static int vbghWaylandClipboardReadGuestDataIntoCache(PSHCLWAYLANDCTX pThis, int if (RT_SUCCESS(rc)) LogRel5(("Put %zu bytes into cache for %#x (from %s)\n", cbVBoxData, fVBoxFmt, pszMimeType)); else - LogRel2(("Failed to put %zu bytes into cache for %#x (from %s): %Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to put %zu bytes into Wayland cache for %#x (from %s): %Rrc\n", cbVBoxData, fVBoxFmt, pszMimeType, rc)); } else { rc = VERR_VERSION_MISMATCH; RTCritSectLeave(&pThis->CritSect); - LogRel2(("Failed to put %zu bytes into cache for %#x (from %s): version changed\n", + LogRelMax(16, ("Shared Clipboard: Failed to put %zu bytes into Wayland cache for %#x (from %s): version changed\n", cbVBoxData, fVBoxFmt, pszMimeType)); } RTMemFree(pvVBoxData); diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp index 3fcb76e1ccef..df8232be4f48 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-common.cpp 114830 2026-07-31 10:02:47Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-common.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common helper objects. */ @@ -29,8 +29,10 @@ #define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include #include #include +#include #include #include #include @@ -38,12 +40,9 @@ #include #include +#include #include #include -#include -#ifdef VBOX_WITH_SHARED_CLIPBOARD_HOST -# include -#endif /********************************************************************************************************************************* @@ -55,6 +54,9 @@ static int shClEventSourceUnregisterEvent(PSHCLEVENTSOURCE pSource, PSHCLEVENT p static void shClEventDestroy(PSHCLEVENT pEvent); DECLINLINE(PSHCLEVENT) shclEventGet(PSHCLEVENTSOURCE pSource, SHCLEVENTID idEvent); +/** Exclusive upper bound for usable event IDs. */ +static uint32_t const g_idShClEventEnd = UINT32_MAX - 1; + /********************************************************************************************************************************* * Implementation * @@ -158,7 +160,7 @@ int ShClEventSourceInit(PSHCLEVENTSOURCE pSource, SHCLEVENTSOURCEID uID) pSource->uID = uID; /* Choose a random event ID starting point. */ - pSource->idNextEvent = RTRandU32Ex(1, VBOX_SHCL_MAX_EVENTS - 1); + pSource->idNextEvent = RTRandU32Ex(1, g_idShClEventEnd - 1); return VINF_SUCCESS; } @@ -213,13 +215,12 @@ static void shClEventSourceResetInternal(PSHCLEVENTSOURCE pSource) if (!fDealloc) Log3Func(("Event %RU32 has %RU32 references left, skipping de-allocation\n", pEvIt->idEvent, pEvIt->cRefs)); - shClEventDestroy(pEvIt); - int rc2 = shClEventSourceUnregisterEvent(pSource, pEvIt); AssertRC(rc2); if (fDealloc) { + shClEventDestroy(pEvIt); RTMemFree(pEvIt); pEvIt = NULL; } @@ -269,7 +270,7 @@ int ShClEventSourceGenerateAndRegisterEvent(PSHCLEVENTSOURCE pSource, PSHCLEVENT for (uint32_t cTries = 0;; cTries++) { SHCLEVENTID idEvent = ++pSource->idNextEvent; - if (idEvent < VBOX_SHCL_MAX_EVENTS) + if (idEvent < g_idShClEventEnd) { /* likely */ } else pSource->idNextEvent = idEvent = 1; /* zero == error, remember! */ @@ -533,7 +534,7 @@ int ShClEventWaitEx(PSHCLEVENT pEvent, RTMSINTERVAL uTimeoutMs, int *pRc, PSHCLE } if (RT_FAILURE(rc)) - LogRel2(("Shared Clipboard: Waiting for event %RU32 failed, rc=%Rrc\n", pEvent->idEvent, rc)); + LogRelMax(16, ("Shared Clipboard: Waiting for event %RU32 failed, rc=%Rrc\n", pEvent->idEvent, rc)); LogFlowFuncLeaveRC(rc); return rc; @@ -583,27 +584,38 @@ uint32_t ShClEventRelease(PSHCLEVENT pEvent) AssertReturn(ASMAtomicReadU32(&pEvent->cRefs) > 0, UINT32_MAX); - uint32_t const cRefs = ASMAtomicDecU32(&pEvent->cRefs); - if (cRefs == 0) + /* Serialize the final release with a source reset, which can detach the event while we wait for the source lock. */ + uint32_t cRefs; + int rc = VINF_SUCCESS; + PSHCLEVENTSOURCE pParent = pEvent->pParent; + if ( pParent + && RTCritSectIsInitialized(&pParent->CritSect)) { - int rc; - PSHCLEVENTSOURCE pParent = pEvent->pParent; - if ( pParent - && RTCritSectIsInitialized(&pParent->CritSect)) + rc = RTCritSectEnter(&pParent->CritSect); + if (RT_SUCCESS(rc)) { - rc = RTCritSectEnter(&pParent->CritSect); - if (RT_SUCCESS(rc)) - { + cRefs = ASMAtomicDecU32(&pEvent->cRefs); + if ( cRefs == 0 + && pEvent->pParent == pParent) rc = shClEventSourceUnregisterEvent(pParent, pEvent); - int rc2 = RTCritSectLeave(&pParent->CritSect); - if (RT_SUCCESS(rc)) - rc = rc2; - } + int rc2 = RTCritSectLeave(&pParent->CritSect); + if (RT_SUCCESS(rc)) + rc = rc2; } - else + else if (pEvent->pParent == NULL) + { + cRefs = ASMAtomicDecU32(&pEvent->cRefs); rc = VINF_SUCCESS; + } + else + return UINT32_MAX; + } + else + cRefs = ASMAtomicDecU32(&pEvent->cRefs); + if (cRefs == 0) + { if (RT_SUCCESS(rc)) { shClEventDestroy(pEvent); @@ -710,103 +722,6 @@ void ShClDbgDumpData(const void *pv, size_t cb, SHCLFORMAT uFormat) #endif /* LOG_ENABLED */ -/** - * Translates a Shared Clipboard host function number to a string. - * - * @returns Function ID string name. - * @param uFn The function to translate. - */ -const char *ShClHostFunctionToStr(uint32_t uFn) -{ - switch (uFn) - { - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_MODE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_SET_HEADLESS); - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_CANCEL); - RT_CASE_RET_STR(VBOX_SHCL_HOST_FN_ERROR); - } - return "Unknown"; -} - -/** - * Translates a Shared Clipboard host message enum to a string. - * - * @returns Message ID string name. - * @param uMsg The message to translate. - */ -const char *ShClHostMsgToStr(uint32_t uMsg) -{ - switch (uMsg) - { - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_QUIT); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_FORMATS_REPORT); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_CANCELED); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_READ_DATA_CID); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_HDR_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ROOT_LIST_ENTRY_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_OPEN); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_CLOSE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_HDR_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_LIST_ENTRY_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_OPEN); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_CLOSE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_READ); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_OBJ_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_CANCEL); - RT_CASE_RET_STR(VBOX_SHCL_HOST_MSG_TRANSFER_ERROR); - } - return "Unknown"; -} - -/** - * Translates a Shared Clipboard guest message enum to a string. - * - * @returns Message ID string name. - * @param uMsg The message to translate. - */ -const char *ShClGuestMsgToStr(uint32_t uMsg) -{ - switch (uMsg) - { - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FORMATS); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_DATA_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_CONNECT); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPORT_FEATURES); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_QUERY_FEATURES); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_GET); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_MSG_CANCEL); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_REPLY); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_HDR_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ROOT_LIST_ENTRY_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_OPEN); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_CLOSE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_HDR_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_LIST_ENTRY_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_OPEN); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_CLOSE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_READ); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_OBJ_WRITE); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_ERROR); - RT_CASE_RET_STR(VBOX_SHCL_GUEST_FN_NEGOTIATE_CHUNK_SIZE); - } - return "Unknown"; -} - /** * Converts Shared Clipboard formats to a string. * @@ -861,6 +776,62 @@ char *ShClFormatsToStrA(SHCLFORMATS fFormats) } +/********************************************************************************************************************************* +* Shared Clipboard validation * +*********************************************************************************************************************************/ + +/** + * Checks whether a value names exactly one Shared Clipboard format. + * + * @returns true if @a uFmt is a single valid VBOX_SHCL_FMT_XXX bit, false otherwise. + * @param uFmt Format value to validate. + */ +VBGH_DECL(bool) ShClFormatIsValid(SHCLFORMAT uFmt) +{ + return uFmt != VBOX_SHCL_FMT_NONE + && (uFmt & ~VBOX_SHCL_FMT_VALID_MASK) == 0 + && (uFmt & (uFmt - 1)) == 0; +} + + +/** + * Checks whether a Shared Clipboard format mask contains only known format bits. + * + * @returns true if @a fFormats only contains VBOX_SHCL_FMT_XXX bits, false otherwise. + * @param fFormats Format mask to validate. VBOX_SHCL_FMT_NONE is valid. + */ +VBGH_DECL(bool) ShClFormatsAreValid(SHCLFORMATS fFormats) +{ + return (fFormats & ~VBOX_SHCL_FMT_VALID_MASK) == 0; +} + + +/** + * Checks whether a Shared Clipboard transfer direction is valid. + * + * @returns true if @a enmDir is valid, false otherwise. + * @param enmDir Transfer direction to validate. + */ +VBGH_DECL(bool) ShClTransferDirIsValid(SHCLTRANSFERDIR enmDir) +{ + return enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST + || enmDir == SHCLTRANSFERDIR_HOST_TO_GUEST; +} + + +/** + * Checks whether a Shared Clipboard source is valid. + * + * @returns true if @a enmSource is valid, false otherwise. + * @param enmSource Source to validate. + */ +VBGH_DECL(bool) ShClSourceIsValid(SHCLSOURCE enmSource) +{ + return enmSource == SHCLSOURCE_LOCAL + || enmSource == SHCLSOURCE_REMOTE; +} + + /********************************************************************************************************************************* * Shared Clipboard Cache * *********************************************************************************************************************************/ @@ -986,22 +957,6 @@ VBGH_DECL(void) ShClCacheInvalidate(PSHCLCACHE pCache) shClCacheReInitAllEntries(pCache); } -/** - * Invalidates a specific cache entry. - * - * @param pCache Cache to invalidate. - * @param uFmt Format to invalidate entry for. - */ -VBGH_DECL(void) ShClCacheInvalidateEntry(PSHCLCACHE pCache, SHCLFORMAT uFmt) -{ - AssertPtrReturnVoid(pCache); - AssertMsgReturnVoid(pCache->u32Magic == SHCLCACHE_MAGIC, ("%#x\n", pCache->u32Magic)); - int const idxFmt = ShClFormatToBitNo(uFmt); - AssertMsgReturnVoid((unsigned)idxFmt < RT_ELEMENTS(pCache->aEntries), ("%#x/%d\n", uFmt, idxFmt)); - - shClCacheEntryReInit(&pCache->aEntries[idxFmt]); -} - /** * Gets an entry for a Shared Clipboard format. * @@ -1112,7 +1067,7 @@ VBGH_DECL(int) ShClCacheSetMultiple(PSHCLCACHE pCache, SHCLFORMATS uFmts, const AssertPtrReturn(pvData, VERR_INVALID_POINTER); AssertReturn(cbData, VERR_INVALID_PARAMETER); AssertReturn(uFmts != VBOX_SHCL_FMT_NONE, VERR_INVALID_PARAMETER); - AssertReturn(!(uFmts & ~VBOX_SHCL_FMT_VALID_MASK), VERR_INVALID_FLAGS); + AssertReturn(ShClFormatsAreValid(uFmts), VERR_INVALID_FLAGS); int rc = VINF_SUCCESS; SHCLFORMATS uFmtsLeft = uFmts; @@ -1220,317 +1175,3 @@ VBGH_DECL(int) ShClCacheTransferAll(PSHCLCACHE pCache, PSHCLCACHE pOtherCache) } return VINF_SUCCESS; } - - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_HOST -/** - * Handles clipboard formats. - * - * This validates file transfer announcements against the negotiated transfer - * features and keeps host-to-guest transfer offers separate from ordinary - * clipboard formats. Older Windows Guest Additions with transfer support - * (for example 7.2.6 and 7.2.10) expect URI-list offers to be reported on - * their own so they can replace the normal clipboard announcement with an OLE - * IDataObject. Do not rely on VBOX_SHCL_GF_0_TRANSFERS_FRONTEND here, as not - * all transfer-capable Guest Additions reliably advertise that extra bit. - * - * @returns The new Shared Clipboard formats. - * @param fHostToGuest Reporting direction. - * \c true from host -> guest. - * \c false from guest -> host. - * @param pClient Pointer to client instance. - * @param fFormats Reported clipboard formats. - */ -SHCLFORMATS shClSvcHandleFormats(bool fHostToGuest, PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - SHCLFORMATS const fFormatsOrg = fFormats; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - bool fSkipTransfers = false; - if (fFormats & VBOX_SHCL_FMT_URI_LIST) - { - uint32_t const fTransferMode = ShClSvcClientGetTransferMode(pClient); - if (!(fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) - { - LogRelMax(16, ("Shared Clipboard: File transfer format %#x was reported by %s, but file transfers are disabled (mode=%#x), masking it\n", - VBOX_SHCL_FMT_URI_LIST, fHostToGuest ? "host" : "guest", fTransferMode)); - fSkipTransfers = true; - } - - uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - uint64_t const fGuestFeatures0 = ShClSvcClientGetGuestFeatures0(pClient); - if ((fGuestFeatures0 & fRequired) != fRequired) - { - LogRelMax(16, ("Shared Clipboard: File transfer format %#x was reported by %s, but Guest Additions did not negotiate required features (features0=%#RX64, required=%#RX64), masking it\n", - VBOX_SHCL_FMT_URI_LIST, fHostToGuest ? "host" : "guest", fGuestFeatures0, fRequired)); - fSkipTransfers = true; - } - - if (fSkipTransfers) - fFormats &= ~VBOX_SHCL_FMT_URI_LIST; - else if (fHostToGuest) - { - if (fFormats != VBOX_SHCL_FMT_URI_LIST) - LogRelMax2(16, ("Shared Clipboard: Host reported file transfer together with regular formats %#x; announcing URI-list alone for Guest Additions compatibility\n", - fFormats & ~VBOX_SHCL_FMT_URI_LIST)); - fFormats = VBOX_SHCL_FMT_URI_LIST; - } - } -#else - RT_NOREF(pClient, fHostToGuest); -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - - if (LogRelIs2Enabled()) - { - char *pszFmts = ShClFormatsToStrA(fFormats); - LogRel2(("Shared Clipboard: %s reported formats %#x/'%s' to %s\n", - fHostToGuest ? "Host" : "Guest", - fFormats, pszFmts ? pszFmts : "", - fHostToGuest ? "guest" : "host")); - RTStrFree(pszFmts); - } - - if (fFormats != fFormatsOrg) - LogRelMax2(16, ("Shared Clipboard: Adjusted %s clipboard formats from %#x to %#x before reporting to %s\n", - fHostToGuest ? "host" : "guest", fFormatsOrg, fFormats, fHostToGuest ? "guest" : "host")); - - return fFormats; -} - -void ShClSvcClientLock(PSHCLCLIENT pClient) -{ - int rc2 = RTCritSectEnter(&pClient->CritSect); - AssertRC(rc2); -} - -void ShClSvcClientUnlock(PSHCLCLIENT pClient) -{ - int rc2 = RTCritSectLeave(&pClient->CritSect); - AssertRC(rc2); -} - -/** - * Allocates a new clipboard message. - * - * @returns Allocated clipboard message, or NULL on failure. - * @param pClient The client which is target of this message. - * @param idMsg The message ID (VBOX_SHCL_HOST_MSG_XXX) to use - * @param cParms The number of parameters the message takes. - */ -PSHCLCLIENTMSG ShClSvcClientMsgAlloc(PSHCLCLIENT pClient, uint32_t idMsg, uint32_t cParms) -{ - RT_NOREF(pClient); - PSHCLCLIENTMSG pMsg = (PSHCLCLIENTMSG)RTMemAllocZ(RT_UOFFSETOF_DYN(SHCLCLIENTMSG, aParms[cParms])); - if (pMsg) - { - uint32_t cAllocated = ASMAtomicIncU32(&pClient->cMsgAllocated); - if (cAllocated <= 4096) - { - RTListInit(&pMsg->ListEntry); - pMsg->cParms = cParms; - pMsg->idMsg = idMsg; - return pMsg; - } - AssertMsgFailed(("Too many messages allocated for client %u! (%u)\n", pClient->State.uClientID, cAllocated)); - ASMAtomicDecU32(&pClient->cMsgAllocated); - RTMemFree(pMsg); - } - return NULL; -} - -/** - * Frees a formerly allocated client clipboard message. - * - * @param pClient The client which was the target of this message. - * @param pMsg Clipboard message to free. - */ -void ShClSvcClientMsgFree(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) -{ - RT_NOREF(pClient); - /** @todo r=bird: Do accounting. */ - if (pMsg) - { - pMsg->idMsg = UINT32_C(0xdeadface); - RTMemFree(pMsg); - - uint32_t cAllocated = ASMAtomicDecU32(&pClient->cMsgAllocated); - Assert(cAllocated < UINT32_MAX / 2); - RT_NOREF(cAllocated); - } -} - -/** - * Sets the VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT - * return parameters. - * - * @param pMsg Message to set return parameters to. - * @param paDstParms The peek parameter vector. - * @param cDstParms The number of peek parameters (at least two). - * @remarks ASSUMES the parameters has been cleared by clientMsgPeek. - */ -void shClSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) -{ - Assert(cDstParms >= 2); - if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT) - paDstParms[0].u.uint32 = pMsg->idMsg; - else - paDstParms[0].u.uint64 = pMsg->idMsg; - paDstParms[1].u.uint32 = pMsg->cParms; - - uint32_t i = RT_MIN(cDstParms, pMsg->cParms + 2); - while (i-- > 2) - switch (pMsg->aParms[i - 2].type) - { - case VBOX_HGCM_SVC_PARM_32BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint32_t); break; - case VBOX_HGCM_SVC_PARM_64BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint64_t); break; - case VBOX_HGCM_SVC_PARM_PTR: paDstParms[i].u.uint32 = pMsg->aParms[i - 2].u.pointer.size; break; - } -} - -/** - * Sets the VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT return parameters. - * - * @returns VBox status code. - * @param pMsg The message which parameters to return to the guest. - * @param paDstParms The peek parameter vector. - * @param cDstParms The number of peek parameters should be exactly two - */ -int shClSvcMsgSetOldWaitReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) -{ - /* - * Assert sanity. - */ - AssertPtr(pMsg); - AssertPtrReturn(paDstParms, VERR_INVALID_POINTER); - AssertReturn(cDstParms >= 2, VERR_INVALID_PARAMETER); - - Assert(pMsg->cParms == 2); - Assert(pMsg->aParms[0].u.uint32 == pMsg->idMsg); - switch (pMsg->idMsg) - { - case VBOX_SHCL_HOST_MSG_READ_DATA: - case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: - break; - default: - AssertFailed(); - } - - /* - * Set the parameters. - */ - if (pMsg->cParms > 0) - paDstParms[0] = pMsg->aParms[0]; - if (pMsg->cParms > 1) - paDstParms[1] = pMsg->aParms[1]; - return VINF_SUCCESS; -} - - -/** - * Wakes up a pending client (i.e. waiting for new messages). - * - * @returns VBox status code. - * @retval VINF_NO_CHANGE if the client is not in pending mode. - * @param pClient Client to wake up. - * - * @note Caller must enter critical section. - */ -int ShClSvcClientWakeup(PSHCLCLIENT pClient) -{ - Assert(RTCritSectIsOwner(&pClient->CritSect)); - int rc = VINF_NO_CHANGE; - - if (pClient->Pending.uType != 0) - { - LogFunc(("[Client %RU32] Waking up ...\n", pClient->State.uClientID)); - - PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); - AssertReturn(pFirstMsg, VERR_INTERNAL_ERROR); - - LogFunc(("[Client %RU32] Current host message is %s (%RU32), cParms=%RU32\n", - pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); - - if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) - shClSvcMsgSetPeekReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); - else if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) /* Legacy, Guest Additions < 6.1. */ - shClSvcMsgSetOldWaitReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); - else - AssertMsgFailedReturn(("pClient->Pending.uType=%u\n", pClient->Pending.uType), VERR_INTERNAL_ERROR_3); - - rc = pClient->pBackend->pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); - - if ( rc != VERR_CANCELLED - && pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) - { - RTListNodeRemove(&pFirstMsg->ListEntry); - ShClSvcClientMsgFree(pClient, pFirstMsg); - } - - pClient->Pending.hHandle = NULL; - pClient->Pending.paParms = NULL; - pClient->Pending.cParms = 0; - pClient->Pending.uType = 0; - } - else - LogFunc(("[Client %RU32] Not in pending state, skipping wakeup\n", pClient->State.uClientID)); - - return rc; -} - -/** - * Appends a message to the client's queue and wake it up. - * - * @returns VBox status code, though the message is consumed regardless of what - * is returned. - * @param pClient The client to queue the message on. - * @param pMsg The message to queue. Ownership is always - * transfered to the queue. - * - * @note Caller must enter critical section. - */ -int shClSvcClientMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) -{ - Assert(RTCritSectIsOwner(&pClient->CritSect)); - AssertPtr(pMsg); - AssertPtr(pClient); - LogFlowFunc(("idMsg=%s (%u) cParms=%u\n", ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms)); - - RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); - int const rc = ShClSvcClientWakeup(pClient); - if (RT_FAILURE(rc)) - { - PSHCLCLIENTMSG pQueued; - RTListForEach(&pClient->MsgQueue, pQueued, SHCLCLIENTMSG, ListEntry) - if (pQueued == pMsg) - { - RTListNodeRemove(&pQueued->ListEntry); - ShClSvcClientMsgFree(pClient, pQueued); - break; - } - } - return rc; -} - -/** - * Adds a new message to a client'S message queue. - * - * @param pClient Pointer to the client data structure to add new message to. - * @param pMsg Pointer to message to add. The queue then owns the pointer. - * @param fAppend Whether to append or prepend the message to the queue. - * - * @note Caller must enter critical section. - */ -void ShClSvcClientMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend) -{ - Assert(RTCritSectIsOwner(&pClient->CritSect)); - AssertPtr(pMsg); - - LogFlowFunc(("idMsg=%s (%RU32) cParms=%RU32 fAppend=%RTbool\n", - ShClHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms, fAppend)); - - if (fAppend) - RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); - else - RTListPrepend(&pClient->MsgQueue, &pMsg->ListEntry); -} - -#endif /* VBOX_WITH_SHARED_CLIPBOARD_HOST */ diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp index 0af574ba3f9b..0eeeab45fc43 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-helper.cpp 114834 2026-07-31 11:53:00Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-helper.cpp 115045 2026-08-17 14:51:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Helper functions. */ @@ -29,7 +29,7 @@ #include #include -#include +#include #include #include #include @@ -73,31 +73,6 @@ int ShClHlpUtf8ValidateExact(const char *pchSrc, size_t cbSrc, size_t *pcchText) return rc; } -int ShClHlpUtf16DupExact(PCRTUTF16 pwszSrc, size_t cwcSrc, PRTUTF16 *ppwszDst) -{ - AssertPtrReturn(pwszSrc, VERR_INVALID_POINTER); - AssertPtrReturn(ppwszDst, VERR_INVALID_POINTER); - *ppwszDst = NULL; - AssertReturn(cwcSrc, VERR_INVALID_PARAMETER); - - size_t cwcText = 0; - int rc = RTUtf16LenAndValidateEncoding(pwszSrc, cwcSrc, 0 /* fFlags */, NULL /* pcuc */, &cwcText); - if (RT_FAILURE(rc)) - return rc; - if (cwcText < cwcSrc - 1) - return VERR_BUFFER_UNDERFLOW; - if (cwcText >= RTSTR_MAX / sizeof(RTUTF16)) - return VERR_TOO_MUCH_DATA; - - PRTUTF16 pwszDst = RTUtf16Alloc((cwcText + 1) * sizeof(RTUTF16)); - if (!pwszDst) - return VERR_NO_UTF16_MEMORY; - memcpy(pwszDst, pwszSrc, cwcText * sizeof(RTUTF16)); - pwszDst[cwcText] = '\0'; - *ppwszDst = pwszDst; - return VINF_SUCCESS; -} - int ShClHlpConvUtf16CRLFToUtf8LF(PCRTUTF16 pwszSrc, size_t cwcSrc, char *pszBuf, size_t cbBuf, size_t *pcbLen) { AssertPtrReturn(pwszSrc, VERR_INVALID_POINTER); @@ -852,31 +827,6 @@ const char *ShClHlpTransferStateToString(uint32_t uState) } -/** - * Converts a Main API clipboard event type value to a printable string. - * - * @returns Printable event type name. - * @param uEventType Main API VBoxEventType value. - */ -const char *ShClHlpVBoxEventTypeToString(uint32_t uEventType) -{ - switch (uEventType) - { - case 72: return "OnClipboardModeChanged"; - case 104: return "OnClipboardFileTransferModeChanged"; - case 122: return "OnClipboardError"; - case 126: return "OnClipboardSourceChanged"; - case 127: return "OnClipboardFormatChanged"; - case 128: return "OnClipboardDataChanged"; - case 129: return "OnClipboardTransfer"; - case 130: return "OnClipboardDataRequested"; - default: break; - } - - AssertFailedReturn("unknown"); -} - - /** * Parses a clipboard sharing mode value. * @@ -992,22 +942,37 @@ void ShClHlpPrintEscapedString(PRTSTREAM pStrm, const char *pszText, size_t cchT return; AssertPtrReturnVoid(pszText); + size_t offPending = 0; for (size_t i = 0; i < cchText; i++) { unsigned char const ch = (unsigned char)pszText[i]; + const char *pszEscape = NULL; + size_t cchEscape = 0; switch (ch) { - case '\n': RTStrmWrite(pStrm, RT_STR_TUPLE("\\n")); break; - case '\r': RTStrmWrite(pStrm, RT_STR_TUPLE("\\r")); break; - case '\t': RTStrmWrite(pStrm, RT_STR_TUPLE("\\t")); break; - case '\\': RTStrmWrite(pStrm, RT_STR_TUPLE("\\\\")); break; - case '"': RTStrmWrite(pStrm, RT_STR_TUPLE("\\\"")); break; + case '\n': pszEscape = "\\n"; cchEscape = 2; break; + case '\r': pszEscape = "\\r"; cchEscape = 2; break; + case '\t': pszEscape = "\\t"; cchEscape = 2; break; + case '\\': pszEscape = "\\\\"; cchEscape = 2; break; + case '"': pszEscape = "\\\""; cchEscape = 2; break; default: - if (ch >= 0x20) - RTStrmPutCh(pStrm, ch); - else + if (ch < 0x20) + { + if (i > offPending) + RTStrmWrite(pStrm, &pszText[offPending], i - offPending); RTStrmPrintf(pStrm, "\\x%02x", ch); + offPending = i + 1; + } break; } + if (pszEscape) + { + if (i > offPending) + RTStrmWrite(pStrm, &pszText[offPending], i - offPending); + RTStrmWrite(pStrm, pszEscape, cchEscape); + offPending = i + 1; + } } + if (offPending < cchText) + RTStrmWrite(pStrm, &pszText[offPending], cchText - offPending); } diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-mime.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-mime.cpp deleted file mode 100644 index 63a5fbe02696..000000000000 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-mime.cpp +++ /dev/null @@ -1,53 +0,0 @@ -/* $Id: clipboard-mime.cpp 114575 2026-06-30 15:58:06Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard - Path list class. - */ - -/* - * Copyright (C) 2019-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD -#include - -#include - - -bool ShClMIMEHasFileURLs(const char *pcszFormat, size_t cchFormatMax) -{ - /** @todo "text/uri" also an official variant? */ - return ( RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax) == 0 - || RTStrNICmp(pcszFormat, "x-special/gnome-icon-list", cchFormatMax) == 0); -} - -bool ShClMIMENeedsCache(const char *pcszFormat, size_t cchFormatMax) -{ - bool fNeedsDropDir = false; - if (!RTStrNICmp(pcszFormat, "text/uri-list", cchFormatMax)) /** @todo Add "x-special/gnome-icon-list"? */ - fNeedsDropDir = true; - - return fNeedsDropDir; -} - diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp index b7cc16959b20..9fdfc3ee01d0 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-path.cpp 114830 2026-07-31 10:02:47Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-path.cpp 115086 2026-08-19 13:09:34Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Path handling. */ @@ -32,23 +32,57 @@ #define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD #include -#include #include -#include #include #include -#include + #ifdef RT_OS_WINDOWS -# include -#endif +/** + * Returns whether a filename uses a reserved Windows device name. + * + * Matching is case-insensitive and only considers the part before the first + * dot, as reserved device names remain reserved when followed by an extension. + * + * @returns Whether @a pszName is a reserved Windows device filename. + * @param pszName Zero-terminated UTF-8 filename to inspect. + */ +static bool shClPathIsWindowsReservedFilename(const char *pszName) +{ + size_t const cchBase = strcspn(pszName, "."); + if (cchBase == 3) + { + if ( RTStrNICmp(pszName, "CON", 3) == 0 + || RTStrNICmp(pszName, "PRN", 3) == 0 + || RTStrNICmp(pszName, "AUX", 3) == 0 + || RTStrNICmp(pszName, "NUL", 3) == 0) + return true; + } + else if ( cchBase >= 4 + && ( RTStrNICmp(pszName, "COM", 3) == 0 + || RTStrNICmp(pszName, "LPT", 3) == 0)) + { + size_t const cbSuffix = cchBase - 3; + if ( ( cbSuffix == 1 + && pszName[3] >= '1' + && pszName[3] <= '9') + || ( cbSuffix == 2 + && ( !memcmp(&pszName[3], "\xc2\xb9", 2) /* SUPERSCRIPT ONE */ + || !memcmp(&pszName[3], "\xc2\xb2", 2) /* SUPERSCRIPT TWO */ + || !memcmp(&pszName[3], "\xc2\xb3", 2) /* SUPERSCRIPT THREE */))) + return true; + } + + return false; +} +#endif /* RT_OS_WINDOWS */ /** - * Sanitizes the file name component so that unsupported characters - * will be replaced by an underscore ("_"). + * Sanitizes the file name component so that unsupported characters and + * reserved Windows device names will be replaced by an underscore ("_"). * - * @return IPRT status code. + * @returns IPRT status code. * @param pszPath Path to sanitize. * @param cbPath Size (in bytes) of path to sanitize. */ @@ -57,23 +91,28 @@ int ShClPathSanitizeFilename(char *pszPath, size_t cbPath) int rc = VINF_SUCCESS; #ifdef RT_OS_WINDOWS RT_NOREF1(cbPath); - /* Replace out characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */ + /* Replace characters not allowed on Windows platforms, put in by RTTimeSpecToString(). */ /** @todo Use something like RTPathSanitize() if available later some time. */ - static const RTUNICP s_uszValidRangePairs[] = + static const RTUNICP s_aValidRangePairs[] = { - ' ', ' ', - '(', ')', - '-', '.', + ' ', '!', + '#', ')', + '+', '.', '0', '9', - 'A', 'Z', - 'a', 'z', - '_', '_', - 0xa0, 0xd7af, + ';', ';', + '=', '=', + '@', '[', + ']', '{', + '}', '~', + 0x80, 0xd7ff, + 0xe000, 0x10ffff, '\0' }; - ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_uszValidRangePairs, '_' /* chReplacement */); + ssize_t cReplaced = RTStrPurgeComplementSet(pszPath, s_aValidRangePairs, '_' /* chReplacement */); if (cReplaced < 0) rc = VERR_INVALID_UTF8_ENCODING; + else if (shClPathIsWindowsReservedFilename(pszPath)) + pszPath[0] = '_'; #else RT_NOREF2(pszPath, cbPath); #endif @@ -141,222 +180,3 @@ int ShClPathSanitize(char *pszPath, size_t cbPath) return VINF_SUCCESS; } - - -static int shClPathQueryIsSymlink(const char *pszPath, PCRTFSOBJINFO pObjInfo, bool *pfIsSymlink) -{ - *pfIsSymlink = RTFS_IS_SYMLINK(pObjInfo->Attr.fMode); -#ifdef RT_OS_WINDOWS - int rc = VINF_SUCCESS; - if (!*pfIsSymlink) - { - PRTUTF16 pwszPath = NULL; - rc = RTPathWinFromUtf8(&pwszPath, pszPath, 0 /* fFlags */); - if (RT_SUCCESS(rc)) - { - DWORD const fAttributes = GetFileAttributesW(pwszPath); - if (fAttributes == INVALID_FILE_ATTRIBUTES) - rc = RTErrConvertFromWin32(GetLastError()); - else - *pfIsSymlink = RT_BOOL(fAttributes & FILE_ATTRIBUTE_REPARSE_POINT); - RTPathWinFree(pwszPath); - } - } - return rc; -#elif defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) - /* Keep a positive result from either the portable query or the native backend. */ - if (RTSymlinkExists(pszPath)) - *pfIsSymlink = true; - return VINF_SUCCESS; -#else - RT_NOREF(pszPath); - return VINF_SUCCESS; -#endif -} - - -/** - * Checks whether a path is a symbolic link or equivalent. - * - * On Windows this includes all reparse points, such as directory junctions. - * - * @returns Whether @a pszPath is a symbolic link or equivalent. - * @param pszPath Path to inspect. - */ -bool ShClPathIsSymlink(const char *pszPath) -{ - AssertPtrReturn(pszPath, false); - - RTFSOBJINFO ObjInfo; - int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); - bool fIsSymlink = false; - if (RT_SUCCESS(rc)) - rc = shClPathQueryIsSymlink(pszPath, &ObjInfo, &fIsSymlink); - return RT_SUCCESS(rc) && fIsSymlink; -} - - -static int shClPathValidateDirectory(const char *pszPath) -{ - RTFSOBJINFO ObjInfo; - int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); - bool fIsSymlink = false; - if (RT_SUCCESS(rc)) - rc = shClPathQueryIsSymlink(pszPath, &ObjInfo, &fIsSymlink); - if (RT_SUCCESS(rc)) - { - if (fIsSymlink) - rc = VERR_IS_A_SYMLINK; - else if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) - rc = VERR_NOT_A_DIRECTORY; - } - return rc; -} - - -/** - * Checks whether a path is a real directory rather than a symbolic link or - * Windows reparse point. - * - * @returns Whether @a pszPath is a real directory. - * @param pszPath Path to inspect. - */ -bool ShClPathIsDirectory(const char *pszPath) -{ - AssertPtrReturn(pszPath, false); - return RT_SUCCESS(shClPathValidateDirectory(pszPath)); -} - - -/** - * Creates missing components of an absolute directory path. - * - * Every component is validated without accepting symbolic links or Windows - * reparse points. A concurrent creator is accepted only if it created the - * expected real directory. - * - * @returns VBox status code. - * @param pszPath Absolute directory path to create. - */ -int ShClDirectoryCreate(const char *pszPath) -{ - AssertPtrReturn(pszPath, VERR_INVALID_POINTER); - - int rc = RTStrValidateEncoding(pszPath); - if (RT_FAILURE(rc)) - return rc; - - PRTPATHSPLIT pSplit = NULL; - rc = RTPathSplitA(pszPath, &pSplit, RTPATH_STR_F_STYLE_HOST); - if (RT_FAILURE(rc)) - return rc; - if ( !(pSplit->fProps & RTPATH_PROP_ABSOLUTE) - || !pSplit->cComps) - { - RTPathSplitFree(pSplit); - return VERR_INVALID_NAME; - } - - char szPath[RTPATH_MAX]; - rc = RTStrCopy(szPath, sizeof(szPath), pSplit->apszComps[0]); - for (uint16_t i = 0; RT_SUCCESS(rc) && i < pSplit->cComps; ++i) - { - if (i) - rc = RTPathAppend(szPath, sizeof(szPath), pSplit->apszComps[i]); - if (RT_FAILURE(rc)) - break; - - rc = shClPathValidateDirectory(szPath); - if ( RT_FAILURE(rc) - && i > 0 - && ( rc == VERR_FILE_NOT_FOUND - || rc == VERR_PATH_NOT_FOUND)) - { - rc = RTDirCreate(szPath, 0700, 0 /* fCreate */); - if (RT_FAILURE(rc)) - { - int const rcCreate = rc; - rc = shClPathValidateDirectory(szPath); - if ( rc == VERR_FILE_NOT_FOUND - || rc == VERR_PATH_NOT_FOUND) - rc = rcCreate; - } - else - rc = shClPathValidateDirectory(szPath); - } - } - - RTPathSplitFree(pSplit); - return rc; -} - - -/** - * Builds a host path below a transfer destination from a validated - * transfer-relative path. - * - * @returns VBox status code. - * @param pszDestination Absolute host destination directory. - * @param pszTransferPath Transfer-relative path. - * @param pszHostPath Where to return the host path. - * @param cbHostPath Size of @a pszHostPath. - */ -int ShClHlpTransferPathToHostPath(const char *pszDestination, const char *pszTransferPath, - char *pszHostPath, size_t cbHostPath) -{ - AssertPtrReturn(pszDestination, VERR_INVALID_POINTER); - AssertPtrReturn(pszTransferPath, VERR_INVALID_POINTER); - AssertPtrReturn(pszHostPath, VERR_INVALID_POINTER); - AssertReturn(cbHostPath, VERR_INVALID_PARAMETER); - if ( !*pszDestination - || !*pszTransferPath - || pszTransferPath[0] == '/' - || pszTransferPath[0] == '\\' - || strchr(pszTransferPath, '\\') - || strchr(pszTransferPath, ':')) - return VERR_INVALID_NAME; - - int rc = RTStrValidateEncoding(pszTransferPath); - if (RT_FAILURE(rc)) - return rc; - for (const char *pszCur = pszTransferPath; *pszCur; pszCur = RTStrNextCp(pszCur)) - { - RTUNICP const uc = RTStrGetCp(pszCur); - if ( uc < 0x20 - || (uc >= 0x7f && uc <= 0x9f)) - return VERR_INVALID_NAME; - } - rc = RTStrValidateEncoding(pszDestination); - if (RT_FAILURE(rc)) - return rc; - rc = RTStrCopy(pszHostPath, cbHostPath, pszDestination); - if (RT_FAILURE(rc)) - return rc; - - char *pszCopy = RTStrDup(pszTransferPath); - if (!pszCopy) - return VERR_NO_MEMORY; - - char *psz = pszCopy; - while (RT_SUCCESS(rc) && *psz) - { - char *pszSlash = strchr(psz, '/'); - if (pszSlash) - *pszSlash = '\0'; - if ( !*psz - || !strcmp(psz, ".") - || !strcmp(psz, "..") - || (pszSlash && !pszSlash[1])) - { - rc = VERR_INVALID_NAME; - break; - } - rc = RTPathAppend(pszHostPath, cbHostPath, psz); - if (!pszSlash) - break; - psz = pszSlash + 1; - } - - RTStrFree(pszCopy); - return rc; -} diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp index 31c8796b2eaa..a630e69a673e 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-http.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-http.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: HTTP server implementation for Shared Clipboard transfers on UNIX-y guests / hosts. */ @@ -60,8 +60,7 @@ #include #include -#include -#include +#include /********************************************************************************************************************************* @@ -78,18 +77,40 @@ typedef struct _SHCLHTTPSERVERTRANSFER { /** The node list. */ RTLISTNODE Node; - /** Pointer to associated transfer. */ + /** Pointer to associated transfer. Held by one transfer reference while this structure exists. */ PSHCLTRANSFER pTransfer; - /** Critical section for serializing access. */ + /** Critical section protecting the request count and drain event state. */ RTCRITSECT CritSect; - /** The handle we're going to use for this HTTP transfer. */ - SHCLOBJHANDLE hObj; + /** Number of references to this registration (list owner, requests and an eventual drain waiter). */ + volatile uint32_t cRefs; + /** Number of active HTTP requests using this registration. */ + uint32_t cRequests; + /** Signaled when the active request count transitions to zero. */ + RTSEMEVENTMULTI hRequestsDrained; + /** Whether the registration still is visible in the server lookup list. Protected by the server lock. */ + bool fRegistered; + /** Service session portion of the immutable transfer key. */ + SHCLSESSIONID idSession; + /** Transfer ID portion of the immutable transfer key. */ + SHCLTRANSFERID idTransfer; + /** Generation portion of the immutable transfer key. */ + SHCLTRANSFERGEN uGeneration; /** The virtual path of the HTTP server's root directory for this transfer. * Always has to start with a "/". Unescaped. */ char szPathVirtual[RTPATH_MAX]; } SHCLHTTPSERVERTRANSFER; typedef SHCLHTTPSERVERTRANSFER *PSHCLHTTPSERVERTRANSFER; +/** Per-request state. In particular, object handles must never be shared by two HTTP requests. */ +typedef struct _SHCLHTTPSERVERREQUEST +{ + /** Retained registration used by this request. */ + PSHCLHTTPSERVERTRANSFER pSrvTx; + /** Object handle opened for this request. */ + SHCLOBJHANDLE hObj; +} SHCLHTTPSERVERREQUEST; +typedef SHCLHTTPSERVERREQUEST *PSHCLHTTPSERVERREQUEST; + /********************************************************************************************************************************* * Prototypes * @@ -146,7 +167,6 @@ DECLINLINE(void) shClTransferHttpServerUnlock(PSHCLHTTPSERVER pSrv) AssertRC(rc2); } -#if 0 /* unused */ /** * Locks an HTTP transfer. * @@ -168,7 +188,131 @@ DECLINLINE(void) shClHttpTransferUnlock(PSHCLHTTPSERVERTRANSFER pSrvTx) int rc2 = RTCritSectLeave(&pSrvTx->CritSect); AssertRC(rc2); } -#endif + +/** + * Retains an HTTP transfer registration. + * + * @returns New reference count. + * @param pSrvTx HTTP transfer registration to retain. + */ +static uint32_t shClHttpTransferRetain(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + uint32_t const cRefs = ASMAtomicIncU32(&pSrvTx->cRefs); + Assert(cRefs > 1 && cRefs < UINT32_MAX / 2); + return cRefs; +} + +/** + * Releases an HTTP transfer registration and destroys it on the final release. + * + * @returns New reference count. + * @param pSrvTx HTTP transfer registration to release. + */ +static uint32_t shClHttpTransferRelease(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + Assert(ASMAtomicReadU32(&pSrvTx->cRefs) > 0); + + uint32_t const cRefs = ASMAtomicDecU32(&pSrvTx->cRefs); + if (cRefs == 0) + { + Assert(!pSrvTx->fRegistered); + Assert(pSrvTx->cRequests == 0); + + if (RTCritSectIsInitialized(&pSrvTx->CritSect)) + { + int rc2 = RTCritSectDelete(&pSrvTx->CritSect); + AssertRC(rc2); + } + + int rc2 = RTSemEventMultiDestroy(pSrvTx->hRequestsDrained); + AssertRC(rc2); + pSrvTx->hRequestsDrained = NIL_RTSEMEVENTMULTI; + + ShClTransferRelease(pSrvTx->pTransfer); + pSrvTx->pTransfer = NULL; + + RTMemFree(pSrvTx); + } + + return cRefs; +} + +/** + * Adds an active request reference to a registered HTTP transfer. + * + * @returns VBox status code. + * @param pSrvTx HTTP transfer registration to retain for a request. + * + * @note The caller must own the HTTP server lock, which prevents the + * registration from being detached while the request is added. + */ +static int shClHttpTransferRequestRetain(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + Assert(pSrvTx->fRegistered); + + shClHttpTransferRetain(pSrvTx); + shClHttpTransferLock(pSrvTx); + + int rc = VINF_SUCCESS; + if (pSrvTx->cRequests == 0) + rc = RTSemEventMultiReset(pSrvTx->hRequestsDrained); + if (RT_SUCCESS(rc)) + pSrvTx->cRequests++; + + shClHttpTransferUnlock(pSrvTx); + + if (RT_FAILURE(rc)) + shClHttpTransferRelease(pSrvTx); + return rc; +} + +/** + * Releases an active request reference to an HTTP transfer registration. + * + * @param pSrvTx HTTP transfer registration to release for a request. + */ +static void shClHttpTransferRequestRelease(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + shClHttpTransferLock(pSrvTx); + + Assert(pSrvTx->cRequests > 0); + pSrvTx->cRequests--; + if (pSrvTx->cRequests == 0) + { + int rc2 = RTSemEventMultiSignal(pSrvTx->hRequestsDrained); + AssertRC(rc2); + } + + shClHttpTransferUnlock(pSrvTx); + shClHttpTransferRelease(pSrvTx); +} + +/** + * Waits for all requests using a detached HTTP transfer registration to end. + * + * @returns VBox status code. + * @param pSrvTx Detached HTTP transfer registration to drain. + */ +static int shClHttpTransferDrainRequests(PSHCLHTTPSERVERTRANSFER pSrvTx) +{ + Assert(!pSrvTx->fRegistered); + + int rc = VINF_SUCCESS; + for (;;) + { + shClHttpTransferLock(pSrvTx); + uint32_t const cRequests = pSrvTx->cRequests; + shClHttpTransferUnlock(pSrvTx); + if (cRequests == 0) + break; + + rc = RTSemEventMultiWait(pSrvTx->hRequestsDrained, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + } + + return rc; +} /** * Creates an URL from a given path, extended version. @@ -227,13 +371,46 @@ static int shClTransferHttpURLCreateFromPath(const char *pszPath, char **ppszURL * @returns Pointer to HTTP server transfer if found, NULL if not found. * @param pSrv HTTP server instance. * @param idTransfer Transfer ID to return HTTP server transfer for. + * + * @note Caller needs to take the server critical section. */ DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpServerGetTransferById(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTransfer) { + Assert(RTCritSectIsOwner(&pSrv->CritSect)); + PSHCLHTTPSERVERTRANSFER pSrvTx; RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node) /** @todo Slow O(n) lookup, but does it for now. */ { - if (pSrvTx->pTransfer->State.uID == idTransfer) + if (pSrvTx->idTransfer == idTransfer) + return pSrvTx; + } + + return NULL; +} + +/** + * Returns the HTTP server transfer matching an exact transfer key. + * + * @returns Pointer to HTTP server transfer if found, NULL if not found. + * @param pSrv HTTP server instance. + * @param idSession Service session ID to match. + * @param idTransfer Transfer ID to match. + * @param uGeneration Transfer generation to match. + * + * @note Caller needs to take the server critical section. + */ +DECLINLINE(PSHCLHTTPSERVERTRANSFER) +shClTransferHttpServerGetTransferByKey(PSHCLHTTPSERVER pSrv, SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration) +{ + Assert(RTCritSectIsOwner(&pSrv->CritSect)); + + PSHCLHTTPSERVERTRANSFER pSrvTx; + RTListForEach(&pSrv->lstTransfers, pSrvTx, SHCLHTTPSERVERTRANSFER, Node) + { + if ( pSrvTx->idSession == idSession + && pSrvTx->idTransfer == idTransfer + && pSrvTx->uGeneration == uGeneration) return pSrvTx; } @@ -244,17 +421,20 @@ DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpServerGetTransferById(PSHCLH * Returns a HTTP server transfer from a given URL. * * @returns Pointer to HTTP server transfer if found, NULL if not found. - * @param pThis HTTP server instance data. + * @param pSrv HTTP server instance data. * @param pszUrl URL to validate. + * + * @note Caller needs to take the server critical section. */ -DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTPSERVER pThis, const char *pszUrl) +DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTPSERVER pSrv, const char *pszUrl) { AssertPtrReturn(pszUrl, NULL); + Assert(RTCritSectIsOwner(&pSrv->CritSect)); PSHCLHTTPSERVERTRANSFER pSrvTx = NULL; PSHCLHTTPSERVERTRANSFER pSrvTxCur; - RTListForEach(&pThis->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node) + RTListForEach(&pSrv->lstTransfers, pSrvTxCur, SHCLHTTPSERVERTRANSFER, Node) { AssertPtr(pSrvTxCur->pTransfer); @@ -272,7 +452,7 @@ DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromUrl(PSHCLHTTP } if (!pSrvTx) - LogRel2(("Shared Clipboard: HTTP URL '%s' not valid\n", pszUrl)); + LogRelMax(16, ("Shared Clipboard: HTTP URL '%s' is not valid\n", pszUrl)); LogFlowFunc(("pszUrl=%s, pSrvTx=%p\n", pszUrl, pSrvTx)); return pSrvTx; @@ -311,35 +491,89 @@ DECLINLINE(PSHCLHTTPSERVERTRANSFER) shClTransferHttpGetTransferFromHandle(PSHCLH * HTTP server callback implementations * *********************************************************************************************************************************/ +/** + * Closes the object owned by an HTTP request, if any. + * + * @returns VBox status code. + * @param pHttpReq HTTP request state whose object to close. + * + * @note The request relinquishes the handle even if the provider reports a + * close error. The transfer's final reset remains the last-resort + * cleanup for provider-owned object state. + */ +static int shClTransferHttpRequestClose(PSHCLHTTPSERVERREQUEST pHttpReq) +{ + if (pHttpReq->hObj == NIL_SHCLOBJHANDLE) + return VINF_SUCCESS; + + SHCLOBJHANDLE const hObj = pHttpReq->hObj; + pHttpReq->hObj = NIL_SHCLOBJHANDLE; + + int const rc = ShClTransferObjClose(pHttpReq->pSrvTx->pTransfer, hObj); + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: Error closing HTTP request object (handle %RU64), rc=%Rrc\n", hObj, rc)); + return rc; +} + /** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestBegin */ static DECLCALLBACK(int) shClTransferHttpBegin(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq) { - PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis); + PSHCLHTTPSERVER pSrv = (PSHCLHTTPSERVER)pData->pvUser; Assert(pData->cbUser == sizeof(SHCLHTTPSERVER)); LogRel2(("Shared Clipboard: HTTP request begin\n")); - PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpGetTransferFromUrl(pThis, pReq->pszUrl); + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)RTMemAllocZ(sizeof(SHCLHTTPSERVERREQUEST)); + if (!pHttpReq) + return VERR_NO_MEMORY; + + int rc = VERR_NOT_FOUND; + + shClTransferHttpServerLock(pSrv); + + PSHCLHTTPSERVERTRANSFER pSrvTx = NULL; + if (pSrv->fRunning) + pSrvTx = shClTransferHttpGetTransferFromUrl(pSrv, pReq->pszUrl); if (pSrvTx) { - pReq->pvUser = pSrvTx; + rc = shClHttpTransferRequestRetain(pSrvTx); + if (RT_SUCCESS(rc)) + { + pHttpReq->pSrvTx = pSrvTx; + pHttpReq->hObj = NIL_SHCLOBJHANDLE; + pReq->pvUser = pHttpReq; + } } + shClTransferHttpServerUnlock(pSrv); + + if (RT_FAILURE(rc)) + RTMemFree(pHttpReq); + + /* Keep request-begin lookup failures transparent to the HTTP server. The + * method callback maps a request without private state to HTTP 404. */ return VINF_SUCCESS; } /** @copydoc RTHTTPSERVERCALLBACKS::pfnRequestEnd */ static DECLCALLBACK(int) shClTransferHttpEnd(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq) { - PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis); + RT_NOREF(pData); Assert(pData->cbUser == sizeof(SHCLHTTPSERVER)); LogRel2(("Shared Clipboard: HTTP request end\n")); - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pReq->pvUser; + if (pHttpReq) { pReq->pvUser = NULL; + + int rc2 = shClTransferHttpRequestClose(pHttpReq); + AssertRC(rc2); + + shClHttpTransferRequestRelease(pHttpReq->pSrvTx); + pHttpReq->pSrvTx = NULL; + RTMemFree(pHttpReq); } return VINF_SUCCESS; @@ -349,20 +583,24 @@ static DECLCALLBACK(int) shClTransferHttpEnd(PRTHTTPCALLBACKDATA pData, PRTHTTPS /** @copydoc RTHTTPSERVERCALLBACKS::pfnOpen */ static DECLCALLBACK(int) shClTransferHttpOpen(PRTHTTPCALLBACKDATA pData, PRTHTTPSERVERREQ pReq, void **ppvHandle) { - PSHCLHTTPSERVER pThis = (PSHCLHTTPSERVER)pData->pvUser; RT_NOREF(pThis); + RT_NOREF(pData); Assert(pData->cbUser == sizeof(SHCLHTTPSERVER)); int rc; AssertPtr(pReq->pvUser); - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pReq->pvUser; + if (pHttpReq) { - LogRel2(("Shared Clipboard: HTTP transfer (handle %RU64) started ...\n", pSrvTx->hObj)); + LogRel2(("Shared Clipboard: HTTP transfer (handle %RU64) started ...\n", pHttpReq->hObj)); - Assert(pSrvTx->hObj != NIL_SHCLOBJHANDLE); - *ppvHandle = &pSrvTx->hObj; - rc = VINF_SUCCESS; + if (pHttpReq->hObj != NIL_SHCLOBJHANDLE) + { + *ppvHandle = pHttpReq; + rc = VINF_SUCCESS; + } + else + rc = VERR_NOT_FOUND; } else rc = VERR_NOT_FOUND; @@ -393,19 +631,20 @@ static DECLCALLBACK(int) shClTransferHttpRead(PRTHTTPCALLBACKDATA pData, PRTHTTP LogRel3(("Shared Clipboard: Reading %RU32 bytes from HTTP ...\n", cbBuf)); AssertPtr(pReq->pvUser); - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pvHandle; + if ( pHttpReq + && pReq->pvUser == pHttpReq) { - PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle; - if (phObj) + if (pHttpReq->hObj != NIL_SHCLOBJHANDLE) { uint32_t cbRead; - rc = ShClTransferObjRead(pSrvTx->pTransfer, *phObj, pvBuf, cbBuf, 0 /* fFlags */, &cbRead); + rc = ShClTransferObjRead(pHttpReq->pSrvTx->pTransfer, pHttpReq->hObj, + pvBuf, (uint32_t)cbBuf, 0 /* fFlags */, &cbRead); if (RT_SUCCESS(rc)) *pcbRead = (uint32_t)cbRead; if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Error reading HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc)); + LogRel(("Shared Clipboard: Error reading HTTP transfer (handle %RU64), rc=%Rrc\n", pHttpReq->hObj, rc)); } else rc = VERR_NOT_FOUND; @@ -425,22 +664,18 @@ static DECLCALLBACK(int) shClTransferHttpClose(PRTHTTPCALLBACKDATA pData, PRTHTT int rc; AssertPtr(pReq->pvUser); - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pvHandle; + if ( pHttpReq + && pReq->pvUser == pHttpReq) { - PSHCLOBJHANDLE phObj = (PSHCLOBJHANDLE)pvHandle; - if (phObj) + if (pHttpReq->hObj != NIL_SHCLOBJHANDLE) { - Assert(*phObj != NIL_SHCLOBJHANDLE); - rc = ShClTransferObjClose(pSrvTx->pTransfer, *phObj); + SHCLOBJHANDLE const hObj = pHttpReq->hObj; + rc = shClTransferHttpRequestClose(pHttpReq); if (RT_SUCCESS(rc)) - { - pSrvTx->hObj = NIL_SHCLOBJHANDLE; - LogRel2(("Shared Clipboard: HTTP transfer %RU16 done\n", pSrvTx->pTransfer->State.uID)); - } - - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Error closing HTTP transfer (handle %RU64), rc=%Rrc\n", *phObj, rc)); + LogRel2(("Shared Clipboard: HTTP transfer %RU16 done\n", pHttpReq->pSrvTx->idTransfer)); + else + LogRel(("Shared Clipboard: Error closing HTTP transfer (handle %RU64), rc=%Rrc\n", hObj, rc)); } else rc = VERR_NOT_FOUND; @@ -477,9 +712,10 @@ static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData, size_t const cchParsedPath = strlen(pszParsedPath); /* For now we only know the transfer -- now we need to figure out the entry we want to serve. */ - PSHCLHTTPSERVERTRANSFER pSrvTx = (PSHCLHTTPSERVERTRANSFER)pReq->pvUser; - if (pSrvTx) + PSHCLHTTPSERVERREQUEST pHttpReq = (PSHCLHTTPSERVERREQUEST)pReq->pvUser; + if (pHttpReq) { + PSHCLHTTPSERVERTRANSFER pSrvTx = pHttpReq->pSrvTx; size_t const cchVirtual = strlen(pSrvTx->szPathVirtual); size_t const cchRoot = cchVirtual + 1 /* Skip slash separating the base from the rest */; const char *pszRoot = NULL; @@ -523,7 +759,8 @@ static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData, rc = RTStrCopy(openParms.pszPath, openParms.cbPath, pEntry->pszName); if (RT_SUCCESS(rc)) { - rc = ShClTransferObjOpen(pTx, &openParms, &pSrvTx->hObj); + Assert(pHttpReq->hObj == NIL_SHCLOBJHANDLE); + rc = ShClTransferObjOpen(pTx, &openParms, &pHttpReq->hObj); if (RT_SUCCESS(rc)) { rc = VERR_NOT_SUPPORTED; /* Play safe by default. */ @@ -547,15 +784,14 @@ static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData, rc = VERR_NOT_SUPPORTED; } else - LogRel2(("Shared Clipboard: Supplied entry information for '%s' not supported (fInfo=%#x, cbInfo=%RU32\n", + LogRelMax(16, ("Shared Clipboard: Supplied entry information for '%s' is not supported (fInfo=%#x, cbInfo=%RU32)\n", pEntry->pszName, pEntry->fInfo, pEntry->cbInfo)); /* Note: Directories / symlinks or other fancy stuff is not supported here (yet) -- would require using WebDAV. */ if ( RT_FAILURE(rc) - && pSrvTx->hObj != NIL_SHCLOBJHANDLE) + && pHttpReq->hObj != NIL_SHCLOBJHANDLE) { - int rc2 = ShClTransferObjClose(pTx, pSrvTx->hObj); + int rc2 = shClTransferHttpRequestClose(pHttpReq); AssertRC(rc2); - pSrvTx->hObj = NIL_SHCLOBJHANDLE; } } else if ( rc == VERR_NOT_A_FILE @@ -572,11 +808,10 @@ static DECLCALLBACK(int) shClTransferHttpQueryInfo(PRTHTTPCALLBACKDATA pData, } if ( pReq->enmMethod == RTHTTPMETHOD_HEAD - && pSrvTx->hObj != NIL_SHCLOBJHANDLE) + && pHttpReq->hObj != NIL_SHCLOBJHANDLE) { - int rc2 = ShClTransferObjClose(pSrvTx->pTransfer, pSrvTx->hObj); + int rc2 = shClTransferHttpRequestClose(pHttpReq); AssertRC(rc2); - pSrvTx->hObj = NIL_SHCLOBJHANDLE; } } else @@ -619,14 +854,15 @@ static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv) LogFlowFuncEnter(); - pSrv->fInitialized = false; - pSrv->fRunning = false; + ASMAtomicXchgBool(&pSrv->fInitialized, false); + ASMAtomicXchgBool(&pSrv->fRunning, false); + pSrv->fStopping = false; int rc = VINF_SUCCESS; - PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext; - RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node) + while (!RTListIsEmpty(&pSrv->lstTransfers)) { + PSHCLHTTPSERVERTRANSFER pSrvTx = RTListGetFirst(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node); int rc2 = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx); if (RT_SUCCESS(rc)) rc = rc2; @@ -645,9 +881,6 @@ static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv) rc = rc2; } - RTSemEventDestroy(pSrv->StatusEvent); - pSrv->StatusEvent = NIL_RTSEMEVENT; - LogFlowFuncLeaveRC(rc); return rc; } @@ -660,22 +893,29 @@ static int shClTransferHttpServerDestroyInternal(PSHCLHTTPSERVER pSrv) */ static int shClTransferHttpServerInitInternal(PSHCLHTTPSERVER pSrv) { - int rc = RTCritSectInit(&pSrv->CritSect); - AssertRCReturn(rc, rc); + ASMAtomicXchgBool(&pSrv->fInitialized, false); + ASMAtomicXchgBool(&pSrv->fRunning, false); + pSrv->fStopping = false; + pSrv->hHTTPServer = NIL_RTHTTPSERVER; - rc = RTSemEventCreate(&pSrv->StatusEvent); + int rc = RTCritSectInit(&pSrv->CritSect); AssertRCReturn(rc, rc); - pSrv->hHTTPServer = NIL_RTHTTPSERVER; pSrv->uPort = 0; RTListInit(&pSrv->lstTransfers); pSrv->cTransfers = 0; + pSrv->cDownloaded = 0; + pSrv->enmStatus = SHCLHTTPSERVERSTATUS_NONE; rc = RTHttpServerResponseInit(&pSrv->Resp); - AssertRCReturn(rc, rc); + if (RT_FAILURE(rc)) + { + RTCritSectDelete(&pSrv->CritSect); + return rc; + } ASMAtomicXchgBool(&pSrv->fInitialized, true); - ASMAtomicXchgBool(&pSrv->fRunning, false); + pSrv->fStopping = false; return rc; } @@ -733,11 +973,20 @@ int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort) { AssertPtrReturn(pSrv, VERR_INVALID_POINTER); AssertReturn(uPort, VERR_INVALID_PARAMETER); + AssertReturn(ASMAtomicReadBool(&pSrv->fInitialized), VERR_WRONG_ORDER); AssertReturn(!shClTransferHttpServerPortIsBuggy(uPort), VERR_ADDRESS_CONFLICT); shClTransferHttpServerLock(pSrv); + if ( pSrv->fRunning + || pSrv->fStopping + || pSrv->hHTTPServer != NIL_RTHTTPSERVER) + { + shClTransferHttpServerUnlock(pSrv); + return VERR_WRONG_ORDER; + } + RTHTTPSERVERCALLBACKS Callbacks; RT_ZERO(Callbacks); @@ -769,26 +1018,6 @@ int ShClTransferHttpServerStartEx(PSHCLHTTPSERVER pSrv, uint16_t uPort) return rc; } -/** - * Returns a Shared Clipboard HTTP server status as a string. - * - * @returns Status as a string, or "Unknown" if invalid / unknown. - * @param enmStatus HTTP server status to return as a string. - */ -DECLINLINE(const char *) shClTransferHttpServerStatusToStr(SHCLHTTPSERVERSTATUS enmStatus) -{ - switch (enmStatus) - { - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_NONE); - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_STARTED); - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_STOPPED); - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED); - RT_CASE_RET_STR(SHCLHTTPSERVERSTATUS_TRANSFER_UNREGISTERED); - } - - AssertFailedReturn("Unknown"); -} - /** * Starts the Shared Clipboard HTTP server instance using a random port (>= 49152). * @@ -862,36 +1091,60 @@ int ShClTransferHttpServerStart(PSHCLHTTPSERVER pSrv, unsigned cMaxAttempts, uin */ int ShClTransferHttpServerStop(PSHCLHTTPSERVER pSrv) { - LogFlowFuncEnter(); + AssertPtrReturn(pSrv, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + if (!ASMAtomicReadBool(&pSrv->fInitialized)) + return VINF_SUCCESS; + + shClTransferHttpServerLock(pSrv); + + int rc = VINF_SUCCESS; + RTHTTPSERVER hHTTPServer = NIL_RTHTTPSERVER; - shClTransferHttpServerLock(pSrv); + if (pSrv->fStopping) + rc = VERR_WRONG_ORDER; + else if (pSrv->fRunning) + { + Assert(pSrv->hHTTPServer != NIL_RTHTTPSERVER); - int rc = VINF_SUCCESS; + hHTTPServer = pSrv->hHTTPServer; + pSrv->fRunning = false; + pSrv->fStopping = true; + } - if (pSrv->fRunning) - { - Assert(pSrv->hHTTPServer != NIL_RTHTTPSERVER); + shClTransferHttpServerUnlock(pSrv); - rc = RTHttpServerDestroy(pSrv->hHTTPServer); - if (RT_SUCCESS(rc)) - { - pSrv->hHTTPServer = NIL_RTHTTPSERVER; - pSrv->fRunning = false; + if (hHTTPServer != NIL_RTHTTPSERVER) + { + /* Do not hold the server lock while stopping worker callbacks. Request + * begin needs that lock in order to take a registration reference. */ + rc = RTHttpServerDestroy(hHTTPServer); - /* Let any eventual waiters know. */ - shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_STOPPED); + shClTransferHttpServerLock(pSrv); - LogRel2(("Shared Clipboard: HTTP server stopped\n")); - } - } + pSrv->fStopping = false; + if (RT_SUCCESS(rc)) + { + pSrv->hHTTPServer = NIL_RTHTTPSERVER; - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: HTTP server failed to stop, rc=%Rrc\n", rc)); + /* Let any eventual waiters know. */ + shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_STOPPED); - shClTransferHttpServerUnlock(pSrv); + LogRel2(("Shared Clipboard: HTTP server stopped\n")); + } + else + pSrv->fRunning = true; - LogFlowFuncLeaveRC(rc); - return rc; + shClTransferHttpServerUnlock(pSrv); + } + + if (RT_FAILURE(rc)) + LogRel(("Shared Clipboard: HTTP server failed to stop, rc=%Rrc\n", rc)); + + LogFlowFuncLeaveRC(rc); + return rc; } /** @@ -904,13 +1157,13 @@ int ShClTransferHttpServerDestroy(PSHCLHTTPSERVER pSrv) { AssertPtrReturn(pSrv, VERR_INVALID_POINTER); + if (!ASMAtomicReadBool(&pSrv->fInitialized)) + return VINF_SUCCESS; + int rc = ShClTransferHttpServerStop(pSrv); if (RT_FAILURE(rc)) return rc; - if (!ASMAtomicReadBool(&pSrv->fInitialized)) - return VINF_SUCCESS; - shClTransferHttpServerLock(pSrv); rc = shClTransferHttpServerDestroyInternal(pSrv); @@ -940,42 +1193,45 @@ static const char *shClTransferHttpServerGetHost(PSHCLHTTPSERVER pSrv) * @returns VBox status code. * @param pSrv HTTP server instance to unregister transfer from. * @param pSrvTx HTTP server transfer to destroy. - * The pointer will be invalid on success. + * The pointer must not be used after return. * - * @note Caller needs to take the server critical section. + * @note Caller needs to take the server critical section. This function + * temporarily releases it while synchronously draining requests and + * owns it again on return. */ static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx) { Assert(RTCritSectIsOwner(&pSrv->CritSect)); + Assert(pSrvTx->fRegistered); - if (pSrvTx->hObj != NIL_SHCLOBJHANDLE) - { - int rc = ShClTransferObjClose(pSrvTx->pTransfer, pSrvTx->hObj); - AssertRCReturn(rc, rc); - pSrvTx->hObj = NIL_SHCLOBJHANDLE; - } - - if (RTCritSectIsInitialized(&pSrvTx->CritSect)) - { - int rc = RTCritSectDelete(&pSrvTx->CritSect); - AssertRCReturn(rc, rc); - } + /* Keep the registration alive for this drain operation before dropping + * the list-owner reference below. */ + shClHttpTransferRetain(pSrvTx); + pSrvTx->fRegistered = false; RTListNodeRemove(&pSrvTx->Node); Assert(pSrv->cTransfers); pSrv->cTransfers--; - LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n", - pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers)); + LogFunc(("pTransfer=%p, idSession=%RU16, idTransfer=%RU16, uGeneration=%RU64, szPath=%s -> %RU32 transfers\n", + pSrvTx->pTransfer, pSrvTx->idSession, pSrvTx->idTransfer, pSrvTx->uGeneration, + pSrvTx->szPathVirtual, pSrv->cTransfers)); LogRel2(("Shared Clipboard: Destroyed HTTP transfer %RU16, now %RU32 HTTP transfers total\n", - pSrvTx->pTransfer->State.uID, pSrv->cTransfers)); + pSrvTx->idTransfer, pSrv->cTransfers)); - RTMemFree(pSrvTx); - pSrvTx = NULL; + /* Drop the list owner only after the registration is unreachable to new + * requests. Existing requests keep their own references. */ + shClHttpTransferRelease(pSrvTx); - return VINF_SUCCESS; + shClTransferHttpServerUnlock(pSrv); + + int rc = shClHttpTransferDrainRequests(pSrvTx); + shClHttpTransferRelease(pSrvTx); /* Drain waiter reference. */ + + shClTransferHttpServerLock(pSrv); + return rc; } @@ -987,7 +1243,7 @@ static int shClTransferHttpServerDestroyTransfer(PSHCLHTTPSERVER pSrv, PSHCLHTTP * Registers a Shared Clipboard transfer to a HTTP server instance. * * @returns VBox status code. - * @retval VERR_ALREADY_EXISTS if the transfer ID already is registered. + * @retval VERR_ALREADY_EXISTS if the exact transfer key already is registered. * @param pSrv HTTP server instance to register transfer for. * @param pTransfer Transfer to register. Needs to be on the heap. */ @@ -995,11 +1251,13 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p { AssertPtrReturn(pSrv, VERR_INVALID_POINTER); AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + AssertReturn(ASMAtomicReadBool(&pSrv->fInitialized), VERR_WRONG_ORDER); - AssertMsgReturn( pTransfer->State.uID != NIL_SHCLTRANSFERID - && pTransfer->State.uID > 0 - && pTransfer->State.uID < VBOX_SHCL_MAX_TRANSFERS - 1, - ("Transfer needs to be registered with a transfer context first\n"), VERR_INVALID_PARAMETER); + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + SHCLTRANSFERGEN const uGeneration = ShClTransferGetGeneration(pTransfer); + AssertMsgReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), + ("Transfer needs a valid session/ID/generation key before HTTP registration\n"), VERR_INVALID_PARAMETER); uint64_t const cRoots = ShClTransferRootsCount(pTransfer); AssertMsgReturn(cRoots > 0, ("Transfer has no root entries\n"), VERR_INVALID_PARAMETER); @@ -1009,9 +1267,10 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p PSHCLHTTPSERVERTRANSFER pSrvTx = NULL; bool fCritSectInitialized = false; + bool fDrainEventCreated = false; int rc = VINF_SUCCESS; - if (shClTransferHttpServerGetTransferById(pSrv, pTransfer->State.uID)) + if (shClTransferHttpServerGetTransferByKey(pSrv, idSession, idTransfer, uGeneration)) rc = VERR_ALREADY_EXISTS; else { @@ -1031,18 +1290,26 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p { fCritSectInitialized = true; + rc = RTSemEventMultiCreate(&pSrvTx->hRequestsDrained); + if (RT_SUCCESS(rc)) + fDrainEventCreated = true; + /* Create the virtual HTTP path for the transfer. * Every transfer has a dedicated HTTP path (but live in the same URL namespace). */ - char *pszPath; + char *pszPath = NULL; + ssize_t cch = -1; + if (RT_SUCCESS(rc)) + { #ifdef VBOX_SHCL_DEBUG_HTTPSERVER # ifdef DEBUG_andy /** Too lazy to specify a different transfer ID for debugging. */ - ssize_t cch = RTStrAPrintf(&pszPath, "/transfer"); + cch = RTStrAPrintf(&pszPath, "/transfer"); # else - ssize_t cch = RTStrAPrintf(&pszPath, "/transfer%RU16", pTransfer->State.uID); + cch = RTStrAPrintf(&pszPath, "/transfer%RU16", idTransfer); # endif #else /* Release mode */ - ssize_t cch = RTStrAPrintf(&pszPath, "/%s/%s", SHCL_HTTPT_URL_NAMESPACE, szUuid); + cch = RTStrAPrintf(&pszPath, "/%s/%s", SHCL_HTTPT_URL_NAMESPACE, szUuid); #endif + } if (cch >= 0) { char *pszURI; @@ -1061,30 +1328,39 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p else rc = VERR_NO_MEMORY; - RTStrFree(pszPath); - pszPath = NULL; } - else + else if (RT_SUCCESS(rc)) rc = VERR_NO_MEMORY; + RTStrFree(pszPath); + pszPath = NULL; if (RT_SUCCESS(rc)) { - pSrvTx->pTransfer = pTransfer; - pSrvTx->hObj = NIL_SHCLOBJHANDLE; + pSrvTx->pTransfer = pTransfer; + pSrvTx->cRefs = 1; /* Registration list owner. */ + pSrvTx->cRequests = 0; + pSrvTx->fRegistered = true; + pSrvTx->idSession = idSession; + pSrvTx->idTransfer = idTransfer; + pSrvTx->uGeneration = uGeneration; + + ShClTransferAcquire(pTransfer); RTListAppend(&pSrv->lstTransfers, &pSrvTx->Node); pSrv->cTransfers++; shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_REGISTERED); - LogFunc(("pTransfer=%p, idTransfer=%RU16, szPath=%s -> %RU32 transfers\n", - pSrvTx->pTransfer, pSrvTx->pTransfer->State.uID, pSrvTx->szPathVirtual, pSrv->cTransfers)); + LogFunc(("pTransfer=%p, idSession=%RU16, idTransfer=%RU16, uGeneration=%RU64, szPath=%s -> %RU32 transfers\n", + pSrvTx->pTransfer, pSrvTx->idSession, pSrvTx->idTransfer, pSrvTx->uGeneration, + pSrvTx->szPathVirtual, pSrv->cTransfers)); LogRel2(("Shared Clipboard: Registered HTTP transfer %RU16, now %RU32 HTTP transfers total\n", - pTransfer->State.uID, pSrv->cTransfers)); + idTransfer, pSrv->cTransfers)); pSrvTx = NULL; fCritSectInitialized = false; + fDrainEventCreated = false; } } } @@ -1096,6 +1372,12 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p if (pSrvTx) { + if (fDrainEventCreated) + { + int rc2 = RTSemEventMultiDestroy(pSrvTx->hRequestsDrained); + AssertRC(rc2); + pSrvTx->hRequestsDrained = NIL_RTSEMEVENTMULTI; + } if (fCritSectInitialized) { int rc2 = RTCritSectDelete(&pSrvTx->CritSect); @@ -1117,30 +1399,35 @@ int ShClTransferHttpServerRegisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER p * @param pSrv HTTP server instance to unregister transfer from. * @param pTransfer Transfer to unregister. * - * @note Removes all registrations matching the transfer ID to recover from - * stale duplicate entries. + * @note Removes all registrations matching the exact session/ID/generation + * key to recover from stale duplicate entries without disturbing a + * newer generation which happens to reuse the same transfer ID. + * The call synchronously drains active requests before returning so + * the caller may destroy the transfer immediately afterwards. */ int ShClTransferHttpServerUnregisterTransfer(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer) { AssertPtrReturn(pSrv, VERR_INVALID_POINTER); AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + AssertReturn(ASMAtomicReadBool(&pSrv->fInitialized), VERR_WRONG_ORDER); + + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + SHCLTRANSFERGEN const uGeneration = ShClTransferGetGeneration(pTransfer); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), VERR_INVALID_PARAMETER); shClTransferHttpServerLock(pSrv); int rc = VINF_SUCCESS; - PSHCLHTTPSERVERTRANSFER pSrvTx, pSrvTxNext; - RTListForEachSafe(&pSrv->lstTransfers, pSrvTx, pSrvTxNext, SHCLHTTPSERVERTRANSFER, Node) + PSHCLHTTPSERVERTRANSFER pSrvTx; + while ((pSrvTx = shClTransferHttpServerGetTransferByKey(pSrv, idSession, idTransfer, uGeneration)) != NULL) { - AssertPtr(pSrvTx->pTransfer); - if (pSrvTx->pTransfer->State.uID == pTransfer->State.uID) - { - rc = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx); - if (RT_SUCCESS(rc)) - shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_UNREGISTERED); - else - break; - } + rc = shClTransferHttpServerDestroyTransfer(pSrv, pSrvTx); + if (RT_SUCCESS(rc)) + shclTransferHttpServerSetStatusLocked(pSrv, SHCLHTTPSERVERSTATUS_TRANSFER_UNREGISTERED); + else + break; } shClTransferHttpServerUnlock(pSrv); @@ -1168,46 +1455,9 @@ static SHCLHTTPSERVERSTATUS shclTransferHttpServerSetStatusLocked(PSHCLHTTPSERVE pSrv->enmStatus = enmStatus; LogFlowFunc(("fStatus=%#x\n", pSrv->enmStatus)); - int rc2 = RTSemEventSignal(pSrv->StatusEvent); - AssertRC(rc2); - return pSrv->enmStatus; } -/** - * Returns the first transfer in the list. - * - * @returns Pointer to first transfer if found, or NULL if not found. - * @param pSrv HTTP server instance. - */ -PSHCLTRANSFER ShClTransferHttpServerGetTransferFirst(PSHCLHTTPSERVER pSrv) -{ - shClTransferHttpServerLock(pSrv); - - PSHCLHTTPSERVERTRANSFER pHttpTransfer = RTListGetFirst(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node); - - shClTransferHttpServerUnlock(pSrv); - - return pHttpTransfer ? pHttpTransfer->pTransfer : NULL; -} - -/** - * Returns the last transfer in the list. - * - * @returns Pointer to last transfer if found, or NULL if not found. - * @param pSrv HTTP server instance. - */ -PSHCLTRANSFER ShClTransferHttpServerGetTransferLast(PSHCLHTTPSERVER pSrv) -{ - shClTransferHttpServerLock(pSrv); - - PSHCLHTTPSERVERTRANSFER pHttpTransfer = RTListGetLast(&pSrv->lstTransfers, SHCLHTTPSERVERTRANSFER, Node); - - shClTransferHttpServerUnlock(pSrv); - - return pHttpTransfer ? pHttpTransfer->pTransfer : NULL; -} - /** * Returns a transfer for a specific ID. * @@ -1228,25 +1478,6 @@ bool ShClTransferHttpServerGetTransfer(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTr return pTransfer; } -/** - * Returns the used TCP port number of a HTTP server instance. - * - * @returns TCP port number. 0 if not specified yet. - * @param pSrv HTTP server instance to return port for. - */ -uint16_t ShClTransferHttpServerGetPort(PSHCLHTTPSERVER pSrv) -{ - AssertPtrReturn(pSrv, 0); - - shClTransferHttpServerLock(pSrv); - - const uint16_t uPort = pSrv->uPort; - - shClTransferHttpServerUnlock(pSrv); - - return uPort; -} - /** * Returns the number of registered HTTP server transfers of a HTTP server instance. * @@ -1288,6 +1519,62 @@ char *ShClTransferHttpServerGetAddressA(PSHCLHTTPSERVER pSrv) return pszAddress; } +/** + * Returns an allocated URL for a locked HTTP transfer registration. + * + * @returns Allocated URL, or NULL if the entry does not exist or allocation failed. + * @param pSrv HTTP server instance. + * @param pSrvTx HTTP transfer registration. + * @param idxEntry Root entry index, or UINT64_MAX for the base URL. + * + * @note Caller needs to take the server critical section. + */ +static char *shClTransferHttpServerGetUrlLocked(PSHCLHTTPSERVER pSrv, PSHCLHTTPSERVERTRANSFER pSrvTx, uint64_t idxEntry) +{ + Assert(RTCritSectIsOwner(&pSrv->CritSect)); + AssertPtrReturn(pSrvTx, NULL); + + char *pszUrl = NULL; + + if (RT_LIKELY(idxEntry != UINT64_MAX)) + { + /* For now this only supports root entries. */ + PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pSrvTx->pTransfer, idxEntry); + if ( pEntry + && RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX)) + pszUrl = RTStrAPrintf2("%s:%RU16%s/%RMpp", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, + pSrvTx->szPathVirtual, pEntry->pszName); + } + else /* Only return the base. */ + pszUrl = RTStrAPrintf2("%s:%RU16%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual); + + return pszUrl; +} + +/** + * Returns an allocated URL for an exact Shared Clipboard transfer key. + * + * @returns Allocated URL, or NULL if the registration or entry was not found. + * @param pSrv HTTP server instance. + * @param pTransfer Transfer whose exact registration to use. + * @param idxEntry Root entry index, or UINT64_MAX for the base URL. + */ +static char *shClTransferHttpServerGetUrlForTransferA(PSHCLHTTPSERVER pSrv, PSHCLTRANSFER pTransfer, uint64_t idxEntry) +{ + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + SHCLTRANSFERGEN const uGeneration = ShClTransferGetGeneration(pTransfer); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), NULL); + + shClTransferHttpServerLock(pSrv); + + PSHCLHTTPSERVERTRANSFER pSrvTx = shClTransferHttpServerGetTransferByKey(pSrv, idSession, idTransfer, uGeneration); + char *pszUrl = pSrvTx ? shClTransferHttpServerGetUrlLocked(pSrv, pSrvTx, idxEntry) : NULL; + + shClTransferHttpServerUnlock(pSrv); + return pszUrl; +} + /** * Returns an allocated string with the URL of a given Shared Clipboard transfer ID. * @@ -1313,23 +1600,7 @@ char *ShClTransferHttpServerGetUrlA(PSHCLHTTPSERVER pSrv, SHCLTRANSFERID idTrans return NULL; } - PSHCLTRANSFER pTx = pSrvTx->pTransfer; - AssertPtr(pTx); - - char *pszUrl = NULL; - - if (RT_LIKELY(idxEntry != UINT64_MAX)) - { - /* For now this only supports root entries. */ - PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTx, idxEntry); - if (pEntry) - { - AssertReturn(RTStrNLen(pSrvTx->szPathVirtual, RTPATH_MAX), NULL); - pszUrl = RTStrAPrintf2("%s:%RU16%s/%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual, pEntry->pszName); - } - } - else /* Only return the base. */ - pszUrl = RTStrAPrintf2("%s:%RU16%s", shClTransferHttpServerGetHost(pSrv), pSrv->uPort, pSrvTx->szPathVirtual); + char *pszUrl = shClTransferHttpServerGetUrlLocked(pSrv, pSrvTx, idxEntry); shClTransferHttpServerUnlock(pSrv); return pszUrl; @@ -1360,7 +1631,7 @@ static int shClTransferHttpConvertToStringListEx(PSHCLHTTPSERVER pSrv, PSHCLTRAN uint64_t const cRoots = ShClTransferRootsCount(pTransfer); for (uint32_t i = 0; i < cRoots; i++) { - char *pszEntry = ShClTransferHttpServerGetUrlA(pSrv, ShClTransferGetID(pTransfer), i /* Entry index */); + char *pszEntry = shClTransferHttpServerGetUrlForTransferA(pSrv, pTransfer, i /* Entry index */); AssertPtrBreakStmt(pszEntry, rc = VERR_NO_MEMORY); if (i > 0) @@ -1431,62 +1702,6 @@ bool ShClTransferHttpServerIsRunning(PSHCLHTTPSERVER pSrv) return ASMAtomicReadBool(&pSrv->fRunning); } -/** - * Waits for a server status change. - * - * @returns VBox status code. - * @retval VERR_STATE_CHANGED if the HTTP server was uninitialized. - * @param pSrv HTTP server instance to wait for. - * @param fStatus Status to wait for. - * Multiple statuses are possible, @sa SHCLHTTPSERVERSTATUS. - * @param msTimeout Timeout (in ms) to wait. - */ -int ShClTransferHttpServerWaitForStatusChange(PSHCLHTTPSERVER pSrv, SHCLHTTPSERVERSTATUS fStatus, RTMSINTERVAL msTimeout) -{ - AssertPtrReturn(pSrv, VERR_INVALID_POINTER); - AssertMsgReturn(ASMAtomicReadBool(&pSrv->fInitialized), ("Server not initialized yet\n"), VERR_WRONG_ORDER); - - shClTransferHttpServerLock(pSrv); - - uint64_t const tsStartMs = RTTimeMilliTS(); - - int rc = VERR_TIMEOUT; - - LogFlowFunc(("fStatus=%#x, msTimeout=%RU32 -- current is %#x\n", fStatus, msTimeout, pSrv->enmStatus)); - - while (RTTimeMilliTS() - tsStartMs <= msTimeout) - { - if (!pSrv->fInitialized) - { - rc = VERR_STATE_CHANGED; - break; - } - - shClTransferHttpServerUnlock(pSrv); /* Leave lock before waiting. */ - - rc = RTSemEventWait(pSrv->StatusEvent, msTimeout); - - shClTransferHttpServerLock(pSrv); - - if (RT_FAILURE(rc)) - break; - - LogFlowFunc(("Current status now is: %#x\n", pSrv->enmStatus)); - LogRel2(("Shared Clipboard: HTTP server entered status '%s'\n", shClTransferHttpServerStatusToStr(pSrv->enmStatus))); - - if (pSrv->enmStatus & fStatus) - { - rc = VINF_SUCCESS; - break; - } - } - - shClTransferHttpServerUnlock(pSrv); - - LogFlowFuncLeaveRC(rc); - return rc; -} - /********************************************************************************************************************************* * Public Shared Clipboard HTTP context functions * diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp index 4deaa49e627b..fdbafb301713 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers-provider-local.cpp 114661 2026-07-08 10:39:13Z andreas.loeffler@oracle.com $ */ +/* $Id: clipboard-transfers-provider-local.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard - Transfers interface implementation for local file systems. */ @@ -203,7 +203,7 @@ static int shClTransferLocalPathEnsureNoSymlinks(const char *pszPathAbs) break; if (RTFS_IS_SYMLINK(ObjInfo.Attr.fMode)) { - LogRelMax2(16, ("Shared Clipboard: Path component '%s' is a symbolic link\n", szPath)); + LogRelMax(16, ("Shared Clipboard: Path component '%s' is a symbolic link\n", szPath)); rc = VERR_IS_A_SYMLINK; break; } @@ -828,6 +828,8 @@ static DECLCALLBACK(int) shclTransferIfaceLocalObjWrite(PSHCLTXPROVIDERCTX pCtx, */ PSHCLTXPROVIDERIFACE ShClTransferProviderLocalQueryInterface(PSHCLTXPROVIDER pProvider) { + RT_ZERO(pProvider->Interface); + pProvider->Interface.pfnRootListRead = shclTransferIfaceLocalRootListRead; pProvider->Interface.pfnListOpen = shclTransferIfaceLocalListOpen; pProvider->Interface.pfnListClose = shclTransferIfaceLocalListClose; @@ -840,4 +842,3 @@ PSHCLTXPROVIDERIFACE ShClTransferProviderLocalQueryInterface(PSHCLTXPROVIDER pPr return &pProvider->Interface; } - diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp index e03c377565f8..e7e88c10e505 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-transfers.cpp 114770 2026-07-25 11:53:54Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard-transfers.cpp 115086 2026-08-19 13:09:34Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Common clipboard transfer handling code. */ @@ -58,36 +58,59 @@ static void shClTransferSetCallbacks(PSHCLTRANSFER pTransfer, PSHCLTRANSFERCALLB static int shClTransferSetStatus(PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS enmStatus); static int shClTransferThreadCreate(PSHCLTRANSFER pTransfer, PFNSHCLTRANSFERTHREAD pfnThreadFunc, void *pvUser); static int shClTransferThreadDestroy(PSHCLTRANSFER pTransfer, RTMSINTERVAL uTimeoutMs); +static void shClTransferDestroyConsume(PSHCLTRANSFER pTransfer); +static void shclTransferCtxTransferRemoveLocked(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer); +static void shclTransferCtxTransferNotifyUnregistered(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer); static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer); static PSHCLTRANSFER shClTransferCtxGetTransferByIdInternal(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID uId); static PSHCLTRANSFER shClTransferCtxGetTransferByIndexInternal(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_HOST +void ShClSvcTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +void ShClSvcTransferDestroyById(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer); +void ShClSvcTransferDestroyByIdEx(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer, bool fNotifyGuest); +#endif + /** - * Returns whether a transfer direction value is valid. + * Checks whether a transfer ID is in the assignable context-local range. + * + * @returns true if the ID can be used by a transfer context, false otherwise. + * @param idTransfer Transfer ID to check before narrowing to SHCLTRANSFERID. */ -static bool shClTransferDirIsValid(SHCLTRANSFERDIR enmDir) +bool ShClTransferIdIsValid(uint32_t idTransfer) { - return enmDir == SHCLTRANSFERDIR_FROM_REMOTE - || enmDir == SHCLTRANSFERDIR_TO_REMOTE; + return idTransfer > 0 + && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1; } /** - * Returns whether a transfer source value is valid. + * Checks whether a transfer key is usable for lifecycle tracking. + * + * @returns true if the key identifies a non-nil service transfer, false otherwise. + * @param idSession Service session ID. + * @param idTransfer Service transfer ID, before narrowing to SHCLTRANSFERID. + * @param uGeneration Service transfer generation. */ -static bool shClTransferSourceIsValid(SHCLSOURCE enmSource) +bool ShClTransferKeyIsValid(SHCLSESSIONID idSession, uint32_t idTransfer, SHCLTRANSFERGEN uGeneration) { - return enmSource == SHCLSOURCE_LOCAL - || enmSource == SHCLSOURCE_REMOTE; + return idSession != 0 + && idSession != NIL_SHCLSESSIONID + && ShClTransferIdIsValid(idTransfer) + && uGeneration != 0 + && uGeneration != NIL_SHCLTRANSFERGEN; } /** - * Returns whether a transfer status value is valid. + * Checks whether a transfer status is part of the Shared Clipboard protocol. + * + * @returns true if the status is valid, false otherwise. + * @param enmStatus Transfer status to validate. */ -static bool shClTransferStatusIsValid(SHCLTRANSFERSTATUS enmStatus) +bool ShClTransferStatusIsValid(SHCLTRANSFERSTATUS enmStatus) { switch (enmStatus) { @@ -108,6 +131,87 @@ static bool shClTransferStatusIsValid(SHCLTRANSFERSTATUS enmStatus) } +/** + * Checks whether a transfer status ends the transfer lifecycle. + * + * @returns true if the status is terminal, false otherwise. + * @param enmStatus Transfer status to classify. + */ +bool ShClTransferStatusIsTerminal(SHCLTRANSFERSTATUS enmStatus) +{ + return enmStatus == SHCLTRANSFERSTATUS_COMPLETED + || enmStatus == SHCLTRANSFERSTATUS_CANCELED + || enmStatus == SHCLTRANSFERSTATUS_KILLED + || enmStatus == SHCLTRANSFERSTATUS_ERROR + || enmStatus == SHCLTRANSFERSTATUS_UNINITIALIZED; +} + + +/** + * Checks whether a transfer status and result form a valid service reply. + * + * @returns true if the status is valid and the result matches it, false otherwise. + * @param enmStatus Transfer status to validate. + * @param rcTransfer Transfer result associated with the status. + */ +bool ShClTransferStatusResultIsValid(SHCLTRANSFERSTATUS enmStatus, int rcTransfer) +{ + if (!ShClTransferStatusIsValid(enmStatus)) + return false; + + switch (enmStatus) + { + case SHCLTRANSFERSTATUS_CANCELED: + return rcTransfer == VERR_CANCELLED; + + case SHCLTRANSFERSTATUS_KILLED: + case SHCLTRANSFERSTATUS_ERROR: + return RT_FAILURE(rcTransfer); + + default: + return RT_SUCCESS(rcTransfer); + } +} + + +/** + * Checks whether a service-reported transfer status may follow the previous + * service-reported status. This describes lifecycle records, not the + * lower-level transfer state mutation sequence. + * + * @returns true if both statuses are valid and the transition is monotonic, false otherwise. + * @param enmOldStatus Current transfer status. + * @param enmNewStatus Incoming transfer status. + */ +bool ShClTransferStatusTransitionIsValid(SHCLTRANSFERSTATUS enmOldStatus, SHCLTRANSFERSTATUS enmNewStatus) +{ + if ( !ShClTransferStatusIsValid(enmOldStatus) + || !ShClTransferStatusIsValid(enmNewStatus)) + return false; + + if (enmOldStatus == enmNewStatus) + return true; + + switch (enmOldStatus) + { + case SHCLTRANSFERSTATUS_REQUESTED: + return enmNewStatus == SHCLTRANSFERSTATUS_INITIALIZED + || ( ShClTransferStatusIsTerminal(enmNewStatus) + && enmNewStatus != SHCLTRANSFERSTATUS_COMPLETED); + + case SHCLTRANSFERSTATUS_INITIALIZED: + return enmNewStatus == SHCLTRANSFERSTATUS_STARTED + || ShClTransferStatusIsTerminal(enmNewStatus); + + case SHCLTRANSFERSTATUS_STARTED: + return ShClTransferStatusIsTerminal(enmNewStatus); + + default: + return false; + } +} + + /** * Returns whether a transfer path is relative in both Unix and DOS path styles. */ @@ -380,48 +484,6 @@ void ShClTransferListHandleInfoDestroy(PSHCLLISTHANDLEINFO pInfo) } } -/** - * Allocates a transfer list header structure. - * - * @returns VBox status code. - * @param ppListHdr Where to store the allocated transfer list header structure on success. - */ -int ShClTransferListHdrAlloc(PSHCLLISTHDR *ppListHdr) -{ - int rc; - - PSHCLLISTHDR pListHdr = (PSHCLLISTHDR)RTMemAllocZ(sizeof(SHCLLISTHDR)); - if (pListHdr) - { - *ppListHdr = pListHdr; - rc = VINF_SUCCESS; - } - else - rc = VERR_NO_MEMORY; - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Frees a transfer list header structure. - * - * @param pListEntry Transfer list header structure to free. - * The pointer will be invalid on return. - */ -void ShClTransferListHdrFree(PSHCLLISTHDR pListHdr) -{ - if (!pListHdr) - return; - - LogFlowFuncEnter(); - - ShClTransferListHdrDestroy(pListHdr); - - RTMemFree(pListHdr); - pListHdr = NULL; -} - /** * Duplicates (allocates) a transfer list header structure. * @@ -548,32 +610,6 @@ int ShClTransferListOpenParmsCopy(PSHCLLISTOPENPARMS pDst, PSHCLLISTOPENPARMS pS return rc; } -/** - * Duplicates a transfer list open parameters structure. - * - * @returns Duplicated transfer list open parameters structure on success, or NULL on failure. - * @param pParms Transfer list open parameters structure to duplicate. - */ -PSHCLLISTOPENPARMS ShClTransferListOpenParmsDup(PSHCLLISTOPENPARMS pParms) -{ - AssertPtrReturn(pParms, NULL); - - PSHCLLISTOPENPARMS pParmsDup = (PSHCLLISTOPENPARMS)RTMemAllocZ(sizeof(SHCLLISTOPENPARMS)); - if (!pParmsDup) - return NULL; - - int rc = ShClTransferListOpenParmsCopy(pParmsDup, pParms); - if (RT_FAILURE(rc)) - { - ShClTransferListOpenParmsDestroy(pParmsDup); - - RTMemFree(pParmsDup); - pParmsDup = NULL; - } - - return pParmsDup; -} - /** * Initializes a transfer list open parameters structure. * @@ -750,21 +786,11 @@ PSHCLLISTENTRY ShClTransferListEntryDup(PSHCLLISTENTRY pEntry) */ static bool shclTransferListEntryNameIsValid(const char *pszName, size_t cbName) { - if (!pszName) - return false; - - size_t const cchLen = RTStrNLen(pszName, cbName); - - if ( !cbName - || cchLen == cbName - || cchLen == 0 - || cchLen > SHCLLISTENTRY_MAX_NAME /* Includes zero termination */ - 1) - { + int rc = ShClTransferValidatePathEx(pszName, cbName, false /* fMustExist */); + if (RT_FAILURE(rc)) return false; - } - int rc = ShClTransferValidatePath(pszName, false /* fMustExist */); - if (RT_FAILURE(rc)) + if (*pszName == '\0') return false; if (!shClTransferPathIsRelative(pszName)) @@ -780,7 +806,7 @@ static bool shclTransferListEntryNameIsValid(const char *pszName, size_t cbName) * @param pListEntry Clipboard list entry structure to initialize. * @param fInfo Info flags (of type VBOX_SHCL_INFO_F_XXX). * @param pszName Name (e.g. filename) to use. Can be NULL if not being used. - * Up to SHCLLISTENTRY_MAX_NAME characters. + * Up to SHCLLISTENTRY_MAX_NAME bytes, including the terminator. * @param pvInfo Pointer to info data to assign. Must match \a fInfo. * The list entry takes the ownership of the data on success. * @param cbInfo Size (in bytes) of \a pvInfo data to assign. @@ -788,17 +814,24 @@ static bool shclTransferListEntryNameIsValid(const char *pszName, size_t cbName) int ShClTransferListEntryInitEx(PSHCLLISTENTRY pListEntry, uint32_t fInfo, const char *pszName, void *pvInfo, uint32_t cbInfo) { AssertPtrReturn(pListEntry, VERR_INVALID_POINTER); - AssertReturn ( pszName == NULL - || shclTransferListEntryNameIsValid(pszName, strlen(pszName) + 1), VERR_INVALID_PARAMETER); + + size_t cchName = 0; + if (pszName) + { + cchName = RTStrNLen(pszName, SHCLLISTENTRY_MAX_NAME); + if ( cchName >= SHCLLISTENTRY_MAX_NAME + || !shclTransferListEntryNameIsValid(pszName, cchName + 1)) + return VERR_INVALID_PARAMETER; + } /* pvInfo + cbInfo depend on fInfo. See below. */ RT_BZERO(pListEntry, sizeof(SHCLLISTENTRY)); if (pszName) { - pListEntry->pszName = RTStrDupN(pszName, SHCLLISTENTRY_MAX_NAME); + pListEntry->pszName = RTStrDupN(pszName, cchName); AssertPtrReturn(pListEntry->pszName, VERR_NO_MEMORY); - pListEntry->cbName = (uint32_t)strlen(pListEntry->pszName) + 1 /* Include terminator */; + pListEntry->cbName = (uint32_t)cchName + 1 /* Include terminator */; } pListEntry->pvInfo = pvInfo; @@ -874,7 +907,7 @@ bool ShClTransferListEntryIsValid(PSHCLLISTENTRY pListEntry) { size_t const cbName = RT_MIN((size_t)pListEntry->cbName, (size_t)SHCLLISTENTRY_MAX_NAME); size_t const cchName = pListEntry->pszName && cbName ? RTStrNLen(pListEntry->pszName, cbName) : 0; - LogRel2(("Shared Clipboard: List entry '%.*s' is invalid\n", + LogRelMax(16, ("Shared Clipboard: List entry '%.*s' is invalid\n", (int)RT_MIN(cchName, (size_t)128), pListEntry->pszName ? pListEntry->pszName : "")); } @@ -886,47 +919,6 @@ bool ShClTransferListEntryIsValid(PSHCLLISTENTRY pListEntry) * Transfer Object * ********************************************************************************************************************************/ -/** - * Initializes a transfer object context. - * - * @returns VBox status code. - * @param pObjCtx Transfer object context to initialize. - */ -int ShClTransferObjCtxInit(PSHCLCLIENTTRANSFEROBJCTX pObjCtx) -{ - AssertPtrReturn(pObjCtx, VERR_INVALID_POINTER); - - LogFlowFuncEnter(); - - pObjCtx->uHandle = NIL_SHCLOBJHANDLE; - - return VINF_SUCCESS; -} - -/** - * Destroys a transfer object context. - * - * @param pObjCtx Transfer object context to destroy. - */ -void ShClTransferObjCtxDestroy(PSHCLCLIENTTRANSFEROBJCTX pObjCtx) -{ - AssertPtrReturnVoid(pObjCtx); - - LogFlowFuncEnter(); -} - -/** - * Returns if a transfer object context is valid or not. - * - * @returns \c true if valid, \c false if not. - * @param pObjCtx Transfer object context to check. - */ -bool ShClTransferObjCtxIsValid(PSHCLCLIENTTRANSFEROBJCTX pObjCtx) -{ - return ( pObjCtx - && pObjCtx->uHandle != NIL_SHCLOBJHANDLE); -} - /** * Initializes a transfer object structure. * @@ -1014,37 +1006,6 @@ int ShClTransferObjOpenParmsInit(PSHCLOBJOPENCREATEPARMS pParms) return rc; } -/** - * Copies a transfer object open parameters structure from source to destination. - * - * @returns VBox status code. - * @param pParmsDst Where to copy the source transfer object open parameters to. - * @param pParmsSrc Which source transfer object open parameters to copy. - */ -int ShClTransferObjOpenParmsCopy(PSHCLOBJOPENCREATEPARMS pParmsDst, PSHCLOBJOPENCREATEPARMS pParmsSrc) -{ - int rc; - - *pParmsDst = *pParmsSrc; - - if (pParmsSrc->pszPath) - { - Assert(pParmsSrc->cbPath); - pParmsDst->pszPath = RTStrDup(pParmsSrc->pszPath); - if (pParmsDst->pszPath) - { - rc = VINF_SUCCESS; - } - else - rc = VERR_NO_MEMORY; - } - else - rc = VINF_SUCCESS; - - LogFlowFuncLeaveRC(rc); - return rc; -} - /** * Destroys a transfer object open parameters structure. * @@ -1318,8 +1279,8 @@ static int shClTransferCreateInternal(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSour PSHCLTRANSFER *ppTransfer) { AssertPtrReturn(ppTransfer, VERR_INVALID_POINTER); - AssertReturn(shClTransferDirIsValid(enmDir), VERR_INVALID_PARAMETER); - AssertReturn(shClTransferSourceIsValid(enmSource), VERR_INVALID_PARAMETER); + AssertReturn(ShClTransferDirIsValid(enmDir), VERR_INVALID_PARAMETER); + AssertReturn(ShClSourceIsValid(enmSource), VERR_INVALID_PARAMETER); AssertReturn(cbMaxChunkSize, VERR_INVALID_PARAMETER); AssertReturn(cMaxListHandles, VERR_INVALID_PARAMETER); AssertReturn(cMaxObjHandles, VERR_INVALID_PARAMETER); @@ -1345,6 +1306,7 @@ static int shClTransferCreateInternal(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSour pTransfer->Thread.fStop = false; pTransfer->pszPathRootAbs = NULL; + pTransfer->pOwnerCtx = NULL; pTransfer->uTimeoutMs = SHCL_TIMEOUT_DEFAULT_MS; pTransfer->cbMaxChunkSize = cbMaxChunkSize; @@ -1367,26 +1329,34 @@ static int shClTransferCreateInternal(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSour ShClTransferListInit(&pTransfer->lstRoots); pTransfer->StatusChangeEvent = NIL_RTSEMEVENT; + pTransfer->hNoRefsEvent = NIL_RTSEMEVENTMULTI; int rc = RTCritSectInit(&pTransfer->CritSect); if (RT_SUCCESS(rc)) { - rc = RTSemEventCreate(&pTransfer->StatusChangeEvent); + rc = RTSemEventMultiCreate(&pTransfer->hNoRefsEvent); if (RT_SUCCESS(rc)) { - rc = ShClEventSourceInit(&pTransfer->Events, 0 /* uID */); + rc = RTSemEventCreate(&pTransfer->StatusChangeEvent); if (RT_SUCCESS(rc)) { - if (pTransfer->Callbacks.pfnOnCreated) - pTransfer->Callbacks.pfnOnCreated(&pTransfer->CallbackCtx); + rc = ShClEventSourceInit(&pTransfer->Events, 0 /* uID */); + if (RT_SUCCESS(rc)) + { + if (pTransfer->Callbacks.pfnOnCreated) + pTransfer->Callbacks.pfnOnCreated(&pTransfer->CallbackCtx); - *ppTransfer = pTransfer; - LogFlowFuncLeaveRC(rc); - return rc; + *ppTransfer = pTransfer; + LogFlowFuncLeaveRC(rc); + return rc; + } + + RTSemEventDestroy(pTransfer->StatusChangeEvent); + pTransfer->StatusChangeEvent = NIL_RTSEMEVENT; } - RTSemEventDestroy(pTransfer->StatusChangeEvent); - pTransfer->StatusChangeEvent = NIL_RTSEMEVENT; + RTSemEventMultiDestroy(pTransfer->hNoRefsEvent); + pTransfer->hNoRefsEvent = NIL_RTSEMEVENTMULTI; } RTCritSectDelete(&pTransfer->CritSect); @@ -1451,12 +1421,8 @@ int ShClTransferDestroy(PSHCLTRANSFER pTransfer) if (!RTCritSectIsInitialized(&pTransfer->CritSect)) return VINF_SUCCESS; - /* Must come before the refcount check below, as the callback might release a reference. */ - if (pTransfer->Callbacks.pfnOnDestroy) - pTransfer->Callbacks.pfnOnDestroy(&pTransfer->CallbackCtx); - - AssertMsgReturn(ASMAtomicReadU32(&pTransfer->cRefs) == 0, - ("Number of references > 0 (%RU32)\n", pTransfer->cRefs), VERR_WRONG_ORDER); + AssertMsgReturn(pTransfer->pOwnerCtx == NULL, + ("Transfer is still registered with context %p\n", pTransfer->pOwnerCtx), VERR_WRONG_ORDER); LogFlowFuncEnter(); @@ -1464,6 +1430,17 @@ int ShClTransferDestroy(PSHCLTRANSFER pTransfer) if (RT_FAILURE(rc)) return rc; + AssertMsgReturn(ASMAtomicReadU32(&pTransfer->cRefs) == 0, + ("Number of references > 0 (%RU32)\n", pTransfer->cRefs), VERR_WRONG_ORDER); + + /* Callback-owned state may still be used by the worker or a temporary + * reference holder. Destroy it only after both have been drained. */ + if (pTransfer->Callbacks.pfnOnDestroy) + { + pTransfer->Callbacks.pfnOnDestroy(&pTransfer->CallbackCtx); + pTransfer->Callbacks.pfnOnDestroy = NULL; + } + ShClTransferReset(pTransfer); if (RTCritSectIsInitialized(&pTransfer->CritSect)) @@ -1475,6 +1452,10 @@ int ShClTransferDestroy(PSHCLTRANSFER pTransfer) ShClEventSourceTerm(&pTransfer->Events); + rc = RTSemEventMultiDestroy(pTransfer->hNoRefsEvent); + AssertRCReturn(rc, rc); + pTransfer->hNoRefsEvent = NIL_RTSEMEVENTMULTI; + RTMemFree(pTransfer); pTransfer = NULL; @@ -1482,25 +1463,90 @@ int ShClTransferDestroy(PSHCLTRANSFER pTransfer) return VINF_SUCCESS; } - - /** - * Returns whether a transfer is in a running state or not. + * Consumes a transfer during owner shutdown. * - * @returns @c true if in running state, or @c false if not. - * @param pTransfer Clipboard transfer to return status for. + * Unlike ShClTransferDestroy(), this cannot leave a detached transfer behind: + * callbacks, the transfer worker and outstanding reference holders are drained + * before the transfer is freed. + * + * @param pTransfer Clipboard transfer to consume. The pointer is + * invalid after return. */ -bool ShClTransferIsRunning(PSHCLTRANSFER pTransfer) +static void shClTransferDestroyConsume(PSHCLTRANSFER pTransfer) { + if (!pTransfer) + return; + + AssertMsgReturnVoid(!RTCritSectIsOwner(&pTransfer->CritSect), + ("The transfer lock must not be held while consuming a transfer\n")); + + int rc = shClTransferThreadDestroy(pTransfer, SHCL_TIMEOUT_DEFAULT_MS); + shClTransferLock(pTransfer); + bool const fThreadActive = pTransfer->Thread.hThread != NIL_RTTHREAD; + shClTransferUnlock(pTransfer); + if (fThreadActive) + { + LogRel(("Shared Clipboard: Transfer worker did not stop within the normal teardown timeout (%Rrc); " + "continuing to wait safely\n", rc)); + rc = shClTransferThreadDestroy(pTransfer, RT_INDEFINITE_WAIT); + + shClTransferLock(pTransfer); + bool const fThreadStillActive = pTransfer->Thread.hThread != NIL_RTTHREAD + || pTransfer->Thread.fStarted; + shClTransferUnlock(pTransfer); + AssertFatalMsg(!fThreadStillActive, ("Reaping the transfer worker failed with %Rrc\n", rc)); + } + + if (RT_FAILURE(rc)) + LogFlowFunc(("Ignoring completed transfer worker status %Rrc during consuming teardown\n", rc)); + + uint32_t cRefs; + for (;;) + { + /* Serialize the zero observation with the final releaser. It signals + * hNoRefsEvent before dropping this lock, so the event cannot be + * destroyed while ShClTransferRelease() is still using it. */ + shClTransferLock(pTransfer); + cRefs = ASMAtomicReadU32(&pTransfer->cRefs); + shClTransferUnlock(pTransfer); + if (!cRefs) + break; + + LogRel2(("Shared Clipboard: Waiting for %RU32 transfer reference(s) to drain during teardown\n", cRefs)); + rc = RTSemEventMultiWait(pTransfer->hNoRefsEvent, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(rc, ("Waiting for transfer references to drain failed with %Rrc\n", rc)); + } - bool const fRunning = pTransfer->State.enmStatus == SHCLTRANSFERSTATUS_STARTED; + /* A retained user may have started a worker while the initial stop was in + * progress. With all retained users gone, no legitimate caller can + * publish another one, so drain the final worker before callback state. */ + rc = shClTransferThreadDestroy(pTransfer, RT_INDEFINITE_WAIT); + shClTransferLock(pTransfer); + bool const fThreadStillActive = pTransfer->Thread.hThread != NIL_RTTHREAD + || pTransfer->Thread.fStarted; shClTransferUnlock(pTransfer); + AssertFatalMsg(!fThreadStillActive, ("Final transfer worker drain failed with %Rrc\n", rc)); + + if (RT_FAILURE(rc)) + LogFlowFunc(("Ignoring completed transfer worker status %Rrc after reference drain\n", rc)); - return fRunning; + /* Temporary reference holders may use callback-owned per-transfer state. + * Destroy that state only after all of them and the worker have drained. */ + if (pTransfer->Callbacks.pfnOnDestroy) + { + pTransfer->Callbacks.pfnOnDestroy(&pTransfer->CallbackCtx); + pTransfer->Callbacks.pfnOnDestroy = NULL; + } + + rc = ShClTransferDestroy(pTransfer); + AssertFatalMsgRC(rc, ("Final transfer destruction failed with %Rrc\n", rc)); } + + /** * Returns whether a transfer has been (successfully) completed or not. * @@ -1567,7 +1613,21 @@ DECLINLINE(void) shClTransferUnlock(PSHCLTRANSFER pTransfer) */ uint32_t ShClTransferAcquire(PSHCLTRANSFER pTransfer) { - return ASMAtomicIncU32(&pTransfer->cRefs); + shClTransferLock(pTransfer); + + uint32_t const cRefs = ASMAtomicReadU32(&pTransfer->cRefs); + AssertRelease(cRefs < UINT32_MAX); + + if (cRefs == 0) + { + int rc = RTSemEventMultiReset(pTransfer->hNoRefsEvent); + AssertFatalMsgRC(rc, ("Resetting the transfer reference event failed with %Rrc\n", rc)); + } + + uint32_t const cRefsNew = ASMAtomicIncU32(&pTransfer->cRefs); + + shClTransferUnlock(pTransfer); + return cRefsNew; } /** @@ -1578,8 +1638,17 @@ uint32_t ShClTransferAcquire(PSHCLTRANSFER pTransfer) */ uint32_t ShClTransferRelease(PSHCLTRANSFER pTransfer) { - const uint32_t cRefs = ASMAtomicDecU32(&pTransfer->cRefs); - Assert(pTransfer->cRefs <= VBOX_SHCL_MAX_TRANSFERS); /* Not perfect, but better than nothing. */ + shClTransferLock(pTransfer); + + AssertRelease(ASMAtomicReadU32(&pTransfer->cRefs) > 0); + uint32_t const cRefs = ASMAtomicDecU32(&pTransfer->cRefs); + if (cRefs == 0) + { + int rc = RTSemEventMultiSignal(pTransfer->hNoRefsEvent); + AssertFatalMsgRC(rc, ("Signalling the transfer reference event failed with %Rrc\n", rc)); + } + + shClTransferUnlock(pTransfer); return cRefs; } @@ -1712,28 +1781,6 @@ PSHCLLISTHANDLEINFO ShClTransferListGetByHandle(PSHCLTRANSFER pTransfer, SHCLLIS return NULL; } -/** - * Returns the a transfer object of a transfer list. - * - * Currently not implemented and wil return NULL. - * - * @returns Pointer to transfer object, or NULL if not found / invalid. - * @param pTransfer Clipboard transfer to return transfer object for. - * @param hList Handle of clipboard transfer list to get object for. - * @param uIdx Index of object to get. - */ -PSHCLTRANSFEROBJ ShClTransferListGetObj(PSHCLTRANSFER pTransfer, - SHCLLISTHANDLE hList, uint64_t uIdx) -{ - AssertPtrReturn(pTransfer, NULL); - - RT_NOREF(hList, uIdx); - - LogFlowFunc(("hList=%RU64\n", hList)); - - return NULL; -} - /** * Reads a single transfer list entry. * @@ -1791,31 +1838,6 @@ int ShClTransferListWrite(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList, return rc; } -/** - * Returns whether a given transfer list handle is valid or not. - * - * @returns \c true if list handle is valid, \c false if not. - * @param pTransfer Clipboard transfer to handle. - * @param hList List handle to check. - */ -bool ShClTransferListHandleIsValid(PSHCLTRANSFER pTransfer, SHCLLISTHANDLE hList) -{ - bool fIsValid = false; - - if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL) - { - fIsValid = ShClTransferListGetByHandle(pTransfer, hList) != NULL; - } - else if (pTransfer->State.enmSource == SHCLSOURCE_REMOTE) - { - AssertFailed(); /** @todo Implement. */ - } - else - AssertFailedStmt(fIsValid = false); - - return fIsValid; -} - /** * Copies a transfer callback table from source to destination. * @@ -1914,7 +1936,7 @@ int ShClTransferSetProvider(PSHCLTRANSFER pTransfer, PSHCLTXPROVIDER pProvider) static int shClTransferSetStatus(PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS enmStatus) { Assert(RTCritSectIsOwner(&pTransfer->CritSect)); - AssertReturn(shClTransferStatusIsValid(enmStatus), VERR_INVALID_PARAMETER); + AssertReturn(ShClTransferStatusIsValid(enmStatus), VERR_INVALID_PARAMETER); #if 0 AssertMsgReturn(pTransfer->State.enmStatus != enmStatus, ("Setting the same status twice in a row (%#x), please report this!\n", enmStatus), VERR_WRONG_ORDER); @@ -2173,10 +2195,10 @@ int ShClTransferRootsSetFromStringListEx(PSHCLTRANSFER pTransfer, const char *ps PSHCLFSOBJINFO pFsObjInfo = (PSHCLFSOBJINFO)RTMemAllocZ(sizeof(SHCLFSOBJINFO)); if (pFsObjInfo) { - if (pTransfer->State.enmDir == SHCLTRANSFERDIR_TO_REMOTE) + if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL) rc = ShClFsObjInfoQueryLocal(pszPathCur, pFsObjInfo); if ( RT_SUCCESS(rc) - && pTransfer->State.enmDir == SHCLTRANSFERDIR_TO_REMOTE) + && pTransfer->State.enmSource == SHCLSOURCE_LOCAL) { if ( !RTFS_IS_DIRECTORY(pFsObjInfo->Attr.fMode) && !RTFS_IS_FILE(pFsObjInfo->Attr.fMode)) @@ -2283,60 +2305,6 @@ int ShClTransferRootsSetFromStringList(PSHCLTRANSFER pTransfer, const char *pszR return ShClTransferRootsSetFromStringListEx(pTransfer, pszRoots, cbRoots, SHCL_TRANSFER_URI_LIST_SEP_STR); } -/** - * Sets the root list entries for a given clipboard transfer, UTF-16 (Unicode) version. - * - * @returns VBox status code. - * @param pTransfer Transfer to set transfer list entries for. - * @param pwszRoots Unicode string list (separated by CRLF) of root entries to set. - * All entries must have the same root path. - * @param cbRoots Size (in bytes) of string list. Includes zero terminator. - * - * @note Accepts local paths or URI string lists (absolute only). - */ -int ShClTransferRootsSetFromStringListUnicode(PSHCLTRANSFER pTransfer, PRTUTF16 pwszRoots, size_t cbRoots) -{ - AssertPtrReturn(pwszRoots, VERR_INVALID_POINTER); - AssertReturn(cbRoots, VERR_INVALID_PARAMETER); - AssertReturn(cbRoots % sizeof(RTUTF16) == 0, VERR_INVALID_PARAMETER); - - size_t cwcRoots = cbRoots / sizeof(RTUTF16); - - /* This may slightly overestimate the space needed. */ -#if 0 - size_t cbDst = 0; - int rc = ShClHlpUtf16LenUtf8(pwszRoots, cwcRoots, &cbDst); - if (RT_SUCCESS(rc)) - { - cbDst++; /* Add space for terminator. */ - - char *pszDst = (char *)RTStrAlloc(cbDst); - if (pszDst) - { - size_t cbActual = 0; - rc = ShClHlpConvUtf16CRLFToUtf8LF(pwszRoots, cwcRoots, pszDst, cbDst, &cbActual); - if (RT_SUCCESS(rc)) - rc = ShClTransferRootsSetFromStringList(pTransfer, pszDst, cbActual + 1 /* Include terminator */); - - RTStrFree(pszDst); - } - else - rc = VERR_NO_MEMORY; - } -#else - char *pszTmp = NULL; - size_t cbLenSansTerm = 0; - int rc = ShClHlpConvUtf16CRLFToUtf8LFA(pwszRoots, cwcRoots, &pszTmp, &cbLenSansTerm); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferRootsSetFromStringList(pTransfer, pszTmp, cbLenSansTerm + 1 /* Include terminator */); - RTMemFree(pszTmp); - } -#endif - - return rc; -} - /** * Sets a single path as a transfer root. * @@ -2437,29 +2405,6 @@ SHCLTRANSFERDIR ShClTransferGetDir(PSHCLTRANSFER pTransfer) return enmDir; } -/** - * Returns the absolute root path of a transfer. - * - * @returns VBox status code. - * @param pTransfer Clipboard transfer to return absolute root path for. - * @param pszPath Where to store the returned path. - * @param cbPath Size (in bytes) of \a pszPath. - */ -int ShClTransferGetRootPathAbs(PSHCLTRANSFER pTransfer, char *pszPath, size_t cbPath) -{ - AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); - - shClTransferLock(pTransfer); - - AssertMsgReturn(pTransfer->pszPathRootAbs, ("Transfer has no root path set (yet)\n"), VERR_WRONG_ORDER); - - int const rc = RTStrCopy(pszPath, cbPath, pTransfer->pszPathRootAbs); - - shClTransferUnlock(pTransfer); - - return rc; -} - /** * Returns the transfer's source. * @@ -2744,7 +2689,6 @@ static DECLCALLBACK(int) shClTransferThreadWorker(RTTHREAD ThreadSelf, void *pvU shClTransferLock(pTransfer); pTransfer->Thread.fStarted = true; - pTransfer->Thread.fStop = false; shClTransferUnlock(pTransfer); @@ -2823,29 +2767,36 @@ static int shClTransferThreadDestroy(PSHCLTRANSFER pTransfer, RTMSINTERVAL uTime shClTransferLock(pTransfer); - if (!pTransfer->Thread.fStarted) + /* A handle is published before the worker can set fStarted. Treat the + * handle as authoritative so teardown cannot miss a starting worker. */ + if (pTransfer->Thread.hThread == NIL_RTTHREAD) { shClTransferUnlock(pTransfer); return VINF_SUCCESS; } + pTransfer->Thread.fStop = true; LogFlowFuncEnter(); - /* Set stop indicator. */ - pTransfer->Thread.fStop = true; + /* Snapshot the waitable handle. A finite-timeout failure leaves it intact + * for the consuming indefinite retry. */ + RTTHREAD const hThread = pTransfer->Thread.hThread; shClTransferUnlock(pTransfer); /* Leave lock while waiting. */ int rcThread = VERR_IPE_UNINITIALIZED_STATUS; - Assert(pTransfer->Thread.hThread != NIL_RTTHREAD); - int rc = RTThreadWait(pTransfer->Thread.hThread, uTimeoutMs, &rcThread); + Assert(hThread != NIL_RTTHREAD); + int rc = RTThreadWait(hThread, uTimeoutMs, &rcThread); LogFlowFunc(("Waiting for thread resulted in %Rrc (thread exited with %Rrc)\n", rc, rcThread)); if (RT_SUCCESS(rc)) { + shClTransferLock(pTransfer); pTransfer->Thread.fStarted = false; - pTransfer->Thread.hThread = NIL_RTTHREAD; + if (pTransfer->Thread.hThread == hThread) + pTransfer->Thread.hThread = NIL_RTTHREAD; + shClTransferUnlock(pTransfer); rc = rcThread; /* Return the thread rc to the caller. */ } @@ -2885,25 +2836,6 @@ static int shClTransferWaitForStatusChangeInternal(PSHCLTRANSFER pTransfer, RTMS return rc; } -/** - * Waits for the transfer status to change. - * - * @returns VBox status code. - * @param pTransfer Clipboard transfer to wait for. - * @param msTimeout Timeout (in ms) to wait. - * @param penmStatus Where to return the new (current) transfer status on success. - * Optional and can be NULL. - */ -int ShClTransferWaitForStatusChange(PSHCLTRANSFER pTransfer, RTMSINTERVAL msTimeout, SHCLTRANSFERSTATUS *penmStatus) -{ - AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); - - int rc = shClTransferWaitForStatusChangeInternal(pTransfer, msTimeout, penmStatus); - - LogFlowFuncLeaveRC(rc); - return rc; -} - /** * Waits for a specific transfer status. * @@ -2985,27 +2917,19 @@ int ShClTransferCtxInit(PSHCLTRANSFERCTX pTransferCtx) int rc = RTCritSectInit(&pTransferCtx->CritSect); if (RT_SUCCESS(rc)) { - rc = RTSemEventCreate(&pTransferCtx->ChangedEvent); - if (RT_SUCCESS(rc)) - { - RT_ZERO(pTransferCtx->ChangedEventData); + RTListInit(&pTransferCtx->List); - RTListInit(&pTransferCtx->List); - - pTransferCtx->cTransfers = 0; - pTransferCtx->cRunning = 0; - pTransferCtx->cMaxRunning = 64; /** @todo Make this configurable? */ - pTransferCtx->cTransferIdsUsed = 0; - pTransferCtx->idSession = NIL_SHCLSESSIONID; - pTransferCtx->uNextGeneration = 1; + pTransferCtx->cTransfers = 0; + pTransferCtx->cRunning = 0; + pTransferCtx->cMaxRunning = 64; /** @todo Make this configurable? */ + pTransferCtx->cTransferIdsUsed = 0; + pTransferCtx->idSession = NIL_SHCLSESSIONID; + pTransferCtx->uNextGeneration = 1; - RT_ZERO(pTransferCtx->bmTransferIds); - RT_ZERO(pTransferCtx->bmTransferIdsUsed); + RT_ZERO(pTransferCtx->bmTransferIds); + RT_ZERO(pTransferCtx->bmTransferIdsUsed); - ShClTransferCtxReset(pTransferCtx); - } - else - RTCritSectDelete(&pTransferCtx->CritSect); + ShClTransferCtxReset(pTransferCtx); } return rc; @@ -3023,13 +2947,16 @@ void ShClTransferCtxDestroy(PSHCLTRANSFERCTX pTransferCtx) LogFlowFunc(("pTransferCtx=%p\n", pTransferCtx)); + RTLISTANCHOR ListDestroy; + RTListInit(&ListDestroy); + shClTransferCtxLock(pTransferCtx); PSHCLTRANSFER pTransfer, pTransferNext; RTListForEachSafe(&pTransferCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node) { - shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer); - ShClTransferDestroy(pTransfer); + shclTransferCtxTransferRemoveLocked(pTransferCtx, pTransfer); + RTListAppend(&ListDestroy, &pTransfer->Node); } pTransferCtx->cRunning = 0; @@ -3037,8 +2964,12 @@ void ShClTransferCtxDestroy(PSHCLTRANSFERCTX pTransferCtx) shClTransferCtxUnlock(pTransferCtx); - RTSemEventDestroy(pTransferCtx->ChangedEvent); - pTransferCtx->ChangedEvent = NIL_RTSEMEVENT; + RTListForEachSafe(&ListDestroy, pTransfer, pTransferNext, SHCLTRANSFER, Node) + { + RTListNodeRemove(&pTransfer->Node); + shclTransferCtxTransferNotifyUnregistered(pTransferCtx, pTransfer); + shClTransferDestroyConsume(pTransfer); + } if (RTCritSectIsInitialized(&pTransferCtx->CritSect)) RTCritSectDelete(&pTransferCtx->CritSect); @@ -3085,7 +3016,6 @@ int ShClTransferCtxBeginSession(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idS int rc; if (pTransferCtx->cTransfers == 0) { - RT_ZERO(pTransferCtx->ChangedEventData); RT_ZERO(pTransferCtx->bmTransferIds); RT_ZERO(pTransferCtx->bmTransferIdsUsed); pTransferCtx->idSession = idSession; @@ -3106,26 +3036,6 @@ int ShClTransferCtxBeginSession(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idS return rc; } -/** - * Signals a change event. - * - * @returns VBox status code. - * @param pTransferCtx Transfer context to return transfer for. - * @param fRegistered Whether a transfer got registered or unregistered. - * @param pTransfer Transfer bound to the event. - */ -static int shClTransferCtxSignal(PSHCLTRANSFERCTX pTransferCtx, bool fRegistered, PSHCLTRANSFER pTransfer) -{ - Assert(RTCritSectIsOwner(&pTransferCtx->CritSect)); - - LogFlowFunc(("fRegistered=%RTbool, pTransfer=%p\n", fRegistered, pTransfer)); - - pTransferCtx->ChangedEventData.fRegistered = fRegistered; - pTransferCtx->ChangedEventData.pTransfer = pTransfer; - - return RTSemEventSignal(pTransferCtx->ChangedEvent); -} - /** * Returns a specific clipboard transfer, internal version. * @@ -3193,6 +3103,29 @@ PSHCLTRANSFER ShClTransferCtxGetTransferById(PSHCLTRANSFERCTX pTransferCtx, uint return pTransfer; } +/** + * Returns and retains a clipboard transfer for a specific transfer ID. + * + * @returns Retained clipboard transfer, or NULL if not found. + * @param pTransferCtx Transfer context to return transfer for. + * @param uID ID of the transfer to return. + * + * @note The caller must release a returned transfer with ShClTransferRelease(). + */ +PSHCLTRANSFER ShClTransferCtxGetTransferByIdRetained(PSHCLTRANSFERCTX pTransferCtx, uint32_t uID) +{ + AssertPtrReturn(pTransferCtx, NULL); + + shClTransferCtxLock(pTransferCtx); + + PSHCLTRANSFER pTransfer = shClTransferCtxGetTransferByIdInternal(pTransferCtx, uID); + if (pTransfer) + ShClTransferAcquire(pTransfer); + + shClTransferCtxUnlock(pTransferCtx); + return pTransfer; +} + /** * Returns a clipboard transfer for a specific service-session/transfer/generation key. * @@ -3206,9 +3139,7 @@ PSHCLTRANSFER ShClTransferCtxGetTransferByKey(PSHCLTRANSFERCTX pTransferCtx, SHC SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration) { AssertPtrReturn(pTransferCtx, NULL); - AssertReturn(idSession != 0 && idSession != NIL_SHCLSESSIONID, NULL); - AssertReturn(idTransfer != NIL_SHCLTRANSFERID && idTransfer > 0 && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1, NULL); - AssertReturn(uGeneration != 0 && uGeneration != NIL_SHCLTRANSFERGEN, NULL); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), NULL); shClTransferCtxLock(pTransferCtx); @@ -3234,58 +3165,80 @@ PSHCLTRANSFER ShClTransferCtxGetTransferByKey(PSHCLTRANSFERCTX pTransferCtx, SHC } /** - * Returns a clipboard transfer for a specific list index. + * Returns and retains a clipboard transfer for a service-session/transfer/generation key. * - * @returns Clipboard transfer found, or NULL if not found. - * @param pTransferCtx Transfer context to return transfer for. - * @param uIdx List index of the transfer to return. + * @returns Retained clipboard transfer, or NULL if not found or the key does not match. + * @param pTransferCtx Transfer context to return transfer for. + * @param idSession Service session ID to match. + * @param idTransfer Transfer ID to match. + * @param uGeneration Host-private transfer generation to match. + * + * @note The caller must release a returned transfer with ShClTransferRelease(). */ -PSHCLTRANSFER ShClTransferCtxGetTransferByIndex(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx) +PSHCLTRANSFER ShClTransferCtxGetTransferByKeyRetained(PSHCLTRANSFERCTX pTransferCtx, SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration) { + AssertPtrReturn(pTransferCtx, NULL); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), NULL); + shClTransferCtxLock(pTransferCtx); - PSHCLTRANSFER const pTransfer = shClTransferCtxGetTransferByIndexInternal(pTransferCtx, uIdx); + PSHCLTRANSFER pTransfer = NULL; + if (pTransferCtx->idSession == idSession) + { + pTransfer = shClTransferCtxGetTransferByIdInternal(pTransferCtx, idTransfer); + if (pTransfer) + { + PSHCLTRANSFER const pTransferToUnlock = pTransfer; + shClTransferLock(pTransferToUnlock); + if ( pTransferToUnlock->State.idSession != idSession + || pTransferToUnlock->State.uID != idTransfer + || pTransferToUnlock->State.uGeneration != uGeneration) + pTransfer = NULL; + else + ShClTransferAcquire(pTransferToUnlock); + shClTransferUnlock(pTransferToUnlock); + } + } shClTransferCtxUnlock(pTransferCtx); - return pTransfer; } - /** - * Returns the last clipboard transfer registered. + * Returns a clipboard transfer for a specific list index. * * @returns Clipboard transfer found, or NULL if not found. * @param pTransferCtx Transfer context to return transfer for. + * @param uIdx List index of the transfer to return. */ -PSHCLTRANSFER ShClTransferCtxGetTransferLast(PSHCLTRANSFERCTX pTransferCtx) +PSHCLTRANSFER ShClTransferCtxGetTransferByIndex(PSHCLTRANSFERCTX pTransferCtx, uint32_t uIdx) { shClTransferCtxLock(pTransferCtx); - PSHCLTRANSFER const pTransfer = RTListGetLast(&pTransferCtx->List, SHCLTRANSFER, Node); + PSHCLTRANSFER const pTransfer = shClTransferCtxGetTransferByIndexInternal(pTransferCtx, uIdx); shClTransferCtxUnlock(pTransferCtx); return pTransfer; } + /** - * Returns the number of running clipboard transfers for a given transfer context. + * Returns the last clipboard transfer registered. * - * @returns Number of running transfers. - * @param pTransferCtx Transfer context to return number for. + * @returns Clipboard transfer found, or NULL if not found. + * @param pTransferCtx Transfer context to return transfer for. */ -uint32_t ShClTransferCtxGetRunningTransfers(PSHCLTRANSFERCTX pTransferCtx) +PSHCLTRANSFER ShClTransferCtxGetTransferLast(PSHCLTRANSFERCTX pTransferCtx) { - AssertPtrReturn(pTransferCtx, 0); - shClTransferCtxLock(pTransferCtx); - uint32_t const cRunning = pTransferCtx->cRunning; + PSHCLTRANSFER const pTransfer = RTListGetLast(&pTransferCtx->List, SHCLTRANSFER, Node); shClTransferCtxUnlock(pTransferCtx); - return cRunning; + return pTransfer; } /** @@ -3307,20 +3260,6 @@ uint32_t ShClTransferCtxGetTotalTransfers(PSHCLTRANSFERCTX pTransferCtx) return cTransfers; } -/** - * Checks whether a transfer ID is in the assignable context-local range. - * - * @returns true if the ID can be used by a transfer context, false otherwise. - * @param idTransfer Transfer ID to check. - */ -static bool shClTransferCtxIsValidTransferId(SHCLTRANSFERID idTransfer) -{ - return idTransfer != NIL_SHCLTRANSFERID - && idTransfer > 0 - && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1; -} - - /** * Creates the next non-reserved transfer generation for a locked transfer context. * @@ -3379,7 +3318,7 @@ static int shClTransferCreateIDInternal(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANS } idTransfer++; - if (!shClTransferCtxIsValidTransferId(idTransfer)) + if (!ShClTransferIdIsValid(idTransfer)) idTransfer = 1; } @@ -3387,28 +3326,6 @@ static int shClTransferCreateIDInternal(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANS return VERR_SHCLPB_MAX_TRANSFERS_REACHED; } -/** - * Creates a new transfer ID for a given transfer context. - * - * @returns VBox status code. - * @retval VERR_SHCLPB_MAX_TRANSFERS_REACHED if the maximum of concurrent transfers is reached. - * @param pTransferCtx Transfer context to create transfer ID for. - * @param pidTransfer Where to return the transfer ID on success. - */ -int ShClTransferCtxCreateId(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFERID pidTransfer) -{ - AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); - AssertPtrReturn(pidTransfer, VERR_INVALID_POINTER); - - shClTransferCtxLock(pTransferCtx); - - int rc = shClTransferCreateIDInternal(pTransferCtx, pidTransfer); - - shClTransferCtxUnlock(pTransferCtx); - - return rc; -} - /** * Registers a clipboard transfer with a new transfer ID. * @@ -3423,19 +3340,20 @@ static int shClTransferCtxTransferRegisterExInternal(PSHCLTRANSFERCTX pTransferC AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); Assert(RTCritSectIsOwner(&pTransferCtx->CritSect)); - Assert(shClTransferCtxIsValidTransferId(idTransfer)); + Assert(ShClTransferIdIsValid(idTransfer)); shClTransferLock(pTransfer); pTransfer->State.uID = idTransfer; pTransfer->State.idSession = pTransferCtx->idSession; pTransfer->State.uGeneration = shClTransferCtxCreateGenerationInternal(pTransferCtx); + pTransfer->pOwnerCtx = pTransferCtx; shClTransferUnlock(pTransfer); RTListAppend(&pTransferCtx->List, &pTransfer->Node); pTransferCtx->cTransfers++; - int rc = shClTransferCtxSignal(pTransferCtx, true /* fRegistered */, pTransfer); + int rc = VINF_SUCCESS; Log2Func(("pTransfer=%p, idTransfer=%RU32, idSession=%RU16, uGeneration=%RU64 -- now %RU16 transfer(s)\n", pTransfer, idTransfer, pTransferCtx->idSession, pTransfer->State.uGeneration, pTransferCtx->cTransfers)); @@ -3470,7 +3388,7 @@ int ShClTransferCtxRegister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransf SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; /* Shut up MSVC. */ int rc; - if (shClTransferCtxIsValidTransferId(pTransfer->State.uID)) + if (ShClTransferIdIsValid(pTransfer->State.uID)) rc = VERR_ALREADY_EXISTS; else rc = shClTransferCreateIDInternal(pTransferCtx, &idTransfer); @@ -3500,12 +3418,12 @@ int ShClTransferCtxRegisterById(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTr { AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); - AssertReturn(shClTransferCtxIsValidTransferId(idTransfer), VERR_INVALID_PARAMETER); + AssertReturn(ShClTransferIdIsValid(idTransfer), VERR_INVALID_PARAMETER); shClTransferCtxLock(pTransferCtx); int rc; - if (shClTransferCtxIsValidTransferId(pTransfer->State.uID)) + if (ShClTransferIdIsValid(pTransfer->State.uID)) rc = VERR_ALREADY_EXISTS; else if (pTransferCtx->cTransfers < VBOX_SHCL_MAX_TRANSFERS - 2 /* First and last are not used */) { @@ -3543,19 +3461,19 @@ int ShClTransferCtxRegisterById(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTr } /** - * Removes and unregisters a transfer from a transfer context. + * Removes a transfer from a transfer context. * * @param pTransferCtx Transfer context to remove transfer from. * @param pTransfer Transfer to remove. * * @note Caller needs to take critical section. */ -static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer) +static void shclTransferCtxTransferRemoveLocked(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer) { Assert(RTCritSectIsOwner(&pTransferCtx->CritSect)); SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); - if (shClTransferCtxIsValidTransferId(idTransfer)) + if (ShClTransferIdIsValid(idTransfer)) ASMBitClear(&pTransferCtx->bmTransferIds[0], idTransfer); RTListNodeRemove(&pTransfer->Node); @@ -3565,14 +3483,47 @@ static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransfe Assert(pTransferCtx->cTransfers >= pTransferCtx->cRunning); - shClTransferCtxUnlock(pTransferCtx); + LogFlowFunc(("Now %RU32 transfers left\n", pTransferCtx->cTransfers)); +} + +/** + * Notifies a transfer that it was removed from a transfer context. + * + * @param pTransferCtx Transfer context the transfer was removed from. + * @param pTransfer Transfer that was removed. + * + * @note No owner or transfer-context lock may be held by the caller. + */ +static void shclTransferCtxTransferNotifyUnregistered(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer) +{ + Assert(!RTCritSectIsOwner(&pTransferCtx->CritSect)); + Assert(pTransfer->pOwnerCtx == pTransferCtx); if (pTransfer->Callbacks.pfnOnUnregistered) pTransfer->Callbacks.pfnOnUnregistered(&pTransfer->CallbackCtx, pTransferCtx); - shClTransferCtxLock(pTransferCtx); + pTransfer->pOwnerCtx = NULL; +} - LogFlowFunc(("Now %RU32 transfers left\n", pTransferCtx->cTransfers)); +/** + * Removes and unregisters a transfer from a transfer context. + * + * @param pTransferCtx Transfer context to remove transfer from. + * @param pTransfer Transfer to remove. + * + * @note Caller needs to take critical section. + */ +static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransferCtx, PSHCLTRANSFER pTransfer) +{ + Assert(RTCritSectIsOwner(&pTransferCtx->CritSect)); + + shclTransferCtxTransferRemoveLocked(pTransferCtx, pTransfer); + + shClTransferCtxUnlock(pTransferCtx); + + shclTransferCtxTransferNotifyUnregistered(pTransferCtx, pTransfer); + + shClTransferCtxLock(pTransferCtx); } /** @@ -3586,7 +3537,7 @@ static void shclTransferCtxTransferRemoveAndUnregister(PSHCLTRANSFERCTX pTransfe int ShClTransferCtxUnregisterById(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID idTransfer) { AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); - AssertReturn(shClTransferCtxIsValidTransferId(idTransfer), VERR_INVALID_PARAMETER); + AssertReturn(ShClTransferIdIsValid(idTransfer), VERR_INVALID_PARAMETER); shClTransferCtxLock(pTransferCtx); @@ -3598,8 +3549,6 @@ int ShClTransferCtxUnregisterById(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID if (pTransfer) { shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer); - - rc = shClTransferCtxSignal(pTransferCtx, false /* fRegistered */, pTransfer); } else if (ASMBitTest(&pTransferCtx->bmTransferIds[0], idTransfer)) { @@ -3615,131 +3564,6 @@ int ShClTransferCtxUnregisterById(PSHCLTRANSFERCTX pTransferCtx, SHCLTRANSFERID return rc; } -/** - * Waits for a transfer context event. - * - * @returns VBox status code. - * @param pTransferCtx Transfer context to wait for. - * @param msTimeout Timeout (in ms) to wait. - * @param pEvent Where to return the event data on success. - */ -static int shClTransferCtxWaitInternal(PSHCLTRANSFERCTX pTransferCtx, RTMSINTERVAL msTimeout, PSHCLTRANSFERCTXEVENT pEvent) -{ - LogFlowFunc(("Waiting for transfer context change (%RU32 timeout) ...\n", msTimeout)); - - int rc = RTSemEventWait(pTransferCtx->ChangedEvent, msTimeout); - if (RT_SUCCESS(rc)) - { - shClTransferCtxLock(pTransferCtx); - - memcpy(pEvent, &pTransferCtx->ChangedEventData, sizeof(SHCLTRANSFERCTXEVENT)); - - shClTransferCtxUnlock(pTransferCtx); - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Waits for transfer to be (un-)registered. - * - * @returns VBox status code. - * @param pTransferCtx Transfer context to wait for. - * @param msTimeout Timeout (in ms) to wait. - * @param fRegister Pass \c true for registering, or \c false for unregistering a transfer. - * @param idTransfer Transfer ID to wait for. - * Pass NIL_SHCLTRANSFERID for any transfer. - * @param ppTransfer Where to return the transfer being (un-)registered. Optional and can be NULL. - */ -int ShClTransferCtxWait(PSHCLTRANSFERCTX pTransferCtx, RTMSINTERVAL msTimeout, bool fRegister, SHCLTRANSFERID idTransfer, - PSHCLTRANSFER *ppTransfer) -{ - AssertPtrReturn(pTransferCtx, VERR_INVALID_POINTER); - - int rc = VERR_TIMEOUT; - - uint64_t const tsStartMs = RTTimeMilliTS(); - uint64_t msLeft = msTimeout; - for (;;) - { - SHCLTRANSFERCTXEVENT Event; - rc = shClTransferCtxWaitInternal(pTransferCtx, msLeft, &Event); - if (RT_FAILURE(rc)) - break; - - shClTransferCtxLock(pTransferCtx); - - if (Event.fRegistered == fRegister) - { - if ( Event.pTransfer - && ( idTransfer == NIL_SHCLTRANSFERID - || ShClTransferGetID(Event.pTransfer) == idTransfer)) - { - if (ppTransfer) - *ppTransfer = Event.pTransfer; - rc = VINF_SUCCESS; - } - } - - shClTransferCtxUnlock(pTransferCtx); - - if (RT_SUCCESS(rc)) - break; - - msLeft -= RT_MIN(msLeft, RTTimeMilliTS() - tsStartMs); - if (msLeft == 0) - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Cleans up all associated transfers which are not needed (anymore). - * This can be due to transfers which only have been announced but not / never being run. - * - * @param pTransferCtx Transfer context to cleanup transfers for. - */ -void ShClTransferCtxCleanup(PSHCLTRANSFERCTX pTransferCtx) -{ - AssertPtrReturnVoid(pTransferCtx); - - shClTransferCtxLock(pTransferCtx); - - LogFlowFunc(("pTransferCtx=%p, cTransfers=%RU16 cRunning=%RU16\n", - pTransferCtx, pTransferCtx->cTransfers, pTransferCtx->cRunning)); - - if (pTransferCtx->cTransfers == 0) - { - shClTransferCtxUnlock(pTransferCtx); - return; - } - - /* Remove all transfers which are not in a running state (e.g. only announced). */ - PSHCLTRANSFER pTransfer, pTransferNext; - RTListForEachSafe(&pTransferCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node) - { - shClTransferLock(pTransfer); - - SHCLTRANSFERSTATUS const enmStatus = shClTransferGetStatusLocked(pTransfer); - LogFlowFunc(("\tTransfer #%RU16: %s\n", pTransfer->State.uID, ShClTransferStatusToStr(enmStatus))); - - if (enmStatus != SHCLTRANSFERSTATUS_STARTED) - { - shClTransferUnlock(pTransfer); - - shclTransferCtxTransferRemoveAndUnregister(pTransferCtx, pTransfer); - ShClTransferDestroy(pTransfer); - } - else - shClTransferUnlock(pTransfer); - } - - shClTransferCtxUnlock(pTransferCtx); -} - /** * Returns whether the maximum of concurrent transfers of a specific transfer contexthas been reached or not. * @@ -3895,23 +3719,54 @@ int ShClTransferTransformPath(char *pszPath, size_t cbPath) * - Symbolic links are forbidden. * * @returns VBox status code. - * @param pcszPath Path to validate. + * @param pcszPath Zero-terminated path to validate. * @param fMustExist Whether the path to validate also must exist. */ int ShClTransferValidatePath(const char *pcszPath, bool fMustExist) { AssertPtrReturn(pcszPath, VERR_INVALID_POINTER); + size_t const cchPath = RTStrNLen(pcszPath, SHCLLISTENTRY_MAX_NAME); + if (cchPath >= SHCLLISTENTRY_MAX_NAME) + return VERR_INVALID_PARAMETER; + + return ShClTransferValidatePathEx(pcszPath, cchPath + 1, fMustExist); +} + +/** + * Validates whether a given path matches our set of rules or not, extended version. + * + * Validates the supplied size and requires the terminator to be the last byte. + * + * @returns VBox status code. + * @param pcszPath Path to validate. + * @param cbPath Size (in bytes) of @a pcszPath, including the terminator. + * Must not exceed SHCLLISTENTRY_MAX_NAME. + * @param fMustExist Whether the path to validate also must exist. + */ +int ShClTransferValidatePathEx(const char *pcszPath, size_t cbPath, bool fMustExist) +{ + if (!pcszPath) + return VERR_INVALID_POINTER; + + if ( !cbPath + || cbPath > SHCLLISTENTRY_MAX_NAME) + return VERR_INVALID_PARAMETER; + + size_t const cchPath = RTStrNLen(pcszPath, cbPath); + if (cchPath != cbPath - 1) + return VERR_INVALID_PARAMETER; + int rc = VINF_SUCCESS; - if (*pcszPath == '\0') + if (!cchPath) return rc; - char *pszSanitized = RTStrDup(pcszPath); + char *pszSanitized = RTStrDupN(pcszPath, cchPath); if (!pszSanitized) return VERR_NO_MEMORY; - rc = ShClPathSanitize(pszSanitized, strlen(pszSanitized) + 1); + rc = ShClPathSanitize(pszSanitized, cbPath); if ( RT_SUCCESS(rc) && shClTransferPathIsRelative(pcszPath) && strcmp(pszSanitized, pcszPath)) @@ -4920,8 +4775,12 @@ static int shClSvcTransferSendStatusExAsync(PSHCLCLIENT pClient, SHCLTRANSFERID rc = ShClSvcClientWakeup(pClient); if (RT_SUCCESS(rc)) { - LogRel2(("Shared Clipboard: Reported status %s (rc=%Rrc) of transfer %RU16 to guest\n", - ShClTransferStatusToStr(enmSts), rcTransfer, idTransfer)); + if (enmSts == SHCLTRANSFERSTATUS_ERROR) + LogRelMax(16, ("Shared Clipboard: Reported error status %Rrc for transfer %RU16 to guest\n", + rcTransfer, idTransfer)); + else + LogRel2(("Shared Clipboard: Reported status %s (rc=%Rrc) of transfer %RU16 to guest\n", + ShClTransferStatusToStr(enmSts), rcTransfer, idTransfer)); if (ppEvent) { @@ -4983,20 +4842,38 @@ static void shClSvcTransferCleanupAllUnused(PSHCLCLIENT pClient) PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; - PSHCLTRANSFER pTransfer, pTransferNext; - RTListForEachSafe(&pTxCtx->List, pTransfer, pTransferNext, SHCLTRANSFER, Node) + for (;;) { - SHCLTRANSFERSTATUS const enmSts = ShClTransferGetStatus(pTransfer); - if (enmSts != SHCLTRANSFERSTATUS_STARTED) - { - /* Let the guest know. */ - int rc2 = shClSvcTransferSendStatusAsync(pClient, pTransfer, - SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); - AssertRC(rc2); + PSHCLTRANSFER pTransfer = NULL; + + shClTransferCtxLock(pTxCtx); - ShClTransferCtxUnregisterById(pTxCtx, pTransfer->State.uID); - ShClTransferDestroy(pTransfer); + PSHCLTRANSFER pIt; + RTListForEach(&pTxCtx->List, pIt, SHCLTRANSFER, Node) + { + if (ShClTransferGetStatus(pIt) != SHCLTRANSFERSTATUS_STARTED) + { + pTransfer = pIt; + shclTransferCtxTransferRemoveLocked(pTxCtx, pTransfer); + break; + } } + + shClTransferCtxUnlock(pTxCtx); + + if (!pTransfer) + break; + + /* Let the guest know while the client state is still serialized. */ + int rc2 = shClSvcTransferSendStatusAsync(pClient, pTransfer, + SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); + AssertRC(rc2); + + ShClSvcClientUnlock(pClient); + shclTransferCtxTransferNotifyUnregistered(pTxCtx, pTransfer); + + shClTransferDestroyConsume(pTransfer); + ShClSvcClientLock(pClient); } } @@ -5008,26 +4885,41 @@ static void shClSvcTransferCleanupAllUnused(PSHCLCLIENT pClient) * @param pClient Client that owns the transfer. * @param enmDir Transfer direction to create. * @param enmSource Transfer source to create. + * @param pCallbacks Callback table to copy into the transfer. Optional and can be NULL. * @param idTransfer Transfer ID to use for creation. * If set to NIL_SHCLTRANSFERID, a new transfer ID will be created. - * @param ppTransfer Where to return the created transfer on success. Optional and can be NULL. + * @param ppTransfer Where to return the retained transfer on success. Optional and can be NULL. + * The caller must release it with ShClTransferRelease(). */ -int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) +int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, + PSHCLTRANSFERCALLBACKS pCallbacks, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) { AssertPtrReturn(pClient, VERR_INVALID_POINTER); - /* ppTransfer is optional. */ + if (ppTransfer) + *ppTransfer = NULL; LogFlowFuncEnter(); ShClSvcClientLock(pClient); - /* When creating a new transfer, this is a good time to clean up old stuff we don't need anymore. */ - shClSvcTransferCleanupAllUnused(pClient); - PSHCLTRANSFER pTransfer = NULL; - int rc = ShClTransferCreate(enmDir, enmSource, &pClient->Transfers.Callbacks, &pTransfer); + bool fReleaseCreationRef = false; + int rc = VERR_ACCESS_DENIED; + if (shClSvcClientTransfersAreAllowed(pClient)) + { + /* Cleanup drops the client lock while consuming stale transfers, so + * recheck policy after it reacquires the lock. */ + shClSvcTransferCleanupAllUnused(pClient); + if (shClSvcClientTransfersAreAllowed(pClient)) + rc = ShClTransferCreate(enmDir, enmSource, pCallbacks, &pTransfer); + } if (RT_SUCCESS(rc)) { + /* Establish pointer ownership before registration publishes the + * transfer to concurrent teardown paths. */ + ShClTransferAcquire(pTransfer); + fReleaseCreationRef = true; + if (idTransfer == NIL_SHCLTRANSFERID) rc = ShClTransferCtxRegister(&pClient->Transfers.Ctx, pTransfer, &idTransfer); else @@ -5035,14 +4927,30 @@ int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURC if (RT_SUCCESS(rc)) { if (ppTransfer) + { *ppTransfer = pTransfer; + fReleaseCreationRef = false; /* The caller takes ownership. */ + } } } ShClSvcClientUnlock(pClient); + SHCLTRANSFERID const idCreated = pTransfer ? ShClTransferGetID(pTransfer) : NIL_SHCLTRANSFERID; + if (fReleaseCreationRef) + ShClTransferRelease(pTransfer); + if (RT_FAILURE(rc)) - ShClTransferDestroy(pTransfer); + { + if (ShClTransferIdIsValid(idCreated)) + ShClSvcTransferDestroyById(pClient, idCreated); + else + { + /* Registration never published this transfer, so the creation + * path still owns its pointer exclusively. */ + shClTransferDestroyConsume(pTransfer); + } + } if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Creating transfer failed with %Rrc\n", rc)); @@ -5052,36 +4960,224 @@ int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURC } /** - * Destroys a transfer on the host. + * Detaches a service transfer by ID. + * + * @returns The exclusively claimed transfer, or NULL if it was not registered. + * @param pClient Client owning the transfer context. + * @param idTransfer ID of the transfer to claim. + * @param pExpected Expected transfer pointer, or NULL when claiming + * solely by ID. + */ +static PSHCLTRANSFER shClSvcTransferDetachById(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer, + PSHCLTRANSFER pExpected) +{ + PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + + shClTransferCtxLock(pTxCtx); + + PSHCLTRANSFER pTransfer = shClTransferCtxGetTransferByIdInternal(pTxCtx, idTransfer); + if (pTransfer) + { + if ( !pExpected + || pTransfer == pExpected) + shclTransferCtxTransferRemoveLocked(pTxCtx, pTransfer); + else + { + AssertMsgFailed(("Transfer ID %RU16 resolved to %p instead of exclusively owned transfer %p\n", + idTransfer, pTransfer, pExpected)); + pTransfer = NULL; + } + } + + shClTransferCtxUnlock(pTxCtx); + return pTransfer; +} + +/** + * Finishes destruction of an exclusively claimed service transfer. + * + * @param pClient Client that owned the transfer. + * @param pTransfer Exclusively claimed transfer to consume. + * @param fNotifyGuest Whether to report UNINITIALIZED to the guest. + */ +static void shClSvcTransferDestroyClaimed(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, bool fNotifyGuest) +{ + PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + + if (fNotifyGuest) + { + ShClSvcClientLock(pClient); + int rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, + SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); + AssertRC(rc); + ShClSvcClientUnlock(pClient); + } + + shclTransferCtxTransferNotifyUnregistered(pTxCtx, pTransfer); + + shClTransferDestroyConsume(pTransfer); +} + +/** + * Destroys a transfer on the host by its context-local ID, extended version. + * + * The ID is not reused during a transfer-context session, so claiming under + * the context lock does not depend on the lifetime of a borrowed pointer. + * + * @param pClient Client to destroy transfer for. + * @param idTransfer ID of the transfer to destroy. + * @param fNotifyGuest Whether to report UNINITIALIZED to the guest. + */ +void ShClSvcTransferDestroyByIdEx(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer, bool fNotifyGuest) +{ + AssertPtrReturnVoid(pClient); + AssertReturnVoid(ShClTransferIdIsValid(idTransfer)); + AssertMsgReturnVoid(!RTCritSectIsOwner(&pClient->CritSect), + ("The client lock must not be held while destroying a transfer\n")); + + LogFlowFuncEnter(); + + PSHCLTRANSFER pTransfer = shClSvcTransferDetachById(pClient, idTransfer, NULL /* pExpected */); + if (pTransfer) + shClSvcTransferDestroyClaimed(pClient, pTransfer, fNotifyGuest); + else + LogRel2(("Shared Clipboard: Transfer %RU16 was already detached\n", idTransfer)); + + LogFlowFuncLeave(); +} + +/** + * Destroys a transfer on the host by its context-local ID. + * + * @param pClient Client to destroy transfer for. + * @param idTransfer ID of the transfer to destroy. + */ +void ShClSvcTransferDestroyById(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer) +{ + ShClSvcTransferDestroyByIdEx(pClient, idTransfer, true /* fNotifyGuest */); +} + +/** + * Destroys an exclusively owned transfer pointer on the host. * * @param pClient Client to destroy transfer for. * @param pTransfer Transfer to destroy. * The pointer will be invalid after return. + * + * @note This pointer form is only safe for a newly created transfer whose + * lifetime and publication remain exclusively controlled by the + * caller. General callers must snapshot its ID while the pointer is + * known valid and use ShClSvcTransferDestroyById(). */ void ShClSvcTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) { if (!pTransfer) return; + AssertPtrReturnVoid(pClient); + AssertMsgReturnVoid(!RTCritSectIsOwner(&pClient->CritSect), + ("The client lock must not be held while destroying a transfer\n")); + LogFlowFuncEnter(); + /* The exclusive-ownership contract makes this dereference safe. */ + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + AssertReturnVoid(ShClTransferIdIsValid(idTransfer)); + + PSHCLTRANSFER pClaimed = shClSvcTransferDetachById(pClient, idTransfer, pTransfer /* pExpected */); + if (pClaimed) + shClSvcTransferDestroyClaimed(pClient, pClaimed, true /* fNotifyGuest */); + else + LogRel2(("Shared Clipboard: Exclusively owned transfer %p was already detached\n", pTransfer)); + + LogFlowFuncLeave(); +} + +/** + * Detaches all transfers from a Shared Clipboard client for later destruction. + * + * @param pClient Client to detach transfers from. + * @param pList Destination list for detached transfers. + * + * @note Destruction callbacks are deferred until + * shClSvcTransferDestroyDetachedAll(), so the caller may hold an + * outer ownership lock while invoking this function. + */ +void shClSvcTransferDetachAll(PSHCLCLIENT pClient, PRTLISTANCHOR pList) +{ + AssertPtrReturnVoid(pClient); + AssertPtrReturnVoid(pList); + ShClSvcClientLock(pClient); PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + for (;;) + { + shClTransferCtxLock(pTxCtx); - ShClTransferCtxUnregisterById(pTxCtx, pTransfer->State.uID); + PSHCLTRANSFER pTransfer = shClTransferCtxGetTransferByIndexInternal(pTxCtx, 0 /* Index */); + if (pTransfer) + shclTransferCtxTransferRemoveLocked(pTxCtx, pTransfer); - /* Make sure to let the guest know. */ - int rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, - SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); - AssertRC(rc); + shClTransferCtxUnlock(pTxCtx); - ShClTransferDestroy(pTransfer); - pTransfer = NULL; + if (!pTransfer) + break; + + int rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, + SHCLTRANSFERSTATUS_UNINITIALIZED, VINF_SUCCESS, NULL /* ppEvent */); + AssertRC(rc); + + RTListAppend(pList, &pTransfer->Node); + } ShClSvcClientUnlock(pClient); +} - LogFlowFuncLeave(); +/** + * Destroys transfers previously detached by shClSvcTransferDetachAll(). + * + * @param pList List of detached transfers to consume. + * + * @note No service, client or transfer-context ownership lock may be held. + */ +void shClSvcTransferDestroyDetachedAll(PRTLISTANCHOR pList) +{ + AssertPtrReturnVoid(pList); + + PSHCLTRANSFER pTransfer, pTransferNext; + RTListForEachSafe(pList, pTransfer, pTransferNext, SHCLTRANSFER, Node) + { + RTListNodeRemove(&pTransfer->Node); + + PSHCLTRANSFERCTX pTxCtx = pTransfer->pOwnerCtx; + AssertPtr(pTxCtx); + shclTransferCtxTransferNotifyUnregistered(pTxCtx, pTransfer); + + shClTransferDestroyConsume(pTransfer); + } +} + + +/** + * Destroys all transfers of a Shared Clipboard client. + * + * @param pClient Client to destroy transfers for. + */ +void shClSvcTransferDestroyAll(PSHCLCLIENT pClient) +{ + if (!pClient) + return; + AssertMsgReturnVoid(!RTCritSectIsOwner(&pClient->CritSect), + ("The client lock must not be held while destroying transfers\n")); + + LogFlowFuncEnter(); + + RTLISTANCHOR ListDestroy; + RTListInit(&ListDestroy); + + shClSvcTransferDetachAll(pClient, &ListDestroy); + shClSvcTransferDestroyDetachedAll(&ListDestroy); } #endif /* VBOX_WITH_SHARED_CLIPBOARD_HOST */ @@ -5100,8 +5196,6 @@ int ShClTransferInit(PSHCLTRANSFER pTransfer) ("Wrong status (currently is %s)\n", ShClTransferStatusToStr(pTransfer->State.enmStatus)), shClTransferUnlock(pTransfer), VERR_WRONG_ORDER); - pTransfer->cRefs = 0; - LogFlowFunc(("uID=%RU32, enmDir=%RU32, enmSource=%RU32\n", pTransfer->State.uID, pTransfer->State.enmDir, pTransfer->State.enmSource)); @@ -5123,7 +5217,7 @@ int ShClTransferInit(PSHCLTRANSFER pTransfer) { /* Sanity: Make sure that the transfer we're gonna report as INITIALIZED * actually has some root entries set, as the other side can query for those at any time then. */ - if (pTransfer->State.enmDir == SHCLTRANSFERDIR_TO_REMOTE) + if (pTransfer->State.enmSource == SHCLSOURCE_LOCAL) AssertMsgStmt(ShClTransferRootsCount(pTransfer), ("Transfer has no root entries set (yet)\n"), rc = VERR_WRONG_ORDER); if (RT_SUCCESS(rc)) @@ -5135,7 +5229,7 @@ int ShClTransferInit(PSHCLTRANSFER pTransfer) /* Note: Callback will be called after we unlocked the transfer, as the caller might access the transfer right away. */ if ( RT_SUCCESS(rc) && pTransfer->Callbacks.pfnOnInitialized) - pTransfer->Callbacks.pfnOnInitialized(&pTransfer->CallbackCtx); + rc = pTransfer->Callbacks.pfnOnInitialized(&pTransfer->CallbackCtx); if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Initialization of transfer failed with %Rrc\n", rc)); @@ -5160,31 +5254,35 @@ int ShClSvcTransferInit(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) ShClSvcClientLock(pClient); - Assert(ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_NONE); + int rc; + if (!shClSvcClientTransfersAreAllowed(pClient)) + rc = VERR_ACCESS_DENIED; + else + { + Assert(ShClTransferGetStatus(pTransfer) == SHCLTRANSFERSTATUS_NONE); - PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; + PSHCLTRANSFERCTX pTxCtx = &pClient->Transfers.Ctx; - int rc; + if (!ShClTransferCtxIsMaximumReached(pTxCtx)) + { + SHCLTRANSFERDIR const enmDir = ShClTransferGetDir(pTransfer); - if (!ShClTransferCtxIsMaximumReached(pTxCtx)) - { - SHCLTRANSFERDIR const enmDir = ShClTransferGetDir(pTransfer); + LogRel2(("Shared Clipboard: Initializing %s transfer ...\n", + enmDir == SHCLTRANSFERDIR_GUEST_TO_HOST ? "guest -> host" : "host -> guest")); - LogRel2(("Shared Clipboard: Initializing %s transfer ...\n", - enmDir == SHCLTRANSFERDIR_FROM_REMOTE ? "guest -> host" : "host -> guest")); + rc = ShClTransferInit(pTransfer); + } + else + rc = VERR_SHCLPB_MAX_TRANSFERS_REACHED; - rc = ShClTransferInit(pTransfer); + /* Tell the guest the outcome. */ + int rc2 = shClSvcTransferSendStatusAsync(pClient, pTransfer, + RT_SUCCESS(rc) + ? SHCLTRANSFERSTATUS_INITIALIZED : SHCLTRANSFERSTATUS_ERROR, rc, + NULL /* ppEvent */); + if (RT_SUCCESS(rc)) + rc = rc2; } - else - rc = VERR_SHCLPB_MAX_TRANSFERS_REACHED; - - /* Tell the guest the outcome. */ - int rc2 = shClSvcTransferSendStatusAsync(pClient, pTransfer, - RT_SUCCESS(rc) - ? SHCLTRANSFERSTATUS_INITIALIZED : SHCLTRANSFERSTATUS_ERROR, rc, - NULL /* ppEvent */); - if (RT_SUCCESS(rc)) - rc2 = rc; if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Initializing transfer failed with %Rrc\n", rc)); diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp index 6c2cdaf19bfa..ffd76b4296c8 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp @@ -1,4 +1,4 @@ -/* $Id: clipboard-win.cpp 114754 2026-07-22 21:18:51Z knut.osmundsen@oracle.com $ */ +/* $Id: clipboard-win.cpp 115067 2026-08-18 14:37:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard: Windows-specific functions for clipboard handling. */ @@ -152,7 +152,7 @@ int ShClWinClear(void) return VINF_SUCCESS; int const rc = RTErrConvertFromWin32(dwLastErr); - LogRel2(("Shared Clipboard: Clearing Windows clipboard failed with %Rrc (0x%x)\n", rc, dwLastErr)); + LogRelMax(16, ("Shared Clipboard: Clearing Windows clipboard failed with %Rrc (0x%x)\n", rc, dwLastErr)); return rc; } @@ -199,11 +199,58 @@ void ShClWinCtxDestroy(PSHCLWINCTX pWinCtx) if (RTCritSectIsInitialized(&pWinCtx->CritSect)) { - int rc2 = RTCritSectDelete(&pWinCtx->CritSect); - AssertRC(rc2); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + int rc2 = RTCritSectEnter(&pWinCtx->CritSect); + AssertFatalMsgRC(rc2, ("Taking the Windows clipboard context lock during teardown failed with %Rrc\n", rc2)); + + /* Keep the owned reference alive while detaching it, then invalidate the + * object before the backend context stored in its callbacks is freed. */ + ShClWinDataObject *pDataObjInFlight = pWinCtx->pDataObjInFlight; + pWinCtx->pDataObjInFlight = NULL; + + rc2 = RTCritSectLeave(&pWinCtx->CritSect); + AssertFatalMsgRC(rc2, ("Releasing the Windows clipboard context lock during teardown failed with %Rrc\n", rc2)); + + if (pDataObjInFlight) + { + pDataObjInFlight->Uninit(); + pDataObjInFlight->Release(); + } +#endif + int const rcDelete = RTCritSectDelete(&pWinCtx->CritSect); + AssertRC(rcDelete); } } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Disables new callbacks on the current in-flight data object and waits for an + * active callback running on another thread to return. + * + * @param pWinCtx Windows context whose data-object callbacks to disable. + */ +void ShClWinCtxDisableDataObjectCallbacks(PSHCLWINCTX pWinCtx) +{ + AssertPtrReturnVoid(pWinCtx); + + int rc = RTCritSectEnter(&pWinCtx->CritSect); + AssertFatalMsgRC(rc, ("Taking the Windows clipboard context lock while disabling callbacks failed with %Rrc\n", rc)); + + ShClWinDataObject *pDataObj = pWinCtx->pDataObjInFlight; + if (pDataObj) + pDataObj->AddRef(); + + rc = RTCritSectLeave(&pWinCtx->CritSect); + AssertFatalMsgRC(rc, ("Releasing the Windows clipboard context lock while disabling callbacks failed with %Rrc\n", rc)); + + if (pDataObj) + { + pDataObj->DisableCallbacks(); + pDataObj->Release(); + } +} +#endif + /** * Checks and initializes function pointer which are required for using * the new clipboard API. @@ -436,13 +483,15 @@ SHCLFORMAT ShClWinClipboardFormatToVBox(UINT uFormat) else if ( (RTUtf16Cmp(szFormatName, RT_LSTR("FileGroupDescriptor")) == 0) || (RTUtf16Cmp(szFormatName, RT_LSTR("FileGroupDescriptorW")) == 0) || (RTUtf16Cmp(szFormatName, RT_LSTR("FileContents")) == 0)) + LogRelMax(16, ("Shared Clipboard: Windows virtual-file clipboard format '%ls' is not yet supported " + "for file transfers from the local Windows clipboard\n", szFormatName)); # else else if ( (RTStrCmp(szFormatName, CFSTR_FILEDESCRIPTORA) == 0) || (RTStrCmp(szFormatName, "FileGroupDescriptorW") == 0) || (RTStrCmp(szFormatName, CFSTR_FILECONTENTS) == 0)) + LogRelMax(16, ("Shared Clipboard: Windows virtual-file clipboard format '%s' is not yet supported " + "for file transfers from the local Windows clipboard\n", szFormatName)); # endif - LogRel2(("Shared Clipboard: Windows virtual-file clipboard format '%s' is not supported as a host file-transfer source yet\n", - szFormatName)); #endif } } @@ -643,7 +692,7 @@ int ShClWinConvertMIMEToCFHTML(const char *pszSource, size_t cb, char **ppszOutp { /* likely */ } else { - LogRel2(("Shared Clipboard: Error: Invalid source fragment.for HTML MIME data, rc=%Rrc\n", rc)); + LogRelMax(16, ("Shared Clipboard: Invalid source fragment for HTML MIME data, rc=%Rrc\n", rc)); return rc; } size_t const cchFragment = strlen(pszSource); /* Unfortunately the validator doesn't return the length. */ @@ -1082,25 +1131,22 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, /* Make sure to enter the critical section before setting the clipboard data, as otherwise WM_CLIPBOARDUPDATE * might get called *before* we had the opportunity to set pWinCtx->hWndClipboardOwnerUs below. */ + ShClWinDataObject *pObjToRelease = NULL; + ShClWinDataObject *pObjReplaced = NULL; + int rc = RTCritSectEnter(&pWinCtx->CritSect); if (RT_SUCCESS(rc)) { LogFlowFunc(("pWinCtx->pDataObjInFlight=%p\n", pWinCtx->pDataObjInFlight)); - /* Create a new data object here, assign it as the the current data object in-flight and - * announce it to Windows below. - * - * The data object will be deleted automatically once its refcount reaches 0. - */ + /* Keep an explicit reference while creating and publishing the object. */ ShClWinDataObject *pObj = new ShClWinDataObject(); if (pObj) { + pObj->AddRef(); rc = pObj->Init(pCtx, pCallbacks); - if (RT_SUCCESS(rc)) - { - if (RT_SUCCESS(rc)) - pWinCtx->pDataObjInFlight = pObj; - } + if (RT_FAILURE(rc)) + pObjToRelease = pObj; } else rc = VERR_NO_MEMORY; @@ -1117,10 +1163,10 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, for (unsigned uTries = 0; uTries < 3; uTries++) { - hr = OleSetClipboard(pWinCtx->pDataObjInFlight); + hr = OleSetClipboard(pObj); if (SUCCEEDED(hr)) { - Assert(OleIsCurrentClipboard(pWinCtx->pDataObjInFlight) == S_OK); /* Sanity. */ + Assert(OleIsCurrentClipboard(pObj) == S_OK); /* Sanity. */ /* * Calling OleSetClipboard() changed the clipboard owner, which in turn will let us receive @@ -1129,6 +1175,11 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, */ pWinCtx->hWndClipboardOwnerUs = GetClipboardOwner(); + /* Transfer the local reference to the context only after OLE + * accepted the new object, preserving the old one on failure. */ + pObjReplaced = pWinCtx->pDataObjInFlight; + pWinCtx->pDataObjInFlight = pObj; + LogFlowFunc(("hWndClipboardOwnerUs=%p\n", pWinCtx->hWndClipboardOwnerUs)); break; } @@ -1140,6 +1191,7 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, if (FAILED(hr)) { rc = VERR_ACCESS_DENIED; /** @todo Fudge; fix this. */ + pObjToRelease = pObj; LogRel(("Shared Clipboard: Failed with %Rhrc when setting data object to clipboard\n", hr)); } } @@ -1148,6 +1200,18 @@ int ShClWinTransferCreateAndSetDataObject(PSHCLWINCTX pWinCtx, AssertRC(rc2); } + if (pObjReplaced) + { + pObjReplaced->Uninit(); + pObjReplaced->Release(); + } + + if (pObjToRelease) + { + pObjToRelease->Uninit(); + pObjToRelease->Release(); + } + LogFlowFuncLeaveRC(rc); return rc; } @@ -1166,13 +1230,57 @@ int ShClWinTransferCreate(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) AssertMsgReturn( pTransfer->pvUser == NULL && pTransfer->cbUser == 0, ("Already initialized Windows-specific data\n"), VERR_WRONG_ORDER); - pTransfer->pvUser = new ShClWinTransferCtx(); /** @todo Can this throw? */ - AssertPtrReturn(pTransfer->pvUser, VERR_INVALID_POINTER); - pTransfer->cbUser = sizeof(ShClWinTransferCtx); + ShClWinTransferCtx *pWinTransferCtx = new ShClWinTransferCtx(); /** @todo Can this throw? */ + AssertPtrReturn(pWinTransferCtx, VERR_NO_MEMORY); + + int rc = RTCritSectInit(&pWinTransferCtx->CritSect); + if (RT_FAILURE(rc)) + { + delete pWinTransferCtx; + return rc; + } + + pTransfer->pvUser = pWinTransferCtx; + pTransfer->cbUser = sizeof(*pWinTransferCtx); return VINF_SUCCESS; } +/** + * Unregisters the data object associated with a Windows transfer. + * + * This disables its backend callbacks and drops its long-lived transfer + * reference while leaving the per-transfer context intact for temporary users. + * + * @param pTransfer Shared Clipboard transfer to unregister. + */ +void ShClWinTransferUnregister(PSHCLTRANSFER pTransfer) +{ + AssertPtrReturnVoid(pTransfer); + + if (pTransfer->pvUser) + { + Assert(pTransfer->cbUser == sizeof(ShClWinTransferCtx)); + ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)pTransfer->pvUser; + AssertPtr(pWinURITransferCtx); + + int rc = RTCritSectEnter(&pWinURITransferCtx->CritSect); + AssertFatalMsgRC(rc, ("Taking the Windows transfer-context lock during teardown failed with %Rrc\n", rc)); + + ShClWinDataObject *pDataObj = pWinURITransferCtx->pDataObj; + pWinURITransferCtx->pDataObj = NULL; + + rc = RTCritSectLeave(&pWinURITransferCtx->CritSect); + AssertFatalMsgRC(rc, ("Releasing the Windows transfer-context lock during teardown failed with %Rrc\n", rc)); + + if (pDataObj) + { + pDataObj->Uninit(); + pDataObj->Release(); + } + } +} + /** * Destroys implementation-specific data for a Windows Shared Clipboard transfer. * @@ -1195,13 +1303,13 @@ void ShClWinTransferDestroy(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) ShClWinTransferCtx *pWinURITransferCtx = (ShClWinTransferCtx *)pTransfer->pvUser; AssertPtr(pWinURITransferCtx); - /* If the transfer has a data object assigned, uninitialize it here. - * Note: We don't free the object here, as other processes like the Windows Explorer still might refer to it. */ - if (pWinURITransferCtx->pDataObj) - { - pWinURITransferCtx->pDataObj->Uninit(); - pWinURITransferCtx->pDataObj = NULL; - } + /* Fallback for direct, non-context destruction. Registered consuming + * teardown normally disables and detaches this object in the + * unregistration callback. */ + ShClWinTransferUnregister(pTransfer); + + int const rc = RTCritSectDelete(&pWinURITransferCtx->CritSect); + AssertRC(rc); delete pWinURITransferCtx; pWinURITransferCtx = NULL; @@ -1283,6 +1391,8 @@ int ShClWinTransferStart(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) { RT_NOREF(pTransfer); + ShClWinDataObject *pObjHandedOff = NULL; + int rc = RTCritSectEnter(&pWinCtx->CritSect); if (RT_SUCCESS(rc)) { @@ -1291,7 +1401,10 @@ int ShClWinTransferStart(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) { rc = shClWinTransferStartInternal(pWinCtx, pObj); if (RT_SUCCESS(rc)) + { pWinCtx->pDataObjInFlight = NULL; /* Hand off to Windows on success. */ + pObjHandedOff = pObj; + } } else /* No current in-flight data object. */ rc = VERR_WRONG_ORDER; @@ -1300,6 +1413,9 @@ int ShClWinTransferStart(PSHCLWINCTX pWinCtx, PSHCLTRANSFER pTransfer) AssertRC(rc2); } + if (pObjHandedOff) + pObjHandedOff->Release(); + return rc; } diff --git a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp index bcf38199f302..384d0f306021 100644 --- a/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp @@ -70,6 +70,9 @@ # include # include #endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +# include +#endif #include #include @@ -142,14 +145,61 @@ SHCL_X11_DECL(SHCLX11FMTTABLE) g_aFormats[] = { "x-special/gnome-copied-files", SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES, VBOX_SHCL_FMT_URI_LIST }, { "x-special/mate-copied-files", SHCLX11FMT_URI_LIST_MATE_COPIED_FILES, VBOX_SHCL_FMT_URI_LIST }, { "x-special/nautilus-clipboard", SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD, VBOX_SHCL_FMT_URI_LIST }, - /* KDE uses this as cut/copy metadata; the actual file list is in text/uri-list. */ - { "application/x-kde-cutselection", SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, VBOX_SHCL_FMT_NONE }, + /* Associate KDE cut-selection with the VBox URI format so that we advertise + * it when exporting files. It is metadata only, so s_aTransferTargets + * explicitly prevents selecting or parsing it in the other direction. */ + { "application/x-kde-cutselection", SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, VBOX_SHCL_FMT_URI_LIST }, /** @todo Anything else we need to add here? */ /** @todo Add Wayland / Weston support. */ #endif }; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** X11 transfer target can be read from the X11 clipboard. */ +# define SHCLX11TRANSFERDIR_F_FROM_X11 RT_BIT_32(0) +/** X11 transfer target can be offered to the X11 clipboard. */ +# define SHCLX11TRANSFERDIR_F_TO_X11 RT_BIT_32(1) +/** X11 transfer target supports both directions. */ +# define SHCLX11TRANSFERDIR_F_BIDIRECTIONAL (SHCLX11TRANSFERDIR_F_FROM_X11 | SHCLX11TRANSFERDIR_F_TO_X11) + +/** + * Direction and selection policy for an X11 file-transfer target. + * + * The X11 format enum identifies the representation; it does not define + * preference or whether the representation actually contains file names. + */ +typedef struct SHCLX11TRANSFERTARGET +{ + /** The X11 transfer format. */ + SHCLX11FMT enmFmt; + /** SHCLX11TRANSFERDIR_F_XXX direction flags. */ + uint32_t fDirections; + /** Incoming selection priority; higher values have higher priority, while + * zero means the target cannot be read as a file list. */ + uint8_t uPriorityFromX11; +} SHCLX11TRANSFERTARGET; + +/** + * Transfer target capabilities and incoming selection order. + * + * Keeping this policy separate from SHCLX11FMT makes adding or reordering enum + * values harmless. Prefer the standard URI-list target when several file-list + * representations are offered, followed by the file-manager-specific formats. + * KDE cut-selection carries only cut/copy state, not file names, and therefore + * is offered when exporting but never accepted as an incoming file list. + */ +static const SHCLX11TRANSFERTARGET s_aTransferTargets[] = +{ + { SHCLX11FMT_URI_LIST, SHCLX11TRANSFERDIR_F_BIDIRECTIONAL, 100 }, + { SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES, SHCLX11TRANSFERDIR_F_BIDIRECTIONAL, 90 }, + { SHCLX11FMT_URI_LIST_MATE_COPIED_FILES, SHCLX11TRANSFERDIR_F_BIDIRECTIONAL, 80 }, + { SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD, SHCLX11TRANSFERDIR_F_BIDIRECTIONAL, 70 }, + { SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, SHCLX11TRANSFERDIR_F_TO_X11, 0 } +}; +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + #ifdef TESTCASE # ifdef RT_OS_SOLARIS_10 char XtStrings [] = ""; @@ -243,6 +293,39 @@ static SHCLFORMAT clipVBoxFormatForX11Format(SHCLX11FMTIDX uFmtIdx) return g_aFormats[uFmtIdx].uFmtVBox; } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Looks up the direction and priority policy of an X11 transfer target. + * + * @returns Transfer target capabilities, or NULL if the format is not a transfer target. + * @param enmFmt X11 format to look up. + */ +static const SHCLX11TRANSFERTARGET *shClX11TransferTargetLookup(SHCLX11FMT enmFmt) +{ + for (size_t i = 0; i < RT_ELEMENTS(s_aTransferTargets); i++) + if (s_aTransferTargets[i].enmFmt == enmFmt) + return &s_aTransferTargets[i]; + return NULL; +} + + +/** + * Checks whether an X11 transfer target supports a direction. + * + * Unknown transfer targets are deliberately rejected so they cannot silently + * become importable or exportable merely by being added to g_aFormats. + * + * @returns true if supported, false if not. + * @param enmFmt X11 format to check. + * @param fDirection SHCLX11TRANSFERDIR_F_XXX direction to check. + */ +static bool shClX11TransferTargetSupportsDirection(SHCLX11FMT enmFmt, uint32_t fDirection) +{ + const SHCLX11TRANSFERTARGET *pTarget = shClX11TransferTargetLookup(enmFmt); + return pTarget && (pTarget->fDirections & fDirection) != 0; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + /** * Looks up the X11 format matching a given X11 atom. * @@ -274,8 +357,19 @@ static SHCLX11FMTIDX clipEnumX11Formats(SHCLFORMATS uFormatsVBox, { for (unsigned i = lastFmtIdx + 1; i < RT_ELEMENTS(g_aFormats); ++i) { - if (uFormatsVBox & clipVBoxFormatForX11Format(i)) - return i; + SHCLFORMAT const uFmtVBox = clipVBoxFormatForX11Format(i); + if (uFormatsVBox & uFmtVBox) + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* URI targets are filtered by the explicit export policy. This + * includes KDE metadata, while excluding any future import-only + * representations. */ + if ( uFmtVBox != VBOX_SHCL_FMT_URI_LIST + || shClX11TransferTargetSupportsDirection(clipRealFormatForX11Format(i), + SHCLX11TRANSFERDIR_F_TO_X11)) +#endif + return i; + } } return NIL_CLIPX11FORMAT; @@ -408,9 +502,28 @@ static int clipThreadScheduleCall(PSHCLX11CTX pCtx, XtAppAddTimeOut(pCtx->pAppContext, 0, (XtTimerCallbackProc)proc, (XtPointer)client_data); - ssize_t cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN); - Assert(cbWritten == WAKE_UP_STRING_LEN); - RT_NOREF(cbWritten); + + ssize_t cbWritten; + do + cbWritten = write(pCtx->wakeupPipeWrite, WAKE_UP_STRING, WAKE_UP_STRING_LEN); + while (cbWritten < 0 && errno == EINTR); + if (cbWritten < 0) + { + /* A full non-blocking pipe is already readable and will wake the worker. */ +# if EAGAIN == EWOULDBLOCK + if (errno != EAGAIN) +# else + if (errno != EAGAIN && errno != EWOULDBLOCK) +# endif + { + int const rc = RTErrConvertFromErrno(errno); + /* The Xt callback is already queued and cannot be cancelled without racing the worker. */ + AssertFatalMsgRC(rc, ("Waking the X11 event thread failed with %Rrc\n", rc)); + } + } + else if (cbWritten != WAKE_UP_STRING_LEN) + AssertFatalMsgFailed(("Waking the X11 event thread wrote only %zd of %zu bytes\n", + cbWritten, (size_t)WAKE_UP_STRING_LEN)); #else RT_NOREF(pCtx); tstThreadScheduleCall(proc, client_data); @@ -594,8 +707,13 @@ static SHCLX11FMTIDX clipGetHtmlFormatFromTargets(PSHCLX11CTX pCtx, # ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** - * Goes through an array of X11 clipboard targets to see if they contain an URI list - * format we can support, and if so choose the ones we prefer. + * Goes through an array of X11 clipboard targets to find the preferred file-list + * representation that is safe to import. + * + * Selection uses s_aTransferTargets rather than SHCLX11FMT enum values or the + * order in which the clipboard owner returned its targets. Targets which only + * carry transfer metadata are ignored even when they map to VBOX_SHCL_FMT_URI_LIST + * for export purposes. * * @return Supported X clipboard format. * @param pCtx The X11 clipboard context to use. @@ -610,27 +728,29 @@ SHCL_X11_DECL(SHCLX11FMTIDX) clipGetURIListFormatFromTargets(PSHCLX11CTX pCtx, AssertReturn(RT_VALID_PTR(paIdxFmtTargets) || cTargets == 0, NIL_CLIPX11FORMAT); SHCLX11FMTIDX idxFmtURI = NIL_CLIPX11FORMAT; - SHCLX11FMT fmtURIX11 = SHCLX11FMT_INVALID; + uint8_t uPriority = 0; bool fSawUnsupportedTransferMetadata = false; for (unsigned i = 0; i < cTargets; ++i) { SHCLX11FMTIDX idxFmt = paIdxFmtTargets[i]; if (idxFmt != NIL_CLIPX11FORMAT) { - SHCLX11FMT const fmtReal = clipRealFormatForX11Format(idxFmt); - if ( fmtReal == SHCLX11FMT_URI_LIST_KDE_CUTSELECTION - && clipVBoxFormatForX11Format(idxFmt) != VBOX_SHCL_FMT_URI_LIST) + SHCLX11FMT const enmFmtX11 = clipRealFormatForX11Format(idxFmt); + const SHCLX11TRANSFERTARGET *pTarget = shClX11TransferTargetLookup(enmFmtX11); + if ( pTarget + && !(pTarget->fDirections & SHCLX11TRANSFERDIR_F_FROM_X11)) { fSawUnsupportedTransferMetadata = true; - LogRelMax2(16, ("Shared Clipboard: Ignoring X11 clipboard target '%s'; it only describes cut/copy state, not file names\n", + LogRelMax2(16, ("Shared Clipboard: Ignoring X11 clipboard target '%s'; it only describes cut/copy " + "state, not file names\n", g_aFormats[idxFmt].pcszAtom)); } - - if ( (clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_URI_LIST) - && fmtURIX11 < fmtReal) + else if ( pTarget + && clipVBoxFormatForX11Format(idxFmt) == VBOX_SHCL_FMT_URI_LIST + && uPriority < pTarget->uPriorityFromX11) { - fmtURIX11 = fmtReal; - idxFmtURI = idxFmt; + uPriority = pTarget->uPriorityFromX11; + idxFmtURI = idxFmt; } } } @@ -639,7 +759,8 @@ SHCL_X11_DECL(SHCLX11FMTIDX) clipGetURIListFormatFromTargets(PSHCLX11CTX pCtx, LogRelMax2(16, ("Shared Clipboard: Selected X11 URI-list target '%s' for host file transfer\n", g_aFormats[idxFmtURI].pcszAtom)); else if (fSawUnsupportedTransferMetadata) - LogRelMax2(16, ("Shared Clipboard: X11 clipboard had file-transfer metadata but no supported file list target (for example text/uri-list); host file transfer will not be announced\n")); + LogRelMax(16, ("Shared Clipboard: X11 clipboard had file-transfer metadata but no supported file list target " + "(for example text/uri-list); host file transfer will not be announced\n")); return idxFmtURI; } @@ -1175,22 +1296,6 @@ static void clipUninitInternal(PSHCLX11CTX pCtx) LogFlowFuncLeaveRC(VINF_SUCCESS); } -/** - * Helper function for public X11 Shared Clipboard APIs to know whether we're running in headless mode or not. - * - * Headless mode either could mean that we don't want to touch the X11 clipboard, or that X simply isn't installed and/or - * isn't available (e.g. running on a pure server installation w/o any desktop environment). - * - * Goal here is to make the X11 API transparent for the caller whether X is available or not. - * - * @returns \c true if running in headless mode, or \c false if not. - * @param pCtx The X11 clipboard context to use. - */ -DECLINLINE(bool) shClX11HeadlessIsEnabled(PSHCLX11CTX pCtx) -{ - return pCtx->fHeadless; -} - /** * Sets the callback table, internal version. * @@ -1207,17 +1312,6 @@ static void shClX11SetCallbacksInternal(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallba RT_ZERO(pCtx->Callbacks); } -/** - * Sets the callback table. - * - * @param pCtx The clipboard context. - * @param pCallbacks Callback table to set. If NULL, the current callback table will be cleared. - */ -void ShClX11SetCallbacks(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks) -{ - shClX11SetCallbacksInternal(pCtx, pCallbacks); -} - /** * Initializes a X11 context of the Shared Clipboard. * @@ -1225,15 +1319,16 @@ void ShClX11SetCallbacks(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks) * @param pCtx The clipboard context to initialize. * @param pCallbacks Callbacks to use (copied, not used directly). Optional. * @param pParent Parent context to use. - * @param fHeadless Whether the code runs in a headless environment or not. */ -int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent, bool fHeadless) +int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParent) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); LogFlowFunc(("pCtx=%p\n", pCtx)); RT_BZERO(pCtx, sizeof(SHCLX11CTX)); + pCtx->wakeupPipeRead = -1; + pCtx->wakeupPipeWrite = -1; /* Init clipboard cache. */ ShClCacheInit(&pCtx->Cache); @@ -1241,8 +1336,7 @@ int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParen /* Install given callbacks. */ shClX11SetCallbacksInternal(pCtx, pCallbacks); - pCtx->fHeadless = fHeadless; - pCtx->pFrontend = pParent; + pCtx->pFrontend = pParent; #ifdef VBOX_WITH_SHARED_CLIPBOARD_XT_BUSY pCtx->fXtBusy = false; @@ -1251,24 +1345,21 @@ int ShClX11Init(PSHCLX11CTX pCtx, PSHCLCALLBACKS pCallbacks, PSHCLCONTEXT pParen int rc = VINF_SUCCESS; - LogRel(("Shared Clipboard: Initializing X11 clipboard (%s mode)\n", fHeadless ? "headless" : "regular")); + LogRel(("Shared Clipboard: Initializing X11 clipboard\n")); - if (!pCtx->fHeadless) - { #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - rc = ShClTransferHttpServerInit(&pCtx->HttpCtx.HttpServer); + rc = ShClTransferHttpServerInit(&pCtx->HttpCtx.HttpServer); #endif #ifdef TESTCASE + if (RT_SUCCESS(rc)) + { + /** @todo The testcases currently do not utilize the threading code. So init stuff here. */ + rc = clipInitInternal(pCtx); if (RT_SUCCESS(rc)) - { - /** @todo The testcases currently do not utilize the threading code. So init stuff here. */ - rc = clipInitInternal(pCtx); - if (RT_SUCCESS(rc)) - rc = clipRegisterContext(pCtx); - } -#endif + rc = clipRegisterContext(pCtx); } +#endif if (RT_FAILURE(rc)) LogRel(("Shared Clipboard: Initializing X11 clipboard failed with %Rrc\n", rc)); @@ -1295,8 +1386,7 @@ int ShClX11Term(PSHCLX11CTX pCtx) int rc = VINF_SUCCESS; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - if (!shClX11HeadlessIsEnabled(pCtx)) - rc = ShClTransferHttpServerDestroy(&pCtx->HttpCtx.HttpServer); + rc = ShClTransferHttpServerDestroy(&pCtx->HttpCtx.HttpServer); #endif #ifdef TESTCASE @@ -1305,18 +1395,31 @@ int ShClX11Term(PSHCLX11CTX pCtx) clipUninitInternal(pCtx); #endif - if (!shClX11HeadlessIsEnabled(pCtx)) - { - /* We set this to NULL when the event thread exits. It really should - * have exited at this point, when we are about to unload the code from - * memory. */ - AssertStmt(pCtx->pWidget == NULL, rc = VERR_WRONG_ORDER); - } + /* We set this to NULL when the event thread exits. It really should + * have exited at this point, when we are about to unload the code from + * memory. */ + AssertStmt(pCtx->pWidget == NULL, rc = VERR_WRONG_ORDER); return rc; } #ifndef TESTCASE +/** Closes the wakeup pipe created for the X11 event thread. */ +static void clipThreadCloseWakeupPipe(PSHCLX11CTX pCtx) +{ + if (pCtx->wakeupPipeRead >= 0) + { + close(pCtx->wakeupPipeRead); + pCtx->wakeupPipeRead = -1; + } + + if (pCtx->wakeupPipeWrite >= 0) + { + close(pCtx->wakeupPipeWrite); + pCtx->wakeupPipeWrite = -1; + } +} + /** * Starts our own Xt even thread for handling Shared Clipboard messages, extended version. * @@ -1329,9 +1432,8 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) { AssertPtrReturn(pCtx, VERR_INVALID_POINTER); - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; - + pCtx->Thread = NIL_RTTHREAD; + pCtx->fThreadStarted = false; pCtx->fGrabClipboardOnStart = fGrab; clipResetX11Formats(pCtx); @@ -1348,12 +1450,16 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) pCtx->wakeupPipeRead = pipes[0]; pCtx->wakeupPipeWrite = pipes[1]; - if (!fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK)) + if ( !fcntl(pCtx->wakeupPipeRead, F_SETFL, O_NONBLOCK) + && !fcntl(pCtx->wakeupPipeWrite, F_SETFL, O_NONBLOCK)) { rc = VINF_SUCCESS; } else + { rc = RTErrConvertFromErrno(errno); + clipThreadCloseWakeupPipe(pCtx); + } } else rc = RTErrConvertFromErrno(errno); @@ -1365,7 +1471,13 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) rc = RTThreadCreate(&pCtx->Thread, clipThreadMain, pCtx, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, pszName); if (RT_SUCCESS(rc)) - rc = RTThreadUserWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */); + { + /* The backend must not release pCtx while the worker initializes it. */ + rc = RTThreadUserWait(pCtx->Thread, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(rc, ("Waiting for X11 event thread startup failed with %Rrc\n", rc)); + } + else + clipThreadCloseWakeupPipe(pCtx); if (RT_FAILURE(rc)) { @@ -1376,7 +1488,15 @@ int ShClX11ThreadStartEx(PSHCLX11CTX pCtx, const char *pszName, bool fGrab) { if (!pCtx->fThreadStarted) { - LogRel(("Shared Clipboard: X11 event thread reported an error while starting\n")); + /* The worker signalled its terminal startup failure; reap it before returning. */ + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int rc2 = RTThreadWait(pCtx->Thread, RT_INDEFINITE_WAIT, &rcThread); + AssertFatalMsgRC(rc2, ("Reaping X11 event thread after startup failure failed with %Rrc\n", rc2)); + pCtx->Thread = NIL_RTTHREAD; + rc = RT_FAILURE(rcThread) ? rcThread : VERR_GENERAL_FAILURE; + + clipThreadCloseWakeupPipe(pCtx); + LogRel(("Shared Clipboard: X11 event thread reported an error while starting: %Rrc\n", rc)); } else LogRel2(("Shared Clipboard: X11 event thread started\n")); @@ -1412,44 +1532,43 @@ int ShClX11ThreadStart(PSHCLX11CTX pCtx, bool fGrab) */ int ShClX11ThreadStop(PSHCLX11CTX pCtx) { - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->Thread != NIL_RTTHREAD, VERR_INVALID_STATE); + + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int rcWait = RTThreadWait(pCtx->Thread, 0 /* cMillies */, &rcThread); + if (RT_SUCCESS(rcWait)) + { + pCtx->Thread = NIL_RTTHREAD; + pCtx->fThreadStarted = false; + clipThreadCloseWakeupPipe(pCtx); + return rcThread; + } + AssertFatalMsg(rcWait == VERR_TIMEOUT, ("Probing X11 event thread failed with %Rrc\n", rcWait)); LogRel2(("Shared Clipboard: Signalling the X11 event thread to stop\n")); /* Write to the "stop" pipe. */ - int rc = clipThreadScheduleCall(pCtx, clipThreadSignalStop, (XtPointer)pCtx); - if (RT_FAILURE(rc)) - { - LogRel(("Shared Clipboard: cannot notify X11 event thread on shutdown with %Rrc\n", rc)); - return rc; - } + int const rcSignal = clipThreadScheduleCall(pCtx, clipThreadSignalStop, (XtPointer)pCtx); + AssertFatalMsgRC(rcSignal, ("Cannot notify X11 event thread on shutdown with %Rrc\n", rcSignal)); LogRel2(("Shared Clipboard: Waiting for X11 event thread to stop ...\n")); - int rcThread; - rc = RTThreadWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */, &rcThread); - if (RT_SUCCESS(rc)) - rc = rcThread; - if (RT_SUCCESS(rc)) + rcWait = RTThreadWait(pCtx->Thread, RT_MS_30SEC /* msTimeout */, &rcThread); + if (RT_FAILURE(rcWait)) { - if (pCtx->wakeupPipeRead != 0) - { - close(pCtx->wakeupPipeRead); - pCtx->wakeupPipeRead = 0; - } - - if (pCtx->wakeupPipeWrite != 0) - { - close(pCtx->wakeupPipeWrite); - pCtx->wakeupPipeWrite = 0; - } + LogRel(("Shared Clipboard: X11 event thread did not stop promptly (%Rrc); waiting indefinitely\n", rcWait)); + rcWait = RTThreadWait(pCtx->Thread, RT_INDEFINITE_WAIT, &rcThread); } + AssertFatalMsgRC(rcWait, ("Reaping X11 event thread failed with %Rrc\n", rcWait)); + + pCtx->Thread = NIL_RTTHREAD; + pCtx->fThreadStarted = false; + clipThreadCloseWakeupPipe(pCtx); + int const rc = rcThread; if (RT_SUCCESS(rc)) - { LogRel2(("Shared Clipboard: X11 event thread stopped successfully\n")); - } else LogRel(("Shared Clipboard: Stopping X11 event thread failed with %Rrc\n", rc)); @@ -1547,13 +1666,18 @@ static int shClX11RequestDataForX11CallbackHelper(PSHCLX11CTX pCtx, SHCLFORMAT u PSHCLCACHEENTRY pCacheEntry = ShClCacheGet(&pCtx->Cache, uFmt); if (!pCacheEntry) /* Cache miss */ { - AssertPtrReturn(pCtx->Callbacks.pfnOnRequestDataFromSource, VERR_INVALID_POINTER); - rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pv, &cb, - NULL /* pvUser */); - if (RT_SUCCESS(rc)) + if (pCtx->fCacheOnlyFormats & uFmt) + rc = VERR_SHCLPB_NO_DATA; + else { - rc = ShClCacheSet(&pCtx->Cache, uFmt, pv, cb); - /** @todo r=bird: Leaks pv/cb on ShClCacheSet error? */ + AssertPtrReturn(pCtx->Callbacks.pfnOnRequestDataFromSource, VERR_INVALID_POINTER); + rc = pCtx->Callbacks.pfnOnRequestDataFromSource(pCtx->pFrontend, uFmt, &pv, &cb, + NULL /* pvUser */); + if (RT_SUCCESS(rc)) + { + rc = ShClCacheSet(&pCtx->Cache, uFmt, pv, cb); + /** @todo r=bird: Leaks pv/cb on ShClCacheSet error? */ + } } } else /* Cache hit */ @@ -1853,12 +1977,7 @@ static int clipConvertToX11Data(PSHCLX11CTX pCtx, Atom *atomTarget, } } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - else if ( fmtX11 == SHCLX11FMT_URI_LIST - || fmtX11 == SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES - /** @todo BUGBUG Not sure about the following ones; test those. */ - || fmtX11 == SHCLX11FMT_URI_LIST_MATE_COPIED_FILES - || fmtX11 == SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD - || fmtX11 == SHCLX11FMT_URI_LIST_KDE_CUTSELECTION) + else if (shClX11TransferTargetSupportsDirection(fmtX11, SHCLX11TRANSFERDIR_F_TO_X11)) { if (pCtx->vboxFormats & VBOX_SHCL_FMT_URI_LIST) { @@ -2020,10 +2139,11 @@ static void shClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)pvUserData; AssertReturnVoid(pReq->enmType == SHCLX11EVENTTYPE_REPORT_FORMATS); - PSHCLX11CTX pCtx = pReq->pCtx; - SHCLFORMATS fFormats = pReq->Formats.fFormats; - - RTMemFree(pReq); + PSHCLX11CTX pCtx = pReq->pCtx; + SHCLFORMATS fFormats = pReq->Formats.fFormats; + SHCLFORMAT uFmtCache = pReq->Formats.uFmtCache; + void *pvCache = pReq->Formats.pvCache; + uint32_t cbCache = pReq->Formats.cbCache; if (LogRelIs2Enabled()) { @@ -2034,6 +2154,23 @@ static void shClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval } clipInvalidateClipboardCache(pCtx); + pCtx->fCacheOnlyFormats = uFmtCache; + + if (uFmtCache != VBOX_SHCL_FMT_NONE) + { + int rc = ShClCacheSet(&pCtx->Cache, uFmtCache, pvCache, cbCache); + if (RT_FAILURE(rc)) + { + LogRel(("Shared Clipboard: Caching format %#x before advertising it to X11 failed with %Rrc; " + "suppressing the format\n", uFmtCache, rc)); + fFormats &= ~uFmtCache; + } + } + + RTMemFree(pvCache); + pReq->Formats.pvCache = NULL; + RTMemFree(pReq); + clipGrabX11Clipboard(pCtx, fFormats); clipResetX11Formats(pCtx); @@ -2045,28 +2182,75 @@ static void shClX11ReportFormatsToX11Worker(void *pvUserData, void * /* interval * * @returns VBox status code. * @param pCtx Context data for the clipboard backend. - * @param uFormats Clipboard formats offered. + * @param fFormats Clipboard formats offered. * * @note When calling this function, data for the clipboard already has to be available, * as we grab the clipboard, which in turn then calls the X11 data conversion callback. */ -int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS uFormats) +int ShClX11ReportFormatsToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS fFormats) { - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; + return ShClX11ReportFormatsToX11AsyncEx(pCtx, fFormats, VBOX_SHCL_FMT_NONE, NULL, 0); +} + +/** + * Announces new clipboard formats and atomically seeds one cache entry on the + * X11 event thread before taking ownership of the selections. + * + * This is used for data which must be prepared asynchronously. Once the + * formats become visible to X11 clients, conversion of @a uFmtCache is + * guaranteed to be a cache hit and cannot block on the data source. + * + * @returns VBox status code. + * @param pCtx Context data for the clipboard backend. + * @param fFormats Clipboard formats offered. + * @param uFmtCache Format of @a pvCache, or VBOX_SHCL_FMT_NONE. + * @param pvCache Data to cache before advertising @a fFormats. + * @param cbCache Size of @a pvCache in bytes. + * + * @thread Any thread. Cache installation and selection ownership happen on + * the X11 event thread. + */ +int ShClX11ReportFormatsToX11AsyncEx(PSHCLX11CTX pCtx, SHCLFORMATS fFormats, SHCLFORMAT uFmtCache, + const void *pvCache, uint32_t cbCache) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + if (uFmtCache != VBOX_SHCL_FMT_NONE) + { + AssertReturn(ShClFormatIsValid(uFmtCache), VERR_INVALID_PARAMETER); + AssertReturn((fFormats & uFmtCache) == uFmtCache, VERR_INVALID_PARAMETER); + AssertPtrReturn(pvCache, VERR_INVALID_POINTER); + AssertReturn(cbCache, VERR_INVALID_PARAMETER); + } + else + AssertReturn(!pvCache && !cbCache, VERR_INVALID_PARAMETER); int rc; PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)RTMemAllocZ(sizeof(SHCLX11REQUEST)); if (pReq) { - pReq->enmType = SHCLX11EVENTTYPE_REPORT_FORMATS; - pReq->pCtx = pCtx; - pReq->Formats.fFormats = uFormats; + pReq->enmType = SHCLX11EVENTTYPE_REPORT_FORMATS; + pReq->pCtx = pCtx; + pReq->Formats.fFormats = fFormats; + pReq->Formats.uFmtCache = uFmtCache; + + if (uFmtCache != VBOX_SHCL_FMT_NONE) + { + pReq->Formats.pvCache = RTMemDup(pvCache, cbCache); + if (!pReq->Formats.pvCache) + { + RTMemFree(pReq); + return VERR_NO_MEMORY; + } + pReq->Formats.cbCache = cbCache; + } rc = clipThreadScheduleCall(pCtx, shClX11ReportFormatsToX11Worker, (XtPointer)pReq); if (RT_FAILURE(rc)) + { + RTMemFree(pReq->Formats.pvCache); RTMemFree(pReq); + } } else rc = VERR_NO_MEMORY; @@ -2103,13 +2287,22 @@ int ShClX11TransferConvertToX11(const char *pszSrc, size_t cbSrc, SHCLX11FMT en switch (enmFmtX11) { + case SHCLX11FMT_URI_LIST_KDE_CUTSELECTION: + { + /* KDE stores only cut/copy state in this target: "0" means copy + * and "1" means cut. Shared Clipboard exports copies, while the + * file names themselves are provided through text/uri-list. */ + pszDst = RTStrDup("0"); /* Copy. */ + if (!pszDst) + rc = VERR_NO_MEMORY; + break; + } + case SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_MATE_COPIED_FILES: RT_FALL_THROUGH(); case SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_KDE_CUTSELECTION: { const char chSep = '\n'; /* Currently (?) all entries need to be separated by '\n'. */ @@ -2235,7 +2428,7 @@ int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppsz int rc = RTStrValidateEncodingEx((char *)pvData, cbData, 0 /* fFlags */); if (RT_FAILURE(rc)) { - LogRelMax2(16, ("Shared Clipboard: X11 URI-list clipboard data is not valid UTF-8 (%zu bytes), rc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: X11 URI-list clipboard data is not valid UTF-8 (%zu bytes), rc=%Rrc\n", cbData, rc)); return rc; } @@ -2267,7 +2460,7 @@ int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppsz if ( pchCur < pchEnd && *pchCur == '\0') { - LogRelMax2(16, ("Shared Clipboard: X11 URI-list clipboard data contains an embedded NUL byte; refusing file transfer\n")); + LogRelMax(16, ("Shared Clipboard: X11 URI-list clipboard data contains an embedded NUL byte; refusing file transfer\n")); rc = VERR_INVALID_PARAMETER; break; } @@ -2324,7 +2517,7 @@ int ShClX11TransferConvertFromX11(const char *pvData, size_t cbData, char **ppsz if (!cEntries) { - LogRelMax2(16, ("Shared Clipboard: X11 URI-list clipboard data contained no file entries; refusing file transfer\n")); + LogRelMax(16, ("Shared Clipboard: X11 URI-list clipboard data contained no file entries; refusing file transfer\n")); return VERR_SHCLPB_NO_DATA; } @@ -2515,28 +2708,20 @@ SHCL_X11_DECL(void) clipConvertDataFromX11Worker(void *pClient, void *pvSrc, uns # ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS else if (pReq->Read.uFmtVBox == VBOX_SHCL_FMT_URI_LIST) { - /* In which format is the clipboard data? */ - switch (clipRealFormatForX11Format(pReq->Read.idxFmtX11)) + SHCLX11FMT const enmFmtX11 = clipRealFormatForX11Format(pReq->Read.idxFmtX11); + /* Recheck the direction at the conversion boundary. Normally target + * selection already guarantees this, but the check also protects + * forced, stale or otherwise inconsistent format indices from feeding + * metadata such as KDE's "0"/"1" value to the URI-list parser. */ + if (shClX11TransferTargetSupportsDirection(enmFmtX11, SHCLX11TRANSFERDIR_F_FROM_X11)) + rc = ShClX11TransferConvertFromX11((const char *)pvSrc, cbSrc, (char **)&pvDst, &cbDst); + else { - case SHCLX11FMT_URI_LIST: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_MATE_COPIED_FILES: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_NAUTILUS_CLIPBOARD: - RT_FALL_THROUGH(); - case SHCLX11FMT_URI_LIST_KDE_CUTSELECTION: - { - rc = ShClX11TransferConvertFromX11((const char *)pvSrc, cbSrc, (char **)&pvDst, &cbDst); - break; - } - - default: - { - AssertFailedStmt(rc = VERR_NOT_SUPPORTED); /* Missing code? */ - break; - } + const char *pszTarget = pReq->Read.idxFmtX11 < RT_ELEMENTS(g_aFormats) + ? g_aFormats[pReq->Read.idxFmtX11].pcszAtom : ""; + LogRelMax(16, ("Shared Clipboard: Refusing to parse X11 clipboard target '%s' as a file list\n", + pszTarget)); + rc = VERR_NOT_SUPPORTED; } } # endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -2584,7 +2769,10 @@ SHCL_X11_DECL(void) clipConvertDataFromX11Worker(void *pClient, void *pvSrc, uns pPayload = NULL; } - LogRel2(("Shared Clipboard: Converting X11 clipboard data completed with %Rrc\n", rc)); + if (RT_FAILURE(rc)) + LogRelMax(16, ("Shared Clipboard: Converting X11 clipboard data failed with %Rrc\n", rc)); + else + LogRel2(("Shared Clipboard: Converting X11 clipboard data completed with %Rrc\n", rc)); RTMemFree(pReq); RTMemFree(pvDst); @@ -2794,7 +2982,10 @@ static void ShClX11ReadDataFromX11Worker(void *pvUserData, void * /* interval */ RTMemFree(pReq); } - LogRel2(("Shared Clipboard: Reading X11 clipboard data completed with %Rrc\n", rc)); + if (RT_FAILURE(rc)) + LogRelMax(16, ("Shared Clipboard: Reading X11 clipboard data failed with %Rrc\n", rc)); + else + LogRel2(("Shared Clipboard: Reading X11 clipboard data completed with %Rrc\n", rc)); LogFlowFuncLeaveRC(rc); } @@ -2814,9 +3005,6 @@ int ShClX11ReadDataFromX11Async(PSHCLX11CTX pCtx, SHCLFORMAT uFmt, uint32_t cbMa { AssertPtrReturn(pEvent, VERR_INVALID_POINTER); - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; - int rc = VINF_SUCCESS; PSHCLX11REQUEST pReq = (PSHCLX11REQUEST)RTMemAllocZ(sizeof(SHCLX11REQUEST)); @@ -2926,12 +3114,6 @@ int ShClX11ReadDataFromX11Ex(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RT AssertPtrReturn(ppvBuf, VERR_INVALID_POINTER); AssertPtrReturn(pcbBuf, VERR_INVALID_POINTER); - if (shClX11HeadlessIsEnabled(pCtx)) - { - *pcbBuf = 0; - return VINF_SUCCESS; - } - PSHCLX11RESPONSE pResp; int rc = shClX11ReadDataFromX11Internal(pCtx, pEventSource, msTimeout, uFmt, UINT32_MAX, &pResp); if (RT_SUCCESS(rc)) @@ -2972,13 +3154,6 @@ int ShClX11ReadDataFromX11(PSHCLX11CTX pCtx, PSHCLEVENTSOURCE pEventSource, RTMS AssertReturn(cbBuf, VERR_INVALID_PARAMETER); /* pcbRead is optional. */ - if (shClX11HeadlessIsEnabled(pCtx)) - { - if (pcbRead) - *pcbRead = 0; - return VINF_SUCCESS; - } - PSHCLX11RESPONSE pResp; int rc = shClX11ReadDataFromX11Internal(pCtx, pEventSource, msTimeout, uFmt, cbBuf, &pResp); if (RT_SUCCESS(rc)) @@ -3014,9 +3189,6 @@ int ShClX11WriteDataToX11Async(PSHCLX11CTX pCtx, SHCLFORMATS uFmts, const void * AssertReturn(cbBuf, VERR_INVALID_PARAMETER); /* pEvent not used yet. */ RT_NOREF(pEvent); - if (shClX11HeadlessIsEnabled(pCtx)) - return VINF_SUCCESS; - int rc = ShClCacheSetMultiple(&pCtx->Cache, uFmts, pvBuf, cbBuf); if (RT_SUCCESS(rc)) { diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk b/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk index 8390a36122ff..c4e2c6213fb6 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk +++ b/src/VBox/GuestHost/SharedClipboard/testcase/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114650 2026-07-08 09:14:39Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115068 2026-08-18 14:45:28Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the Shared Clipboard Guest/Host testcases. # @@ -89,6 +89,33 @@ if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBO $(tstClipboardMimeConv_1_STAGE_TARGET) quiet $(QUIET)$(APPEND) -t "$@" "done" + ifeq ($(KBUILD_TARGET),win) + # + # Shared Clipboard Windows stream testcase. + # + PROGRAMS += tstClipboardWinStream + tstClipboardWinStream_TEMPLATE = VBoxR3TstExe + tstClipboardWinStream_DEFS = VBOX_WITH_SHARED_CLIPBOARD VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardWinStream_SOURCES = \ + tstClipboardWinStream.cpp \ + ../ClipboardDataObjectImpl-win.cpp \ + ../ClipboardEnumFormatEtcImpl-win.cpp \ + ../ClipboardStreamImpl-win.cpp \ + ../clipboard-common.cpp \ + ../clipboard-helper.cpp \ + ../clipboard-path.cpp \ + ../clipboard-transfers.cpp + tstClipboardWinStream_CLEAN = $(tstClipboardWinStream_0_OUTDIR)/tstClipboardWinStream.run + + $$(tstClipboardWinStream_0_OUTDIR)/tstClipboardWinStream.run: $$(tstClipboardWinStream_1_STAGE_TARGET) + export VBOX_LOG_DEST=nofile; $(tstClipboardWinStream_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardWinStream_0_OUTDIR)/tstClipboardWinStream.run + endif + endif + if defined(VBOX_WITH_LIBCURL) && defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) PROGRAMS += tstClipboardHttpServer tstClipboardHttpServer_TEMPLATE = VBoxR3TstExe diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp index e763d17facdc..c258b785da99 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardGH-X11.cpp 114767 2026-07-24 22:06:05Z knut.osmundsen@oracle.com $ */ +/* $Id: tstClipboardGH-X11.cpp 114972 2026-08-10 17:34:02Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard guest/host X11 code test cases. */ @@ -97,6 +97,7 @@ void tstThreadScheduleCall(void (*proc)(void *, void *), void *client_data) static int g_tst_rcDataVBox = VINF_SUCCESS; static void *g_tst_pvDataVBox = NULL; static uint32_t g_tst_cbDataVBox = 0; +static uint32_t g_tst_cDataRequests = 0; static SHCLEVENTSOURCE g_EventSource; /* Set empty data in the simulated VBox clipboard. */ @@ -401,6 +402,7 @@ static DECLCALLBACK(int) tstShClReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_ static DECLCALLBACK(int) tstShClOnRequestDataFromSourceCallback(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppv, uint32_t *pcb, void *pvUser) { RT_NOREF(pCtx, uFmt, pvUser); + g_tst_cDataRequests++; *pcb = g_tst_cbDataVBox; if (g_tst_pvDataVBox != NULL) { @@ -485,17 +487,90 @@ static bool tstClipURIListFormatConversion(PSHCLX11CTX pCtx) SHCLX11FMTIDX aTargets[2]; SHCLX11FMTIDX idxFmtX11; - aTargets[0] = tstClipFindX11FormatByAtomText("application/x-kde-cutselection"); + /* Prefer the standard target over a higher-valued format enum. */ + aTargets[0] = tstClipFindX11FormatByAtomText("x-special/gnome-copied-files"); aTargets[1] = tstClipFindX11FormatByAtomText("text/uri-list"); idxFmtX11 = clipGetURIListFormatFromTargets(pCtx, aTargets, 2); if (clipRealFormatForX11Format(idxFmtX11) != SHCLX11FMT_URI_LIST) fSuccess = false; + /* Target enumeration order must not affect the result. */ + aTargets[0] = tstClipFindX11FormatByAtomText("text/uri-list"); + aTargets[1] = tstClipFindX11FormatByAtomText("x-special/gnome-copied-files"); + idxFmtX11 = clipGetURIListFormatFromTargets(pCtx, aTargets, 2); + if (clipRealFormatForX11Format(idxFmtX11) != SHCLX11FMT_URI_LIST) + fSuccess = false; + + /* KDE cut-selection is metadata, but another target can provide the file list. */ + aTargets[0] = tstClipFindX11FormatByAtomText("application/x-kde-cutselection"); + aTargets[1] = tstClipFindX11FormatByAtomText("x-special/gnome-copied-files"); + idxFmtX11 = clipGetURIListFormatFromTargets(pCtx, aTargets, 2); + if (clipRealFormatForX11Format(idxFmtX11) != SHCLX11FMT_URI_LIST_GNOME_COPIED_FILES) + fSuccess = false; + aTargets[0] = tstClipFindX11FormatByAtomText("application/x-kde-cutselection"); idxFmtX11 = clipGetURIListFormatFromTargets(pCtx, aTargets, 1); if (idxFmtX11 != NIL_CLIPX11FORMAT) fSuccess = false; + /* Even a forced read must not treat KDE cut/copy metadata as a file list. */ + static const char s_szKdeCopy[] = "0"; + tstClipSetSelectionValues("application/x-kde-cutselection", XA_STRING, + s_szKdeCopy, sizeof(s_szKdeCopy) - 1, 8); + pCtx->idxFmtURI = aTargets[0]; + uint8_t abBuf[TESTCASE_MAX_BUF_SIZE]; + uint32_t cbRead = 0; + int rc = ShClX11ReadDataFromX11(pCtx, &g_EventSource, g_msTimeout, VBOX_SHCL_FMT_URI_LIST, + abBuf, sizeof(abBuf), &cbRead); + if (rc != VERR_SHCLPB_NO_DATA) + fSuccess = false; + pCtx->idxFmtURI = NIL_CLIPX11FORMAT; + + /* When exporting files, KDE receives copy metadata separately from text/uri-list. */ + static const char s_szURI[] = "file:///tmp/a"; + void *pvKde = NULL; + size_t cbKde = 0; + rc = ShClX11TransferConvertToX11(s_szURI, sizeof(s_szURI) - 1, SHCLX11FMT_URI_LIST_KDE_CUTSELECTION, + &pvKde, &cbKde); + if ( RT_FAILURE(rc) + || cbKde != sizeof(s_szKdeCopy) - 1 + || memcmp(pvKde, s_szKdeCopy, sizeof(s_szKdeCopy) - 1)) + fSuccess = false; + XtFree((char *)pvKde); + + /* KDE metadata and the standard URI list must both be offered to X11 consumers. */ + rc = ShClX11ReportFormatsToX11Async(pCtx, VBOX_SHCL_FMT_URI_LIST); + if (RT_FAILURE(rc)) + fSuccess = false; + else + { + Atom atomType; + XtPointer pvTargets = NULL; + unsigned long cTargets; + int iFormat; + if (!tstClipConvertSelection("TARGETS", &atomType, &pvTargets, &cTargets, &iFormat)) + fSuccess = false; + else + { + bool fFoundURI = false; + bool fFoundKDE = false; + Atom const *paTargets = (Atom const *)pvTargets; + for (size_t i = 0; i < cTargets; i++) + { + if (paTargets[i] == XInternAtom(NULL, "text/uri-list", 0)) + fFoundURI = true; + else if (paTargets[i] == XInternAtom(NULL, "application/x-kde-cutselection", 0)) + fFoundKDE = true; + } + if ( atomType != XA_ATOM + || iFormat != 32 + || !fFoundURI + || !fFoundKDE) + fSuccess = false; + } + XtFree((char *)pvTargets); + } + return fSuccess; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -615,12 +690,6 @@ static void tstStringFromVBoxFailed(RTTEST hTest, PSHCLX11CTX pCtx, const char * XtFree((char *)value); } -static void tstNoSelectionOwnership(PSHCLX11CTX pCtx, const char *pcszTestCtx) -{ - RT_NOREF(pCtx); - RTTESTI_CHECK_MSG(!g_tst_fOwnsSel, ("context: %s\n", pcszTestCtx)); -} - static void tstBadFormatRequestFromHost(RTTEST hTest, PSHCLX11CTX pCtx) { tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world", @@ -700,7 +769,7 @@ int main() Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDestCallback; SHCLX11CTX X11Ctx; - rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */); + rc = ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */); AssertRCReturn(rc, RTEXITCODE_FAILURE); uint32_t cbActual = 0; @@ -836,6 +905,20 @@ int main() #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS RTTEST_CHECK_MSG(hTest, tstClipURIListFormatConversion(&X11Ctx), (hTest, "failed to select the right X11 URI-list formats\n")); + + RTTestSub(hTest, "cache-only X11 URI-list offer"); + static const char s_szUriList[] = "http://localhost/a\r\nhttp://localhost/b\r\n"; + uint32_t const cDataRequestsBefore = g_tst_cDataRequests; + RTTEST_CHECK_RC_OK(hTest, ShClX11ReportFormatsToX11AsyncEx(&X11Ctx, VBOX_SHCL_FMT_URI_LIST, + VBOX_SHCL_FMT_URI_LIST, s_szUriList, + sizeof(s_szUriList))); + tstStringFromVBox(hTest, &X11Ctx, "text/uri-list", clipGetAtom(&X11Ctx, "text/uri-list"), s_szUriList); + RTTEST_CHECK_MSG(hTest, g_tst_cDataRequests == cDataRequestsBefore, + (hTest, "Cached URI-list conversion unexpectedly requested source data\n")); + ShClCacheInvalidate(&X11Ctx.Cache); + tstStringFromVBoxFailed(hTest, &X11Ctx, "text/uri-list"); + RTTEST_CHECK_MSG(hTest, g_tst_cDataRequests == cDataRequestsBefore, + (hTest, "Cache-only URI-list miss unexpectedly requested source data\n")); #endif /* * UTF-8 from VBox @@ -939,34 +1022,7 @@ int main() ShClX11Term(&X11Ctx); - /* - * Headless clipboard tests - */ - RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, true /* fHeadless */)); - - /* Read from X11 */ - RTTestSub(hTest, "reading from X11, headless clipboard"); - - /* Simple test */ - tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "", sizeof("") * 2); - tstClipSetSelectionValues("UTF8_STRING", XA_STRING, "hello world", sizeof("hello world"), 8); - rc = ShClX11ReadDataFromX11(&X11Ctx, &g_EventSource, g_msTimeout, VBOX_SHCL_FMT_UNICODETEXT, abBuf, sizeof(abBuf), &cbActual); - RTTEST_CHECK_MSG(hTest, cbActual == 0, (hTest, "expected 0 but got %RU32\n", cbActual)); - RTTEST_CHECK_MSG(hTest, rc == VINF_SUCCESS, (hTest, "expected VINF_SUCCESS but got %Rrc\n", rc)); - - /* Read from VBox */ - RTTestSub(hTest, "reading from VBox, headless clipboard"); - - /* Simple test */ - tstClipEmptyVBox(&X11Ctx, VERR_WRONG_ORDER); - tstClipSetSelectionValues("TEXT", XA_STRING, "", sizeof(""), 8); - tstClipSetVBoxUtf16(&X11Ctx, VINF_SUCCESS, "hello world", - sizeof("hello world") * 2); - tstNoSelectionOwnership(&X11Ctx, "reading from VBox, headless clipboard"); - - RTTEST_CHECK_RC_OK(hTest, ShClX11Term(&X11Ctx)); ShClEventSourceTerm(&g_EventSource); return RTTestSummaryAndDestroy(hTest); } - diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp index 89742260f6fb..dfb4b5706ae6 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardGH-X11Smoke.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardGH-X11Smoke.cpp 114412 2026-06-17 21:20:59Z knut.osmundsen@oracle.com $ */ +/* $Id: tstClipboardGH-X11Smoke.cpp 114971 2026-08-10 17:29:14Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard guest/host X11 code smoke tests. */ @@ -89,7 +89,7 @@ int main() Callbacks.pfnOnSendDataToDest = tstShClOnSendDataToDest; SHCLX11CTX X11Ctx; - RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */)); + RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */)); RTTEST_CHECK_RC_OK(hTest, ShClX11ThreadStart(&X11Ctx, false /* fGrab */)); /* Give the clipboard time to synchronise. */ @@ -100,4 +100,3 @@ int main() return RTTestSummaryAndDestroy(hTest); } - diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp index bc00b698a40e..92946f68d7fa 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardHttpServer.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardHttpServer.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: tstClipboardHttpServer.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard HTTP server test case. */ @@ -34,16 +34,18 @@ #include #include #include +#include #include #include #include +#include #include #ifdef TESTCASE_WITH_X11 #include #endif -#include +#include /** The release logger. */ @@ -69,7 +71,7 @@ static struct RTFMODE fMode; /** Local path to serve via HTTP server. */ const char *pszPath; - /** URL to use for downloading the file via RTHttp APIs. Has to be fully escaped. */ + /** Expected URL path component. Has to be fully percent-encoded. */ const char *pszUrl; /** File allocation size. * Specify UINT64_MAX for random size. */ @@ -84,6 +86,7 @@ static struct { RTFS_TYPE_FILE, "file1.txt", "file1.txt", _64K, VINF_SUCCESS }, /* Note: For RTHttpGetFile() the URL needs to be percent-encoded. */ { RTFS_TYPE_FILE, "file2 with spaces.txt", "file2%20with%20spaces.txt", _64K, VINF_SUCCESS }, + { RTFS_TYPE_FILE, "file #%20?.txt", "file%20%23%2520%3F.txt", 42, VINF_SUCCESS }, { RTFS_TYPE_FILE, "bigfile.bin", "bigfile.bin", _512M, VINF_SUCCESS }, { RTFS_TYPE_FILE, "zerobytes", "zerobytes", 0, VINF_SUCCESS }, { RTFS_TYPE_FILE, "file\\with\\slashes", "file%5Cwith%5Cslashes", 42, VINF_SUCCESS }, @@ -117,7 +120,7 @@ static int tstCreateTransferSingle(RTTEST hTest, PSHCLTRANSFERCTX pTransferCtx, do { - RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, + RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferCreate(SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTx)); RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferSetProvider(pTx, pProvider)); RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferRootsSetFromPath(pTx, pszPath)); @@ -212,7 +215,7 @@ static void tstDuplicateTransferRegistration(RTTEST hTest, PSHCLTRANSFERCTX pTra do { - RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, + RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferCreate(SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTx)); RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferSetProvider(pTx, pProvider)); RTTEST_CHECK_RC_OK_BREAK(hTest, rc = ShClTransferRootsSetFromPath(pTx, pszPath)); @@ -263,6 +266,778 @@ static void tstDuplicateTransferRegistration(RTTEST hTest, PSHCLTRANSFERCTX pTra } } + +/** Provider wrapper state used for observing and pausing HTTP object access. */ +typedef struct TSTHTTPPROVIDERCTX +{ + /** The wrapped local provider interface. */ + SHCLTXPROVIDERIFACE LocalIface; + /** Signalled whenever a read enters the provider. */ + RTSEMEVENT hReadEntered; + /** Releases all reads paused by the test. */ + RTSEMEVENTMULTI hReadContinue; + /** Number of successful object opens. */ + volatile uint32_t cObjOpens; + /** Number of successful object closes. */ + volatile uint32_t cObjCloses; + /** Number of object reads. */ + volatile uint32_t cObjReads; + /** Number of initial reads to pause. */ + uint32_t cReadsToPause; +} TSTHTTPPROVIDERCTX; +/** Pointer to an HTTP provider wrapper state. */ +typedef TSTHTTPPROVIDERCTX *PTSTHTTPPROVIDERCTX; + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjOpen */ +static DECLCALLBACK(int) tstHttpProviderObjOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTHTTPPROVIDERCTX), VERR_INVALID_PARAMETER); + PTSTHTTPPROVIDERCTX pThis = (PTSTHTTPPROVIDERCTX)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + int rc = pThis->LocalIface.pfnObjOpen(pCtx, pCreateParms, phObj); + if (RT_SUCCESS(rc)) + ASMAtomicIncU32(&pThis->cObjOpens); + return rc; +} + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjClose */ +static DECLCALLBACK(int) tstHttpProviderObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTHTTPPROVIDERCTX), VERR_INVALID_PARAMETER); + PTSTHTTPPROVIDERCTX pThis = (PTSTHTTPPROVIDERCTX)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + int rc = pThis->LocalIface.pfnObjClose(pCtx, hObj); + if (RT_SUCCESS(rc)) + ASMAtomicIncU32(&pThis->cObjCloses); + return rc; +} + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjRead */ +static DECLCALLBACK(int) tstHttpProviderObjRead(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, void *pvData, + uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTHTTPPROVIDERCTX), VERR_INVALID_PARAMETER); + PTSTHTTPPROVIDERCTX pThis = (PTSTHTTPPROVIDERCTX)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + uint32_t const iRead = ASMAtomicIncU32(&pThis->cObjReads); + if (iRead <= pThis->cReadsToPause) + { + int rc = RTSemEventSignal(pThis->hReadEntered); + if (RT_SUCCESS(rc)) + rc = RTSemEventMultiWait(pThis->hReadContinue, RT_MS_30SEC); + if (RT_FAILURE(rc)) + return rc; + } + + return pThis->LocalIface.pfnObjRead(pCtx, hObj, pvData, cbData, fFlags, pcbRead); +} + +/** + * Initializes an observable local provider. + * + * @returns VBox status code. + * @param pThis Provider wrapper state to initialize. + * @param pProvider Provider interface to initialize. + */ +static int tstHttpProviderInit(PTSTHTTPPROVIDERCTX pThis, PSHCLTXPROVIDER pProvider) +{ + RT_ZERO(*pThis); + RT_ZERO(*pProvider); + + int rc = RTSemEventCreate(&pThis->hReadEntered); + if (RT_SUCCESS(rc)) + { + rc = RTSemEventMultiCreate(&pThis->hReadContinue); + if (RT_SUCCESS(rc)) + { + AssertPtrReturn(ShClTransferProviderLocalQueryInterface(pProvider), VERR_INTERNAL_ERROR); + pThis->LocalIface = pProvider->Interface; + + pProvider->Interface.pfnObjOpen = tstHttpProviderObjOpen; + pProvider->Interface.pfnObjClose = tstHttpProviderObjClose; + pProvider->Interface.pfnObjRead = tstHttpProviderObjRead; + pProvider->pvUser = pThis; + pProvider->cbUser = sizeof(*pThis); + return VINF_SUCCESS; + } + + RTSemEventDestroy(pThis->hReadEntered); + pThis->hReadEntered = NIL_RTSEMEVENT; + } + + return rc; +} + +/** + * Terminates an observable local provider. + * + * @param pThis Provider wrapper state to terminate. + */ +static void tstHttpProviderTerm(PTSTHTTPPROVIDERCTX pThis) +{ + if (pThis->hReadContinue != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiSignal(pThis->hReadContinue); + RTSemEventMultiDestroy(pThis->hReadContinue); + pThis->hReadContinue = NIL_RTSEMEVENTMULTI; + } + if (pThis->hReadEntered != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pThis->hReadEntered); + pThis->hReadEntered = NIL_RTSEMEVENT; + } +} + +/** + * Creates a deterministic file for an HTTP lifetime test. + * + * @returns VBox status code. + * @param pszPath File path. + * @param cbFile File size in bytes. + */ +static int tstCreatePatternFile(const char *pszPath, size_t cbFile) +{ + RTFILE hFile; + int rc = RTFileOpen(&hFile, pszPath, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_NONE); + if (RT_SUCCESS(rc)) + { + uint8_t abBuf[_64K]; + for (size_t i = 0; i < sizeof(abBuf); i++) + abBuf[i] = (uint8_t)(i * 131U + 17U); + + while (cbFile > 0 && RT_SUCCESS(rc)) + { + size_t const cbToWrite = RT_MIN(cbFile, sizeof(abBuf)); + rc = RTFileWrite(hFile, abBuf, cbToWrite, NULL); + cbFile -= cbToWrite; + } + + int rc2 = RTFileClose(hFile); + if (RT_SUCCESS(rc)) + rc = rc2; + } + return rc; +} + +/** + * Creates and registers one transfer with both a transfer context and HTTP server. + * + * @returns VBox status code. + * @param pTransferCtx Transfer context to register with. + * @param pSrv HTTP server to register with. + * @param pszPath Local path to serve. + * @param pProvider Provider to use. + * @param idTransfer Transfer ID to request, or NIL_SHCLTRANSFERID for an automatically allocated ID. + * @param ppTransfer Where to return the transfer on success. + */ +static int tstCreateRegisteredTransfer(PSHCLTRANSFERCTX pTransferCtx, PSHCLHTTPSERVER pSrv, const char *pszPath, + PSHCLTXPROVIDER pProvider, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) +{ + PSHCLTRANSFER pTransfer = NULL; + bool fCtxRegistered = false; + bool fHttpRegistered = false; + + int rc = ShClTransferCreate(SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); + if (RT_SUCCESS(rc)) + rc = ShClTransferSetProvider(pTransfer, pProvider); + if (RT_SUCCESS(rc)) + rc = ShClTransferRootsSetFromPath(pTransfer, pszPath); + if (RT_SUCCESS(rc)) + rc = ShClTransferInit(pTransfer); + if (RT_SUCCESS(rc)) + { + if (idTransfer == NIL_SHCLTRANSFERID) + rc = ShClTransferCtxRegister(pTransferCtx, pTransfer, NULL); + else + rc = ShClTransferCtxRegisterById(pTransferCtx, pTransfer, idTransfer); + fCtxRegistered = RT_SUCCESS(rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferHttpServerRegisterTransfer(pSrv, pTransfer); + fHttpRegistered = RT_SUCCESS(rc); + } + + if (RT_SUCCESS(rc)) + { + *ppTransfer = pTransfer; + return VINF_SUCCESS; + } + + if (fHttpRegistered) + ShClTransferHttpServerUnregisterTransfer(pSrv, pTransfer); + if (fCtxRegistered) + ShClTransferCtxUnregisterById(pTransferCtx, ShClTransferGetID(pTransfer)); + if (pTransfer) + ShClTransferDestroy(pTransfer); + return rc; +} + +/** HTTP GET worker context. */ +typedef struct TSTHTTPGETCTX +{ + /** URL to download. */ + const char *pszUrl; + /** Destination file path. */ + const char *pszDst; + /** Result of the HTTP operation. */ + int rc; +} TSTHTTPGETCTX; +/** Pointer to an HTTP GET worker context. */ +typedef TSTHTTPGETCTX *PTSTHTTPGETCTX; + +/** Performs an HTTP GET on a worker thread. */ +static DECLCALLBACK(int) tstHttpGetThread(RTTHREAD hThread, void *pvUser) +{ + PTSTHTTPGETCTX pCtx = (PTSTHTTPGETCTX)pvUser; + + int rc = RTThreadUserSignal(hThread); + if (RT_SUCCESS(rc)) + { + RTHTTP hClient; + rc = RTHttpCreate(&hClient); + if (RT_SUCCESS(rc)) + { + rc = RTHttpSetProxy(hClient, NULL /* pszProxyUrl */, 0 /* uPort */, + NULL /* pszProxyUser */, NULL /* pszProxyPwd */); + if (RT_SUCCESS(rc)) + rc = RTHttpGetFile(hClient, pCtx->pszUrl, pCtx->pszDst); + + int rc2 = RTHttpDestroy(hClient); + if (RT_SUCCESS(rc)) + rc = rc2; + } + } + + pCtx->rc = rc; + return rc; +} + +/** HTTP transfer unregister worker context. */ +typedef struct TSTHTTPUNREGISTERCTX +{ + /** HTTP server to unregister from. */ + PSHCLHTTPSERVER pSrv; + /** Transfer to unregister. */ + PSHCLTRANSFER pTransfer; + /** Result of the unregister operation. */ + int rc; +} TSTHTTPUNREGISTERCTX; +/** Pointer to an HTTP transfer unregister worker context. */ +typedef TSTHTTPUNREGISTERCTX *PTSTHTTPUNREGISTERCTX; + +/** Unregisters an HTTP transfer on a worker thread. */ +static DECLCALLBACK(int) tstHttpUnregisterThread(RTTHREAD hThread, void *pvUser) +{ + PTSTHTTPUNREGISTERCTX pCtx = (PTSTHTTPUNREGISTERCTX)pvUser; + + int rc = RTThreadUserSignal(hThread); + if (RT_SUCCESS(rc)) + rc = ShClTransferHttpServerUnregisterTransfer(pCtx->pSrv, pCtx->pTransfer); + + pCtx->rc = rc; + return rc; +} + +/** + * Checks that HEAD and sequential GET requests each own and close their object handle. + * + * @param hTest The test handle. + * @param pszTempDir Temporary directory for test files. + */ +static void tstRepeatedRequestHandles(RTTEST hTest, const char *pszTempDir) +{ + RTTestSub(hTest, "request-owned object handles"); + + char szSrcFile[RTPATH_MAX]; + RTTEST_CHECK_RETV(hTest, RTStrPrintf2(szSrcFile, sizeof(szSrcFile), "%s", pszTempDir) > 0); + RTTEST_CHECK_RC_OK_RETV(hTest, RTPathAppend(szSrcFile, sizeof(szSrcFile), "request-handles.bin")); + RTTEST_CHECK_RC_OK_RETV(hTest, tstCreatePatternFile(szSrcFile, _128K)); + + TSTHTTPPROVIDERCTX ProviderCtx; + SHCLTXPROVIDER Provider; + bool fProviderInitialized = false; + bool fServerInitialized = false; + bool fCtxInitialized = false; + PSHCLTRANSFER pTransfer = NULL; + SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; + char *pszUrl = NULL; + RTHTTP hClient = NIL_RTHTTP; + + SHCLHTTPSERVER HttpSrv; + SHCLTRANSFERCTX TransferCtx; + uint16_t uPort = 0; + int rc = tstHttpProviderInit(&ProviderCtx, &Provider); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + fProviderInitialized = true; + rc = ShClTransferHttpServerInit(&HttpSrv); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fServerInitialized = true; + rc = ShClTransferHttpServerStart(&HttpSrv, 32 /* cMaxAttempts */, &uPort); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxInit(&TransferCtx); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fCtxInitialized = true; + rc = ShClTransferCtxBeginSession(&TransferCtx, 101); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = tstCreateRegisteredTransfer(&TransferCtx, &HttpSrv, szSrcFile, &Provider, + NIL_SHCLTRANSFERID, &pTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + idTransfer = ShClTransferGetID(pTransfer); + pszUrl = ShClTransferHttpServerGetUrlA(&HttpSrv, idTransfer, 0 /* idxEntry */); + RTTEST_CHECK(hTest, pszUrl != NULL); + if (!pszUrl) + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + rc = RTHttpCreate(&hClient); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = RTHttpSetProxy(hClient, NULL /* pszProxyUrl */, 0 /* uPort */, + NULL /* pszProxyUser */, NULL /* pszProxyPwd */); + RTTEST_CHECK_RC_OK(hTest, rc); + } + + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < 3; i++) + { + void *pvResponse = NULL; + size_t cbResponse = 0; + rc = RTHttpGetHeaderBinary(hClient, pszUrl, &pvResponse, &cbResponse); + RTTEST_CHECK_RC_OK(hTest, rc); + RTHttpFreeResponse(pvResponse); + if (RT_FAILURE(rc)) + break; + + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjOpens) == i + 1); + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjCloses) == i + 1); + } + } + + if (RT_SUCCESS(rc)) + { + for (uint32_t i = 0; i < 2; i++) + { + char szDstFile[RTPATH_MAX]; + RTTEST_CHECK_BREAK(hTest, RTStrPrintf(szDstFile, sizeof(szDstFile), "%s/request-handles-%RU32.bin", + pszTempDir, i) > 0); + + rc = RTHttpGetFile(hClient, pszUrl, szDstFile); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + RTTEST_CHECK_RC_OK(hTest, RTFileCompare(szSrcFile, szDstFile)); + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szDstFile)); + if (RT_FAILURE(rc)) + break; + + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjOpens) == i + 4); + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjCloses) == i + 4); + } + } + + if (hClient != NIL_RTHTTP) + RTTEST_CHECK_RC_OK(hTest, RTHttpDestroy(hClient)); + RTStrFree(pszUrl); + + if (pTransfer) + { + if (fServerInitialized && ShClTransferHttpServerGetTransfer(&HttpSrv, idTransfer)) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pTransfer)); + if (fCtxInitialized && ShClTransferCtxGetTransferById(&TransferCtx, idTransfer) == pTransfer) + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxUnregisterById(&TransferCtx, idTransfer)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferDestroy(pTransfer)); + } + if (fCtxInitialized) + ShClTransferCtxDestroy(&TransferCtx); + if (fServerInitialized) + { + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerDestroy(&HttpSrv)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerDestroy(&HttpSrv)); + } + if (fProviderInitialized) + tstHttpProviderTerm(&ProviderCtx); + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szSrcFile)); +} + +/** + * Checks that a stale transfer key cannot unregister a newer transfer which reused its numeric ID. + * + * @param hTest The test handle. + * @param pszTempDir Temporary directory for test files. + */ +static void tstStaleKeyUnregister(RTTEST hTest, const char *pszTempDir) +{ + RTTestSub(hTest, "stale transfer key unregister"); + + char szSrcFile[RTPATH_MAX]; + RTTEST_CHECK_RETV(hTest, RTStrPrintf2(szSrcFile, sizeof(szSrcFile), "%s", pszTempDir) > 0); + RTTEST_CHECK_RC_OK_RETV(hTest, RTPathAppend(szSrcFile, sizeof(szSrcFile), "stale-key.bin")); + RTTEST_CHECK_RC_OK_RETV(hTest, tstCreatePatternFile(szSrcFile, _4K)); + + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); + RTTEST_CHECK_RETV(hTest, ShClTransferProviderLocalQueryInterface(&Provider) != NULL); + + SHCLHTTPSERVER HttpSrv; + SHCLTRANSFERCTX OldTransferCtx; + SHCLTRANSFERCTX NewTransferCtx; + bool fServerInitialized = false; + bool fOldCtxInitialized = false; + bool fNewCtxInitialized = false; + PSHCLTRANSFER pOldTransfer = NULL; + PSHCLTRANSFER pNewTransfer = NULL; + char *pszOldUrl = NULL; + char *pszNewUrl = NULL; + size_t cbOldUrl = 0; + size_t cbNewUrl = 0; + uint16_t uPort = 0; + + int rc = ShClTransferHttpServerInit(&HttpSrv); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + fServerInitialized = true; + rc = ShClTransferHttpServerStart(&HttpSrv, 32 /* cMaxAttempts */, &uPort); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxInit(&OldTransferCtx); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fOldCtxInitialized = true; + rc = ShClTransferCtxBeginSession(&OldTransferCtx, 201); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxInit(&NewTransferCtx); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fNewCtxInitialized = true; + rc = ShClTransferCtxBeginSession(&NewTransferCtx, 202); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = tstCreateRegisteredTransfer(&OldTransferCtx, &HttpSrv, szSrcFile, &Provider, 42, &pOldTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = tstCreateRegisteredTransfer(&NewTransferCtx, &HttpSrv, szSrcFile, &Provider, 42, &pNewTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + RTTEST_CHECK(hTest, ShClTransferKeyIsValid(ShClTransferGetSessionId(pOldTransfer), 42, + ShClTransferGetGeneration(pOldTransfer))); + RTTEST_CHECK(hTest, ShClTransferKeyIsValid(ShClTransferGetSessionId(pNewTransfer), 42, + ShClTransferGetGeneration(pNewTransfer))); + RTTEST_CHECK(hTest, ShClTransferGetSessionId(pOldTransfer) != ShClTransferGetSessionId(pNewTransfer)); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 2); + + rc = ShClTransferHttpConvertToStringList(&HttpSrv, pOldTransfer, &pszOldUrl, &cbOldUrl); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferHttpConvertToStringList(&HttpSrv, pNewTransfer, &pszNewUrl, &cbNewUrl); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + RTTEST_CHECK(hTest, cbOldUrl == strlen(pszOldUrl) + 1); + RTTEST_CHECK(hTest, cbNewUrl == strlen(pszNewUrl) + 1); + RTTEST_CHECK(hTest, RTStrCmp(pszOldUrl, pszNewUrl) != 0); + + rc = ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pOldTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 1); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransfer(&HttpSrv, 42)); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pOldTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 1); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransfer(&HttpSrv, 42)); + } + if (RT_SUCCESS(rc)) + { + RTHTTP hClient; + rc = RTHttpCreate(&hClient); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + rc = RTHttpSetProxy(hClient, NULL /* pszProxyUrl */, 0 /* uPort */, + NULL /* pszProxyUser */, NULL /* pszProxyPwd */); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + tstMalformedGet(hTest, hClient, pszOldUrl, VERR_HTTP_NOT_FOUND); + + char szDstFile[RTPATH_MAX]; + RTTEST_CHECK(hTest, RTStrPrintf(szDstFile, sizeof(szDstFile), "%s/stale-key-copy.bin", pszTempDir) > 0); + rc = RTHttpGetFile(hClient, pszNewUrl, szDstFile); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + RTTEST_CHECK_RC_OK(hTest, RTFileCompare(szSrcFile, szDstFile)); + if (RTFileExists(szDstFile)) + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szDstFile)); + } + + int rc2 = RTHttpDestroy(hClient); + RTTEST_CHECK_RC_OK(hTest, rc2); + } + } + + RTStrFree(pszNewUrl); + RTStrFree(pszOldUrl); + + if (pNewTransfer) + { + if (fServerInitialized) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pNewTransfer)); + if (fNewCtxInitialized && ShClTransferCtxGetTransferById(&NewTransferCtx, 42) == pNewTransfer) + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxUnregisterById(&NewTransferCtx, 42)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferDestroy(pNewTransfer)); + } + if (pOldTransfer) + { + if (fServerInitialized) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pOldTransfer)); + if (fOldCtxInitialized && ShClTransferCtxGetTransferById(&OldTransferCtx, 42) == pOldTransfer) + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxUnregisterById(&OldTransferCtx, 42)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferDestroy(pOldTransfer)); + } + if (fNewCtxInitialized) + ShClTransferCtxDestroy(&NewTransferCtx); + if (fOldCtxInitialized) + ShClTransferCtxDestroy(&OldTransferCtx); + if (fServerInitialized) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerDestroy(&HttpSrv)); + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szSrcFile)); +} + +/** + * Checks that unregister waits for an active request while retaining its transfer until request end. + * + * @param hTest The test handle. + * @param pszTempDir Temporary directory for test files. + */ +static void tstUnregisterDuringRequest(RTTEST hTest, const char *pszTempDir) +{ + RTTestSub(hTest, "unregister during active request"); + + char szSrcFile[RTPATH_MAX]; + char szDstFile[RTPATH_MAX]; + RTTEST_CHECK_RETV(hTest, RTStrPrintf2(szSrcFile, sizeof(szSrcFile), "%s", pszTempDir) > 0); + RTTEST_CHECK_RC_OK_RETV(hTest, RTPathAppend(szSrcFile, sizeof(szSrcFile), "active-request.bin")); + RTTEST_CHECK_RETV(hTest, RTStrPrintf2(szDstFile, sizeof(szDstFile), "%s", pszTempDir) > 0); + RTTEST_CHECK_RC_OK_RETV(hTest, RTPathAppend(szDstFile, sizeof(szDstFile), "active-request-copy.bin")); + RTTEST_CHECK_RC_OK_RETV(hTest, tstCreatePatternFile(szSrcFile, _128K)); + + TSTHTTPPROVIDERCTX ProviderCtx; + SHCLTXPROVIDER Provider; + bool fProviderInitialized = false; + bool fServerInitialized = false; + bool fCtxInitialized = false; + PSHCLTRANSFER pTransfer = NULL; + SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; + char *pszUrl = NULL; + RTTHREAD hGetThread = NIL_RTTHREAD; + RTTHREAD hUnregisterThread = NIL_RTTHREAD; + bool fGetThreadStarted = false; + bool fUnregisterThreadStarted = false; + bool fUnregisterThreadJoined = false; + + SHCLHTTPSERVER HttpSrv; + SHCLTRANSFERCTX TransferCtx; + uint16_t uPort = 0; + int rc = tstHttpProviderInit(&ProviderCtx, &Provider); + RTTEST_CHECK_RC_OK(hTest, rc); + if (RT_SUCCESS(rc)) + { + fProviderInitialized = true; + ProviderCtx.cReadsToPause = 1; + rc = ShClTransferHttpServerInit(&HttpSrv); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fServerInitialized = true; + rc = ShClTransferHttpServerStart(&HttpSrv, 32 /* cMaxAttempts */, &uPort); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCtxInit(&TransferCtx); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fCtxInitialized = true; + rc = ShClTransferCtxBeginSession(&TransferCtx, 301); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = tstCreateRegisteredTransfer(&TransferCtx, &HttpSrv, szSrcFile, &Provider, + NIL_SHCLTRANSFERID, &pTransfer); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + idTransfer = ShClTransferGetID(pTransfer); + pszUrl = ShClTransferHttpServerGetUrlA(&HttpSrv, idTransfer, 0 /* idxEntry */); + RTTEST_CHECK(hTest, pszUrl != NULL); + if (!pszUrl) + rc = VERR_NO_MEMORY; + } + + TSTHTTPGETCTX GetCtx = { pszUrl, szDstFile, VERR_IPE_UNINITIALIZED_STATUS }; + TSTHTTPUNREGISTERCTX UnregisterCtx = { &HttpSrv, pTransfer, VERR_IPE_UNINITIALIZED_STATUS }; + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&hGetThread, tstHttpGetThread, &GetCtx, 0 /* cbStack */, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "ShClHttpGet"); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fGetThreadStarted = true; + rc = RTThreadUserWait(hGetThread, RT_MS_5SEC); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + rc = RTSemEventWait(ProviderCtx.hReadEntered, RT_MS_10SEC); + RTTEST_CHECK_RC_OK(hTest, rc); + } + + if (RT_SUCCESS(rc)) + { + rc = RTThreadCreate(&hUnregisterThread, tstHttpUnregisterThread, &UnregisterCtx, 0 /* cbStack */, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "ShClHttpUnreg"); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + fUnregisterThreadStarted = true; + rc = RTThreadUserWait(hUnregisterThread, RT_MS_5SEC); + RTTEST_CHECK_RC_OK(hTest, rc); + } + if (RT_SUCCESS(rc)) + { + /* + * Unregister removes the transfer from lookup before waiting for the + * active request. Wait for that point so the thread timeout below + * proves the request drain, rather than merely a scheduling delay. + */ + for (uint32_t i = 0; i < 1000 && ShClTransferHttpServerGetTransferCount(&HttpSrv) != 0; ++i) + RTThreadSleep(1); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 0); + + int rcUnregisterThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rcWait = RTThreadWait(hUnregisterThread, 100 /* msTimeout */, &rcUnregisterThread); + RTTEST_CHECK_RC(hTest, rcWait, VERR_TIMEOUT); + } + + if (fProviderInitialized) + RTTEST_CHECK_RC_OK(hTest, RTSemEventMultiSignal(ProviderCtx.hReadContinue)); + if (fGetThreadStarted) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int rcWait = RTThreadWait(hGetThread, RT_MS_10SEC, &rcThread); + RTTEST_CHECK_RC_OK(hTest, rcWait); + if (RT_FAILURE(rcWait)) + rcWait = RTThreadWait(hGetThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rcWait)) + { + RTTEST_CHECK_RC_OK(hTest, rcThread); + RTTEST_CHECK_RC_OK(hTest, GetCtx.rc); + if (RT_SUCCESS(GetCtx.rc)) + RTTEST_CHECK_RC_OK(hTest, RTFileCompare(szSrcFile, szDstFile)); + } + } + if (fUnregisterThreadStarted) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int rcWait = RTThreadWait(hUnregisterThread, RT_MS_10SEC, &rcThread); + RTTEST_CHECK_RC_OK(hTest, rcWait); + if (RT_FAILURE(rcWait)) + rcWait = RTThreadWait(hUnregisterThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rcWait)) + { + fUnregisterThreadJoined = true; + RTTEST_CHECK_RC_OK(hTest, rcThread); + RTTEST_CHECK_RC_OK(hTest, UnregisterCtx.rc); + RTTEST_CHECK(hTest, ShClTransferHttpServerGetTransferCount(&HttpSrv) == 0); + RTTEST_CHECK(hTest, !ShClTransferHttpServerGetTransfer(&HttpSrv, idTransfer)); + } + } + + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjOpens) == 1); + RTTEST_CHECK(hTest, ASMAtomicReadU32(&ProviderCtx.cObjCloses) == 1); + + RTStrFree(pszUrl); + if (fUnregisterThreadStarted && !fUnregisterThreadJoined) + { + int rcThread; + RTTEST_CHECK_RC_OK(hTest, RTThreadWait(hUnregisterThread, RT_INDEFINITE_WAIT, &rcThread)); + } + if (pTransfer) + { + if (fServerInitialized && ShClTransferHttpServerGetTransfer(&HttpSrv, idTransfer)) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerUnregisterTransfer(&HttpSrv, pTransfer)); + if (fCtxInitialized && ShClTransferCtxGetTransferById(&TransferCtx, idTransfer) == pTransfer) + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxUnregisterById(&TransferCtx, idTransfer)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferDestroy(pTransfer)); + } + if (fCtxInitialized) + ShClTransferCtxDestroy(&TransferCtx); + if (fServerInitialized) + RTTEST_CHECK_RC_OK(hTest, ShClTransferHttpServerDestroy(&HttpSrv)); + if (fProviderInitialized) + tstHttpProviderTerm(&ProviderCtx); + if (RTFileExists(szDstFile)) + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szDstFile)); + RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szSrcFile)); +} + /** * Run a manual (i.e. interacive) test. * @@ -313,7 +1088,7 @@ static void tstManual(RTTEST hTest, PSHCLTRANSFERCTX pTransferCtx, PSHCLHTTPSERV SHCLCALLBACKS Callbacks; RT_ZERO(Callbacks); - RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */, false /* fHeadless */)); + RTTEST_CHECK_RC_OK(hTest, ShClX11Init(&X11Ctx, &Callbacks, NULL /* pParent */)); RTTEST_CHECK_RC_OK(hTest, ShClX11ThreadStart(&X11Ctx, false /* fGrab */)); RTTEST_CHECK_RC_OK(hTest, ShClEventSourceInit(&EventSource, 0)); RTTEST_CHECK_RC_OK(hTest, ShClX11WriteDataToX11(&X11Ctx, &EventSource, RT_MS_30SEC, @@ -503,9 +1278,11 @@ int main(int argc, char *argv[]) SHCLTRANSFERCTX TxCtx; RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxInit(&TxCtx)); + RTTEST_CHECK_RC_OK(hTest, ShClTransferCtxBeginSession(&TxCtx, 1)); /* Query the local transfer provider. */ SHCLTXPROVIDER Provider; + RT_ZERO(Provider); RTTESTI_CHECK(ShClTransferProviderLocalQueryInterface(&Provider) != NULL); /* Parse options again, but this time we only fetch all files we want to serve. @@ -533,6 +1310,13 @@ int main(int argc, char *argv[]) RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardHttpServer-XXXXXX")); RTTEST_CHECK_RC_OK(hTest, RTDirCreateTemp(szTempDir, 0700)); + if (!g_fManual) + { + tstRepeatedRequestHandles(hTest, szTempDir); + tstStaleKeyUnregister(hTest, szTempDir); + tstUnregisterDuringRequest(hTest, szTempDir); + } + tstDuplicateTransferRegistration(hTest, &TxCtx, &HttpSrv, szTempDir, &Provider); if (!g_fManual) @@ -616,7 +1400,7 @@ int main(int argc, char *argv[]) RTTEST_CHECK_RC_OK(hTest, RTHttpSetProxy(hClient, NULL /*pszProxyUrl*/, 0 /*uPort*/, NULL /*pszProxyUser*/, NULL /*pszProxyPwd*/)); - char szURL[RTPATH_MAX]; + char szExpectedUrl[RTPATH_MAX]; if (ShClTransferCtxGetTotalTransfers(&TxCtx) > 0) { PSHCLTRANSFER pTx = ShClTransferCtxGetTransferByIndex(&TxCtx, 0); @@ -631,7 +1415,19 @@ int main(int argc, char *argv[]) { PSHCLTRANSFER pTx = ShClTransferCtxGetTransferByIndex(&TxCtx, i); char *pszUrlBase = ShClTransferHttpServerGetUrlA(&HttpSrv, ShClTransferGetID(pTx), UINT64_MAX); - RTTEST_CHECK(hTest, RTStrPrintf2(szURL, sizeof(szURL), "%s/%s", pszUrlBase, g_aTests[i].pszUrl)); + char *pszUrl = ShClTransferHttpServerGetUrlA(&HttpSrv, ShClTransferGetID(pTx), 0 /* idxEntry */); + RTTEST_CHECK(hTest, pszUrlBase != NULL); + RTTEST_CHECK(hTest, pszUrl != NULL); + if (!pszUrlBase || !pszUrl) + { + RTStrFree(pszUrlBase); + RTStrFree(pszUrl); + continue; + } + RTTEST_CHECK(hTest, RTStrPrintf2(szExpectedUrl, sizeof(szExpectedUrl), "%s/%s", + pszUrlBase, g_aTests[i].pszUrl)); + RTTEST_CHECK_MSG(hTest, RTStrCmp(pszUrl, szExpectedUrl) == 0, + (hTest, "Expected URL '%s', got '%s'\n", szExpectedUrl, pszUrl)); RTStrFree(pszUrlBase); switch (g_aTests[i].fMode & RTFS_TYPE_MASK) @@ -643,8 +1439,8 @@ int main(int argc, char *argv[]) RTTEST_CHECK_RC_OK(hTest, RTPathTemp(szDstFile, sizeof(szDstFile))); RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szDstFile, sizeof(szDstFile), "tstClipboardHttpServer-XXXXXX")); RTTEST_CHECK_RC_OK(hTest, RTFileCreateTemp(szDstFile, 0600)); - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Downloading file '%s' -> '%s'\n", szURL, szDstFile); - RTTEST_CHECK_RC_OK(hTest, RTHttpGetFile(hClient, szURL, szDstFile)); + RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Downloading file '%s' -> '%s'\n", pszUrl, szDstFile); + RTTEST_CHECK_RC_OK(hTest, RTHttpGetFile(hClient, pszUrl, szDstFile)); /* Compare files. */ char szSrcFile[RTPATH_MAX]; @@ -665,7 +1461,7 @@ int main(int argc, char *argv[]) RTTEST_CHECK_RC_OK(hTest, RTPathTemp(szDstFile, sizeof(szDstFile))); RTTEST_CHECK_RC_OK(hTest, RTPathAppend(szDstFile, sizeof(szDstFile), "tstClipboardHttpServer-XXXXXX")); RTTEST_CHECK_RC_OK(hTest, RTFileCreateTemp(szDstFile, 0600)); - RTTEST_CHECK_RC (hTest, RTHttpGetFile(hClient, szURL, szDstFile), g_aTests[i].rc); + RTTEST_CHECK_RC (hTest, RTHttpGetFile(hClient, pszUrl, szDstFile), g_aTests[i].rc); RTTEST_CHECK_RC_OK(hTest, RTFileDelete(szDstFile)); break; } @@ -673,6 +1469,7 @@ int main(int argc, char *argv[]) default: break; } + RTStrFree(pszUrl); } RTTEST_CHECK_RC_OK(hTest, RTHttpDestroy(hClient)); diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardMimeConv.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardMimeConv.cpp index 731fd2446e27..19e147645f58 100644 --- a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardMimeConv.cpp +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardMimeConv.cpp @@ -1,4 +1,4 @@ -/* $Id: tstClipboardMimeConv.cpp 114767 2026-07-24 22:06:05Z knut.osmundsen@oracle.com $ */ +/* $Id: tstClipboardMimeConv.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard MIME converter testcase. */ @@ -30,9 +30,13 @@ * Header Files * *********************************************************************************************************************************/ #include +#include #include #include +#include +#include +#include #include #include #include @@ -137,6 +141,74 @@ static void testUriListMapping(void) #endif + +/** + * Tests escaped output containing multibyte UTF-8 on a text-mode stream. + */ +static void testEscapedString(void) +{ + RTTestISub("escaped UTF-8 output"); + + static const char s_szInput[] = + "A\"\\\n\t" + "\xc3\xa9\xe2\x82\xac\xf0\x9f\x98\x80"; + static const char s_szExpected[] = + "A\\\"\\\\\\n\\t" + "\xc3\xa9\xe2\x82\xac\xf0\x9f\x98\x80"; + + char szFilename[RTPATH_MAX]; + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpenTemp(&hFile, szFilename, sizeof(szFilename), + RTFILE_O_CREATE | RTFILE_O_READWRITE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTFileOpenTemp failed: %Rrc", rc); + return; + } + rc = RTFileClose(hFile); + if (RT_FAILURE(rc)) + { + RTTestIFailed("RTFileClose failed: %Rrc", rc); + RTFileDelete(szFilename); + return; + } + + PRTSTREAM pStrm = NULL; + rc = RTStrmOpen(szFilename, "w", &pStrm); + if (RT_SUCCESS(rc)) + rc = RTStrmSetMode(pStrm, false /* fBinary */, true /* fCurrentCodeSet */); + if (RT_SUCCESS(rc)) + { + ShClHlpPrintEscapedString(pStrm, s_szInput, sizeof(s_szInput) - 1); + rc = RTStrmError(pStrm); + } + if (RT_FAILURE(rc)) + RTTestIFailed("Writing escaped UTF-8 failed: %Rrc", rc); + int const rcClose = RTStrmClose(pStrm); + if (RT_FAILURE(rcClose)) + RTTestIFailed("RTStrmClose failed: %Rrc", rcClose); + + void *pvOutput = NULL; + size_t cbOutput = 0; + rc = RTFileReadAll(szFilename, &pvOutput, &cbOutput); + if (RT_FAILURE(rc)) + RTTestIFailed("RTFileReadAll failed: %Rrc", rc); + else + { + RTTESTI_CHECK_MSG(cbOutput == sizeof(s_szExpected) - 1, + ("cbOutput=%zu expected=%zu\n", cbOutput, sizeof(s_szExpected) - 1)); + if (cbOutput == sizeof(s_szExpected) - 1) + RTTESTI_CHECK_MSG(memcmp(pvOutput, s_szExpected, cbOutput) == 0, + ("output='%.*Rhxs' expected='%.*Rhxs'\n", + cbOutput, pvOutput, sizeof(s_szExpected) - 1, s_szExpected)); + RTFileReadAllFree(pvOutput, cbOutput); + } + + rc = RTFileDelete(szFilename); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +} + + static void testText(RTTEST hTest) { RTTestISub("text"); @@ -336,6 +408,7 @@ int main(int argc, char **argv) return rcExit; RTTestBanner(hTest); + testEscapedString(); testText(hTest); #if 0 /** @todo r=bird: file transfers require a very different approach... */ testUriListMapping(); diff --git a/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp new file mode 100644 index 000000000000..44d20430cde7 --- /dev/null +++ b/src/VBox/GuestHost/SharedClipboard/testcase/tstClipboardWinStream.cpp @@ -0,0 +1,627 @@ +/* $Id: tstClipboardWinStream.cpp 115086 2026-08-19 13:09:34Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard Windows stream testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + +#include +#include +#include +#include + + +/** @page pg_tstClipboardWinStream Shared Clipboard Windows stream testcase + * + * This testcase exercises the Windows IStream adapter with a mock transfer + * provider. It verifies exact boundary cases and deterministic randomized + * reads crossing the default 64 KiB transfer-chunk boundary. + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Size of the original boundary regression read. */ +#define TST_WIN_STREAM_BOUNDARY_READ_SIZE (_64K + 1) +/** Amount of data reserved for the final read past EOF. */ +#define TST_WIN_STREAM_FINAL_DATA_SIZE (_64K + 37) +/** Maximum randomized IStream read size. */ +#define TST_WIN_STREAM_MAX_READ_SIZE (_128K + 17) +/** Number of deterministic randomized IStream reads. */ +#define TST_WIN_STREAM_RANDOM_READ_COUNT 32 +/** Seed for the deterministic randomized reads. */ +#define TST_WIN_STREAM_RANDOM_SEED UINT64_C(0x4f1bbcdc676f2c35) +/** Maximum number of mock-provider reads recorded. */ +#define TST_WIN_STREAM_MAX_PROVIDER_READS 256 +/** Value used to detect writes beyond the returned data. */ +#define TST_WIN_STREAM_BUFFER_FILL UINT8_C(0xa5) +/** Object handle returned by the mock provider. */ +#define TST_WIN_STREAM_OBJ_HANDLE UINT64_C(1) + +/** Explicit boundary read sizes, performed from unaligned stream offsets. */ +static uint32_t const g_acbBoundaryReads[] = +{ + 17, + _64K - 1, + _64K, + _64K + 1, + _128K - 1, + _128K, + _128K + 1 +}; + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** A recorded mock-provider read. */ +typedef struct TSTWINSTREAMPROVIDERREAD +{ + /** Source offset before the read. */ + uint32_t offData; + /** Requested byte count. */ + uint32_t cbRequested; + /** Returned byte count. */ + uint32_t cbRead; +} TSTWINSTREAMPROVIDERREAD; + +/** State for the mock transfer provider. */ +typedef struct TSTWINSTREAMPROVIDER +{ + /** Deterministic source data. */ + uint8_t *pbData; + /** Size of the source data. */ + uint32_t cbData; + /** Current source offset. */ + uint32_t offData; + /** Maximum transfer chunk size. */ + uint32_t cbMaxChunkSize; + /** Recorded object reads. */ + TSTWINSTREAMPROVIDERREAD aReads[TST_WIN_STREAM_MAX_PROVIDER_READS]; + /** Number of object opens. */ + uint32_t cOpens; + /** Number of object reads. */ + uint32_t cReads; + /** Number of object closes. */ + uint32_t cCloses; + /** Whether the read trace overflowed. */ + bool fReadLogOverflow; +} TSTWINSTREAMPROVIDER; +/** Pointer to mock transfer provider state. */ +typedef TSTWINSTREAMPROVIDER *PTSTWINSTREAMPROVIDER; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** Tests Windows filename sanitization and transfer-list name validation. */ +static void tstWinPathSanitizeFilename(void) +{ + static const char * const s_apszValid[] = + { + "test-64KiB-name-url-#%+&=,.bin", + "unicode-\xed\x9e\xb0-\xee\x80\x80-\xf0\x9f\x98\x80.bin", + "COM0.txt", + "COM10.txt", + "LPT0.txt", + "CONSOLE.txt", + "NULL.txt" + }; + static const char * const s_apszReserved[] = + { + "CON", + "con.txt", + "PRN.tar.gz", + "AUX", + "NUL.txt", + "COM1", + "com9.log", + "LPT1", + "lpt9.txt", + "COM\xc2\xb9.txt", + "LPT\xc2\xb2", + "COM\xc2\xb3.bin", + "directory/CON.txt" + }; + char szPath[128]; + + RTTestISub("Windows filename sanitization"); + for (size_t i = 0; i < RT_ELEMENTS(s_apszValid); i++) + { + RTTESTI_CHECK_RC_OK(RTStrCopy(szPath, sizeof(szPath), s_apszValid[i])); + RTTESTI_CHECK_RC_OK(ShClPathSanitize(szPath, sizeof(szPath))); + RTTESTI_CHECK_MSG(!strcmp(szPath, s_apszValid[i]), + ("Valid name '%s' was changed to '%s'\n", s_apszValid[i], szPath)); + RTTESTI_CHECK_RC_OK(ShClTransferValidatePath(s_apszValid[i], false /* fMustExist */)); + } + + for (size_t i = 0; i < RT_ELEMENTS(s_apszReserved); i++) + { + RTTESTI_CHECK_RC_OK(RTStrCopy(szPath, sizeof(szPath), s_apszReserved[i])); + RTTESTI_CHECK_RC_OK(ShClPathSanitize(szPath, sizeof(szPath))); + RTTESTI_CHECK_MSG(strcmp(szPath, s_apszReserved[i]), + ("Reserved name '%s' was not changed\n", s_apszReserved[i])); + RTTESTI_CHECK_RC(ShClTransferValidatePath(s_apszReserved[i], false /* fMustExist */), VERR_INVALID_PARAMETER); + } +} + + +/** Tests transfer-list name length and termination invariants. */ +static void tstTransferListEntryNameValidation(void) +{ + RTTestISub("Transfer-list entry name validation"); + + char szValid[] = "valid.bin"; + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(NULL, sizeof(szValid), false /* fMustExist */), VERR_INVALID_POINTER); + RTTESTI_CHECK_RC_OK(ShClTransferValidatePathEx(szValid, sizeof(szValid), false /* fMustExist */)); + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(szValid, 0, false /* fMustExist */), VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(szValid, 1, false /* fMustExist */), VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(szValid, sizeof(szValid) + 1, false /* fMustExist */), + VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RC(ShClTransferValidatePathEx(szValid, SHCLLISTENTRY_MAX_NAME + 1, false /* fMustExist */), + VERR_INVALID_PARAMETER); + + char szEmpty[] = ""; + RTTESTI_CHECK_RC_OK(ShClTransferValidatePathEx(szEmpty, sizeof(szEmpty), false /* fMustExist */)); + + SHCLLISTENTRY Entry; + RT_ZERO(Entry); + Entry.pszName = szValid; + Entry.cbName = sizeof(szValid); + RTTESTI_CHECK(ShClTransferListEntryIsValid(&Entry)); + + Entry.pszName = szEmpty; + Entry.cbName = sizeof(szEmpty); + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + Entry.pszName = szValid; + + Entry.cbName = 0; + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + Entry.cbName = 1; + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + Entry.cbName = sizeof(szValid) + 1; + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + Entry.cbName = SHCLLISTENTRY_MAX_NAME + 1; + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + char szEmbeddedNul[] = { 'n', 'a', 'm', 'e', '\0', 'x', '\0' }; + Entry.pszName = szEmbeddedNul; + Entry.cbName = sizeof(szEmbeddedNul); + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + char szNoTerminator[] = { 'n', 'a', 'm', 'e' }; + Entry.pszName = szNoTerminator; + Entry.cbName = sizeof(szNoTerminator); + RTTESTI_CHECK(!ShClTransferListEntryIsValid(&Entry)); + + char szTooLong[SHCLLISTENTRY_MAX_NAME]; + memset(szTooLong, 'a', sizeof(szTooLong)); + RTTESTI_CHECK_RC(ShClTransferListEntryInitEx(&Entry, VBOX_SHCL_INFO_F_NONE, szTooLong, + NULL /* pvInfo */, 0 /* cbInfo */), VERR_INVALID_PARAMETER); + + szTooLong[sizeof(szTooLong) - 1] = '\0'; + RTTESTI_CHECK_RC_OK(ShClTransferListEntryInitEx(&Entry, VBOX_SHCL_INFO_F_NONE, szTooLong, + NULL /* pvInfo */, 0 /* cbInfo */)); + RTTESTI_CHECK(Entry.cbName == SHCLLISTENTRY_MAX_NAME); + ShClTransferListEntryDestroy(&Entry); + + RTTESTI_CHECK_RC_OK(ShClTransferListEntryInitEx(&Entry, VBOX_SHCL_INFO_F_NONE, szValid, + NULL /* pvInfo */, 0 /* cbInfo */)); + RTTESTI_CHECK(Entry.cbName == sizeof(szValid)); + ShClTransferListEntryDestroy(&Entry); +} + + +/** @copydoc SHCLTXPROVIDERIFACE::pfnRootListRead */ +static DECLCALLBACK(int) tstWinStreamProviderRootListRead(PSHCLTXPROVIDERCTX pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + return VINF_SUCCESS; +} + + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjOpen */ +static DECLCALLBACK(int) tstWinStreamProviderObjOpen(PSHCLTXPROVIDERCTX pCtx, PSHCLOBJOPENCREATEPARMS pCreateParms, + PSHCLOBJHANDLE phObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCreateParms, VERR_INVALID_POINTER); + AssertPtrReturn(phObj, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTWINSTREAMPROVIDER), VERR_INVALID_PARAMETER); + + PTSTWINSTREAMPROVIDER const pThis = (PTSTWINSTREAMPROVIDER)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + pThis->offData = 0; + pThis->cOpens++; + *phObj = TST_WIN_STREAM_OBJ_HANDLE; + return VINF_SUCCESS; +} + + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjRead */ +static DECLCALLBACK(int) tstWinStreamProviderObjRead(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj, void *pvData, + uint32_t cbData, uint32_t fFlags, uint32_t *pcbRead) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTWINSTREAMPROVIDER), VERR_INVALID_PARAMETER); + AssertReturn(hObj == TST_WIN_STREAM_OBJ_HANDLE, VERR_INVALID_HANDLE); + AssertReturn(fFlags == 0, VERR_INVALID_FLAGS); + + PTSTWINSTREAMPROVIDER const pThis = (PTSTWINSTREAMPROVIDER)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pThis->pbData, VERR_INVALID_POINTER); + AssertReturn(pThis->offData <= pThis->cbData, VERR_OUT_OF_RANGE); + AssertReturn(cbData <= pThis->cbMaxChunkSize, VERR_BUFFER_OVERFLOW); + + uint32_t const offData = pThis->offData; + uint32_t const cbRemaining = pThis->cbData - offData; + uint32_t const cbRead = RT_MIN(cbData, cbRemaining); + + uint32_t const iRead = pThis->cReads++; + if (iRead < RT_ELEMENTS(pThis->aReads)) + { + pThis->aReads[iRead].offData = offData; + pThis->aReads[iRead].cbRequested = cbData; + pThis->aReads[iRead].cbRead = cbRead; + } + else + pThis->fReadLogOverflow = true; + + memcpy(pvData, &pThis->pbData[offData], cbRead); + pThis->offData += cbRead; + + if (pcbRead) + *pcbRead = cbRead; + return VINF_SUCCESS; +} + + +/** @copydoc SHCLTXPROVIDERIFACE::pfnObjClose */ +static DECLCALLBACK(int) tstWinStreamProviderObjClose(PSHCLTXPROVIDERCTX pCtx, SHCLOBJHANDLE hObj) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->cbUser == sizeof(TSTWINSTREAMPROVIDER), VERR_INVALID_PARAMETER); + AssertReturn(hObj == TST_WIN_STREAM_OBJ_HANDLE, VERR_INVALID_HANDLE); + + PTSTWINSTREAMPROVIDER const pThis = (PTSTWINSTREAMPROVIDER)pCtx->pvUser; + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + + pThis->cCloses++; + return VINF_SUCCESS; +} + + +/** + * Performs and validates one non-empty IStream read. + */ +static bool tstWinStreamReadAndCheck(IStream *pStream, PTSTWINSTREAMPROVIDER pProvider, uint8_t *pbBuffer, + uint32_t cbBuffer, uint32_t *poffExpected, uint32_t cbRequest, uint32_t iStreamRead) +{ + AssertPtrReturn(pStream, false); + AssertPtrReturn(pProvider, false); + AssertPtrReturn(pbBuffer, false); + AssertPtrReturn(poffExpected, false); + AssertReturn(cbRequest > 0, false); + AssertReturn(cbBuffer > cbRequest, false); + AssertReturn(*poffExpected <= pProvider->cbData, false); + + uint32_t const offExpected = *poffExpected; + uint32_t const cbExpected = RT_MIN(cbRequest, pProvider->cbData - offExpected); + HRESULT const hrcExpected = cbExpected == cbRequest ? S_OK : S_FALSE; + uint32_t const iProviderReadFirst = pProvider->cReads; + + memset(pbBuffer, TST_WIN_STREAM_BUFFER_FILL, cbRequest + 1); + + ULONG cbRead = UINT32_MAX; + HRESULT const hrc = pStream->Read(pbBuffer, cbRequest, &cbRead); + RTTESTI_CHECK_MSG(hrc == hrcExpected, + ("read=%RU32 off=%RU32 cbRequest=%RU32: hrc=%Rhrc, expected %Rhrc\n", + iStreamRead, offExpected, cbRequest, hrc, hrcExpected)); + RTTESTI_CHECK_MSG(cbRead == cbExpected, + ("read=%RU32 off=%RU32 cbRequest=%RU32: cbRead=%RU32, expected %RU32\n", + iStreamRead, offExpected, cbRequest, cbRead, cbExpected)); + RTTESTI_CHECK_MSG(memcmp(pbBuffer, &pProvider->pbData[offExpected], cbExpected) == 0, + ("read=%RU32 off=%RU32 cbRequest=%RU32: data mismatch\n", + iStreamRead, offExpected, cbRequest)); + + bool fTailUntouched = true; + for (uint32_t off = cbExpected; off <= cbRequest; off++) + if (pbBuffer[off] != TST_WIN_STREAM_BUFFER_FILL) + { + fTailUntouched = false; + break; + } + RTTESTI_CHECK_MSG(fTailUntouched, + ("read=%RU32 off=%RU32 cbRequest=%RU32: buffer overwritten past returned data\n", + iStreamRead, offExpected, cbRequest)); + + uint32_t const cProviderReadsExpected = cbExpected + ? (cbExpected + pProvider->cbMaxChunkSize - 1) / pProvider->cbMaxChunkSize + : 0; + uint32_t const cProviderReadsActual = pProvider->cReads - iProviderReadFirst; + RTTESTI_CHECK_MSG(cProviderReadsActual == cProviderReadsExpected, + ("read=%RU32 off=%RU32 cbRequest=%RU32: provider reads=%RU32, expected %RU32\n", + iStreamRead, offExpected, cbRequest, cProviderReadsActual, cProviderReadsExpected)); + RTTESTI_CHECK_MSG(!pProvider->fReadLogOverflow, + ("read=%RU32: provider read trace overflowed\n", iStreamRead)); + + if ( !pProvider->fReadLogOverflow + && cProviderReadsActual == cProviderReadsExpected + && pProvider->cReads <= RT_ELEMENTS(pProvider->aReads)) + { + uint32_t offTrace = offExpected; + uint32_t cbLeft = cbExpected; + for (uint32_t i = 0; i < cProviderReadsExpected; i++) + { + uint32_t const cbChunkExpected = RT_MIN(cbLeft, pProvider->cbMaxChunkSize); + TSTWINSTREAMPROVIDERREAD const *pRead = &pProvider->aReads[iProviderReadFirst + i]; + RTTESTI_CHECK_MSG(pRead->offData == offTrace, + ("read=%RU32 chunk=%RU32: offData=%RU32, expected %RU32\n", + iStreamRead, i, pRead->offData, offTrace)); + RTTESTI_CHECK_MSG(pRead->cbRequested == cbChunkExpected, + ("read=%RU32 chunk=%RU32: cbRequested=%RU32, expected %RU32\n", + iStreamRead, i, pRead->cbRequested, cbChunkExpected)); + RTTESTI_CHECK_MSG(pRead->cbRead == cbChunkExpected, + ("read=%RU32 chunk=%RU32: cbRead=%RU32, expected %RU32\n", + iStreamRead, i, pRead->cbRead, cbChunkExpected)); + offTrace += cbChunkExpected; + cbLeft -= cbChunkExpected; + } + } + + bool const fSucceeded = hrc == hrcExpected && cbRead == cbExpected; + if (fSucceeded) + *poffExpected += cbExpected; + return fSucceeded; +} + + +/** + * Tests varied IStream reads which cross the default chunk boundary. + */ +static void tstWinStreamReadsAcrossChunkBoundaries(void) +{ + RTTestISub("64 KiB + 1 byte IStream read"); + + RTRAND hRand = NIL_RTRAND; + uint32_t acbRandomReads[TST_WIN_STREAM_RANDOM_READ_COUNT]; + RT_ZERO(acbRandomReads); + + int rc = RTRandAdvCreateParkMiller(&hRand); + RTTESTI_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + rc = RTRandAdvSeed(hRand, TST_WIN_STREAM_RANDOM_SEED); + RTTESTI_CHECK_RC_OK(rc); + } + + uint64_t cbData = TST_WIN_STREAM_BOUNDARY_READ_SIZE + TST_WIN_STREAM_FINAL_DATA_SIZE; + for (size_t i = 0; i < RT_ELEMENTS(g_acbBoundaryReads); i++) + cbData += g_acbBoundaryReads[i]; + if (RT_SUCCESS(rc)) + { + for (size_t i = 0; i < RT_ELEMENTS(acbRandomReads); i++) + { + acbRandomReads[i] = RTRandAdvU32Ex(hRand, 1, TST_WIN_STREAM_MAX_READ_SIZE); + cbData += acbRandomReads[i]; + } + } + RTTESTI_CHECK_MSG(cbData <= UINT32_MAX, ("cbData=%RU64\n", cbData)); + if (cbData > UINT32_MAX) + rc = VERR_OUT_OF_RANGE; + + TSTWINSTREAMPROVIDER ProviderCtx; + RT_ZERO(ProviderCtx); + ProviderCtx.cbData = (uint32_t)cbData; + + uint8_t *pbBuffer = NULL; + if (RT_SUCCESS(rc)) + { + ProviderCtx.pbData = (uint8_t *)RTMemAlloc(ProviderCtx.cbData); + pbBuffer = (uint8_t *)RTMemAlloc(TST_WIN_STREAM_MAX_READ_SIZE + 1); + RTTESTI_CHECK(ProviderCtx.pbData != NULL); + RTTESTI_CHECK(pbBuffer != NULL); + if (!ProviderCtx.pbData || !pbBuffer) + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + RTRandAdvBytes(hRand, ProviderCtx.pbData, ProviderCtx.cbData); + + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); + Provider.Interface.pfnRootListRead = tstWinStreamProviderRootListRead; + Provider.Interface.pfnObjOpen = tstWinStreamProviderObjOpen; + Provider.Interface.pfnObjRead = tstWinStreamProviderObjRead; + Provider.Interface.pfnObjClose = tstWinStreamProviderObjClose; + Provider.pvUser = &ProviderCtx; + Provider.cbUser = sizeof(ProviderCtx); + + PSHCLTRANSFER pTransfer = NULL; + ShClWinDataObject *pParent = NULL; + IStream *pStream = NULL; + uint8_t bFrontendCtx = 0; + + if (RT_SUCCESS(rc)) + { + rc = ShClTransferCreate(SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, NULL /* pCallbacks */, &pTransfer); + RTTESTI_CHECK_RC_OK(rc); + } + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK(pTransfer->cbMaxChunkSize == _64K); + ProviderCtx.cbMaxChunkSize = pTransfer->cbMaxChunkSize; + rc = ShClTransferSetProvider(pTransfer, &Provider); + RTTESTI_CHECK_RC_OK(rc); + } + if (RT_SUCCESS(rc)) + { + rc = ShClTransferRootListRead(pTransfer); + RTTESTI_CHECK_RC_OK(rc); + } + if (RT_SUCCESS(rc)) + { + pParent = new ShClWinDataObject(); + RTTESTI_CHECK(pParent != NULL); + if (pParent) + { + pParent->AddRef(); /* Testcase reference. */ + + ShClWinDataObject::CALLBACKS Callbacks; + RT_ZERO(Callbacks); + rc = pParent->Init((PSHCLCONTEXT)&bFrontendCtx, &Callbacks); + RTTESTI_CHECK_RC_OK(rc); + } + else + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + SHCLFSOBJINFO ObjInfo; + RT_ZERO(ObjInfo); + ObjInfo.cbObject = ProviderCtx.cbData; + + HRESULT const hrc = ShClWinStreamImpl::Create(pParent, pTransfer, Utf8Str("random-boundaries.bin"), &ObjInfo, &pStream); + RTTESTI_CHECK_MSG(hrc == S_OK, ("hrc=%Rhrc\n", hrc)); + if (FAILED(hrc)) + rc = VERR_GENERAL_FAILURE; + } + if (RT_SUCCESS(rc)) + { + ULONG cbRead = UINT32_MAX; + HRESULT const hrc = pStream->Read(pbBuffer, 0, &cbRead); + RTTESTI_CHECK_MSG(hrc == S_OK, ("zero-byte read hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG(cbRead == 0, ("zero-byte read cbRead=%RU32\n", cbRead)); + RTTESTI_CHECK_MSG(ProviderCtx.cOpens == 0, ("zero-byte read cOpens=%RU32\n", ProviderCtx.cOpens)); + RTTESTI_CHECK_MSG(ProviderCtx.cReads == 0, ("zero-byte read cReads=%RU32\n", ProviderCtx.cReads)); + } + + uint32_t offExpected = 0; + uint32_t iStreamRead = 0; + bool fCanContinue = RT_SUCCESS(rc); + if (fCanContinue) + { + fCanContinue = tstWinStreamReadAndCheck(pStream, &ProviderCtx, pbBuffer, TST_WIN_STREAM_MAX_READ_SIZE + 1, + &offExpected, TST_WIN_STREAM_BOUNDARY_READ_SIZE, iStreamRead++); + RTTESTI_CHECK_MSG(ProviderCtx.cReads == 2, ("cReads=%RU32\n", ProviderCtx.cReads)); + RTTESTI_CHECK_MSG(ProviderCtx.cOpens == 1, ("cOpens=%RU32\n", ProviderCtx.cOpens)); + RTTESTI_CHECK_MSG(ProviderCtx.cCloses == 0, ("cCloses=%RU32\n", ProviderCtx.cCloses)); + } + + RTTestISubF("%u randomized boundary reads (seed %#RX64)", + TST_WIN_STREAM_RANDOM_READ_COUNT, TST_WIN_STREAM_RANDOM_SEED); + + for (size_t i = 0; fCanContinue && i < RT_ELEMENTS(g_acbBoundaryReads); i++) + fCanContinue = tstWinStreamReadAndCheck(pStream, &ProviderCtx, pbBuffer, TST_WIN_STREAM_MAX_READ_SIZE + 1, + &offExpected, g_acbBoundaryReads[i], iStreamRead++); + + uint32_t cRandomMultiChunkReads = 0; + uint32_t cRandomFileBoundaryReads = 0; + for (size_t i = 0; fCanContinue && i < RT_ELEMENTS(acbRandomReads); i++) + { + uint32_t const cbRequest = acbRandomReads[i]; + if (cbRequest > ProviderCtx.cbMaxChunkSize) + cRandomMultiChunkReads++; + if (offExpected / ProviderCtx.cbMaxChunkSize != (offExpected + cbRequest - 1) / ProviderCtx.cbMaxChunkSize) + cRandomFileBoundaryReads++; + + fCanContinue = tstWinStreamReadAndCheck(pStream, &ProviderCtx, pbBuffer, TST_WIN_STREAM_MAX_READ_SIZE + 1, + &offExpected, cbRequest, iStreamRead++); + } + + if (fCanContinue) + { + RTTESTI_CHECK_MSG(cRandomMultiChunkReads > 0, + ("No randomized read exceeded the transfer chunk size\n")); + RTTESTI_CHECK_MSG(cRandomFileBoundaryReads > 0, + ("No randomized read crossed a file chunk boundary\n")); + RTTESTI_CHECK_MSG(offExpected == ProviderCtx.cbData - TST_WIN_STREAM_FINAL_DATA_SIZE, + ("offExpected=%RU32, expected %RU32\n", + offExpected, ProviderCtx.cbData - TST_WIN_STREAM_FINAL_DATA_SIZE)); + RTTESTI_CHECK_MSG(ProviderCtx.cCloses == 0, ("pre-EOF cCloses=%RU32\n", ProviderCtx.cCloses)); + + fCanContinue = tstWinStreamReadAndCheck(pStream, &ProviderCtx, pbBuffer, TST_WIN_STREAM_MAX_READ_SIZE + 1, + &offExpected, TST_WIN_STREAM_FINAL_DATA_SIZE + 1, iStreamRead++); + } + + if (fCanContinue) + { + RTTESTI_CHECK_MSG(offExpected == ProviderCtx.cbData, + ("offExpected=%RU32, expected %RU32\n", offExpected, ProviderCtx.cbData)); + RTTESTI_CHECK_MSG(ProviderCtx.offData == ProviderCtx.cbData, + ("provider offData=%RU32, expected %RU32\n", ProviderCtx.offData, ProviderCtx.cbData)); + RTTESTI_CHECK_MSG(ProviderCtx.cOpens == 1, ("cOpens=%RU32\n", ProviderCtx.cOpens)); + RTTESTI_CHECK_MSG(ProviderCtx.cCloses == 1, ("cCloses=%RU32\n", ProviderCtx.cCloses)); + RTTESTI_CHECK_MSG(!ProviderCtx.fReadLogOverflow, ("Provider read trace overflowed\n")); + + uint32_t const cProviderReadsBefore = ProviderCtx.cReads; + memset(pbBuffer, TST_WIN_STREAM_BUFFER_FILL, 2); + ULONG cbRead = UINT32_MAX; + HRESULT const hrc = pStream->Read(pbBuffer, 1, &cbRead); + RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("post-EOF read hrc=%Rhrc\n", hrc)); + RTTESTI_CHECK_MSG(cbRead == 0, ("post-EOF read cbRead=%RU32\n", cbRead)); + RTTESTI_CHECK_MSG(ProviderCtx.cReads == cProviderReadsBefore, + ("post-EOF cReads=%RU32, expected %RU32\n", ProviderCtx.cReads, cProviderReadsBefore)); + RTTESTI_CHECK_MSG(pbBuffer[0] == TST_WIN_STREAM_BUFFER_FILL, + ("post-EOF read modified the buffer\n")); + } + + if (pStream) + pStream->Release(); + if (pParent) + pParent->Release(); + if (pTransfer) + RTTESTI_CHECK_RC_OK(ShClTransferDestroy(pTransfer)); + RTMemFree(pbBuffer); + RTMemFree(ProviderCtx.pbData); + if (hRand != NIL_RTRAND) + RTTESTI_CHECK_RC_OK(RTRandAdvDestroy(hRand)); +} + + +/** Testcase entry point. */ +int main(void) +{ + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardWinStream", &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + tstWinPathSanitizeFilename(); + tstTransferListEntryNameValidation(); + tstWinStreamReadsAcrossChunkBoundaries(); + + return RTTestSummaryAndDestroy(hTest); +} diff --git a/src/VBox/HostDrivers/Support/SUPDrvA.asm b/src/VBox/HostDrivers/Support/SUPDrvA.asm index 5cc107d0fbbe..7e1f4bc0decd 100644 --- a/src/VBox/HostDrivers/Support/SUPDrvA.asm +++ b/src/VBox/HostDrivers/Support/SUPDrvA.asm @@ -1,4 +1,4 @@ -; $Id: SUPDrvA.asm 114843 2026-08-03 12:18:40Z knut.osmundsen@oracle.com $ +; $Id: SUPDrvA.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Driver - Assembly bits. ; @@ -53,3 +53,4 @@ BEGINPROC_EXPORTED SUPR0DispatchHostNmi ret ENDPROC SUPR0DispatchHostNmi +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm b/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm index 5e070e476a39..680f384b67bd 100644 --- a/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm +++ b/src/VBox/HostDrivers/Support/SUPDrvTracerA.asm @@ -1,4 +1,4 @@ -; $Id: SUPDrvTracerA.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: SUPDrvTracerA.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Driver - Tracer Interface, Assembly bits. ; @@ -58,3 +58,4 @@ BEGINPROC_EXPORTED SUPR0TracerFireProbe jmp xAX ENDPROC SUPR0TracerFireProbe +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: SUPR0TracerFireProbe makes an indirect call. diff --git a/src/VBox/HostDrivers/Support/SUPLibTracerA.asm b/src/VBox/HostDrivers/Support/SUPLibTracerA.asm index e495409b57d2..ca100f8c256e 100644 --- a/src/VBox/HostDrivers/Support/SUPLibTracerA.asm +++ b/src/VBox/HostDrivers/Support/SUPLibTracerA.asm @@ -1,4 +1,4 @@ -; $Id: SUPLibTracerA.asm 114517 2026-06-25 07:52:48Z andreas.loeffler@oracle.com $ +; $Id: SUPLibTracerA.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Library - Tracer Interface, Assembly bits. ; @@ -230,3 +230,4 @@ BEGINPROC_EXPORTED SUPTracerFireProbe ret ENDPROC SUPTracerFireProbe +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm index c7a3fa2a7d5a..10c4033f9d25 100644 --- a/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm +++ b/src/VBox/HostDrivers/Support/win/SUPDrvA-win.asm @@ -1,4 +1,4 @@ -; $Id: SUPDrvA-win.asm 114348 2026-06-12 15:03:49Z knut.osmundsen@oracle.com $ +; $Id: SUPDrvA-win.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Driver - Windows NT specific assembly parts. ; @@ -55,6 +55,7 @@ BEGINPROC supdrvNtQueryVirtualMemory_Xxx GLOBALNAME supdrvNtQueryVirtualMemory_ %+ %1 mov eax, %1 jmp supdrvNtQueryVirtualMemory_Jump + int3 %endm NtQueryVirtualMemorySyscall 0xAF NtQueryVirtualMemorySyscall 0xB0 @@ -93,6 +94,7 @@ BEGINPROC supdrvNtQueryVirtualMemory_Xxx GLOBALNAME supdrvNtQueryVirtualMemory_ %+ %1 mov eax, %1 jmp supdrvNtQueryVirtualMemory_Jump + int3 %endm NtQueryVirtualMemorySyscall 0x1F @@ -117,3 +119,4 @@ ENDPROC supdrvNtQueryVirtualMemory_Xxx %endif ; VBOX_WITH_HARDENING +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: there indirect calls here... diff --git a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm index 99c2ca1c77b9..4bf1a4ee86de 100644 --- a/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm +++ b/src/VBox/HostDrivers/Support/win/SUPR3HardenedMainA-win.asm @@ -1,4 +1,4 @@ -; $Id: SUPR3HardenedMainA-win.asm 114350 2026-06-12 17:02:31Z knut.osmundsen@oracle.com $ +; $Id: SUPR3HardenedMainA-win.asm 115013 2026-08-12 23:38:23Z knut.osmundsen@oracle.com $ ;; @file ; VirtualBox Support Library - Hardened main(), Windows assembly bits. ; @@ -386,6 +386,7 @@ BEGINPROC HardenedSyscallHashStack ; Cleanup the call and return. add rsp, MY_STACK_FRAME ret + int3 .alt_stack: ; Check up to the next page. @@ -494,6 +495,7 @@ BEGINPROC HardenedSyscallHashStackPostCheck mov r13, [rsp + 24 + 8] mov r14, [rsp + 32 + 8] ret + int3 .stack_check_failed: mov rbp, 99h @@ -535,6 +537,7 @@ BEGINCODE global SUPHNTIMP_STDCALL_NAME(%1, %2) SUPHNTIMP_STDCALL_NAME(%1, %2): jmp RTCCPTR_PRE [RT_WRT_RIP(NAME(g_pfn %+ %1))] + int3 %if %3 ; @@ -569,6 +572,7 @@ BEGINPROC %1 %+ _SyscallType2 ; Introduced with build 10525 call NAME(HardenedSyscallHashStackPostCheck) %endif ret + int3 .int_alternative: int 2eh %if %5 @@ -632,3 +636,4 @@ section .rwxpg bss execute read write align=4096 GLOBALNAME g_abSupHardReadWriteExecPage resb 4096 +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: There are of course indirect calls/jmps here. diff --git a/src/VBox/HostServices/SharedClipboard/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/Makefile.kmk index 10816e9e919b..a27f8de5bcad 100644 --- a/src/VBox/HostServices/SharedClipboard/Makefile.kmk +++ b/src/VBox/HostServices/SharedClipboard/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114575 2026-06-30 15:58:06Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the Shared Clipboard Host Service. # @@ -45,67 +45,25 @@ VBoxSharedClipboard_INCS.win = \ VBoxSharedClipboard_SOURCES = \ VBoxSharedClipboardSvc.cpp \ - VBoxSharedClipboardSvc-backend.cpp \ + VBoxSharedClipboardSvc-ext.cpp \ VBoxSharedClipboardSvc-client.cpp \ VBoxSharedClipboardSvc-host.cpp \ - $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ + VBoxSharedClipboardSvc-transport.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp VBoxSharedClipboard_SOURCES.win = \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ VBoxSharedClipboardSvc.rc ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - VBoxSharedClipboard_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - VBoxSharedClipboard_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp - endif VBoxSharedClipboard_SOURCES += \ VBoxSharedClipboardSvc-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp - VBoxSharedClipboard_SOURCES.win += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp endif VBoxSharedClipboard_LIBS = \ $(LIB_RUNTIME) -if1of ($(KBUILD_TARGET), linux solaris freebsd) - ifndef VBOX_HEADLESS - VBoxSharedClipboard_LIBPATH = \ - $(VBOX_LIBPATH_X11) - VBoxSharedClipboard_LIBS += \ - Xt \ - X11 - endif -endif VBoxSharedClipboard_LDFLAGS.darwin = \ - -framework ApplicationServices -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedClipboard.dylib - -if 0 ## Disabled for now; needs to be adapted to the new protocol first. - if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) - if1of ($(KBUILD_TARGET), freebsd linux netbsd openbsd solaris) - # - # Set this in LocalConfig.kmk if you are working on the X11 clipboard service - # to automatically run the unit test at build time. - # OTHERS += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run - PROGRAMS += tstClipboardX11-2 - TESTING += $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run - tstClipboardX11-2_TEMPLATE = VBoxR3TstExe - tstClipboardX11-2_DEFS = VBOX_WITH_HGCM TESTCASE - tstClipboardX11-2_SOURCES = VBoxSharedClipboardSvc-x11.cpp - tstClipboardX11-2_LIBS = $(LIB_RUNTIME) - tstClipboardX11-2_CLEAN = $(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run - - $$(tstClipboardX11-2_0_OUTDIR)/tstClipboardX11-2.run: $$(tstClipboardX11-2_1_STAGE_TARGET) - export VBOX_LOG_DEST=nofile; $(tstClipboardX11-2_1_STAGE_TARGET) quiet - $(QUIET)$(APPEND) -t "$@" "done" - endif # 1of ($(KBUILD_TARGET),freebsd linux netbsd openbsd solaris) - endif -endif + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxSharedClipboard.dylib include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp deleted file mode 100644 index ac23fb647293..000000000000 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-backend.cpp +++ /dev/null @@ -1,384 +0,0 @@ -/* $Id: VBoxSharedClipboardSvc-backend.cpp 114425 2026-06-18 08:30:00Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard Service - Backend and extension bridge handling. - */ - -/* - * Copyright (C) 2006-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD -#include -#include /* must be included before hgcmsvc.h */ - -#include -#include -#include - -#include -#include -#include - -#include "VBoxSharedClipboardSvc-internal.h" - - -/********************************************************************************************************************************* -* Internal Functions * -*********************************************************************************************************************************/ -static int shClSvcBackendHostCallback(uint32_t u32Function, PSHCLEXTPARMS pvParms, uint32_t cbParms); -static DECLCALLBACK(int) shClSvcBackendExtensionCallback(uint32_t u32Function, uint32_t u32Format, - void *pvData, uint32_t cbData); - - -/** - * Returns the Shared Clipboard backend in use. - * - * @returns Pointer to backend instance. - */ -PSHCLBACKEND ShClSvcGetBackend(void) -{ - return &g_ShClBackend; -} - - -/** - * Getter for headless setting. Also needed by testcase. - * - * @returns Whether service currently running in headless mode or not. - */ -bool ShClSvcGetHeadless(void) -{ - return g_fHeadless; -} - - -static int shClSvcBackendHostCallback(uint32_t u32Function, PSHCLEXTPARMS pvParms, uint32_t cbParms) -{ - LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); - - int rc; - if (g_ExtState.pfnExtension) - rc = g_ExtState.pfnExtension(g_ExtState.pvExtension, u32Function, pvParms, cbParms); - else - rc = VERR_NOT_SUPPORTED; - - LogFlowFunc(("Returning rc=%Rrc\n", rc)); - return rc; -} - - -int shClSvcBackendInit(VBOXHGCMSVCFNTABLE *pTable) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); - parms.u.ReadWriteData.pTable = pTable; - - /* The backend in Main calls: ShClBackendInit(ShClSvcGetBackend(), pTable); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT, &parms, sizeof(parms)); -} - - -int shClSvcBackendConnect(PSHCLCLIENT pClient) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); - parms.u.ReadWriteData.pClient = pClient; - parms.u.ReadWriteData.fHeadless = ShClSvcGetHeadless(); - - /* The backend in Main calls: ShClBackendConnect(&g_ShClBackend, pClient, ShClSvcGetHeadless())); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT, &parms, sizeof(parms)); -} - - -int shClSvcBackendSync(PSHCLCLIENT pClient) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); - parms.u.ReadWriteData.pClient = pClient; - - /* The backend in Main calls: ShClBackendSync(&g_ShClBackend, pClient); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC, &parms, sizeof(parms)); -} - - -void shClSvcBackendDisconnect(PSHCLCLIENT pClient) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pClient = pClient; - - /* The backend in Main calls: ShClBackendDisconnect(&g_ShClBackend, pClient); */ - shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT, &parms, sizeof(parms)); -} - - -void shClSvcBackendDestroy(void) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.pBackend = ShClSvcGetBackend(); - - /* The backend in Main calls: ShClBackendDestroy(ShClSvcGetBackend()); */ - shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY, &parms, sizeof(parms)); -} - - -/** - * Reports clipboard formats to the guest. - * - * @note Host backend callers must check if it's active (use - * ShClSvcIsBackendActive) before calling to prevent mixing up the - * VRDE clipboard. - * - * @returns VBox status code. - * @param pClient Client to report clipboard formats to. - * @param fFormats The formats to report (VBOX_SHCL_FMT_XXX), zero - * is okay (empty the clipboard). - * @param enmSource Source the reported formats came from. - * - * @thread Backend thread. - */ -int shClSvcBackendReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource) -{ - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - LogFlowFunc(("fFormats=%#x, enmSource=%RU32\n", fFormats, enmSource)); - - /* - * Check if the service mode allows this operation and whether the guest is - * supposed to be reading from the host. Otherwise, silently ignore reporting - * formats and return VINF_SUCCESS in order to do not trigger client - * termination in shClSvcConnect(). - */ - uint32_t uMode = ShClSvcGetMode(); - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) - { /* likely */ } - else - return VINF_SUCCESS; - - fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); - - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReportFormats.uFormats = fFormats; - parms.u.ReportFormats.pClient = pClient; - parms.u.ReportFormats.enmSource = enmSource; - - /* The backend in Main calls: ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); */ - int rc = shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST, &parms, sizeof(parms)); - - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Reporting formats %#x to guest failed with %Rrc\n", fFormats, rc)); - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -int ShClSvcReportFormats(PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - return shClSvcBackendReportFormatsToGuest(pClient, fFormats, SHCLSOURCE_LOCAL); -} - - -int shClSvcBackendReportFormatsToHost(PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReportFormats.uFormats = fFormats; - parms.u.ReportFormats.pClient = pClient; - - /* The backend in Main calls: ShClBackendReportFormats(pClient->pBackend, pClient, fFormats); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, &parms, sizeof(parms)); -} - - -int shClSvcBackendReadData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.uFormat = uFormat; - parms.u.ReadWriteData.pvData = pvData; - parms.u.ReadWriteData.cbData = cbData; - parms.u.ReadWriteData.cbActual = pcbActual ? *pcbActual : 0; - parms.u.ReadWriteData.pClient = pClient; - - /* Read clipboard data from the extension. */ - int rc = shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ, &parms, sizeof(parms)); - if ( RT_SUCCESS(rc) - && pcbActual) - *pcbActual = parms.u.ReadWriteData.cbActual; - - return rc; -} - - -static int shClSvcBackendReadVrdeData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.uFormat = uFormat; - parms.u.ReadWriteData.pvData = pvData; - parms.u.ReadWriteData.cbData = cbData; - parms.u.ReadWriteData.pClient = pClient; - - /* Read clipboard data from the VRDE extension. */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE, &parms, sizeof(parms)); -} - - -int shClSvcBackendWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) -{ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.ReadWriteData.uFormat = uFormat; - parms.u.ReadWriteData.pvData = pvData; - parms.u.ReadWriteData.cbData = cbData; - parms.u.ReadWriteData.pClient = pClient; - parms.u.ReadWriteData.pCmdCtx = pCmdCtx; - - /* The backend in Main calls: ShClBackendWriteData(pClient->pBackend, pClient, pCmdCtx, fFormats, pvData, cbData); */ - return shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof(parms)); -} - - -static DECLCALLBACK(int) shClSvcBackendExtensionCallback(uint32_t u32Function, uint32_t u32Format, void *pvData, uint32_t cbData) -{ - LogFlowFunc(("u32Function=%RU32\n", u32Function)); - - int rc = VINF_SUCCESS; - - /* Figure out if the client in charge for the service extension still is connected. */ - ClipboardClientMap::const_iterator itClient = g_mapClients.find(g_ExtState.uClientID); - if (itClient != g_mapClients.end()) - { - PSHCLCLIENT pClient = itClient->second; - AssertPtr(pClient); - switch (u32Function) - { - /* The service extension announces formats to the host. */ - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - { - LogFlowFunc(("VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: g_ExtState.fReadingData=%RTbool\n", - g_ExtState.fReadingData)); - if (!g_ExtState.fReadingData) - rc = shClSvcBackendReportFormatsToGuest(pClient, u32Format, SHCLSOURCE_REMOTE); - else - { - g_ExtState.fDelayedAnnouncement = true; - g_ExtState.fDelayedFormats = u32Format; - rc = VINF_SUCCESS; - } - break; - } - - /* The service extension wants read data from the guest. */ - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - { - rc = shClSvcBackendReadVrdeData(pClient, u32Format, pvData, cbData); - break; - } - - default: - /* Just skip other messages. */ - break; - } - } - else - rc = VERR_NOT_FOUND; - - LogFlowFuncLeaveRC(rc); - return rc; -} - - -DECLCALLBACK(int) shClSvcRegisterExtension(void *, PFNHGCMSVCEXT pfnExtension, void *pvExtension) -{ - LogFlowFunc(("pfnExtension=%p\n", pfnExtension)); - - SHCLEXTPARMS parms; - RT_ZERO(parms); - - /* - * Reference counting for service extension registration is done a few - * layers up (in ConsoleVRDPServer::ClipboardCreate()). - */ - - int rc = RTCritSectEnter(&g_CritSect); - AssertLogRelRCReturn(rc, rc); - - if (pfnExtension) - { - /* Install extension. */ - g_ExtState.pfnExtension = pfnExtension; - g_ExtState.pvExtension = pvExtension; - - parms.u.SetCallback.pfnCallback = shClSvcBackendExtensionCallback; - - rc = shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - - LogRel2(("Shared Clipboard: registered service extension\n")); - } - else - { - (void) shClSvcBackendHostCallback(VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - - /* - * When a guest VM using the Shared Clipboard shuts down Console::i_powerDown() - * will call HGCMHostUnregisterServiceExtension() and then VMMDev::hgcmShutdown() - * shortly thereafter. The former call lands here to unregister the extension and - * the latter calls shClSvcUnload() to unload the SharedClipboardSvc.so shared object - * and tear down the backend infrastructure via ShClBackendDestroy(). Unregistering - * the extension disables the host callback which means shClSvcBackendHostCallback() isn't - * able to call ShClBackendDestroy() in shClSvcUnload() so we do that here while the - * host callback is still available. - */ - shClSvcBackendDestroy(); - - /* Uninstall extension. */ - g_ExtState.pvExtension = NULL; - g_ExtState.pfnExtension = NULL; - - LogRel2(("Shared Clipboard: de-registered service extension\n")); - } - - RTCritSectLeave(&g_CritSect); - - return rc; -} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp index c34ce6210b8d..bc3c647eedfb 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-client.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-client.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Client/session and message queue handling. */ @@ -56,51 +56,373 @@ using namespace HGCM; /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ -static int shClSvcClientStateInit(PSHCLCLIENTSTATE pState, uint32_t uClientID); +static int shClSvcClientStateInit(PSHCLCLIENTSTATE pState, uint32_t uClientID, SHCLSESSIONID idSession); static int shClSvcClientStateTerm(PSHCLCLIENTSTATE pState); static void shClSvcClientStateReset(PSHCLCLIENTSTATE pState); + /** - * Checks whether a value names exactly one Shared Clipboard format. + * Allocates the next non-zero service session ID. * - * @returns true if \a uFormat is a single valid VBOX_SHCL_FMT_XXX bit, false otherwise. - * @param uFormat Format value to validate. + * @returns Session ID. */ -static bool shClSvcClientIsValidFormat(SHCLFORMAT uFormat) +static SHCLSESSIONID shClSvcClientAllocSessionId(void) { - return uFormat != VBOX_SHCL_FMT_NONE - && (uFormat & ~VBOX_SHCL_FMT_VALID_MASK) == 0 - && (uFormat & (uFormat - 1)) == 0; + shClSvcLock(); + + if ( g_ShClSvc.idNextSession == 0 + || g_ShClSvc.idNextSession == NIL_SHCLSESSIONID) + g_ShClSvc.idNextSession = 1; + + SHCLSESSIONID const idSession = g_ShClSvc.idNextSession++; + if ( g_ShClSvc.idNextSession == 0 + || g_ShClSvc.idNextSession == NIL_SHCLSESSIONID) + g_ShClSvc.idNextSession = 1; + + shClSvcUnlock(); + + return idSession; } +/** + * Handles clipboard formats. + * + * This suppresses file-transfer announcements until transfers are enabled and + * supported by the guest, and keeps host-to-guest transfer offers separate + * from ordinary clipboard formats. Older Windows Guest Additions with + * transfer support + * (for example 7.2.6 and 7.2.10) expect URI-list offers to be reported on + * their own so they can replace the normal clipboard announcement with an OLE + * IDataObject. + * + * @returns The new Shared Clipboard formats. + * @param fHostToGuest Reporting direction. + * \c true from host -> guest. + * \c false from guest -> host. + * @param pClient Pointer to client instance. + * @param fFormats Reported clipboard formats. + */ +SHCLFORMATS shClSvcHandleFormats(bool fHostToGuest, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + SHCLFORMATS const fFormatsOrg = fFormats; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -static SHCLSESSIONID shClSvcClientAllocSessionId(void) + if (fFormats & VBOX_SHCL_FMT_URI_LIST) + { + if (!shClSvcClientTransfersAreAllowed(pClient)) + { + uint32_t const fTransferMode = ShClSvcClientGetTransferMode(pClient); + uint64_t const fGuestFeatures = ShClSvcClientGetGuestFeatures0(pClient); + uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; + LogRelMax(16, ("Shared Clipboard: File transfer format %#x was reported by %s without enabled and negotiated transfers (mode=%#x, features0=%#RX64, required=%#RX64), masking it\n", + VBOX_SHCL_FMT_URI_LIST, fHostToGuest ? "host" : "guest", fTransferMode, + fGuestFeatures, fRequired)); + fFormats &= ~VBOX_SHCL_FMT_URI_LIST; + } + else if (fHostToGuest) + { + if (fFormats != VBOX_SHCL_FMT_URI_LIST) + LogRelMax2(16, ("Shared Clipboard: Host reported file transfer together with regular formats %#x; announcing URI-list alone for Guest Additions compatibility\n", + fFormats & ~VBOX_SHCL_FMT_URI_LIST)); + fFormats = VBOX_SHCL_FMT_URI_LIST; + } + } +#else + RT_NOREF(pClient, fHostToGuest); +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + if (LogRelIs2Enabled()) + { + char *pszFmts = ShClFormatsToStrA(fFormats); + LogRel2(("Shared Clipboard: %s reported formats %#x/'%s' to %s\n", + fHostToGuest ? "Host" : "Guest", + fFormats, pszFmts ? pszFmts : "", + fHostToGuest ? "guest" : "host")); + RTStrFree(pszFmts); + } + + if (fFormats != fFormatsOrg) + LogRelMax2(16, ("Shared Clipboard: Adjusted %s clipboard formats from %#x to %#x before reporting to %s\n", + fHostToGuest ? "host" : "guest", fFormatsOrg, fFormats, fHostToGuest ? "guest" : "host")); + + return fFormats; +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Checks whether file transfers are enabled and supported by a client. + * + * Clipboard direction is deliberately not considered here and must be checked + * separately by the operation being authorized. + * + * @returns true if file transfers may be used, false otherwise. + * @param pClient Client to check. + */ +bool shClSvcClientTransfersAreAllowed(PSHCLCLIENT pClient) +{ + AssertPtrReturn(pClient, false); + + uint64_t const fRequired = VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; + return (ShClSvcClientGetTransferMode(pClient) & VBOX_SHCL_TRANSFER_MODE_F_ENABLED) + && (ShClSvcClientGetGuestFeatures0(pClient) & fRequired) == fRequired; +} +#endif + +/** + * Acquires a Shared Clipboard client's critical section. + * + * @param pClient Client to lock. + * + * Lock acquisition is an internal client-lifetime invariant. Failures are + * reported by a debug assertion rather than propagated to callers. + */ +void ShClSvcClientLock(PSHCLCLIENT pClient) +{ + int rc2 = RTCritSectEnter(&pClient->CritSect); + AssertRC(rc2); +} + +/** + * Releases a Shared Clipboard client's critical section. + * + * @param pClient Client to unlock. + * + * Lock release is an internal client-lifetime invariant. Failures are + * reported by a debug assertion rather than propagated to callers. + */ +void ShClSvcClientUnlock(PSHCLCLIENT pClient) +{ + int rc2 = RTCritSectLeave(&pClient->CritSect); + AssertRC(rc2); +} + +/** + * Allocates a new clipboard message. + * + * @returns Allocated clipboard message, or NULL on failure. + * @param pClient The client which is target of this message. + * @param idMsg The message ID (VBOX_SHCL_HOST_MSG_XXX) to use + * @param cParms The number of parameters the message takes. + */ +PSHCLCLIENTMSG ShClSvcClientMsgAlloc(PSHCLCLIENT pClient, uint32_t idMsg, uint32_t cParms) { - bool const fOwnsSvcLock = RTCritSectIsOwner(&g_CritSect); - if (!fOwnsSvcLock) + RT_NOREF(pClient); + PSHCLCLIENTMSG pMsg = (PSHCLCLIENTMSG)RTMemAllocZ(RT_UOFFSETOF_DYN(SHCLCLIENTMSG, aParms[cParms])); + if (pMsg) { - int rc = RTCritSectEnter(&g_CritSect); - AssertRCReturn(rc, 1); + uint32_t cAllocated = ASMAtomicIncU32(&pClient->cMsgAllocated); + if (cAllocated <= 4096) + { + RTListInit(&pMsg->ListEntry); + pMsg->cParms = cParms; + pMsg->idMsg = idMsg; + return pMsg; + } + AssertMsgFailed(("Too many messages allocated for client %u! (%u)\n", pClient->State.uClientID, cAllocated)); + ASMAtomicDecU32(&pClient->cMsgAllocated); + RTMemFree(pMsg); } + return NULL; +} - if ( g_idNextSession == 0 - || g_idNextSession == NIL_SHCLSESSIONID) - g_idNextSession = 1; +/** + * Frees a formerly allocated client clipboard message. + * + * @param pClient The client which was the target of this message. + * @param pMsg Clipboard message to free. + */ +void ShClSvcClientMsgFree(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + RT_NOREF(pClient); + /** @todo r=bird: Do accounting. */ + if (pMsg) + { + pMsg->idMsg = UINT32_C(0xdeadface); + RTMemFree(pMsg); - SHCLSESSIONID const idSession = g_idNextSession++; - if ( g_idNextSession == 0 - || g_idNextSession == NIL_SHCLSESSIONID) - g_idNextSession = 1; + uint32_t cAllocated = ASMAtomicDecU32(&pClient->cMsgAllocated); + Assert(cAllocated < UINT32_MAX / 2); + RT_NOREF(cAllocated); + } +} + +/** + * Sets the VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT and VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT + * return parameters. + * + * @param pMsg Message to set return parameters to. + * @param paDstParms The peek parameter vector. + * @param cDstParms The number of peek parameters (at least two). + * @remarks ASSUMES the parameters has been cleared by clientMsgPeek. + */ +void shClSvcMsgSetPeekReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) +{ + Assert(cDstParms >= 2); + if (paDstParms[0].type == VBOX_HGCM_SVC_PARM_32BIT) + paDstParms[0].u.uint32 = pMsg->idMsg; + else + paDstParms[0].u.uint64 = pMsg->idMsg; + paDstParms[1].u.uint32 = pMsg->cParms; - if (!fOwnsSvcLock) + uint32_t i = RT_MIN(cDstParms, pMsg->cParms + 2); + while (i-- > 2) + switch (pMsg->aParms[i - 2].type) + { + case VBOX_HGCM_SVC_PARM_32BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint32_t); break; + case VBOX_HGCM_SVC_PARM_64BIT: paDstParms[i].u.uint32 = ~(uint32_t)sizeof(uint64_t); break; + case VBOX_HGCM_SVC_PARM_PTR: paDstParms[i].u.uint32 = pMsg->aParms[i - 2].u.pointer.size; break; + } +} + +/** + * Sets the VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT return parameters. + * + * @returns VBox status code. + * @param pMsg The message which parameters to return to the guest. + * @param paDstParms The peek parameter vector. + * @param cDstParms The number of peek parameters should be exactly two + */ +int shClSvcMsgSetOldWaitReturn(PSHCLCLIENTMSG pMsg, PVBOXHGCMSVCPARM paDstParms, uint32_t cDstParms) +{ + /* + * Assert sanity. + */ + AssertPtr(pMsg); + AssertPtrReturn(paDstParms, VERR_INVALID_POINTER); + AssertReturn(cDstParms >= 2, VERR_INVALID_PARAMETER); + + Assert(pMsg->cParms == 2); + Assert(pMsg->aParms[0].u.uint32 == pMsg->idMsg); + switch (pMsg->idMsg) { - int rc = RTCritSectLeave(&g_CritSect); - AssertRC(rc); + case VBOX_SHCL_HOST_MSG_READ_DATA: + case VBOX_SHCL_HOST_MSG_FORMATS_REPORT: + break; + default: + AssertFailed(); } - return idSession; + /* + * Set the parameters. + */ + if (pMsg->cParms > 0) + paDstParms[0] = pMsg->aParms[0]; + if (pMsg->cParms > 1) + paDstParms[1] = pMsg->aParms[1]; + return VINF_SUCCESS; +} + + +/** + * Wakes up a pending client (i.e. waiting for new messages). + * + * @returns VBox status code. + * @retval VINF_NO_CHANGE if the client is not in pending mode. + * @param pClient Client to wake up. + * + * @note Caller must enter critical section. + */ +int ShClSvcClientWakeup(PSHCLCLIENT pClient) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + int rc = VINF_NO_CHANGE; + + if (pClient->Pending.uType != 0) + { + LogFunc(("[Client %RU32] Waking up ...\n", pClient->State.uClientID)); + + PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); + AssertReturn(pFirstMsg, VERR_INTERNAL_ERROR); + + LogFunc(("[Client %RU32] Current host message is %s (%RU32), cParms=%RU32\n", + pClient->State.uClientID, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + + if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT) + shClSvcMsgSetPeekReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); + else if (pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) /* Legacy, Guest Additions < 6.1. */ + shClSvcMsgSetOldWaitReturn(pFirstMsg, pClient->Pending.paParms, pClient->Pending.cParms); + else + AssertMsgFailedReturn(("pClient->Pending.uType=%u\n", pClient->Pending.uType), VERR_INTERNAL_ERROR_3); + + AssertPtrReturn(pClient->pHelpers, VERR_INVALID_POINTER); + rc = pClient->pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); + + if ( rc != VERR_CANCELLED + && pClient->Pending.uType == VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT) + { + RTListNodeRemove(&pFirstMsg->ListEntry); + ShClSvcClientMsgFree(pClient, pFirstMsg); + } + + pClient->Pending.hHandle = NULL; + pClient->Pending.paParms = NULL; + pClient->Pending.cParms = 0; + pClient->Pending.uType = 0; + } + else + LogFunc(("[Client %RU32] Not in pending state, skipping wakeup\n", pClient->State.uClientID)); + + return rc; } -#endif + +/** + * Appends a message to the client's queue and wake it up. + * + * @returns VBox status code, though the message is consumed regardless of what + * is returned. + * @param pClient The client to queue the message on. + * @param pMsg The message to queue. Ownership is always + * transfered to the queue. + * + * @note Caller must enter critical section. + */ +int shClSvcClientMsgAddAndWakeupClient(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + AssertPtr(pMsg); + AssertPtr(pClient); + LogFlowFunc(("idMsg=%s (%u) cParms=%u\n", ShClSvcHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms)); + + RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); + int const rc = ShClSvcClientWakeup(pClient); + if (RT_FAILURE(rc)) + { + PSHCLCLIENTMSG pQueued; + RTListForEach(&pClient->MsgQueue, pQueued, SHCLCLIENTMSG, ListEntry) + if (pQueued == pMsg) + { + RTListNodeRemove(&pQueued->ListEntry); + ShClSvcClientMsgFree(pClient, pQueued); + break; + } + } + return rc; +} + +/** + * Adds a new message to a client's message queue. + * + * @param pClient Pointer to the client data structure to add new message to. + * @param pMsg Pointer to message to add. The queue then owns the pointer. + * @param fAppend Whether to append or prepend the message to the queue. + * + * @note Caller must enter critical section. + */ +void ShClSvcClientMsgAdd(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg, bool fAppend) +{ + Assert(RTCritSectIsOwner(&pClient->CritSect)); + AssertPtr(pMsg); + + LogFlowFunc(("idMsg=%s (%RU32) cParms=%RU32 fAppend=%RTbool\n", + ShClSvcHostMsgToStr(pMsg->idMsg), pMsg->idMsg, pMsg->cParms, fAppend)); + + if (fAppend) + RTListAppend(&pClient->MsgQueue, &pMsg->ListEntry); + else + RTListPrepend(&pClient->MsgQueue, &pMsg->ListEntry); +} + + /** * Resets a client's state message queue. @@ -131,6 +453,7 @@ static void shClSvcClientMsgQueueReset(PSHCLCLIENT pClient) /** * Initializes a Shared Clipboard client. * + * @returns VBox status code. * @param pClient Client to initialize. * @param uClientID HGCM client ID to assign client to. */ @@ -138,15 +461,18 @@ int ShClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID) { AssertPtrReturn(pClient, VERR_INVALID_POINTER); + SHCLSESSIONID const idSession = shClSvcClientAllocSessionId(); + /* Assign the client ID. */ pClient->State.uClientID = uClientID; + pClient->pHelpers = g_ShClSvc.pHelpers; - /* Cache the current Shared Clipboard mode for the backend. */ - pClient->State.uMode = ShClSvcGetMode(); + /* Cache the current Shared Clipboard mode in the client protocol state. */ + ASMAtomicWriteU32(&pClient->State.uMode, ShClSvcGetMode()); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /* Cache the current Shared Clipboard transfer (file) mode for the backend. */ - pClient->State.Transfers.uTransferMode = g_fTransferMode; + /* Cache the current Shared Clipboard transfer (file) mode in the client protocol state. */ + ASMAtomicWriteU32(&pClient->State.Transfers.uTransferMode, shClSvcTransferModeGet()); #endif RTListInit(&pClient->MsgQueue); @@ -157,6 +483,11 @@ int ShClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID) LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + bool fEventSourceInitialized = false; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fTransferCtxInitialized = false; +#endif + int rc = RTCritSectInit(&pClient->CritSect); if (RT_SUCCESS(rc)) { @@ -164,23 +495,41 @@ int ShClSvcClientInit(PSHCLCLIENT pClient, uint32_t uClientID) rc = ShClEventSourceInit(&pClient->EventSrc, 0 /* ID, ignored */); if (RT_SUCCESS(rc)) { + fEventSourceInitialized = true; LogFlowFunc(("[Client %RU32] Using event source %RU32\n", uClientID, pClient->EventSrc.uID)); /* Reset the client state. */ shClSvcClientStateReset(&pClient->State); /* (Re-)initialize the client state. */ - rc = shClSvcClientStateInit(&pClient->State, uClientID); + rc = shClSvcClientStateInit(&pClient->State, uClientID, idSession); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if (RT_SUCCESS(rc)) { rc = ShClTransferCtxInit(&pClient->Transfers.Ctx); if (RT_SUCCESS(rc)) + { + fTransferCtxInitialized = true; rc = ShClTransferCtxBeginSession(&pClient->Transfers.Ctx, pClient->State.uSessionID); + } } #endif } + + if (RT_FAILURE(rc)) + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fTransferCtxInitialized) + ShClTransferCtxDestroy(&pClient->Transfers.Ctx); +#endif + if (fEventSourceInitialized) + ShClEventSourceTerm(&pClient->EventSrc); + shClSvcClientStateTerm(&pClient->State); + + int const rc2 = RTCritSectDelete(&pClient->CritSect); + AssertRC(rc2); + } } LogFlowFuncLeaveRC(rc); @@ -198,6 +547,11 @@ void shClSvcClientDestroy(PSHCLCLIENT pClient) LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); + shClSvcLock(); + if (g_ShClSvc.pActiveClient == pClient) + g_ShClSvc.pActiveClient = NULL; + shClSvcUnlock(); + /* Make sure to send a quit message to the guest so that it can terminate gracefully. */ ShClSvcClientLock(pClient); @@ -207,7 +561,7 @@ void shClSvcClientDestroy(PSHCLCLIENT pClient) HGCMSvcSetU32(&pClient->Pending.paParms[0], VBOX_SHCL_HOST_MSG_QUIT); if (pClient->Pending.cParms > 2) HGCMSvcSetU32(&pClient->Pending.paParms[1], 0); - g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); + g_ShClSvc.pHelpers->pfnCallComplete(pClient->Pending.hHandle, VINF_SUCCESS); pClient->Pending.uType = 0; pClient->Pending.cParms = 0; pClient->Pending.hHandle = NULL; @@ -215,7 +569,9 @@ void shClSvcClientDestroy(PSHCLCLIENT pClient) } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ShClSvcClientUnlock(pClient); shClSvcTransferDestroyAll(pClient); + ShClSvcClientLock(pClient); ShClTransferCtxDestroy(&pClient->Transfers.Ctx); #endif @@ -231,12 +587,6 @@ void shClSvcClientDestroy(PSHCLCLIENT pClient) int rc2 = RTCritSectDelete(&pClient->CritSect); AssertRC(rc2); - ClipboardClientMap::iterator itClient = g_mapClients.find(pClient->State.uClientID); - if (itClient != g_mapClients.end()) - g_mapClients.erase(itClient); - else - AssertFailed(); - LogFlowFuncLeave(); } @@ -250,8 +600,11 @@ void shClSvcClientReset(PSHCLCLIENT pClient) if (!pClient) return; + /* Allocate outside the client lock to preserve the service -> client lock order. */ + SHCLSESSIONID const idSession = shClSvcClientAllocSessionId(); + LogFlowFunc(("[Client %RU32]\n", pClient->State.uClientID)); - RTCritSectEnter(&pClient->CritSect); + ShClSvcClientLock(pClient); uint32_t const uClientID = pClient->State.uClientID; @@ -265,15 +618,17 @@ void shClSvcClientReset(PSHCLCLIENT pClient) RT_ZERO(pClient->Pending); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ShClSvcClientUnlock(pClient); shClSvcTransferDestroyAll(pClient); + ShClSvcClientLock(pClient); #endif shClSvcClientStateReset(&pClient->State); - int rc2 = shClSvcClientStateInit(&pClient->State, uClientID); + int rc2 = shClSvcClientStateInit(&pClient->State, uClientID, idSession); AssertRC(rc2); - pClient->State.uMode = ShClSvcGetMode(); + ASMAtomicWriteU32(&pClient->State.uMode, ShClSvcGetMode()); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - pClient->State.Transfers.uTransferMode = g_fTransferMode; + ASMAtomicWriteU32(&pClient->State.Transfers.uTransferMode, shClSvcTransferModeGet()); if (RT_SUCCESS(rc2)) { rc2 = ShClTransferCtxBeginSession(&pClient->Transfers.Ctx, pClient->State.uSessionID); @@ -281,7 +636,7 @@ void shClSvcClientReset(PSHCLCLIENT pClient) } #endif - RTCritSectLeave(&pClient->CritSect); + ShClSvcClientUnlock(pClient); } DECLCALLBACK(void) shClSvcClientCall(void *, @@ -296,14 +651,10 @@ DECLCALLBACK(void) shClSvcClientCall(void *, RT_NOREF(u32ClientID, pvClient, tsArrival); PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); - pClient->State.uMode = ShClSvcGetMode(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - pClient->State.Transfers.uTransferMode = g_fTransferMode; -#endif #ifdef LOG_ENABLED Log2Func(("u32ClientID=%RU32, fn=%RU32 (%s), cParms=%RU32, paParms=%p\n", - u32ClientID, u32Function, ShClGuestMsgToStr(u32Function), cParms, paParms)); + u32ClientID, u32Function, ShClSvcGuestMsgToStr(u32Function), cParms, paParms)); for (uint32_t i = 0; i < cParms; i++) { switch (paParms[i].type) @@ -327,7 +678,7 @@ DECLCALLBACK(void) shClSvcClientCall(void *, } } Log2Func(("Client state: fFlags=0x%x, fGuestFeatures0=0x%x, fGuestFeatures1=0x%x\n", - pClient->State.fFlags, pClient->State.fGuestFeatures0, pClient->State.fGuestFeatures1)); + pClient->State.fFlags, ShClSvcClientGetGuestFeatures0(pClient), ShClSvcClientGetGuestFeatures1(pClient))); #endif int rc; @@ -411,7 +762,7 @@ DECLCALLBACK(void) shClSvcClientCall(void *, #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS rc = ShClSvcTransferMsgClientHandler(pClient, callHandle, u32Function, cParms, paParms, tsArrival); #else - LogRel2(("Shared Clipboard: Unknown guest function: %u (%#x)\n", u32Function, u32Function)); + LogRelMax(16, ("Shared Clipboard: Unknown guest function: %u (%#x)\n", u32Function, u32Function)); rc = VERR_NOT_IMPLEMENTED; #endif break; @@ -421,7 +772,7 @@ DECLCALLBACK(void) shClSvcClientCall(void *, LogFlowFunc(("[Client %RU32] rc=%Rrc\n", pClient->State.uClientID, rc)); if (rc != VINF_HGCM_ASYNC_EXECUTE) - g_pHelpers->pfnCallComplete(callHandle, rc); + g_ShClSvc.pHelpers->pfnCallComplete(callHandle, rc); } int shClSvcClientNegotiateChunkSize(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, @@ -453,7 +804,7 @@ int shClSvcClientNegotiateChunkSize(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCal paParms[1].u.uint32 = RT_MIN(cbClientMaxChunkSize, pClient->State.cbChunkSize); /* Preferred */ } - int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + int rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); if (RT_SUCCESS(rc)) { Log(("[Client %RU32] chunk size: %#RU32, max: %#RU32\n", @@ -480,7 +831,7 @@ int shClSvcClientNegotiateChunkSize(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCal * @param paParms Array of parameters. */ int shClSvcClientReportFeatures(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, - uint32_t cParms, VBOXHGCMSVCPARM paParms[]) + uint32_t cParms, VBOXHGCMSVCPARM paParms[]) { /* * Validate the request. @@ -495,17 +846,20 @@ int shClSvcClientReportFeatures(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, /* * Do the work. */ - paParms[0].u.uint64 = g_fHostFeatures0; + paParms[0].u.uint64 = g_ShClSvc.fHostFeatures0; paParms[1].u.uint64 = 0; - int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + int rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); if (RT_SUCCESS(rc)) { - pClient->State.fGuestFeatures0 = fFeatures0; - pClient->State.fGuestFeatures1 = fFeatures1; + ShClSvcClientLock(pClient); + ASMAtomicWriteU64(&pClient->State.fGuestFeatures0, fFeatures0); + ASMAtomicWriteU64(&pClient->State.fGuestFeatures1, fFeatures1); + ShClSvcClientUnlock(pClient); + LogRel2(("Shared Clipboard: Guest reported the following features: %#RX64\n", - pClient->State.fGuestFeatures0)); /* Note: fFeatures1 not used yet. */ - if (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS) + fFeatures0)); /* Note: fFeatures1 not used yet. */ + if (fFeatures0 & VBOX_SHCL_GF_0_TRANSFERS) LogRel2(("Shared Clipboard: Guest supports file transfers\n")); } else @@ -538,9 +892,9 @@ int shClSvcClientMsgQueryFeatures(VBOXHGCMCALLHANDLE hCall, uint32_t cParms, VBO /* * Do the work. */ - paParms[0].u.uint64 = g_fHostFeatures0; + paParms[0].u.uint64 = g_ShClSvc.fHostFeatures0; paParms[1].u.uint64 = 0; - int rc = g_pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); + int rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, VINF_SUCCESS); if (RT_FAILURE(rc)) LogFunc(("pfnCallComplete -> %Rrc\n", rc)); @@ -592,7 +946,7 @@ int shClSvcClientMsgPeek(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t */ if (idRestoreCheck != 0) { - uint64_t idRestore = g_pHelpers->pfnGetVMMDevSessionId(g_pHelpers); + uint64_t idRestore = g_ShClSvc.pHelpers->pfnGetVMMDevSessionId(g_ShClSvc.pHelpers); if (idRestoreCheck != idRestore) { paParms[0].u.uint64 = idRestore; @@ -600,7 +954,7 @@ int shClSvcClientMsgPeek(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t pClient->State.uClientID, idRestoreCheck, idRestore)); return VERR_VM_RESTORED; } - Assert(!g_pHelpers->pfnIsCallRestored(hCall)); + Assert(!g_ShClSvc.pHelpers->pfnIsCallRestored(hCall)); } /* @@ -611,7 +965,7 @@ int shClSvcClientMsgPeek(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t { shClSvcMsgSetPeekReturn(pFirstMsg, paParms, cParms); LogFlowFunc(("[Client %RU32] VBOX_SHCL_GUEST_FN_MSG_PEEK_XXX -> VINF_SUCCESS (idMsg=%s (%u), cParms=%u)\n", - pClient->State.uClientID, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + pClient->State.uClientID, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); return VINF_SUCCESS; } @@ -671,11 +1025,11 @@ int shClSvcClientMsgOldGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32 if (pFirstMsg) { LogFlowFunc(("[Client %RU32] uMsg=%s (%RU32), cParms=%RU32\n", pClient->State.uClientID, - ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); rc = shClSvcMsgSetOldWaitReturn(pFirstMsg, paParms, cParms); - AssertPtr(g_pHelpers); - rc = g_pHelpers->pfnCallComplete(hCall, rc); + AssertPtr(g_ShClSvc.pHelpers); + rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, rc); if (rc != VERR_CANCELLED) { RTListNodeRemove(&pFirstMsg->ListEntry); @@ -731,6 +1085,8 @@ int shClSvcClientMsgGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t /* * Validate the request. */ + ASSERT_GUEST_MSG_RETURN(cParms >= 2, ("cParms=%u!\n", cParms), VERR_WRONG_PARAMETER_COUNT); + uint32_t const idMsgExpected = cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_32BIT ? paParms[0].u.uint32 : cParms > 0 && paParms[0].type == VBOX_HGCM_SVC_PARM_64BIT ? paParms[0].u.uint64 : UINT32_MAX; @@ -741,24 +1097,24 @@ int shClSvcClientMsgGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t PSHCLCLIENTMSG pFirstMsg = RTListGetFirst(&pClient->MsgQueue, SHCLCLIENTMSG, ListEntry); if (pFirstMsg) { - LogFlowFunc(("First message is: %s (%u), cParms=%RU32\n", ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); + LogFlowFunc(("First message is: %s (%u), cParms=%RU32\n", ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->idMsg, pFirstMsg->cParms)); ASSERT_GUEST_MSG_RETURN(pFirstMsg->idMsg == idMsgExpected || idMsgExpected == UINT32_MAX, ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", - pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, - idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms), + pFirstMsg->idMsg, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, + idMsgExpected, ShClSvcHostMsgToStr(idMsgExpected), cParms), VERR_MISMATCH); ASSERT_GUEST_MSG_RETURN(pFirstMsg->cParms == cParms, ("idMsg=%u (%s) cParms=%u, caller expected %u (%s) and %u\n", - pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, - idMsgExpected, ShClHostMsgToStr(idMsgExpected), cParms), + pFirstMsg->idMsg, ShClSvcHostMsgToStr(pFirstMsg->idMsg), pFirstMsg->cParms, + idMsgExpected, ShClSvcHostMsgToStr(idMsgExpected), cParms), VERR_WRONG_PARAMETER_COUNT); /* Check the parameter types. */ for (uint32_t i = 0; i < cParms; i++) ASSERT_GUEST_MSG_RETURN(pFirstMsg->aParms[i].type == paParms[i].type, ("param #%u: type %u, caller expected %u (idMsg=%u %s)\n", i, pFirstMsg->aParms[i].type, - paParms[i].type, pFirstMsg->idMsg, ShClHostMsgToStr(pFirstMsg->idMsg)), + paParms[i].type, pFirstMsg->idMsg, ShClSvcHostMsgToStr(pFirstMsg->idMsg)), VERR_WRONG_PARAMETER_TYPE); /* * Copy out the parameters. @@ -805,8 +1161,8 @@ int shClSvcClientMsgGet(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE hCall, uint32_t * Complete the message and remove the pending message unless the * guest raced us and cancelled this call in the meantime. */ - AssertPtr(g_pHelpers); - rc = g_pHelpers->pfnCallComplete(hCall, rc); + AssertPtr(g_ShClSvc.pHelpers); + rc = g_ShClSvc.pHelpers->pfnCallComplete(hCall, rc); LogFlowFunc(("[Client %RU32] pfnCallComplete -> %Rrc\n", pClient->State.uClientID, rc)); @@ -886,7 +1242,7 @@ int shClSvcClientMsgCancel(PSHCLCLIENT pClient, uint32_t cParms) rcComplete = pClient->Pending.cParms == 2 ? VINF_SUCCESS : VERR_TRY_AGAIN; } - g_pHelpers->pfnCallComplete(pClient->Pending.hHandle, rcComplete); + g_ShClSvc.pHelpers->pfnCallComplete(pClient->Pending.hHandle, rcComplete); pClient->Pending.hHandle = NULL; pClient->Pending.paParms = NULL; @@ -924,7 +1280,7 @@ int shClSvcClientMsgReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCM */ ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS || ( cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B - && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)), + && (ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID)), VERR_WRONG_PARAMETER_COUNT); uintptr_t iParm = 0; @@ -936,6 +1292,7 @@ int shClSvcClientMsgReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCM } ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); uint32_t fFormats = paParms[iParm].u.uint32; + ASSERT_GUEST_RETURN(ShClFormatsAreValid(fFormats), VERR_INVALID_FLAGS); iParm++; if (cParms == VBOX_SHCL_CPARMS_REPORT_FORMATS_61B) { @@ -962,15 +1319,9 @@ int shClSvcClientMsgReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCM #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS fFormats = shClSvcHandleFormats(false /* fHostToGuest */, pClient, fFormats); #endif - rc = RTCritSectEnter(&g_CritSect); - if (RT_SUCCESS(rc)) - { - rc = shClSvcBackendReportFormatsToHost(pClient, fFormats); - - RTCritSectLeave(&g_CritSect); - } - else - LogRel2(("Shared Clipboard: Unable to take internal lock while receiving guest clipboard announcement: %Rrc\n", rc)); + shClSvcLock(); + rc = shClSvcExtReportFormatsToHost(pClient, fFormats); + shClSvcUnlock(); } return rc; @@ -983,6 +1334,9 @@ int shClSvcClientMsgReportFormats(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCM * * @returns VBox status code. * @retval VINF_BUFFER_OVERFLOW if the guest supplied a smaller buffer than needed in order to read the host clipboard data. + * @retval VERR_INVALID_PARAMETER if the requested format is invalid. + * @retval VERR_ACCESS_DENIED if the guest requests file transfer data without + * having file transfers enabled and negotiated. * @param pClient Client that wants to read host clipboard data. * @param cParms Number of HGCM parameters supplied in \a paParms. * @param paParms Array of HGCM parameters. @@ -1011,7 +1365,7 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA */ ASSERT_GUEST_RETURN( cParms == VBOX_SHCL_CPARMS_DATA_READ || ( cParms == VBOX_SHCL_CPARMS_DATA_READ_61B - && (pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)), + && (ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID)), VERR_WRONG_PARAMETER_COUNT); uintptr_t iParm = 0; @@ -1054,12 +1408,29 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA } Assert(iParm == cParms); + if (!ShClFormatIsValid(uFormat)) + { + LogRelMax(16, ("Shared Clipboard: Rejecting host clipboard data request with invalid format %#x\n", uFormat)); + return VERR_INVALID_PARAMETER; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if ( uFormat == VBOX_SHCL_FMT_URI_LIST + && !shClSvcClientTransfersAreAllowed(pClient)) +#else + if (uFormat == VBOX_SHCL_FMT_URI_LIST) +#endif + { + LogRelMax(16, ("Shared Clipboard: Rejecting guest file transfer data request without enabled and negotiated transfers\n")); + return VERR_ACCESS_DENIED; + } + /* * For some reason we need to do this (makes absolutely no sense to bird). */ /** @todo r=bird: I really don't get why you need the State.POD.uFormat * member. I'm sure there is a reason. Incomplete code? */ - if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + if (!(ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID)) { if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE) pClient->State.POD.uFormat = uFormat; @@ -1078,30 +1449,17 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA */ uint32_t cbActual = 0; - int rc = RTCritSectEnter(&g_CritSect); - AssertRCReturn(rc, rc); - - g_ExtState.fReadingData = true; + shClSvcLock(); - /* If there is a service extension active, try reading data from it first. */ - rc = shClSvcBackendReadData(pClient, uFormat, pvData, cbData, &cbActual); + /* Read data from Main, which selects the remote or local host provider. */ + int rc = shClSvcExtReadData(pClient, uFormat, pvData, cbData, &cbActual); - LogRel2(("Shared Clipboard: Read extension clipboard data (fDelayedAnnouncement=%RTbool, fDelayedFormats=%#x, " - "max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", g_ExtState.fDelayedAnnouncement, g_ExtState.fDelayedFormats, - cbData, cbActual, rc)); - - /* Did the extension send the clipboard formats yet? - * Otherwise, do this now. */ - if (g_ExtState.fDelayedAnnouncement) - { - int rc2 = shClSvcBackendReportFormatsToGuest(pClient, g_ExtState.fDelayedFormats, SHCLSOURCE_REMOTE); - AssertRC(rc2); - - g_ExtState.fDelayedAnnouncement = false; - g_ExtState.fDelayedFormats = 0; - } - - g_ExtState.fReadingData = false; + if (RT_FAILURE(rc)) + LogRelMax(16, ("Shared Clipboard: Reading extension clipboard data (max %RU32 bytes) failed after %RU32 bytes: %Rrc\n", + cbData, cbActual, rc)); + else + LogRel2(("Shared Clipboard: Read extension clipboard data (max %RU32 bytes), got %RU32 bytes: rc=%Rrc\n", + cbData, cbActual, rc)); if (RT_SUCCESS(rc)) { @@ -1112,11 +1470,11 @@ int shClSvcClientMsgDataRead(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPA HGCMSvcSetU32(&paParms[3], cbActual); /* If the data to return exceeds the buffer the guest supplies, tell it (and let it try again). */ - if (cbActual >= cbData) + if (cbActual > cbData) rc = VINF_BUFFER_OVERFLOW; } - RTCritSectLeave(&g_CritSect); + shClSvcUnlock(); LogFlowFuncLeaveRC(rc); return rc; @@ -1147,7 +1505,7 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP else return VERR_ACCESS_DENIED; - const bool fReportsContextID = RT_BOOL(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID); + const bool fReportsContextID = RT_BOOL(ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID); /* * Digest parameters. @@ -1189,9 +1547,9 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP ASSERT_GUEST_RETURN(paParms[iParm].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Format bit. */ uFormat = paParms[iParm].u.uint32; iParm++; - if (!shClSvcClientIsValidFormat(uFormat)) + if (!ShClFormatIsValid(uFormat)) { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); return VERR_INVALID_PARAMETER; } if (cParms == VBOX_SHCL_CPARMS_DATA_WRITE_61B) @@ -1242,7 +1600,7 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP */ /** @todo r=bird: I really don't get why you need the State.POD.uFormat * member. I'm sure there is a reason. Incomplete code? */ - if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + if (!(ShClSvcClientGetGuestFeatures0(pClient) & VBOX_SHCL_GF_0_CONTEXT_ID)) { if (pClient->State.POD.uFormat == VBOX_SHCL_FMT_NONE) pClient->State.POD.uFormat = uFormat; @@ -1259,12 +1617,11 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP /* * Write the data to the active host side clipboard. */ - int rc = RTCritSectEnter(&g_CritSect); - AssertRCReturn(rc, rc); + shClSvcLock(); - rc = shClSvcBackendWriteData(pClient, &cmdCtx, uFormat, pvData, cbData); + int const rc = shClSvcExtWriteData(pClient, &cmdCtx, uFormat, pvData, cbData); - RTCritSectLeave(&g_CritSect); + shClSvcUnlock(); LogFlowFuncLeaveRC(rc); return rc; @@ -1312,8 +1669,9 @@ int shClSvcClientMsgError(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM * @returns VBox status code. * @param pClientState Client state to initialize. * @param uClientID Client ID (HGCM) to use for this client state. + * @param idSession Service session ID to assign. */ -static int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID) +static int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClientID, SHCLSESSIONID idSession) { LogFlowFuncEnter(); @@ -1321,9 +1679,7 @@ static int shClSvcClientStateInit(PSHCLCLIENTSTATE pClientState, uint32_t uClien /* Register the client. */ pClientState->uClientID = uClientID; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - pClientState->uSessionID = shClSvcClientAllocSessionId(); -#endif + pClientState->uSessionID = idSession; return VINF_SUCCESS; } @@ -1352,8 +1708,8 @@ static void shClSvcClientStateReset(PSHCLCLIENTSTATE pState) { LogFlowFuncEnter(); - pState->fGuestFeatures0 = VBOX_SHCL_GF_NONE; - pState->fGuestFeatures1 = VBOX_SHCL_GF_NONE; + ASMAtomicWriteU64(&pState->fGuestFeatures0, VBOX_SHCL_GF_NONE); + ASMAtomicWriteU64(&pState->fGuestFeatures1, VBOX_SHCL_GF_NONE); pState->cbChunkSize = VBOX_SHCL_DEFAULT_CHUNK_SIZE; /** @todo Make this configurable. */ pState->enmSource = SHCLSOURCE_INVALID; @@ -1368,4 +1724,3 @@ static void shClSvcClientStateReset(PSHCLCLIENTSTATE pState) pState->Transfers.enmTransferDir = SHCLTRANSFERDIR_UNKNOWN; #endif } - diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp new file mode 100644 index 000000000000..0ec76130c4e5 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp @@ -0,0 +1,382 @@ +/* $Id: VBoxSharedClipboardSvc-ext.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard Service - Service extension bridge handling. + */ + +/* + * Copyright (C) 2006-2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include +#include /* must be included before hgcmsvc.h */ + +#include +#include +#include + +#include +#include + +#include "VBoxSharedClipboardSvc-internal.h" + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +static int shClSvcExtCall(uint32_t u32Function, void *pvParms, uint32_t cbParms); + + +/** + * Stores a service-client transport in a service-extension parameter block. + * + * @param pParms Parameter block to update. + * @param pClient Service-owned client represented by the transport. + */ +static void shClSvcExtSetClient(PSHCLEXTPARMS pParms, PSHCLCLIENT pClient) +{ + SHCLTRANSPORT Transport; + shClSvcCreateTransport(pClient, &Transport); + ShClSvcExtSetTransport(pParms, &Transport); +} + + +/** + * Calls the registered Main service extension. + * + * @returns VBox status code returned by the extension. + * @retval VERR_NOT_SUPPORTED if no extension is registered. + * @param u32Function VBOX_CLIPBOARD_EXT_FN_XXX function number. + * @param pvParms Function parameters. Optional when @a cbParms is zero. + * @param cbParms Size of the function parameters in bytes. + * + * @thread The caller must ensure that the extension remains registered for the + * duration of the call. + */ +static int shClSvcExtCall(uint32_t u32Function, void *pvParms, uint32_t cbParms) +{ + LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); + + int rc; + if (g_ShClSvc.ExtState.pfnExtension) + rc = g_ShClSvc.ExtState.pfnExtension(g_ShClSvc.ExtState.pvExtension, u32Function, pvParms, cbParms); + else + rc = VERR_NOT_SUPPORTED; + + LogFlowFunc(("Returning rc=%Rrc\n", rc)); + return rc; +} + + +/** + * Checks whether a Main service extension is registered. + * + * @returns true if an extension is registered, false otherwise. + */ +bool shClSvcExtIsRegistered(void) +{ + return g_ShClSvc.ExtState.pfnExtension != NULL; +} + + +/** + * Requests process-wide native backend initialization from Main. + * + * @returns VBox status code returned by Main. + */ +int shClSvcExtBackendInit(void) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT, &parms, sizeof(parms)); +} + + +/** + * Connects a service client to the native backend owned by Main. + * + * @returns VBox status code returned by Main. + * @param pClient Service client to connect. + */ +int shClSvcExtBackendConnect(PSHCLCLIENT pClient) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT, &parms, sizeof(parms)); +} + + +/** + * Synchronizes a connected service client with the native backend. + * + * @returns VBox status code returned by Main. + * @param pClient Connected service client to synchronize. + */ +int shClSvcExtBackendSync(PSHCLCLIENT pClient) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC, &parms, sizeof(parms)); +} + + +/** + * Disconnects a service client from the native backend. + * + * @param pClient Connected service client to disconnect. + * + * @note The service-extension callback is synchronous. On return, Main no + * longer retains the service transport for this client. + */ +void shClSvcExtBackendDisconnect(PSHCLCLIENT pClient) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + + shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT, &parms, sizeof(parms)); +} + + +/** + * Requests process-wide native backend destruction from Main. + */ +void shClSvcExtBackendDestroy(void) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + + shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY, &parms, sizeof(parms)); +} + + +/** + * Reports a Shared Clipboard error to Main. + * + * @returns VBox status code returned by Main. + * @param pszId Error identifier. Must remain valid for the call. + * @param pszMsg Human-readable error text. Must remain valid for the call. + * @param rcError VBox status code describing the error. + */ +int shClSvcExtReportError(char *pszId, char *pszMsg, int rcError) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + parms.u.Error.pszId = pszId; + parms.u.Error.rc = rcError; + parms.u.Error.pszMsg = pszMsg; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_ERROR, &parms, sizeof(parms)); +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Queries Main for the native callbacks to attach to a transfer. + * + * @returns VBox status code returned by Main. + * @param pClient Service client owning the transfer. + * @param pCallbacks Where to return the callback table. The table is + * cleared before calling Main. + */ +int shClSvcExtQueryTransferCallbacks(PSHCLCLIENT pClient, PSHCLTRANSFERCALLBACKS pCallbacks) +{ + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER); + + RT_ZERO(*pCallbacks); + + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.TransferCallbacks.pClient = pClient; + parms.u.TransferCallbacks.pCallbacks = pCallbacks; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS, &parms, sizeof(parms)); +} + + +/** + * Notifies Main about a transfer status reply. + * + * @returns VBox status code returned by Main. + * @param pClient Service client owning the transfer. + * @param pTransfer Transfer whose status changed. + * @param enmSource Endpoint which supplied the reply. + * @param pReply Status reply. Valid for the duration of the call. + */ +int shClSvcExtNotifyTransferStatus(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + PSHCLREPLY pReply) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.FileTransferData.pClient = pClient; + parms.u.FileTransferData.pTransfer = pTransfer; + parms.u.FileTransferData.enmShClSource = enmSource; + parms.u.FileTransferData.pReply = pReply; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, &parms, sizeof(parms)); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +/** + * Reports guest clipboard formats to Main. + * + * @returns VBox status code returned by Main. + * @param pClient Service client reporting the formats. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ +int shClSvcExtReportFormatsToHost(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.ReportFormats.uFormats = fFormats; + parms.u.ReportFormats.pClient = pClient; + parms.u.ReportFormats.enmSource = SHCLSOURCE_INVALID; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, &parms, sizeof(parms)); +} + + +/** + * Reads native clipboard data through Main. + * + * @returns VBox status code returned by Main. + * @param pClient Service client requesting the data. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. Optional if @a cbData is zero. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required size. Optional. + */ +int shClSvcExtReadData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.ReadWriteData.uFormat = uFormat; + parms.u.ReadWriteData.pvData = pvData; + parms.u.ReadWriteData.cbData = cbData; + parms.u.ReadWriteData.cbActual = pcbActual ? *pcbActual : 0; + parms.u.ReadWriteData.pClient = pClient; + + /* Read clipboard data from the extension. */ + int rc = shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_DATA_READ, &parms, sizeof(parms)); + if ( RT_SUCCESS(rc) + && pcbActual) + *pcbActual = parms.u.ReadWriteData.cbActual; + + return rc; +} + + +/** + * Writes guest clipboard data through Main. + * + * @returns VBox status code returned by Main. + * @param pClient Service client supplying the data. + * @param pCmdCtx Guest command context identifying the reply. + * @param uFormat Clipboard format of the data. + * @param pvData Data buffer. Optional if @a cbData is zero. + * @param cbData Data size in bytes. + */ +int shClSvcExtWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + SHCLEXTPARMS parms; + RT_ZERO(parms); + shClSvcExtSetClient(&parms, pClient); + parms.u.ReadWriteData.uFormat = uFormat; + parms.u.ReadWriteData.pvData = pvData; + parms.u.ReadWriteData.cbData = cbData; + parms.u.ReadWriteData.pClient = pClient; + parms.u.ReadWriteData.pCmdCtx = pCmdCtx; + + return shClSvcExtCall(VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, &parms, sizeof(parms)); +} + + +/** + * Unregisters the Main service extension, then tears down its backend. + * + * @returns VBox status code. + */ +int shClSvcExtUnregisterAndDestroy(void) +{ + shClSvcLock(); + PFNHGCMSVCEXT const pfnExtension = g_ShClSvc.ExtState.pfnExtension; + void * const pvExtension = g_ShClSvc.ExtState.pvExtension; + shClSvcUnlock(); + + if (!pfnExtension) + return VINF_SUCCESS; + + /* Console unregisters the extension before HGCM disconnects its client. */ + shClSvcExtBackendDestroy(); + + shClSvcLock(); + if ( g_ShClSvc.ExtState.pfnExtension == pfnExtension + && g_ShClSvc.ExtState.pvExtension == pvExtension) + { + g_ShClSvc.ExtState.pvExtension = NULL; + g_ShClSvc.ExtState.pfnExtension = NULL; + } + shClSvcUnlock(); + + LogRel2(("Shared Clipboard: de-registered service extension\n")); + return VINF_SUCCESS; +} + + +/** + * Registers or unregisters the Main Shared Clipboard service extension. + * + * @returns VBox status code. + * @param pvService HGCM service instance. Not used. + * @param pfnExtension Extension callback to register, or NULL to unregister. + * @param pvExtension Opaque callback argument owned by Main. + */ +DECLCALLBACK(int) shClSvcRegisterExtension(void *pvService, PFNHGCMSVCEXT pfnExtension, void *pvExtension) +{ + RT_NOREF(pvService); + LogFlowFunc(("pfnExtension=%p\n", pfnExtension)); + + if (pfnExtension) + { + shClSvcLock(); + g_ShClSvc.ExtState.pfnExtension = pfnExtension; + g_ShClSvc.ExtState.pvExtension = pvExtension; + shClSvcUnlock(); + + LogRel2(("Shared Clipboard: registered service extension\n")); + return VINF_SUCCESS; + } + + return shClSvcExtUnregisterAndDestroy(); +} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp index 511c693acbb5..fb8ff097a9ea 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-host.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-host.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host-controlled service handling. */ @@ -39,7 +39,6 @@ #include #include -#include #include "VBoxSharedClipboardSvc-internal.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS @@ -55,9 +54,20 @@ using namespace HGCM; static void shClSvcHostReset(void); +/** + * Sets the host-controlled Shared Clipboard mode. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if @a uMode is not a VBOX_SHCL_MODE_XXX value. + * @param uMode New VBOX_SHCL_MODE_XXX value. + * + * Invalid values fail closed by switching the effective mode to + * VBOX_SHCL_MODE_OFF. + */ int shClSvcHostModeSet(uint32_t uMode) { int rc = VERR_NOT_SUPPORTED; + uint32_t uModeNew = VBOX_SHCL_MODE_OFF; switch (uMode) { @@ -69,19 +79,25 @@ int shClSvcHostModeSet(uint32_t uMode) RT_FALL_THROUGH(); case VBOX_SHCL_MODE_BIDIRECTIONAL: { - g_uMode = uMode; - + uModeNew = uMode; rc = VINF_SUCCESS; break; } default: - { - g_uMode = VBOX_SHCL_MODE_OFF; break; - } } + shClSvcLock(); + ASMAtomicWriteU32(&g_ShClSvc.uMode, uModeNew); + if (g_ShClSvc.pActiveClient) + { + ShClSvcClientLock(g_ShClSvc.pActiveClient); + ASMAtomicWriteU32(&g_ShClSvc.pActiveClient->State.uMode, uModeNew); + ShClSvcClientUnlock(g_ShClSvc.pActiveClient); + } + shClSvcUnlock(); + LogFlowFuncLeaveRC(rc); return rc; } @@ -92,20 +108,30 @@ int shClSvcHostModeSet(uint32_t uMode) */ static void shClSvcHostReset(void) { - int rc = RTCritSectEnter(&g_CritSect); - AssertRC(rc); - if (RT_FAILURE(rc)) - return; + shClSvcLock(); + + PSHCLCLIENT const pClient = g_ShClSvc.pActiveClient; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RTLISTANCHOR ListDestroy; + RTListInit(&ListDestroy); + + /* Keep the client stable while claiming its transfers, but do not run + * platform callbacks or wait for transfer users under the service lock. */ + if (pClient) + shClSvcTransferDetachAll(pClient, &ListDestroy); - for (ClipboardClientMap::iterator itClient = g_mapClients.begin(); itClient != g_mapClients.end(); ++itClient) - if (itClient->second) - shClSvcClientReset(itClient->second); + shClSvcUnlock(); - g_ExtState.fReadingData = false; - g_ExtState.fDelayedAnnouncement = false; - g_ExtState.fDelayedFormats = 0; + shClSvcTransferDestroyDetachedAll(&ListDestroy); - RTCritSectLeave(&g_CritSect); + shClSvcLock(); +#endif + + if ( pClient + && g_ShClSvc.pActiveClient == pClient) + shClSvcClientReset(pClient); + + shClSvcUnlock(); } @@ -117,7 +143,7 @@ DECLCALLBACK(int) shClSvcHostCall(void *, uint32_t u32Function, uint32_t cParms, int rc = VINF_SUCCESS; LogFlowFunc(("u32Function=%RU32 (%s), cParms=%RU32, paParms=%p\n", - u32Function, ShClHostFunctionToStr(u32Function), cParms, paParms)); + u32Function, ShClSvcHostFunctionToStr(u32Function), cParms, paParms)); switch (u32Function) { @@ -137,21 +163,8 @@ DECLCALLBACK(int) shClSvcHostCall(void *, uint32_t u32Function, uint32_t cParms, } case VBOX_SHCL_HOST_FN_SET_HEADLESS: - { - if (cParms != 1) - rc = VERR_INVALID_PARAMETER; - else - { - uint32_t uHeadless; - rc = HGCMSvcGetU32(&paParms[0], &uHeadless); - if (RT_SUCCESS(rc)) - { - g_fHeadless = RT_BOOL(uHeadless); - LogRel(("Shared Clipboard: Service running in %s mode\n", g_fHeadless ? "headless" : "normal")); - } - } + rc = VERR_NOT_IMPLEMENTED; break; - } case VBOX_SHCL_HOST_FN_CANCEL: { diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h index 36ebd24ad3c0..4bb66d2e771b 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-internal.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-internal.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-internal.h 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal service instance state. */ @@ -34,6 +34,19 @@ #include #include +#include + + +/** + * State of the service extension which bridges the HGCM service to Main. + */ +typedef struct SHCLEXTSTATE +{ + /** Registered service extension entry point, or NULL. */ + PFNHGCMSVCEXT pfnExtension; + /** Opaque extension-provided data. */ + void *pvExtension; +} SHCLEXTSTATE; /** @@ -41,49 +54,39 @@ */ typedef struct SHCLSERVICE { - /** The backend instance data. Only one backend at a time is supported currently. */ - SHCLBACKEND Backend; /** HGCM service helper table. */ PVBOXHGCMSVCHELPERS pHelpers; - /** HGCM service function table. */ - VBOXHGCMSVCFNTABLE *pTable; /** Service-global critical section. */ RTCRITSECT CritSect; /** Current Shared Clipboard mode. */ uint32_t uMode; + /** Next non-zero service session ID to assign to a client. */ + SHCLSESSIONID idNextSession; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** Current Shared Clipboard file transfer mode. */ uint32_t fTransferMode; - /** Next non-zero service session ID to assign to a client. */ - SHCLSESSIONID idNextSession; #endif - /** Whether the service runs in headless mode. */ - bool fHeadless; /** Service extension state. */ SHCLEXTSTATE ExtState; - /** Connected HGCM clients keyed by client ID. */ - ClipboardClientMap mapClients; - /** Deferred clients ready to process new commands. */ - ClipboardClientQueue listClientsDeferred; + /** The one active HGCM client. This is a weak pointer owned by HGCM. */ + PSHCLCLIENT pActiveClient; /** Host feature mask (VBOX_SHCL_HF_0_XXX). */ uint64_t fHostFeatures0; SHCLSERVICE() : pHelpers(NULL) - , pTable(NULL) , uMode(VBOX_SHCL_MODE_OFF) + , idNextSession(1) #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS , fTransferMode(VBOX_SHCL_TRANSFER_MODE_F_NONE) - , idNextSession(1) #endif - , fHeadless(false) + , pActiveClient(NULL) , fHostFeatures0(VBOX_SHCL_HF_0_CONTEXT_ID #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS | VBOX_SHCL_HF_0_TRANSFERS #endif ) { - RT_ZERO(Backend); RT_ZERO(CritSect); RT_ZERO(ExtState); } @@ -94,22 +97,18 @@ typedef SHCLSERVICE *PSHCLSERVICE; /** The single Shared Clipboard HGCM host service instance. */ extern SHCLSERVICE g_ShClSvc; -/* Transitional aliases. These keep the initial instance-state patch small and - will be removed as client/control/state code is split into local units. */ -#define g_ShClBackend (g_ShClSvc.Backend) -#define g_pHelpers (g_ShClSvc.pHelpers) -#define g_pTable (g_ShClSvc.pTable) -#define g_CritSect (g_ShClSvc.CritSect) -#define g_uMode (g_ShClSvc.uMode) +/** @name Service-global locking. + * @{ */ +void shClSvcLock(void); +void shClSvcUnlock(void); +/** @} */ + #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# define g_fTransferMode (g_ShClSvc.fTransferMode) -# define g_idNextSession (g_ShClSvc.idNextSession) +/** @name Service-global transfer policy. + * @{ */ +uint32_t shClSvcTransferModeGet(void); +/** @} */ #endif -#define g_fHeadless (g_ShClSvc.fHeadless) -#define g_ExtState (g_ShClSvc.ExtState) -#define g_mapClients (g_ShClSvc.mapClients) -#define g_listClientsDeferred (g_ShClSvc.listClientsDeferred) -#define g_fHostFeatures0 (g_ShClSvc.fHostFeatures0) /** @name Host-controlled service handling. * @{ */ @@ -143,17 +142,31 @@ int shClSvcClientMsgDataWrite(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCP int shClSvcClientMsgError(PSHCLCLIENT pClient, uint32_t cParms, VBOXHGCMSVCPARM paParms[], int *pRc); /** @} */ -/** @name Backend and extension bridge handling. +/** @name Opaque service-to-Main transport. * @{ */ -int shClSvcBackendInit(VBOXHGCMSVCFNTABLE *pTable); -int shClSvcBackendConnect(PSHCLCLIENT pClient); -int shClSvcBackendSync(PSHCLCLIENT pClient); -void shClSvcBackendDisconnect(PSHCLCLIENT pClient); -void shClSvcBackendDestroy(void); -int shClSvcBackendReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource); -int shClSvcBackendReportFormatsToHost(PSHCLCLIENT pClient, SHCLFORMATS fFormats); -int shClSvcBackendReadData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); -int shClSvcBackendWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); +void shClSvcCreateTransport(PSHCLCLIENT pClient, PSHCLTRANSPORT pTransport); +/** @} */ + +/** @name Service extension bridge handling. + * @{ */ +bool shClSvcExtIsRegistered(void); +int shClSvcExtBackendInit(void); +int shClSvcExtBackendConnect(PSHCLCLIENT pClient); +int shClSvcExtBackendSync(PSHCLCLIENT pClient); +void shClSvcExtBackendDisconnect(PSHCLCLIENT pClient); +void shClSvcExtBackendDestroy(void); +/** Destroys the backend while the extension remains callable, then clears the + * matching registration. */ +int shClSvcExtUnregisterAndDestroy(void); +int shClSvcExtReportFormatsToHost(PSHCLCLIENT pClient, SHCLFORMATS fFormats); +int shClSvcExtReadData(PSHCLCLIENT pClient, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); +int shClSvcExtWriteData(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); +int shClSvcExtReportError(char *pszId, char *pszMsg, int rcError); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int shClSvcExtQueryTransferCallbacks(PSHCLCLIENT pClient, PSHCLTRANSFERCALLBACKS pCallbacks); +int shClSvcExtNotifyTransferStatus(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + PSHCLREPLY pReply); +#endif DECLCALLBACK(int) shClSvcRegisterExtension(void *pvService, PFNHGCMSVCEXT pfnExtension, void *pvExtension); /** @} */ diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp index 16688b2b37d3..38417c7d8ab4 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.cpp 114661 2026-07-08 10:39:13Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal code for transfer (list) handling. */ @@ -57,68 +57,44 @@ static int shClSvcTransferModeSet(uint32_t fMode); /** - * Looks up a transfer by service-session/transfer/generation key across all connected clients. + * Looks up a transfer by service-session/transfer/generation key in the active client. * * @returns VBox status code. * @param idSession Service session ID to look up. * @param idTransfer Transfer ID to look up. * @param uGeneration Host-private transfer generation to look up. * @param ppClient Where to return the owning client. - * @param ppTransfer Where to return the transfer. + * @param ppTransfer Where to return the retained transfer. The caller + * must release it with ShClTransferRelease(). */ static int shClSvcTransferFindByKey(SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration, PSHCLCLIENT *ppClient, PSHCLTRANSFER *ppTransfer) { AssertPtrReturn(ppClient, VERR_INVALID_POINTER); AssertPtrReturn(ppTransfer, VERR_INVALID_POINTER); - AssertReturn(idSession != 0 && idSession != NIL_SHCLSESSIONID, VERR_INVALID_CONTEXT); - AssertReturn( idTransfer != NIL_SHCLTRANSFERID - && idTransfer > 0 - && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1, VERR_INVALID_CONTEXT); - AssertReturn(uGeneration != 0 && uGeneration != NIL_SHCLTRANSFERGEN, VERR_INVALID_CONTEXT); + AssertReturn(ShClTransferKeyIsValid(idSession, idTransfer, uGeneration), VERR_INVALID_CONTEXT); *ppClient = NULL; *ppTransfer = NULL; - int rc = RTCritSectEnter(&g_CritSect); - if (RT_FAILURE(rc)) - return rc; + shClSvcLock(); - ClipboardClientMap::const_iterator itClient = g_mapClients.begin(); - while (itClient != g_mapClients.end()) + PSHCLCLIENT pClient = g_ShClSvc.pActiveClient; + if ( pClient + && pClient->State.uSessionID == idSession) { - PSHCLCLIENT pClient = itClient->second; - if ( pClient - && pClient->State.uSessionID == idSession) + PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferByKeyRetained(&pClient->Transfers.Ctx, idSession, + idTransfer, uGeneration); + if (pTransfer) { - PSHCLTRANSFER pTransfer = ShClTransferCtxGetTransferByKey(&pClient->Transfers.Ctx, idSession, - idTransfer, uGeneration); - if (pTransfer) - { - if (*ppTransfer) - { - rc = VERR_DUPLICATE; - break; - } - - *ppClient = pClient; - *ppTransfer = pTransfer; - } + *ppClient = pClient; + *ppTransfer = pTransfer; } - - ++itClient; } - int rc2 = RTCritSectLeave(&g_CritSect); - AssertRC(rc2); - if (RT_SUCCESS(rc)) - rc = rc2; - - if ( RT_SUCCESS(rc) - && !*ppTransfer) - rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; + shClSvcUnlock(); - return rc; + return *ppTransfer ? VINF_SUCCESS : VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; } @@ -149,13 +125,6 @@ static int shClSvcTransferAbortByHostKey(uint64_t uContextId, SHCLTRANSFERGEN uG ShClSvcClientLock(pClient); - pTransfer = ShClTransferCtxGetTransferByKey(&pClient->Transfers.Ctx, idSession, idTransfer, uGeneration); - if (!pTransfer) - { - ShClSvcClientUnlock(pClient); - return VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; - } - int rcState; if (enmStatus == SHCLTRANSFERSTATUS_CANCELED) rcState = ShClTransferCancel(pTransfer); @@ -166,47 +135,23 @@ static int shClSvcTransferAbortByHostKey(uint64_t uContextId, SHCLTRANSFERGEN uG if (RT_SUCCESS(rcState)) rcStatus = shClSvcTransferSendStatusAsync(pClient, pTransfer, enmStatus, rcTransfer, NULL /* ppEvent */); - int rcUnregister = ShClTransferCtxUnregisterById(&pClient->Transfers.Ctx, idTransfer); - ShClSvcClientUnlock(pClient); - int rcDestroy = ShClTransferDestroy(pTransfer); + /* Drop the lookup retain before the consuming destroy waits for all users. */ + ShClTransferRelease(pTransfer); + + /* The terminal status was already reported above. */ + ShClSvcTransferDestroyByIdEx(pClient, idTransfer, false /* fNotifyGuest */); + if (RT_SUCCESS(rc)) rc = rcState; if (RT_SUCCESS(rc)) rc = rcStatus; - if (RT_SUCCESS(rc)) - rc = rcUnregister; - if (RT_SUCCESS(rc)) - rc = rcDestroy; return rc; } -/** - * Destroys all transfers of a Shared Clipboard client. - * - * @param pClient Client to destroy transfers for. - */ -void shClSvcTransferDestroyAll(PSHCLCLIENT pClient) -{ - if (!pClient) - return; - - LogFlowFuncEnter(); - - /* Unregister and destroy all transfers. - * Also make sure to let the backend know that all transfers are getting destroyed. - * - * Note: The index always will be 0, as the transfer gets unregistered. */ - PSHCLTRANSFER pTransfer; - while ((pTransfer = ShClTransferCtxGetTransferByIndex(&pClient->Transfers.Ctx, 0 /* Index */))) - ShClSvcTransferDestroy(pClient, pTransfer); -} - - - /********************************************************************************************************************************* * HGCM getters / setters * *********************************************************************************************************************************/ @@ -288,7 +233,7 @@ static bool shClSvcTransferMsgIsAllowed(uint32_t uMode, uint32_t uMsg) break; } - LogFlowFunc(("uMsg=%RU32 (%s), uMode=%RU32 -> fAllowed=%RTbool\n", uMsg, ShClGuestMsgToStr(uMsg), uMode, fAllowed)); + LogFlowFunc(("uMsg=%RU32 (%s), uMode=%RU32 -> fAllowed=%RTbool\n", uMsg, ShClSvcGuestMsgToStr(uMsg), uMode, fAllowed)); return fAllowed; } @@ -326,6 +271,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU32(&aParms[idxParm], &pReply->u.TransferStatus.uStatus); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("uTransferStatus=%RU32 (%s)\n", pReply->u.TransferStatus.uStatus, ShClTransferStatusToStr(pReply->u.TransferStatus.uStatus))); @@ -336,6 +283,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ListOpen.uHandle); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("hListOpen=%RU64\n", pReply->u.ListOpen.uHandle)); break; @@ -345,6 +294,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ListClose.uHandle); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("hListClose=%RU64\n", pReply->u.ListClose.uHandle)); break; @@ -354,6 +305,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ObjOpen.uHandle); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("hObjOpen=%RU64\n", pReply->u.ObjOpen.uHandle)); break; @@ -363,6 +316,8 @@ static int shClSvcTransferMsgGetReply(uint32_t cParms, VBOXHGCMSVCPARM aParms[], { if (cParms > idxParm) rc = HGCMSvcGetU64(&aParms[idxParm], &pReply->u.ObjClose.uHandle); + else + rc = VERR_INVALID_PARAMETER; LogFlowFunc(("hObjClose=%RU64\n", pReply->u.ObjClose.uHandle)); break; @@ -696,15 +651,23 @@ static int shClSvcTransferGetObjDataChunk(uint32_t cParms, VBOXHGCMSVCPARM aParm * @param pTransfer Transfer to handle reply for. * @param cParms Number of function parameters supplied. * @param aParms Array function parameters supplied. + * @param fZeroContext Whether the guest supplied the special zero context ID. + * @param pfDestroyTransfer Where to return whether the caller must destroy + * the retained transfer after releasing it. */ -static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, uint32_t cParms, VBOXHGCMSVCPARM aParms[]) +static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, uint32_t cParms, + VBOXHGCMSVCPARM aParms[], bool fZeroContext, bool *pfDestroyTransfer) { + AssertPtrReturn(pfDestroyTransfer, VERR_INVALID_POINTER); + *pfDestroyTransfer = false; + LogFlowFunc(("pTransfer=%p\n", pTransfer)); - int rc; + int rc; + bool fReleaseCreatedTransfer = false; uint32_t cbReply = sizeof(SHCLREPLY); - PSHCLREPLY pReply = (PSHCLREPLY)RTMemAlloc(cbReply); + PSHCLREPLY pReply = (PSHCLREPLY)RTMemAllocZ(cbReply); if (pReply) { rc = shClSvcTransferMsgGetReply(cParms, aParms, pReply); @@ -714,12 +677,14 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra && pReply->u.TransferStatus.uStatus == SHCLTRANSFERSTATUS_REQUESTED) { /* SHCLTRANSFERSTATUS_REQUESTED is special, as it doesn't provide a transfer. */ + if (!fZeroContext) + rc = VERR_INVALID_CONTEXT; } else /* Everything else needs a valid transfer ID. */ { if (!pTransfer) { - LogRelMax2(16, ("Shared Clipboard: Guest reply did not specify a valid transfer context (reply type=%RU32)\n", + LogRelMax(16, ("Shared Clipboard: Guest reply did not specify a valid transfer context (reply type=%RU32)\n", pReply->uType)); rc = VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; } @@ -767,11 +732,22 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra * having a pending transfer around. Report back the new transfer ID to the guest then. */ if (pTransfer == NULL) /* Must not exist yet. */ { - rc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, - NIL_SHCLTRANSFERID /* Creates a new transfer ID */, - &pTransfer); + SHCLTRANSFERCALLBACKS Callbacks; + rc = shClSvcExtQueryTransferCallbacks(pClient, &Callbacks); + if (rc == VERR_NOT_SUPPORTED) + { + RT_ZERO(Callbacks); + rc = VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + rc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, + &Callbacks, + NIL_SHCLTRANSFERID /* Creates a new transfer ID */, + &pTransfer); if (RT_SUCCESS(rc)) { + fReleaseCreatedTransfer = true; + ShClSvcClientLock(pClient); rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, @@ -785,7 +761,7 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra } else { - LogRelMax2(16, ("Shared Clipboard: Guest requested host -> guest transfer, but clipboard mode %RU32 does not allow it\n", + LogRelMax(16, ("Shared Clipboard: Guest requested host -> guest transfer, but clipboard mode %RU32 does not allow it\n", uMode)); rc = VERR_INVALID_PARAMETER; } @@ -797,11 +773,11 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra { switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: /* Already done locally when creating the transfer. */ break; - case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { /* Initialize the transfer on the host side. */ rc = ShClSvcTransferInit(pClient, pTransfer); @@ -819,7 +795,7 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra { /* We only need to start for H->G transfers here. * For G->H transfers we start this as soon as the host clipboard requests data. */ - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_TO_REMOTE) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST) { /* Start the transfer on the host side. */ rc = ShClSvcTransferStart(pClient, pTransfer); @@ -865,20 +841,16 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra LogRelMax(16, ("Shared Clipboard: Guest reported error %Rrc for transfer %RU16\n", pReply->rc, pTransfer->State.uID)); - if (g_ExtState.pfnExtension) + if (shClSvcExtIsRegistered()) { - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.Error.rc = pReply->rc; - parms.u.Error.pszMsg = RTStrAPrintf2("Guest reported error %Rrc for transfer %RU16", /** @todo Make the error messages more fine-grained based on rc. */ - pReply->rc, pTransfer->State.uID); - AssertPtrBreakStmt(parms.u.Error.pszMsg, rc = VERR_NO_MEMORY); + char *pszMsg = RTStrAPrintf2("Guest reported error %Rrc for transfer %RU16", /** @todo Make the error messages more fine-grained based on rc. */ + pReply->rc, pTransfer->State.uID); + AssertPtrBreakStmt(pszMsg, rc = VERR_NO_MEMORY); - g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_ERROR, &parms, sizeof(parms)); + (void) shClSvcExtReportError(NULL, pszMsg, pReply->rc); - RTStrFree(parms.u.Error.pszMsg); - parms.u.Error.pszMsg = NULL; + RTStrFree(pszMsg); + pszMsg = NULL; } rc = ShClTransferError(pTransfer, pReply->rc); @@ -894,23 +866,10 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra } } - /* Tell the backend. */ - if (g_ExtState.pfnExtension) + /* Notify the service extension. */ + if (shClSvcExtIsRegistered()) { - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.FileTransferData.pClient = pClient; - parms.u.FileTransferData.pTransfer = pTransfer; - parms.u.FileTransferData.enmShClSource = SHCLSOURCE_REMOTE; - parms.u.FileTransferData.pReply = pReply; - - /* The Main backend calls: ShClBackendTransferHandleStatusReply(pClient->pBackend, pClient, - * pTransfer, SHCLSOURCE_REMOTE, pReply->u.TransferStatus.uStatus, - * pReply->rc); - */ - int rc2 = g_ExtState.pfnExtension(g_ExtState.pvExtension, VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, - &parms, sizeof(parms)); + int rc2 = shClSvcExtNotifyTransferStatus(pClient, pTransfer, SHCLSOURCE_REMOTE, pReply); if (RT_SUCCESS(rc)) rc = rc2; } @@ -952,12 +911,10 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra break; } - if ( ShClTransferIsAborted(pTransfer) - || ShClTransferIsComplete(pTransfer)) - { - ShClSvcTransferDestroy(pClient, pTransfer); - pTransfer = NULL; - } + if ( pTransfer + && ( ShClTransferIsAborted(pTransfer) + || ShClTransferIsComplete(pTransfer))) + *pfDestroyTransfer = true; if (pPayload) { @@ -983,6 +940,9 @@ static int shClSvcTransferMsgHandleReply(PSHCLCLIENT pClient, PSHCLTRANSFER pTra RTMemFree(pReply); } + if (fReleaseCreatedTransfer) + ShClTransferRelease(pTransfer); + LogFlowFuncLeaveRC(rc); return rc; } @@ -1007,24 +967,27 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, { RT_NOREF(callHandle, aParms, tsArrival); - LogFlowFunc(("uClient=%RU32, u32Function=%RU32 (%s), cParms=%RU32, g_ExtState.pfnExtension=%p\n", - pClient->State.uClientID, u32Function, ShClGuestMsgToStr(u32Function), cParms, g_ExtState.pfnExtension)); + LogFlowFunc(("uClient=%RU32, u32Function=%RU32 (%s), cParms=%RU32, fExtRegistered=%RTbool\n", + pClient->State.uClientID, u32Function, ShClSvcGuestMsgToStr(u32Function), cParms, + shClSvcExtIsRegistered())); + uint64_t const fGuestFeatures0 = ShClSvcClientGetGuestFeatures0(pClient); if ( u32Function > VBOX_SHCL_GUEST_FN_LAST - || !(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + || !(fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) return VERR_NOT_IMPLEMENTED; - if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS)) + if (!(fGuestFeatures0 & VBOX_SHCL_GF_0_TRANSFERS)) { - LogRelMax2(16, ("Shared Clipboard: Guest attempted file transfer message %s without negotiated transfer support (features0=%#RX64)\n", - ShClGuestMsgToStr(u32Function), pClient->State.fGuestFeatures0)); + LogRelMax(16, ("Shared Clipboard: Guest attempted file transfer message %s without negotiated transfer support (features0=%#RX64)\n", + ShClSvcGuestMsgToStr(u32Function), fGuestFeatures0)); return VERR_ACCESS_DENIED; } - if (!(g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) + uint32_t const fTransferMode = shClSvcTransferModeGet(); + if (!(fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) { - LogRelMax2(16, ("Shared Clipboard: Guest attempted file transfer message %s, but file transfers are disabled for this VM (transfer mode=%#x)\n", - ShClGuestMsgToStr(u32Function), g_fTransferMode)); + LogRelMax(16, ("Shared Clipboard: Guest attempted file transfer message %s, but file transfers are disabled for this VM (transfer mode=%#x)\n", + ShClSvcGuestMsgToStr(u32Function), fTransferMode)); return VERR_ACCESS_DENIED; } @@ -1032,8 +995,8 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, uint32_t const uMode = ShClSvcGetMode(); if (!shClSvcTransferMsgIsAllowed(uMode, u32Function)) { - LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s is not allowed in clipboard mode %RU32\n", - ShClGuestMsgToStr(u32Function), uMode)); + LogRelMax(16, ("Shared Clipboard: Guest file transfer message %s is not allowed in clipboard mode %RU32\n", + ShClSvcGuestMsgToStr(u32Function), uMode)); return VERR_ACCESS_DENIED; } @@ -1055,15 +1018,15 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, * SHCLTRANSFERSTATUS_REQUESTED without an existing transfer context. */ if (u32Function != VBOX_SHCL_GUEST_FN_REPLY) { - LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s used zero context ID; only transfer status replies may do this\n", - ShClGuestMsgToStr(u32Function))); + LogRelMax(16, ("Shared Clipboard: Guest file transfer message %s used zero context ID; only transfer status replies may do this\n", + ShClSvcGuestMsgToStr(u32Function))); return VERR_INVALID_CONTEXT; } } else if (VBOX_SHCL_CONTEXTID_GET_SESSION(uCID) != pClient->State.uSessionID) { - LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s used context %#RX64 for session %RU32, expected session %RU32\n", - ShClGuestMsgToStr(u32Function), uCID, VBOX_SHCL_CONTEXTID_GET_SESSION(uCID), pClient->State.uSessionID)); + LogRelMax(16, ("Shared Clipboard: Guest file transfer message %s used context %#RX64 for session %RU32, expected session %RU32\n", + ShClSvcGuestMsgToStr(u32Function), uCID, VBOX_SHCL_CONTEXTID_GET_SESSION(uCID), pClient->State.uSessionID)); return VERR_INVALID_CONTEXT; } @@ -1071,23 +1034,26 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, * Pre-check: For certain messages we need to make sure that a (right) transfer is present. */ const SHCLTRANSFERID idTransfer = fZeroContext ? NIL_SHCLTRANSFERID : VBOX_SHCL_CONTEXTID_GET_TRANSFER(uCID); - PSHCLTRANSFER pTransfer = fZeroContext ? NULL : ShClTransferCtxGetTransferById(&pClient->Transfers.Ctx, idTransfer); + PSHCLTRANSFER pTransfer = fZeroContext ? NULL + : ShClTransferCtxGetTransferByIdRetained(&pClient->Transfers.Ctx, + idTransfer); if ( u32Function != VBOX_SHCL_GUEST_FN_REPLY && !pTransfer) { - LogRelMax2(16, ("Shared Clipboard: Guest file transfer message %s references unknown transfer %RU16 (context=%#RX64)\n", - ShClGuestMsgToStr(u32Function), idTransfer, uCID)); + LogRelMax(16, ("Shared Clipboard: Guest file transfer message %s references unknown transfer %RU16 (context=%#RX64)\n", + ShClSvcGuestMsgToStr(u32Function), idTransfer, uCID)); return VERR_SHCLPB_TRANSFER_ID_NOT_FOUND; } rc = VERR_INVALID_PARAMETER; /* Play safe. */ + bool fDestroyTransfer = false; switch (u32Function) { case VBOX_SHCL_GUEST_FN_REPLY: { - rc = shClSvcTransferMsgHandleReply(pClient, pTransfer, cParms, aParms); + rc = shClSvcTransferMsgHandleReply(pClient, pTransfer, cParms, aParms, fZeroContext, &fDestroyTransfer); break; } @@ -1096,8 +1062,8 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (cParms != VBOX_SHCL_CPARMS_ROOT_LIST_HDR_READ) break; - ASSERT_GUEST_RETURN(aParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Features */ - ASSERT_GUEST_RETURN(aParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); /* # Entries */ + ASSERT_GUEST_STMT_BREAK(aParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Features */ + ASSERT_GUEST_STMT_BREAK(aParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* # Entries */ SHCLLISTHDR rootListHdr; RT_ZERO(rootListHdr); @@ -1145,17 +1111,17 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (cParms != VBOX_SHCL_CPARMS_ROOT_LIST_ENTRY_READ) break; - ASSERT_GUEST_RETURN(aParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Info flags */ - ASSERT_GUEST_RETURN(aParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); /* Entry index # */ - ASSERT_GUEST_RETURN(aParms[3].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Entry name */ - ASSERT_GUEST_RETURN(aParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Info size */ - ASSERT_GUEST_RETURN(aParms[5].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Info data */ + ASSERT_GUEST_STMT_BREAK(aParms[1].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Info flags */ + ASSERT_GUEST_STMT_BREAK(aParms[2].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Entry index # */ + ASSERT_GUEST_STMT_BREAK(aParms[3].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* Entry name */ + ASSERT_GUEST_STMT_BREAK(aParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Info size */ + ASSERT_GUEST_STMT_BREAK(aParms[5].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* Info data */ uint32_t fInfo; rc = HGCMSvcGetU32(&aParms[1], &fInfo); AssertRCBreak(rc); - ASSERT_GUEST_RETURN(fInfo & VBOX_SHCL_INFO_F_FSOBJINFO, VERR_WRONG_PARAMETER_TYPE); /* Validate info flags. */ + ASSERT_GUEST_STMT_BREAK(fInfo & VBOX_SHCL_INFO_F_FSOBJINFO, rc = VERR_WRONG_PARAMETER_TYPE); /* Validate info flags. */ uint64_t uIdx; rc = HGCMSvcGetU64(&aParms[2], &uIdx); @@ -1389,11 +1355,11 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, if (cParms != VBOX_SHCL_CPARMS_OBJ_READ) break; - ASSERT_GUEST_RETURN(aParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, VERR_WRONG_PARAMETER_TYPE); /* Object handle */ - ASSERT_GUEST_RETURN(aParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Bytes to read */ - ASSERT_GUEST_RETURN(aParms[3].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ - ASSERT_GUEST_RETURN(aParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, VERR_WRONG_PARAMETER_TYPE); /* Checksum data size */ - ASSERT_GUEST_RETURN(aParms[5].type == VBOX_HGCM_SVC_PARM_PTR, VERR_WRONG_PARAMETER_TYPE); /* Checksum data buffer*/ + ASSERT_GUEST_STMT_BREAK(aParms[1].type == VBOX_HGCM_SVC_PARM_64BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Object handle */ + ASSERT_GUEST_STMT_BREAK(aParms[2].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Bytes to read */ + ASSERT_GUEST_STMT_BREAK(aParms[3].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* Data buffer */ + ASSERT_GUEST_STMT_BREAK(aParms[4].type == VBOX_HGCM_SVC_PARM_32BIT, rc = VERR_WRONG_PARAMETER_TYPE); /* Checksum data size */ + ASSERT_GUEST_STMT_BREAK(aParms[5].type == VBOX_HGCM_SVC_PARM_PTR, rc = VERR_WRONG_PARAMETER_TYPE); /* Checksum data buffer*/ SHCLOBJHANDLE hObj; rc = HGCMSvcGetU64(&aParms[1], &hObj); /* Get object handle. */ @@ -1485,9 +1451,18 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, ShClSvcClientUnlock(pClient); - ShClSvcTransferDestroy(pClient, pTransfer); + fDestroyTransfer = true; } + if (pTransfer) + { + /* A consuming destroy cannot wait while this handler retains the transfer. */ + ShClTransferRelease(pTransfer); + pTransfer = NULL; + } + if (fDestroyTransfer) + ShClSvcTransferDestroyById(pClient, idTransfer); + LogFlowFunc(("[Client %RU32] Returning rc=%Rrc\n", pClient->State.uClientID, rc)); return rc; } @@ -1583,43 +1558,6 @@ int ShClSvcTransferMsgHostHandler(uint32_t u32Function, return rc; } -int shClSvcTransferHostMsgHandler(PSHCLCLIENT pClient, PSHCLCLIENTMSG pMsg) -{ - RT_NOREF(pClient); - - int rc; - - switch (pMsg->idMsg) - { - default: - rc = VINF_SUCCESS; - break; - } - - LogFlowFuncLeaveRC(rc); - return rc; -} - -/** - * Reports a transfer status to the guest. - * - * @returns VBox status code. - * @param pClient Client that owns the transfer. - * @param pTransfer Transfer to report status for. - * @param enmSts Status to report. - * @param rcTransfer Result code to report. Optional and depending on status. - * @param ppEvent Where to return the wait event on success. Optional. - * Must be released by the caller with ShClEventRelease(). - */ -int ShClSvcTransferSendStatusAsync(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS enmSts, - int rcTransfer, PSHCLEVENT *ppEvent) -{ - return shClSvcTransferSendStatusAsync(pClient, pTransfer, enmSts, rcTransfer, ppEvent); -} - - - - /** * Starts a transfer, communicating the status to the guest side. * @@ -1649,46 +1587,13 @@ int ShClSvcTransferStart(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer) } /** - * Stops (and destroys) a transfer, communicating the status to the guest side. + * Returns the current host service file transfer mode. * - * @returns VBox status code. - * @param pClient Client that owns the transfer. - * @param pTransfer Transfer to stop. The pointer will be invalid on success. - * @param fWaitForGuest Set to \c true to wait for acknowledgement from guest, or \c false to skip waiting. + * @returns File transfer mode (VBOX_SHCL_TRANSFER_MODE_F_XXX). */ -int ShClSvcTransferStop(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, bool fWaitForGuest) +uint32_t shClSvcTransferModeGet(void) { - LogRel2(("Shared Clipboard: Stopping transfer %RU16 ...\n", pTransfer->State.uID)); - - ShClSvcClientLock(pClient); - - PSHCLEVENT pEvent; - int rc = shClSvcTransferSendStatusAsync(pClient, pTransfer, - SHCLTRANSFERSTATUS_COMPLETED, VINF_SUCCESS, &pEvent); - if ( RT_SUCCESS(rc) - && fWaitForGuest) - { - LogRel2(("Shared Clipboard: Waiting for stop of transfer %RU16 on guest ...\n", pTransfer->State.uID)); - - ShClSvcClientUnlock(pClient); - - rc = ShClEventWait(pEvent, pTransfer->uTimeoutMs, NULL /* ppPayload */); - if (RT_SUCCESS(rc)) - LogRel2(("Shared Clipboard: Stopped transfer %RU16 on guest\n", pTransfer->State.uID)); - - ShClEventRelease(pEvent); - - ShClSvcClientLock(pClient); - } - - if (RT_FAILURE(rc)) - LogRelMax(16, ("Shared Clipboard: Unable to stop transfer %RU16 on guest, rc=%Rrc\n", - pTransfer->State.uID, rc)); - - ShClSvcClientUnlock(pClient); - - LogFlowFuncLeaveRC(rc); - return rc; + return ASMAtomicReadU32(&g_ShClSvc.fTransferMode); } /** @@ -1702,26 +1607,35 @@ static int shClSvcTransferModeSet(uint32_t fMode) if (fMode & ~VBOX_SHCL_TRANSFER_MODE_F_VALID_MASK) return VERR_INVALID_FLAGS; - g_fTransferMode = fMode; - - LogRel2(("Shared Clipboard: File transfers are now %s\n", - g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED ? "enabled" : "disabled")); + shClSvcLock(); + ASMAtomicWriteU32(&g_ShClSvc.fTransferMode, fMode); - /* If file transfers are being disabled, make sure to also reset (destroy) all pending transfers. */ - if (!(g_fTransferMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) + PSHCLCLIENT const pClient = g_ShClSvc.pActiveClient; + if (pClient) { - ClipboardClientMap::const_iterator itClient = g_mapClients.begin(); - while (itClient != g_mapClients.end()) - { - PSHCLCLIENT pClient = itClient->second; - AssertPtr(pClient); + ShClSvcClientLock(pClient); + ASMAtomicWriteU32(&pClient->State.Transfers.uTransferMode, fMode); + ShClSvcClientUnlock(pClient); + } + + LogRel2(("Shared Clipboard: File transfers are now %s\n", + fMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED ? "enabled" : "disabled")); - shClSvcTransferDestroyAll(pClient); + RTLISTANCHOR ListDestroy; + RTListInit(&ListDestroy); - ++itClient; - } + /* If file transfers are being disabled, detach all pending transfers from + * the active client while its weak pointer is stable. */ + if (!(fMode & VBOX_SHCL_TRANSFER_MODE_F_ENABLED)) + { + if (pClient) + shClSvcTransferDetachAll(pClient, &ListDestroy); } LogFlowFuncLeaveRC(VINF_SUCCESS); + shClSvcUnlock(); + + shClSvcTransferDestroyDetachedAll(&ListDestroy); + return VINF_SUCCESS; } diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h index 679d36950dbe..26b4ecee4675 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.h @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc-transfers.h 114423 2026-06-18 07:53:57Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc-transfers.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Internal header for transfer (list) handling. */ @@ -35,14 +35,16 @@ int ShClSvcTransferMsgClientHandler(PSHCLCLIENT pClient, VBOXHGCMCALLHANDLE call uint32_t cParms, VBOXHGCMSVCPARM paParms[], uint64_t tsArrival); int ShClSvcTransferMsgHostHandler(uint32_t u32Function, uint32_t cParms, VBOXHGCMSVCPARM paParms[]); -int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer); +/** Returns a retained transfer in @a ppTransfer; the caller must release it. */ +int ShClSvcTransferCreate(PSHCLCLIENT pClient, SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, + PSHCLTRANSFERCALLBACKS pCallbacks, SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer); void ShClSvcTransferDestroy(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); +void ShClSvcTransferDestroyById(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer); +void ShClSvcTransferDestroyByIdEx(PSHCLCLIENT pClient, SHCLTRANSFERID idTransfer, bool fNotifyGuest); +void shClSvcTransferDetachAll(PSHCLCLIENT pClient, PRTLISTANCHOR pList); +void shClSvcTransferDestroyDetachedAll(PRTLISTANCHOR pList); int ShClSvcTransferInit(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); int ShClSvcTransferStart(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); -int ShClSvcTransferStop(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, bool fWaitForGuest); -int ShClSvcTransferSendStatusAsync(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLTRANSFERSTATUS uStatus, int rcTransfer, PSHCLEVENT *ppEvent); -int ShClSvcTransferRootListReadFromGuest(PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer); void shClSvcTransferDestroyAll(PSHCLCLIENT pClient); #endif /* !VBOX_INCLUDED_SRC_SharedClipboard_VBoxSharedClipboardSvc_transfers_h */ - diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp new file mode 100644 index 000000000000..e387efe89b90 --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp @@ -0,0 +1,690 @@ +/* $Id: VBoxSharedClipboardSvc-transport.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard Service - Opaque Main transport implementation. + */ + +/* + * Copyright (C) 2019-2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include + +#include +#include +#include + +#include "VBoxSharedClipboardSvc-internal.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include "VBoxSharedClipboardSvc-transfers.h" +#endif + + +/** + * Reports native clipboard formats to a guest through the Shared Clipboard HGCM queue. + * + * @returns VBox status code. + * @param hClient Opaque clipboard client to report to. + * @param fFormats Host formats, VBOX_SHCL_FMT_XXX. + * @param pfReported Where to return the filtered formats. Optional. + */ +static DECLCALLBACK(int) shClSvcOpReportFormatsToGuest(SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + SHCLFORMATS *pfReported) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); + if (pfReported) + *pfReported = fFormats; + + uint32_t const uMode = ShClSvcClientGetMode(pClient); + if ( uMode != VBOX_SHCL_MODE_BIDIRECTIONAL + && uMode != VBOX_SHCL_MODE_HOST_TO_GUEST) + return VINF_NO_CHANGE; + + PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); + if (!pMsg) + return VERR_NO_MEMORY; + + HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + HGCMSvcSetU32(&pMsg->aParms[1], fFormats); + + ShClSvcClientLock(pClient); + int const vrc = shClSvcClientMsgAddAndWakeupClient(pClient, pMsg); + ShClSvcClientUnlock(pClient); + return vrc; +} + + +/** + * Validates and retains a pending guest-data event. + * + * @returns VBox status code. Stale replies for expired event IDs are ignored; + * in that case @a ppEvent is set to NULL and VINF_SUCCESS is returned. + * @param hClient Opaque client the data was received from. + * @param uContextId HGCM context ID identifying the pending event. + * @param uFormat Clipboard format of data received. + * @param ppEvent Where to return the retained event. Must be + * released with ShClEventRelease(). + * + * @thread Backend thread. + */ +static DECLCALLBACK(int) shClSvcOpRetainGuestDataEvent(SHCLCLIENTHANDLE hClient, uint64_t uContextId, + SHCLFORMAT uFormat, PSHCLEVENT *ppEvent) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + LogFlowFuncEnter(); + + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(ppEvent, VERR_INVALID_POINTER); + *ppEvent = NULL; + + if (!ShClFormatIsValid(uFormat)) + { + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); + return VERR_INVALID_PARAMETER; + } + + SHCLSESSIONID const idSession = VBOX_SHCL_CONTEXTID_GET_SESSION(uContextId); + SHCLEVENTSOURCEID const idEventSource = VBOX_SHCL_CONTEXTID_GET_TRANSFER(uContextId); + const SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(uContextId); + if ( idEvent == 0 + || idEvent == NIL_SHCLEVENTID) + { + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid event %#x in context ID %#RX64\n", + idEvent, uContextId)); + return VERR_WRONG_ORDER; + } + if ( idSession != pClient->State.uSessionID + || idEventSource != pClient->EventSrc.uID) + { + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data with mismatching context ID %#RX64" + " (session %#x/%#x, event source %#x/%#x)\n", + uContextId, idSession, pClient->State.uSessionID, + idEventSource, pClient->EventSrc.uID)); + return VERR_INVALID_CONTEXT; + } + + PSHCLEVENT pEvent = ShClEventSourceRetainFromId(&pClient->EventSrc, idEvent); + if (!RT_VALID_PTR(pEvent)) + { + LogRelMax(16, ("Shared Clipboard: Ignoring late guest clipboard data for expired event %#x\n", idEvent)); + return VINF_SUCCESS; + } + if (pEvent->uUser != uFormat) + { + LogRelMax(16, ("Shared Clipboard: Rejecting guest clipboard data format %#x for event %#x, expected %#x\n", + uFormat, idEvent, pEvent->uUser)); + ShClEventRelease(pEvent); + return VERR_INVALID_CONTEXT; + } + + *ppEvent = pEvent; + LogFlowFuncLeaveRC(VINF_SUCCESS); + return VINF_SUCCESS; +} + + +/** + * Signals a retained guest-data event with clipboard data received from the guest. + * + * @returns VBox status code. + * @param hClient Opaque handle of the connected client. + * @param pEvent Retained event to signal. + * @param idEvent Event ID to use for the optional payload wrapper. + * @param pvData Pointer to clipboard data received. This can be + * NULL if @a cbData is zero. + * @param cbData Size (in bytes) of clipboard data received. + * This can be zero. + * + * @thread Backend thread. + */ +static DECLCALLBACK(int) shClSvcOpSignalGuestDataEvent(SHCLCLIENTHANDLE hClient, PSHCLEVENT pEvent, + SHCLEVENTID idEvent, void *pvData, uint32_t cbData) +{ + RT_NOREF(hClient); + LogFlowFuncEnter(); + + AssertPtrReturn(pEvent, VERR_INVALID_POINTER); + if (cbData > 0) + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + + /* + * Make a copy of the data so we can attach it to the signal. + * + * Note! We still signal the waiter should we run out of memory, + * because otherwise it will be stuck waiting. + */ + int vrc = VINF_SUCCESS; + PSHCLEVENTPAYLOAD pPayload = NULL; + if (cbData > 0) + vrc = ShClPayloadCreateDupData(idEvent, pvData, cbData, &pPayload); + + /* + * Signal the event. + */ + int vrc2 = ShClEventSignalEx(pEvent, vrc, pPayload); + if (RT_FAILURE(vrc2)) + { + vrc = vrc2; + ShClPayloadDestroy(pPayload); + LogRel(("Shared Clipboard: Signalling of guest clipboard data to the host failed: %Rrc\n", vrc)); + } + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + + +/** + * Validates a guest-data reply and retains its pending event as a token. + * + * @returns VBox status code. + * @param hClient Opaque service client receiving the reply. + * @param pCmdCtx Guest command context containing the reply context ID. + * @param uFormat Clipboard format carried by the reply. + * @param phToken Where to return the retained token. On success, + * pass it exactly once to shClSvcOpGuestDataComplete() + * or shClSvcOpGuestDataCancel(). + */ +static DECLCALLBACK(int) shClSvcOpGuestDataBegin(SHCLCLIENTHANDLE hClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken) +{ + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(phToken, VERR_INVALID_POINTER); + *phToken = NULL; + PSHCLEVENT pEvent = NULL; + int vrc = shClSvcOpRetainGuestDataEvent(hClient, pCmdCtx->uContextID, uFormat, &pEvent); + if (RT_SUCCESS(vrc) && pEvent) + *phToken = (SHCLGUESTDATATOKEN)pEvent; + return vrc; +} + + +/** + * Signals and releases a retained guest-data reply token. + * + * @returns VBox status code from signalling the pending event. + * @param hClient Opaque service client owning the token. + * @param hToken Token returned by shClSvcOpGuestDataBegin(). + * @param pvData Reply data. Optional if @a cbData is zero. + * @param cbData Reply data size in bytes. + */ +static DECLCALLBACK(int) shClSvcOpGuestDataComplete(SHCLCLIENTHANDLE hClient, + SHCLGUESTDATATOKEN hToken, + void const *pvData, uint32_t cbData) +{ + PSHCLEVENT const pEvent = (PSHCLEVENT)hToken; + AssertPtrReturn(pEvent, VERR_INVALID_HANDLE); + + int const vrc = shClSvcOpSignalGuestDataEvent(hClient, pEvent, pEvent->idEvent, + (void *)pvData, cbData); + ShClEventRelease(pEvent); + return vrc; +} + + +/** + * Releases a retained guest-data reply token without signalling its event. + * + * @param hClient Opaque service client owning the token. + * @param hToken Token returned by shClSvcOpGuestDataBegin(). + */ +static DECLCALLBACK(void) shClSvcOpGuestDataCancel(SHCLCLIENTHANDLE hClient, + SHCLGUESTDATATOKEN hToken) +{ + RT_NOREF(hClient); + PSHCLEVENT const pEvent = (PSHCLEVENT)hToken; + AssertPtrReturnVoid(pEvent); + ShClEventRelease(pEvent); +} + + +/** + * Reads clipboard data from the guest, asynchronous version. + * + * @returns VBox status code. + * @param hClient Opaque client to request data from. + * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). + * @param ppEvent Where to return the event for waiting for new data on success. + * Must be released by the caller with ShClEventRelease(). Optional. + * + * @thread On X11: Called from the X11 event thread. + * @thread On Windows: Called from the Windows event thread. + * + * @note This will locally initialize a transfer if VBOX_SHCL_FMT_URI_LIST is being requested from the guest. + */ +static DECLCALLBACK(int) shClSvcOpReadDataFromGuestAsync(SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + PSHCLEVENT *ppEvent) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + + LogFlowFunc(("fFormats=%#x\n", fFormats)); + + if (ppEvent) + *ppEvent = NULL; + + SHCLFORMATS const fSupportedFormats = VBOX_SHCL_FMT_UNICODETEXT + | VBOX_SHCL_FMT_BITMAP + | VBOX_SHCL_FMT_HTML +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_FMT_URI_LIST +#endif + ; + if ( fFormats == VBOX_SHCL_FMT_NONE + || (fFormats & ~fSupportedFormats)) + { + LogRelMax(16, ("Shared Clipboard: Rejecting unsupported guest clipboard data request formats %#x\n", fFormats)); + return VERR_NOT_SUPPORTED; + } + if ( ppEvent + && (fFormats & (fFormats - 1)) != 0) + { + LogRelMax(16, ("Shared Clipboard: Rejecting multi-format guest clipboard data request %#x with single event output\n", + fFormats)); + return VERR_INVALID_PARAMETER; + } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if ( (fFormats & VBOX_SHCL_FMT_URI_LIST) + && !shClSvcClientTransfersAreAllowed(pClient)) + { + LogRelMax(16, ("Shared Clipboard: Rejecting host URI-list request without enabled and negotiated transfers\n")); + return VERR_ACCESS_DENIED; + } +#endif + + int vrc = VERR_NOT_SUPPORTED; + + /* Generate a separate message for every (valid) format we support. */ + while (fFormats) + { + /* Pick the next format to get from the mask: */ + /** @todo Make format reporting precedence configurable? */ + SHCLFORMAT fFormat; + if (fFormats & VBOX_SHCL_FMT_UNICODETEXT) + fFormat = VBOX_SHCL_FMT_UNICODETEXT; + else if (fFormats & VBOX_SHCL_FMT_BITMAP) + fFormat = VBOX_SHCL_FMT_BITMAP; + else if (fFormats & VBOX_SHCL_FMT_HTML) + fFormat = VBOX_SHCL_FMT_HTML; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (fFormats & VBOX_SHCL_FMT_URI_LIST) + fFormat = VBOX_SHCL_FMT_URI_LIST; +#endif + else + { + vrc = VERR_NOT_SUPPORTED; + break; + } + + /* Remove it from the mask. */ + fFormats &= ~fFormat; + + if (LogRelIs2Enabled()) + { + char *pszFmt = ShClFormatsToStrA(fFormat); + LogRel2(("Shared Clipboard: Requesting guest clipboard data in format %#x/'%s'\n", + fFormat, pszFmt ? pszFmt : "")); + RTStrFree(pszFmt); + } + /* + * Allocate messages, one for each format. + */ + uint64_t const fGuestFeatures0 = ShClSvcClientGetGuestFeatures0(pClient); + PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, + fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID + ? VBOX_SHCL_HOST_MSG_READ_DATA_CID : VBOX_SHCL_HOST_MSG_READ_DATA, + 2); + if (pMsg) + { + ShClSvcClientLock(pClient); + + PSHCLEVENT pEvent; + vrc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); + if (RT_SUCCESS(vrc)) + { + LogFlowFunc(("fFormats=%#x -> fFormat=%#x, idEvent=%#x\n", fFormats, fFormat, pEvent->idEvent)); + pEvent->uUser = fFormat; + + const uint64_t uCID = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, pEvent->idEvent); + + vrc = VINF_SUCCESS; + + /* Save the context ID in our legacy cruft if we have to deal with old(er) Guest Additions (< 6.1). */ + if (!(fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) + { + AssertStmt(pClient->Legacy.cCID < 4096, vrc = VERR_TOO_MUCH_DATA); + if (RT_SUCCESS(vrc)) + { + PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); + if (pCID) + { + pCID->uCID = uCID; + pCID->enmType = 0; /* Not used yet. */ + pCID->uFormat = fFormat; + RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); + pClient->Legacy.cCID++; + } + else + vrc = VERR_NO_MEMORY; + } + } + + if (RT_SUCCESS(vrc)) + { + /* + * Format the message. + */ + if (pMsg->idMsg == VBOX_SHCL_HOST_MSG_READ_DATA_CID) + HGCMSvcSetU64(&pMsg->aParms[0], uCID); + else + HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_READ_DATA); + HGCMSvcSetU32(&pMsg->aParms[1], fFormat); + + ShClSvcClientMsgAdd(pClient, pMsg, true /* fAppend */); + /* Wake up the client to let it know that there are new messages. */ + ShClSvcClientWakeup(pClient); + + /* Return event to caller. */ + if (ppEvent) + *ppEvent = pEvent; + } + + /* Remove event from list if caller did not request event handle or in case + * of failure (in this case caller should not release event). */ + if ( RT_FAILURE(vrc) + || !ppEvent) + { + ShClEventRelease(pEvent); + pEvent = NULL; + } + } + else + vrc = VERR_SHCLPB_MAX_EVENTS_REACHED; + + if (RT_FAILURE(vrc)) + ShClSvcClientMsgFree(pClient, pMsg); + + ShClSvcClientUnlock(pClient); + } + else + vrc = VERR_NO_MEMORY; + + if (RT_FAILURE(vrc)) + break; + } + + if (RT_FAILURE(vrc)) + LogRel(("Shared Clipboard: Requesting guest clipboard data failed with %Rrc\n", vrc)); + + LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * Reads clipboard data from the guest. + * + * @returns VBox status code. + * @retval VERR_SHCLPB_NO_DATA if no clipboard data is available. + * @param hClient Opaque client to request data from. + * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). + * @param ppv Where to return the allocated data read. + * Must be free'd by the caller. + * @param pcb Where to return number of bytes read. + */ +static DECLCALLBACK(int) shClSvcOpReadDataFromGuest(SHCLCLIENTHANDLE hClient, SHCLFORMAT fFormats, + void **ppv, uint32_t *pcb) +{ + AssertPtrReturn(ppv, VERR_INVALID_POINTER); + AssertPtrReturn(pcb, VERR_INVALID_POINTER); + + LogFlowFuncEnter(); + + /* Request data from the guest and wait for data to arrive. */ + PSHCLEVENT pEvent; + int vrc = shClSvcOpReadDataFromGuestAsync(hClient, fFormats, &pEvent); + if (RT_SUCCESS(vrc)) + { + PSHCLEVENTPAYLOAD pPayload; + vrc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload); + if (RT_SUCCESS(vrc)) + { + if ( pPayload + && pPayload->cbData) + { + *ppv = pPayload->pvData; + *pcb = pPayload->cbData; + + LogFlowFunc(("pv=%p, cb=%RU32\n", pPayload->pvData, pPayload->cbData)); + + pPayload->pvData = NULL; + pPayload->cbData = 0; + ShClPayloadDestroy(pPayload); + } + else + { + ShClPayloadDestroy(pPayload); + vrc = VERR_SHCLPB_NO_DATA; + } + } + + ShClEventRelease(pEvent); + } + + if ( RT_FAILURE(vrc) + && vrc != VERR_SHCLPB_NO_DATA) + LogRel(("Shared Clipboard: Reading data from guest failed with %Rrc\n", vrc)); + return vrc; +} + + +/** + * Applies transfer policy and compatibility rules to a clipboard format announcement. + * + * @returns VBox status code. + * @param hClient Opaque service client whose policy to apply. + * @param fHostToGuest Whether the formats flow from host to guest. + * @param fFormats Input formats, VBOX_SHCL_FMT_XXX. + * @param pfFiltered Where to return the filtered format mask. + */ +static DECLCALLBACK(int) shClSvcOpFilterFormats(SHCLCLIENTHANDLE hClient, bool fHostToGuest, + SHCLFORMATS fFormats, SHCLFORMATS *pfFiltered) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pfFiltered, VERR_INVALID_POINTER); + *pfFiltered = shClSvcHandleFormats(fHostToGuest, pClient, fFormats); + return VINF_SUCCESS; +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Retains a transfer selected by its context-local ID. + * + * @returns Retained transfer, or NULL if it was not found. + * @param hClient Opaque service client owning the transfer. + * @param idTransfer Transfer ID to look up. + * + * @note The caller must release a returned transfer with ShClTransferRelease(). + */ +static DECLCALLBACK(PSHCLTRANSFER) shClSvcOpTransferGetByIdRetained(SHCLCLIENTHANDLE hClient, + SHCLTRANSFERID idTransfer) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, NULL); + return ShClTransferCtxGetTransferByIdRetained(&pClient->Transfers.Ctx, idTransfer); +} + + +/** + * Retains a transfer selected by its complete generation key. + * + * @returns Retained transfer, or NULL if the key is stale or unknown. + * @param hClient Opaque service client owning the transfer. + * @param idSession Service session ID. + * @param idTransfer Transfer ID. + * @param uGeneration Transfer generation. + * + * @note The caller must release a returned transfer with ShClTransferRelease(). + */ +static DECLCALLBACK(PSHCLTRANSFER) shClSvcOpTransferGetByKeyRetained(SHCLCLIENTHANDLE hClient, + SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, NULL); + return ShClTransferCtxGetTransferByKeyRetained(&pClient->Transfers.Ctx, idSession, idTransfer, uGeneration); +} + + +/** + * Creates a retained service-owned transfer. + * + * @returns VBox status code. + * @param hClient Opaque service client which will own the transfer. + * @param enmDir Transfer direction. + * @param enmSource Transfer source. + * @param pCallbacks Callback table to copy. Optional. + * @param idTransfer Requested transfer ID, or NIL_SHCLTRANSFERID. + * @param ppTransfer Where to return the retained transfer. Optional. + */ +static DECLCALLBACK(int) shClSvcOpTransferCreate(SHCLCLIENTHANDLE hClient, SHCLTRANSFERDIR enmDir, + SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, + SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) +{ + return ShClSvcTransferCreate((PSHCLCLIENT)hClient, enmDir, enmSource, pCallbacks, idTransfer, ppTransfer); +} + + +/** + * Initializes a service-owned transfer. + * + * @returns VBox status code. + * @param hClient Opaque service client owning the transfer. + * @param pTransfer Transfer to initialize. + */ +static DECLCALLBACK(int) shClSvcOpTransferInit(SHCLCLIENTHANDLE hClient, PSHCLTRANSFER pTransfer) +{ + return ShClSvcTransferInit((PSHCLCLIENT)hClient, pTransfer); +} + + +/** + * Destroys a service-owned transfer selected by ID. + * + * @param hClient Opaque service client owning the transfer. + * @param idTransfer Transfer ID to destroy. + */ +static DECLCALLBACK(void) shClSvcOpTransferDestroyById(SHCLCLIENTHANDLE hClient, SHCLTRANSFERID idTransfer) +{ + ShClSvcTransferDestroyById((PSHCLCLIENT)hClient, idTransfer); +} + + +/** + * Destroys all transfers owned by a disconnecting service client. + * + * @param hClient Opaque service client whose transfers to destroy. + */ +static DECLCALLBACK(void) shClSvcOpTransferDestroyAll(SHCLCLIENTHANDLE hClient) +{ + shClSvcTransferDestroyAll((PSHCLCLIENT)hClient); +} + + +/** + * Initializes a provider which obtains transfer data from the guest. + * + * @returns VBox status code. + * @param hClient Opaque service client used by the provider callbacks. + * @param pProvider Provider structure to initialize. + */ +static DECLCALLBACK(int) shClSvcOpTransferProviderInitGuest(SHCLCLIENTHANDLE hClient, + PSHCLTXPROVIDER pProvider) +{ + PSHCLCLIENT const pClient = (PSHCLCLIENT)hClient; + AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pProvider, VERR_INVALID_POINTER); + + RT_ZERO(*pProvider); + pProvider->Interface.pfnRootListRead = ShClSvcTransferIfaceGHRootListRead; + pProvider->Interface.pfnListOpen = ShClSvcTransferIfaceGHListOpen; + pProvider->Interface.pfnListClose = ShClSvcTransferIfaceGHListClose; + pProvider->Interface.pfnListHdrRead = ShClSvcTransferIfaceGHListHdrRead; + pProvider->Interface.pfnListEntryRead = ShClSvcTransferIfaceGHListEntryRead; + pProvider->Interface.pfnObjOpen = ShClSvcTransferIfaceGHObjOpen; + pProvider->Interface.pfnObjClose = ShClSvcTransferIfaceGHObjClose; + pProvider->Interface.pfnObjRead = ShClSvcTransferIfaceGHObjRead; + pProvider->enmSource = SHCLSOURCE_REMOTE; + pProvider->pvUser = pClient; + pProvider->cbUser = sizeof(*pClient); + return VINF_SUCCESS; +} +#endif + + +/** The immutable operation table shared by all service-owned clients. */ +static SHCLSVCOPS const s_ShClSvcOps = +{ + sizeof(s_ShClSvcOps), + shClSvcOpFilterFormats, + shClSvcOpReportFormatsToGuest, + shClSvcOpReadDataFromGuestAsync, + shClSvcOpReadDataFromGuest, + shClSvcOpGuestDataBegin, + shClSvcOpGuestDataComplete, + shClSvcOpGuestDataCancel, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClSvcOpTransferGetByIdRetained, + shClSvcOpTransferGetByKeyRetained, + shClSvcOpTransferCreate, + shClSvcOpTransferInit, + shClSvcOpTransferDestroyById, + shClSvcOpTransferDestroyAll, + shClSvcOpTransferProviderInitGuest, +#endif +}; + + +/** + * Creates the synchronous, non-owning Main transport for a service client. + * + * @param pClient Service-owned client represented by the transport. + * @param pTransport Where to return the transport value. + * + * @note The transport is valid only while the service client remains connected. + * Callers must not retain it past the synchronous disconnect callback. + */ +void shClSvcCreateTransport(PSHCLCLIENT pClient, PSHCLTRANSPORT pTransport) +{ + AssertPtrReturnVoid(pClient); + AssertPtrReturnVoid(pTransport); + pTransport->hClient = (SHCLCLIENTHANDLE)pClient; + pTransport->pOps = &s_ShClSvcOps; +} diff --git a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp index 0578021aee23..96e819ce08a5 100644 --- a/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp +++ b/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedClipboardSvc.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxSharedClipboardSvc.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Host service entry points. */ @@ -33,9 +33,10 @@ * and VBoxService depending on the OS, with code shared between host and guest * under src/VBox/GuestHost/SharedClipboard/. * - * The service is split into a platform-independent core and platform-specific - * backends. The backends also make use of the aforementioned shared guest/host - * clipboard code, to avoid code duplication. + * The service implements the HGCM protocol and transport state. Host integration + * and the platform-specific backends live in Main (VBoxC) and are reached through + * the registered service extension. The backends also use the aforementioned + * shared guest/host clipboard code to avoid code duplication. * * @section sec_hostclip_guest_proto The guest communication protocol * @@ -68,8 +69,7 @@ * used after the host has requested data from the guest. * * - * @section sec_hostclip_backend_proto The communication protocol with the - * platform-specific backend + * @section sec_hostclip_protocol_versions Protocol versioning and context IDs * * The initial protocol implementation (called protocol v0) was very simple, * and could only handle simple data (like copied text and so on). It also @@ -274,62 +274,55 @@ SHCLSERVICE g_ShClSvc; /** - * Returns the current Shared Clipboard service mode. + * Acquires the service-global critical section. * - * @returns Current Shared Clipboard service mode. + * Lock acquisition is an internal service-lifetime invariant. Failures are + * reported by a debug assertion rather than propagated to callers. */ -uint32_t ShClSvcGetMode(void) +void shClSvcLock(void) { - return g_uMode; + int const rc = RTCritSectEnter(&g_ShClSvc.CritSect); + AssertRC(rc); } - /** - * Takes the global Shared Clipboard service lock. + * Releases the service-global critical section. * - * @returns \c true if locking was successful, or \c false if not. + * Lock release is an internal service-lifetime invariant. Failures are + * reported by a debug assertion rather than propagated to callers. */ -bool ShClSvcLock(void) +void shClSvcUnlock(void) { - return RT_SUCCESS(RTCritSectEnter(&g_CritSect)); + int const rc = RTCritSectLeave(&g_ShClSvc.CritSect); + AssertRC(rc); } + /** - * Unlocks the formerly locked global Shared Clipboard service lock. + * Returns the current Shared Clipboard service mode. + * + * @returns Current Shared Clipboard service mode. */ -void ShClSvcUnlock(void) +uint32_t ShClSvcGetMode(void) { - int rc2 = RTCritSectLeave(&g_CritSect); - AssertRC(rc2); + return ASMAtomicReadU32(&g_ShClSvc.uMode); } - -static int shClSvcInit(VBOXHGCMSVCFNTABLE *pTable) +/** + * Initializes the service-global Shared Clipboard state. + * + * @returns VBox status code. + */ +static int shClSvcInit(void) { - int rc = RTCritSectInit(&g_CritSect); + int rc = RTCritSectInit(&g_ShClSvc.CritSect); if (RT_SUCCESS(rc)) { shClSvcHostModeSet(VBOX_SHCL_MODE_OFF); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - g_idNextSession = 1; -#endif - - /* Normally we would call ShClBackendInit() here but the service extension - * has not been loaded at this early stage of the Shared Clipboard service - * bringup so thus we save the HGCM service function table now so that we can - * pass it along to ShClBackendInit() later at shClSvcConnect() time. */ - g_pTable = pTable; - - /* Clean up on failure, because 'shClSvcUnload' will not be called - * if 'shClSvcInit' returns an error. - */ - if (RT_FAILURE(rc)) - { - RTCritSectDelete(&g_CritSect); - } + g_ShClSvc.idNextSession = 1; } return rc; @@ -339,31 +332,29 @@ static DECLCALLBACK(int) shClSvcUnload(void *) { LogFlowFuncEnter(); - shClSvcBackendDestroy(); + int const rc = shClSvcExtUnregisterAndDestroy(); + AssertLogRelRC(rc); - RTCritSectDelete(&g_CritSect); + RTCritSectDelete(&g_ShClSvc.CritSect); - return VINF_SUCCESS; + return rc; } static DECLCALLBACK(int) shClSvcDisconnect(void *, uint32_t u32ClientID, void *pvClient) { LogFunc(("u32ClientID=%RU32\n", u32ClientID)); + RT_NOREF(u32ClientID); PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); - /* In order to communicate with guest service, HGCM VRDP clipboard extension - * needs to know its connection client ID. Currently, in shClSvcConnect() we always - * cache ID of the first ever connected client. When client disconnects, - * we need to forget its ID and let shClSvcConnect() pick up the next ID when a new - * connection will be requested by guest service (see #10115). */ - if (g_ExtState.uClientID == u32ClientID) - { - g_ExtState.uClientID = 0; - } + shClSvcLock(); + Assert(g_ShClSvc.pActiveClient == pClient); + if (g_ShClSvc.pActiveClient == pClient) + g_ShClSvc.pActiveClient = NULL; + shClSvcUnlock(); - shClSvcBackendDisconnect(pClient); + shClSvcExtBackendDisconnect(pClient); shClSvcClientDestroy(pClient); @@ -376,47 +367,51 @@ static DECLCALLBACK(int) shClSvcConnect(void *, uint32_t u32ClientID, void *pvCl PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pvClient); - pClient->pBackend = &g_ShClBackend; - int rc = ShClSvcClientInit(pClient, u32ClientID); if (RT_SUCCESS(rc)) { - /* Assign weak pointer to client map. */ - /** @todo r=bird: The g_mapClients is only there for looking up - * g_ExtState.uClientID (unserialized btw), so why not use store the - * pClient value directly in g_ExtState instead of the ID? It cannot - * crash any worse that racing map insertion/removal. */ - g_mapClients[u32ClientID] = pClient; /** @todo Handle OOM / collisions? */ - - rc = shClSvcBackendInit(g_pTable); + shClSvcLock(); + + if (g_ShClSvc.pActiveClient) + { + shClSvcUnlock(); + shClSvcClientDestroy(pClient); + return VERR_RESOURCE_BUSY; + } + + /* Refresh the backend policy cache and publish the client while holding + * the service lock, so that policy changes cannot miss this client. */ + ASMAtomicWriteU32(&pClient->State.uMode, ShClSvcGetMode()); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ASMAtomicWriteU32(&pClient->State.Transfers.uTransferMode, shClSvcTransferModeGet()); +#endif + g_ShClSvc.pActiveClient = pClient; + + rc = shClSvcExtBackendInit(); if (RT_SUCCESS(rc)) { - rc = shClSvcBackendConnect(pClient); + rc = shClSvcExtBackendConnect(pClient); if (RT_SUCCESS(rc)) { - rc = shClSvcBackendSync(pClient); + rc = shClSvcExtBackendSync(pClient); if (RT_SUCCESS(rc)) { - /* For now we ASSUME that the first client that connects is in charge for - communicating with the service extension. */ - /** @todo This isn't optimal, but only the guest really knows which client is in - * focus on the console. See @bugref{10115} for details. */ - if (g_ExtState.uClientID == 0) - g_ExtState.uClientID = u32ClientID; - /* The sync could return VINF_NO_CHANGE if nothing has changed on the host, but older Guest Additions didn't use RT_SUCCESS to but == VINF_SUCCESS to check for success. So just return VINF_SUCCESS here to not break older Guest Additions. */ - LogFunc(("Successfully connected client %#x%s\n", - u32ClientID, g_ExtState.uClientID == u32ClientID ? " - Use by ExtState too" : "")); + LogFunc(("Successfully connected client %#x\n", u32ClientID)); + shClSvcUnlock(); return VINF_SUCCESS; } - LogFunc(("ShClBackendSync failed: %Rrc\n", rc)); - shClSvcBackendDisconnect(pClient); + LogFunc(("Service extension BACKEND_SYNC failed: %Rrc\n", rc)); + shClSvcExtBackendDisconnect(pClient); } - LogFunc(("ShClBackendConnect failed: %Rrc\n", rc)); + LogFunc(("Service extension BACKEND_CONNECT failed: %Rrc\n", rc)); } - LogFunc(("ShClBackendInit failed: %Rrc\n", rc)); + LogFunc(("Service extension BACKEND_INIT failed: %Rrc\n", rc)); + Assert(g_ShClSvc.pActiveClient == pClient); + g_ShClSvc.pActiveClient = NULL; + shClSvcUnlock(); shClSvcClientDestroy(pClient); } @@ -529,12 +524,13 @@ static DECLCALLBACK(int) shClSvcSaveState(void *, uint32_t u32ClientID, void *pv PSHCLCLIENT pClient = (PSHCLCLIENT)pvClient; AssertPtr(pClient); - pClient->State.uMode = ShClSvcGetMode(); + ASMAtomicWriteU32(&pClient->State.uMode, ShClSvcGetMode()); /* Write Shared Clipboard saved state version. */ - pVMM->pfnSSMR3PutU32(pSSM, VBOX_SHCL_SAVED_STATE_VER_CURRENT); + int rc = pVMM->pfnSSMR3PutU32(pSSM, VBOX_SHCL_SAVED_STATE_VER_CURRENT); + AssertRCReturn(rc, rc); - int rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL); + rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /*fFlags*/, &s_aShClSSMClientState[0], NULL); AssertRCReturn(rc, rc); rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /*fFlags*/, &s_aShClSSMClientPODState[0], NULL); @@ -543,29 +539,50 @@ static DECLCALLBACK(int) shClSvcSaveState(void *, uint32_t u32ClientID, void *pv rc = pVMM->pfnSSMR3PutStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /*fFlags*/, &s_aShClSSMClientTransferState[0], NULL); AssertRCReturn(rc, rc); - /* Serialize the client's internal message queue. */ - rc = pVMM->pfnSSMR3PutU64(pSSM, pClient->cMsgAllocated); - AssertRCReturn(rc, rc); + /* Serialize a stable view of the queued messages. cMsgAllocated also + * includes messages temporarily owned by producers and consumers. */ + ShClSvcClientLock(pClient); + uint64_t cMsgs = 0; PSHCLCLIENTMSG pMsg; + RTListForEach(&pClient->MsgQueue, pMsg, SHCLCLIENTMSG, ListEntry) + cMsgs++; + + rc = pVMM->pfnSSMR3PutU64(pSSM, cMsgs); + RTListForEach(&pClient->MsgQueue, pMsg, SHCLCLIENTMSG, ListEntry) { - pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgHdr[0], NULL); - pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL); + if (RT_FAILURE(rc)) + break; - for (uint32_t iParm = 0; iParm < pMsg->cParms; iParm++) - HGCMSvcSSMR3Put(&pMsg->aParms[iParm], pSSM, pVMM); - } + rc = pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, + &s_aShClSSMClientMsgHdr[0], NULL); + if (RT_SUCCESS(rc)) + rc = pVMM->pfnSSMR3PutStructEx(pSSM, pMsg, sizeof(SHCLCLIENTMSG), 0 /*fFlags*/, + &s_aShClSSMClientMsgCtx[0], NULL); - rc = pVMM->pfnSSMR3PutU64(pSSM, pClient->Legacy.cCID); - AssertRCReturn(rc, rc); + for (uint32_t iParm = 0; iParm < pMsg->cParms && RT_SUCCESS(rc); iParm++) + rc = HGCMSvcSSMR3Put(&pMsg->aParms[iParm], pSSM, pVMM); + } + uint64_t cCID = 0; PSHCLCLIENTLEGACYCID pCID; + RTListForEach(&pClient->Legacy.lstCID, pCID, SHCLCLIENTLEGACYCID, Node) + cCID++; + + if (RT_SUCCESS(rc)) + rc = pVMM->pfnSSMR3PutU64(pSSM, cCID); + RTListForEach(&pClient->Legacy.lstCID, pCID, SHCLCLIENTLEGACYCID, Node) { - rc = pVMM->pfnSSMR3PutStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /*fFlags*/, &s_aShClSSMClientLegacyCID[0], NULL); - AssertRCReturn(rc, rc); + if (RT_FAILURE(rc)) + break; + rc = pVMM->pfnSSMR3PutStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /*fFlags*/, + &s_aShClSSMClientLegacyCID[0], NULL); } + + ShClSvcClientUnlock(pClient); + AssertRCReturn(rc, rc); #else /* UNIT_TEST */ RT_NOREF(u32ClientID, pvClient, pSSM, pVMM); #endif /* UNIT_TEST */ @@ -579,8 +596,9 @@ static int shClSvcLoadStateV0(uint32_t u32ClientID, void *pvClient, PSSMHANDLE p uint32_t uMarker; int rc = pVMM->pfnSSMR3GetU32(pSSM, &uMarker); /* Begin marker. */ - AssertRC(rc); - Assert(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */); + AssertRCReturn(rc, rc); + AssertLogRelMsgReturn(uMarker == UINT32_C(0x19200102) /* SSMR3STRUCT_BEGIN */, + ("Invalid begin marker: %#RX32\n", uMarker), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); rc = pVMM->pfnSSMR3Skip(pSSM, sizeof(uint32_t)); /* Client ID */ AssertRCReturn(rc, rc); @@ -601,7 +619,8 @@ static int shClSvcLoadStateV0(uint32_t u32ClientID, void *pvClient, PSSMHANDLE p rc = pVMM->pfnSSMR3GetU32(pSSM, &uMarker); /* End marker. */ AssertRCReturn(rc, rc); - Assert(uMarker == UINT32_C(0x19920406) /* SSMR3STRUCT_END */); + AssertLogRelMsgReturn(uMarker == UINT32_C(0x19920406) /* SSMR3STRUCT_END */, + ("Invalid end marker: %#RX32\n", uMarker), VERR_SSM_DATA_UNIT_FORMAT_CHANGED); return VINF_SUCCESS; } @@ -634,14 +653,16 @@ static DECLCALLBACK(int) shClSvcLoadState(void *, uint32_t u32ClientID, void *pv { if (lenOrVer >= VBOX_SHCL_SAVED_STATE_VER_6_1RC1) { - pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, - &s_aShClSSMClientState[0], NULL); - pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /* fFlags */, - &s_aShClSSMClientPODState[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, + &s_aShClSSMClientState[0], NULL); + AssertRCReturn(rc, rc); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.POD, sizeof(pClient->State.POD), 0 /* fFlags */, + &s_aShClSSMClientPODState[0], NULL); } else - pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, - &s_aShClSSMClientState61B1[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State, sizeof(pClient->State), 0 /* fFlags */, + &s_aShClSSMClientState61B1[0], NULL); + AssertRCReturn(rc, rc); rc = pVMM->pfnSSMR3GetStructEx(pSSM, &pClient->State.Transfers, sizeof(pClient->State.Transfers), 0 /* fFlags */, &s_aShClSSMClientTransferState[0], NULL); AssertRCReturn(rc, rc); @@ -660,8 +681,9 @@ static DECLCALLBACK(int) shClSvcLoadState(void *, uint32_t u32ClientID, void *pv uint8_t abPadding[RT_UOFFSETOF(SHCLCLIENTMSG, aParms) + sizeof(VBOXHGCMSVCPARM) * 2]; } u; - pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, - &s_aShClSSMClientMsgHdr[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, + &s_aShClSSMClientMsgHdr[0], NULL); + AssertRCReturn(rc, rc); rc = pVMM->pfnSSMR3GetStructEx(pSSM, &u.Msg, RT_UOFFSETOF(SHCLCLIENTMSG, aParms), 0 /*fFlags*/, &s_aShClSSMClientMsgCtx[0], NULL); AssertRCReturn(rc, rc); @@ -697,9 +719,18 @@ static DECLCALLBACK(int) shClSvcLoadState(void *, uint32_t u32ClientID, void *pv PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); AssertPtrReturn(pCID, VERR_NO_MEMORY); - pVMM->pfnSSMR3GetStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /* fFlags */, - &s_aShClSSMClientLegacyCID[0], NULL); + rc = pVMM->pfnSSMR3GetStructEx(pSSM, pCID, sizeof(SHCLCLIENTLEGACYCID), 0 /* fFlags */, + &s_aShClSSMClientLegacyCID[0], NULL); + if (RT_FAILURE(rc)) + { + RTMemFree(pCID); + return rc; + } + + ShClSvcClientLock(pClient); RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); + pClient->Legacy.cCID++; + ShClSvcClientUnlock(pClient); } } } @@ -710,7 +741,7 @@ static DECLCALLBACK(int) shClSvcLoadState(void *, uint32_t u32ClientID, void *pv } /* Actual host data are to be reported to guest (SYNC). */ - (void) shClSvcBackendSync(pClient); + (void) shClSvcExtBackendSync(pClient); #else /* UNIT_TEST */ RT_NOREF(u32ClientID, pvClient, pSSM, pVMM, uVersion); @@ -741,18 +772,16 @@ extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTa } else { - g_pHelpers = pTable->pHelpers; + g_ShClSvc.pHelpers = pTable->pHelpers; pTable->cbClient = sizeof(SHCLCLIENT); /* Map legacy clients to root. */ pTable->idxLegacyClientCategory = HGCM_CLIENT_CATEGORY_ROOT; - /* Limit the number of clients to 128 in each category (should be enough), - but set kernel clients to 1. */ + /* Main and the native clipboard backends support one active client. */ for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) - pTable->acMaxClients[i] = 128; - pTable->acMaxClients[HGCM_CLIENT_CATEGORY_KERNEL] = 1; + pTable->acMaxClients[i] = 1; /* Only 16 pending calls per client (1 should be enough). */ for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) @@ -770,7 +799,7 @@ extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTa pTable->pvService = NULL; /* Service specific initialization. */ - rc = shClSvcInit(pTable); + rc = shClSvcInit(); } } diff --git a/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings index 5deb90e352ed..19425caf6213 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings +++ b/src/VBox/HostServices/SharedClipboard/testcase/.scm-settings @@ -1,4 +1,4 @@ -# $Id: .scm-settings 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: .scm-settings 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ ## @file # Source code massager settings for the host HGCM services. # @@ -26,8 +26,3 @@ # /*.h: --guard-relative-to-dir . - -# The file ends with a '\0' byte, so we need to treat it as a binary. -# This also makes sure we use CRLF as EOL, so VBoxOrgCfHtml1.h can be recreated -# w/o differencing between unix & windows hosts. -/VBoxOrgCfHtml1.txt: --treat-as binary diff --git a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk index 63973a3f3bcd..3e3a7b4eef0e 100644 --- a/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk +++ b/src/VBox/HostServices/SharedClipboard/testcase/Makefile.kmk @@ -1,6 +1,6 @@ -# $Id: Makefile.kmk 114575 2026-06-30 15:58:06Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ ## @file -# Sub-Makefile for the Shared Clipboard Host Service testcases. +# Sub-Makefile for the Shared Clipboard Host Service testcase. # # @@ -29,206 +29,36 @@ SUB_DEPTH = ../../../../.. include $(KBUILD_PATH)/subheader.kmk if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_ADDITIONS) && !defined(VBOX_ONLY_SDK) - - # - # Testcase which mocks HGCM to also test the VbglR3-side of Shared Clipboard. - # - # Goal is to use and test as much guest side code as possible as a self-contained - # binary on the host here. - # - # Note: No #ifdef TESTCASE hacks or similar allowed, has to run - # without #ifdef modifications to the core code! - # - tstClipboardMockHGCM_TEMPLATE = VBoxR3TstExe - tstClipboardMockHGCM_DEFS = VBOX_WITH_HGCM VBOX_WITH_SHARED_CLIPBOARD VBOX_WITH_SHARED_CLIPBOARD_HOST - tstClipboardMockHGCM_SOURCES = \ - tstClipboardMockHGCM.cpp \ + PROGRAMS += tstClipboardHostService + tstClipboardHostService_TEMPLATE = VBoxR3TstExe + tstClipboardHostService_DEFS = VBOX_WITH_HGCM VBOX_WITH_SHARED_CLIPBOARD_HOST UNIT_TEST + tstClipboardHostService_SOURCES = \ ../VBoxSharedClipboardSvc.cpp \ - ../VBoxSharedClipboardSvc-backend.cpp \ + ../VBoxSharedClipboardSvc-ext.cpp \ ../VBoxSharedClipboardSvc-client.cpp \ ../VBoxSharedClipboardSvc-host.cpp \ - ../../testcase/TstHGCMMock.cpp \ - ../../testcase/TstHGCMMockUtils.cpp \ + ../VBoxSharedClipboardSvc-transport.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp \ - $(PATH_ROOT)/src/VBox/Additions/common/VBoxGuest/lib/VBoxGuestR3LibClipboard.cpp \ - $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp - tstClipboardMockHGCM_LIBS = $(LIB_RUNTIME) - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - tstClipboardMockHGCM_INCS += $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard - endif - - if1of ($(KBUILD_TARGET), linux solaris) - PROGRAMS += tstClipboardMockHGCM - tstClipboardMockHGCM_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/DisplayServerType.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp - tstClipboardMockHGCM_LIBPATH = \ - $(VBOX_LIBPATH_X11) - tstClipboardMockHGCM_LIBS += \ - Xt \ - X11 - endif - if1of ($(KBUILD_TARGET), win) - PROGRAMS += tstClipboardMockHGCM - tstClipboardMockHGCM_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp - endif - - tstClipboardMockHGCM_CLEAN = $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run + tstClipboardHostService.cpp + tstClipboardHostService_LIBS = $(LIB_RUNTIME) + tstClipboardHostService_CLEAN = $(tstClipboardHostService_0_OUTDIR)/tstClipboardHostService.run if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - tstClipboardMockHGCM_DEFS += \ - VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS \ - $(if $(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP),VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP,) - tstClipboardMockHGCM_SOURCES += \ + tstClipboardHostService_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardHostService_SOURCES += \ ../VBoxSharedClipboardSvc-transfers.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) - if1of ($(KBUILD_TARGET), linux solaris) - tstClipboardMockHGCM_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-http.cpp - endif - endif - tstClipboardMockHGCM_SOURCES.win += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp endif - if 0 # Enable this if you want automatic runs after compilation. - $$(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run: $$(tstClipboardMockHGCM_1_STAGE_TARGET) - export VBOX_LOG_DEST=nofile; $(tstClipboardMockHGCM_1_STAGE_TARGET) quiet + $$(tstClipboardHostService_0_OUTDIR)/tstClipboardHostService.run: $$(tstClipboardHostService_1_STAGE_TARGET) | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstClipboardHostService_1_STAGE_TARGET) quiet $(QUIET)$(APPEND) -t "$@" "done" - OTHERS += $(tstClipboardMockHGCM_0_OUTDIR)/tstClipboardMockHGCM.run - endif - - # - # - # - PROGRAMS += tstClipboardServiceHost - tstClipboardServiceHost_TEMPLATE = VBoxR3TstExe - tstClipboardServiceHost_DEFS = VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD_HOST - tstClipboardServiceHost_SOURCES = \ - ../VBoxSharedClipboardSvc.cpp \ - ../VBoxSharedClipboardSvc-backend.cpp \ - ../VBoxSharedClipboardSvc-client.cpp \ - ../VBoxSharedClipboardSvc-host.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp \ - $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ - tstClipboardServiceHost.cpp - tstClipboardServiceHost_INCS += $(PATH_ROOT)/src/VBox/Main/include - tstClipboardServiceHost_LIBS = $(LIB_RUNTIME) - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - tstClipboardServiceHost_INCS += $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard - endif - - if1of ($(KBUILD_TARGET), linux solaris) - tstClipboardServiceHost_DEFS += VBOX_WITH_SHARED_CLIPBOARD_X11_LAZY_LOAD - tstClipboardServiceHost_SOURCES += \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-x11.cpp \ - $(PATH_ROOT)/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp - define def_VBoxTstClipboardServiceHost_libToLazyLoad - tstClipboardServiceHost_SOURCES += \ - $$(tstClipboardServiceHost_0_OUTDIR)/$(2)LazyLoad.asm - tstClipboardServiceHost_CLEAN += \ - $$(tstClipboardServiceHost_0_OUTDIR)/$(2)LazyLoad.asm - $$$$(tstClipboardServiceHost_0_OUTDIR)/$(2)LazyLoad.asm: $$(PATH_ROOT)/src/VBox/GuestHost/$(2).def $(VBOX_DEF_2_LAZY_LOAD) | $$$$(dir $$@) - $$(call MSG_TOOL,VBoxDef2LazyLoad,tstClipboardServiceHost,$$(filter %.def, $$^),$$@) - $$(QUIET)$$(RM) -f -- "$$@" - $$(VBOX_DEF_2_LAZY_LOAD) --explicit-load-function --system --library $(2)$$(SUFF_DLL)$$(if $(3),.$(3),) --output "$$@" $$(filter %.def, $$^) - endef - if1of ($(KBUILD_TARGET), linux) - VBOX_TST_CLIPBOARD_SERVICE_HOST_LIBX11_VER = 6 - VBOX_TST_CLIPBOARD_SERVICE_HOST_LIBXT_VER = 6 - tstClipboardServiceHost_LDFLAGS.linux += $(VBOX_GCC_no-pie) - endif - $(evalcall2 def_VBoxTstClipboardServiceHost_libToLazyLoad,$(PATH_ROOT)/src/VBox/GuestHost/libX11.def,libX11,$(VBOX_TST_CLIPBOARD_SERVICE_HOST_LIBX11_VER)) - $(evalcall2 def_VBoxTstClipboardServiceHost_libToLazyLoad,$(PATH_ROOT)/src/VBox/GuestHost/libXt.def,libXt,$(VBOX_TST_CLIPBOARD_SERVICE_HOST_LIBXT_VER)) - endif - - tstClipboardServiceHost_CLEAN += $(tstClipboardServiceHost_0_OUTDIR)/tstClipboardServiceHost.run - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - tstClipboardServiceHost_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - tstClipboardServiceHost_SOURCES += \ - ../VBoxSharedClipboardSvc-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp - endif - - # - # - # - PROGRAMS += tstClipboardServiceImpl - tstClipboardServiceImpl_TEMPLATE = VBoxR3TstExe - tstClipboardServiceImpl_DEFS = VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD_HOST - tstClipboardServiceImpl_SOURCES = \ - ../VBoxSharedClipboardSvc.cpp \ - ../VBoxSharedClipboardSvc-backend.cpp \ - ../VBoxSharedClipboardSvc-client.cpp \ - ../VBoxSharedClipboardSvc-host.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ - $(PATH_ROOT)/src/VBox/HostServices/common/message.cpp \ - tstClipboardServiceImpl.cpp - tstClipboardServiceImpl_SOURCES.win = \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp - tstClipboardServiceImpl_LIBS = $(LIB_RUNTIME) - tstClipboardServiceImpl_CLEAN = $(tstClipboardServiceImpl_0_OUTDIR)/tstClipboardServiceImpl.run - - if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) - # - # File transfer tests. - # - PROGRAMS += tstClipboardTransfers - tstClipboardTransfers_TEMPLATE = VBoxR3TstExe - tstClipboardTransfers_DEFS = VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - tstClipboardTransfers_SOURCES = \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp \ - tstClipboardTransfers.cpp - - # - # Windows IDataObject tests. - # - PROGRAMS.win += tstClipboardDataObjectWin - tstClipboardDataObjectWin_TEMPLATE = VBoxR3TstExe - tstClipboardDataObjectWin_DEFS += UNICODE VBOX_WITH_HGCM UNIT_TEST VBOX_WITH_SHARED_CLIPBOARD VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - tstClipboardDataObjectWin_SOURCES = \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardDataObjectImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardEnumFormatEtcImpl-win.cpp \ - $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/ClipboardStreamImpl-win.cpp \ - tstClipboardDataObjectWin.cpp - tstClipboardDataObjectWin_LIBS = $(LIB_RUNTIME) + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardHostService_0_OUTDIR)/tstClipboardHostService.run endif endif -# -# List of above testcases that will be included in the ValKit. -# -ifdef VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING - if1of ($(KBUILD_TARGET), linux solaris win) - VALKIT_UNITTESTS_WHITELIST_GUEST_ADDITIONS += \ - tstClipboardMockHGCM - endif -endif # VBOX_WITH_VALIDATIONKIT_UNITTESTS_PACKING - include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h deleted file mode 100644 index e398bbc34e14..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.h +++ /dev/null @@ -1,165 +0,0 @@ -/* $Id: VBoxOrgCfHtml1.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ -/** @file - * Shared Clipboard host service test case C data file of VBoxOrgCfHtml1.txt. - */ - -/* - * Copyright (C) 2022-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#ifndef VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h -#define VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include - -const unsigned char g_abVBoxOrgCfHtml1[] = -{ - 0x56, 0x65, 0x72, 0x73, 0x69, 0x6f, 0x6e, 0x3a, 0x30, 0x2e, 0x39, 0x0d, 0x0a, 0x53, 0x74, 0x61, /* 0x00000000: Version:0.9..Sta */ - 0x72, 0x74, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x34, /* 0x00000010: rtHTML:000000014 */ - 0x34, 0x0d, 0x0a, 0x45, 0x6e, 0x64, 0x48, 0x54, 0x4d, 0x4c, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, /* 0x00000020: 4..EndHTML:00000 */ - 0x30, 0x31, 0x39, 0x36, 0x31, 0x0d, 0x0a, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, /* 0x00000030: 01961..StartFrag */ - 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x38, 0x30, 0x0d, /* 0x00000040: ment:0000000180. */ - 0x0a, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, 0x74, 0x3a, 0x30, 0x30, 0x30, /* 0x00000050: .EndFragment:000 */ - 0x30, 0x30, 0x30, 0x31, 0x39, 0x32, 0x35, 0x0d, 0x0a, 0x53, 0x6f, 0x75, 0x72, 0x63, 0x65, 0x55, /* 0x00000060: 0001925..SourceU */ - 0x52, 0x4c, 0x3a, 0x68, 0x74, 0x74, 0x70, 0x73, 0x3a, 0x2f, 0x2f, 0x77, 0x77, 0x77, 0x2e, 0x76, /* 0x00000070: RL:https:..www.v */ - 0x69, 0x72, 0x74, 0x75, 0x61, 0x6c, 0x62, 0x6f, 0x78, 0x2e, 0x6f, 0x72, 0x67, 0x2f, 0x0d, 0x0a, /* 0x00000080: irtualbox.org... */ - 0x3c, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x0d, 0x0a, 0x3c, 0x62, 0x6f, 0x64, 0x79, 0x3e, 0x0d, 0x0a, /* 0x00000090: .... */ - 0x3c, 0x21, 0x2d, 0x2d, 0x53, 0x74, 0x61, 0x72, 0x74, 0x46, 0x72, 0x61, 0x67, 0x6d, 0x65, 0x6e, /* 0x000000a0: See "<.span>About Vir */ - 0x74, 0x75, 0x61, 0x6c, 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, /* 0x00000520: tualBox<.a>" for an */ - 0x20, 0x69, 0x6e, 0x74, 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, /* 0x00000770: introduction.<. */ - 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x21, 0x2d, 0x2d, 0x45, 0x6e, 0x64, 0x46, 0x72, 0x61, 0x67, /* 0x00000780: span>..<.body> */ - 0x0d, 0x0a, 0x3c, 0x2f, 0x68, 0x74, 0x6d, 0x6c, 0x3e, 0x00, /* 0x000007a0: ..<.html>. */ -}; - -const unsigned g_cbVBoxOrgCfHtml1 = sizeof(g_abVBoxOrgCfHtml1); - -#endif /* !VBOX_INCLUDED_SRC_VBoxOrgCfHtml1_h */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt deleted file mode 100644 index cef5f7c9490f..000000000000 Binary files a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgCfHtml1.txt and /dev/null differ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h deleted file mode 100644 index a1c56bc5b22d..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.h +++ /dev/null @@ -1,152 +0,0 @@ -/* $Id: VBoxOrgMimeHtml1.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ -/** @file - * Shared Clipboard host service test case C data file of VBoxOrgMimeHtml1.txt. - */ - -/* - * Copyright (C) 2022-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#ifndef VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h -#define VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h -#ifndef RT_WITHOUT_PRAGMA_ONCE -# pragma once -#endif - -#include - -const unsigned char g_abVBoxOrgMimeHtml1[] = -{ - 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, 0x6c, 0x65, 0x3d, 0x22, 0x63, 0x6f, 0x6c, /* 0x00000000: See */ - 0x20, 0x22, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, 0x3e, 0x3c, 0x61, 0x20, 0x63, 0x6c, 0x61, 0x73, /* 0x00000240: "<.span>About Virtual */ - 0x42, 0x6f, 0x78, 0x3c, 0x2f, 0x61, 0x3e, 0x3c, 0x73, 0x70, 0x61, 0x6e, 0x20, 0x73, 0x74, 0x79, /* 0x00000470: Box<.a>" for an int */ - 0x72, 0x6f, 0x64, 0x75, 0x63, 0x74, 0x69, 0x6f, 0x6e, 0x2e, 0x3c, 0x2f, 0x73, 0x70, 0x61, 0x6e, /* 0x000006c0: roduction.<.span */ - 0x3e, 0x00 /* 0x000006d0: > */ -}; - -const unsigned g_cbVBoxOrgMimeHtml1 = sizeof(g_abVBoxOrgMimeHtml1); - -#endif /* !VBOX_INCLUDED_SRC_VBoxOrgMimeHtml1_h */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt b/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt deleted file mode 100644 index 6d5b49011ac3..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/VBoxOrgMimeHtml1.txt +++ /dev/null @@ -1 +0,0 @@ -See "About VirtualBox" for an introduction. \ No newline at end of file diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardDataObjectWin.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardDataObjectWin.cpp deleted file mode 100644 index 206aa5ec3d03..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardDataObjectWin.cpp +++ /dev/null @@ -1,1227 +0,0 @@ -/* $Id: tstClipboardDataObjectWin.cpp 114651 2026-07-08 09:46:19Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard Windows IDataObject testcase. - */ - -/* - * Copyright (C) 2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include - -#include - - -typedef struct TESTFILE -{ - const char *pszNameUtf8; - const WCHAR *pwszName; -} TESTFILE; - - -/* - * Keep all filenames escaped so the source file itself does not depend on the - * editor/source encoding. The narrow strings are UTF-8. The wide strings are - * UTF-16 code units expected in FILEDESCRIPTORW::cFileName. - */ -static const TESTFILE g_aFiles[] = -{ - { - /* - * plain-ascii.txt - * - * Pure ASCII baseline. - */ - "\x70\x6c\x61\x69\x6e\x2d\x61\x73\x63\x69\x69\x2e\x74\x78\x74", - L"\x0070\x006c\x0061\x0069\x006e\x002d\x0061\x0073\x0063\x0069\x0069\x002e\x0074\x0078\x0074" - }, - { - /* - * cafe-with-accents: - * - * c a f U+00E9 - U+00FC b e r - U+00C5 n g s t r U+00F6 m .txt - * - * Latin characters: - * U+00E9 LATIN SMALL LETTER E WITH ACUTE - * U+00FC LATIN SMALL LETTER U WITH DIAERESIS - * U+00C5 LATIN CAPITAL LETTER A WITH RING ABOVE - * U+00F6 LATIN SMALL LETTER O WITH DIAERESIS - */ - "\x63\x61\x66\xc3\xa9\x2d\xc3\xbc\x62\x65\x72\x2d\xc3\x85\x6e\x67\x73\x74\x72\xc3\xb6\x6d\x2e\x74\x78\x74", - L"\x0063\x0061\x0066\x00e9\x002d\x00fc\x0062\x0065\x0072\x002d\x00c5\x006e\x0067\x0073\x0074\x0072\x00f6\x006d\x002e\x0074\x0078\x0074" - }, - { - /* - * cafe + combining acute accent: - * - * c a f e U+0301 - n f d .txt - * - * This visually resembles precomposed "cafe with acute", but contains: - * U+0065 LATIN SMALL LETTER E - * U+0301 COMBINING ACUTE ACCENT - * - * The IDataObject path must preserve this exact sequence and must not - * normalize it to precomposed U+00E9. - */ - "\x63\x61\x66\x65\xcc\x81\x2d\x6e\x66\x64\x2e\x74\x78\x74", - L"\x0063\x0061\x0066\x0065\x0301\x002d\x006e\x0066\x0064\x002e\x0074\x0078\x0074" - }, - { - /* - * Russian Cyrillic: - * - * Privet-mir.txt, roughly "hello-world.txt" - * - * Unicode: - * U+041F U+0440 U+0438 U+0432 U+0435 U+0442 - * U+002D - * U+043C U+0438 U+0440 - * - * This is the main mojibake regression case. If UTF-8 bytes are - * exposed through FILEDESCRIPTORA instead of FILEDESCRIPTORW, this - * often turns into text beginning with U+00D0 / U+00D1 mojibake. - */ - "\xd0\x9f\xd1\x80\xd0\xb8\xd0\xb2\xd0\xb5\xd1\x82\x2d\xd0\xbc\xd0\xb8\xd1\x80\x2e\x74\x78\x74", - L"\x041f\x0440\x0438\x0432\x0435\x0442\x002d\x043c\x0438\x0440\x002e\x0074\x0078\x0074" - }, - { - /* - * Greek: - * - * dokimi-kosmos.txt, roughly "test-world.txt" - * - * Includes: - * U+03AE GREEK SMALL LETTER ETA WITH TONOS - * U+03CC GREEK SMALL LETTER OMICRON WITH TONOS - * U+03C2 GREEK SMALL LETTER FINAL SIGMA - */ - "\xce\xb4\xce\xbf\xce\xba\xce\xb9\xce\xbc\xce\xae\x2d\xce\xba\xcf\x8c\xcf\x83\xce\xbc\xce\xbf\xcf\x82\x2e\x74\x78\x74", - L"\x03b4\x03bf\x03ba\x03b9\x03bc\x03ae\x002d\x03ba\x03cc\x03c3\x03bc\x03bf\x03c2\x002e\x0074\x0078\x0074" - }, - { - /* - * Polish / Central European Latin: - * - * za + U+017C U+00F3 U+0142 U+0107 - * ge + U+0119 U+015B l U+0105 - * ja + U+017A U+0144 - * - * Useful for catching accidental host ANSI code page assumptions. - */ - "\x7a\x61\xc5\xbc\xc3\xb3\xc5\x82\xc4\x87\x2d\x67\xc4\x99\xc5\x9b\x6c\xc4\x85\x2d\x6a\x61\xc5\xba\xc5\x84\x2e\x74\x78\x74", - L"\x007a\x0061\x017c\x00f3\x0142\x0107\x002d\x0067\x0119\x015b\x006c\x0105\x002d\x006a\x0061\x017a\x0144\x002e\x0074\x0078\x0074" - }, - { - /* - * Hebrew, right-to-left: - * - * shalom-olam.txt, roughly "hello-world" / "peace-world" - * - * The test compares logical Unicode order, not visual display order. - */ - "\xd7\xa9\xd7\x9c\xd7\x95\xd7\x9d\x2d\xd7\xa2\xd7\x95\xd7\x9c\xd7\x9d\x2e\x74\x78\x74", - L"\x05e9\x05dc\x05d5\x05dd\x002d\x05e2\x05d5\x05dc\x05dd\x002e\x0074\x0078\x0074" - }, - { - /* - * Arabic, right-to-left: - * - * marhaba-alam.txt, "hello-world.txt" - * - * The test compares logical Unicode order, not visual display order. - */ - "\xd9\x85\xd8\xb1\xd8\xad\xd8\xa8\xd8\xa7\x2d\xd8\xb9\xd8\xa7\xd9\x84\xd9\x85\x2e\x74\x78\x74", - L"\x0645\x0631\x062d\x0628\x0627\x002d\x0639\x0627\x0644\x0645\x002e\x0074\x0078\x0074" - }, - { - /* - * Simplified Chinese: - * - * U+4E2D U+6587 - U+6D4B U+8BD5 .txt - * - * Meaning: - * first word: Chinese language/text - * second word: test - */ - "\xe4\xb8\xad\xe6\x96\x87\x2d\xe6\xb5\x8b\xe8\xaf\x95\x2e\x74\x78\x74", - L"\x4e2d\x6587\x002d\x6d4b\x8bd5\x002e\x0074\x0078\x0074" - }, - { - /* - * Japanese: - * - * U+65E5 U+672C U+8A9E - U+30C6 U+30B9 U+30C8 .txt - * - * Meaning: - * first word: Japanese language - * second word: test, written in katakana - */ - "\xe6\x97\xa5\xe6\x9c\xac\xe8\xaa\x9e\x2d\xe3\x83\x86\xe3\x82\xb9\xe3\x83\x88\x2e\x74\x78\x74", - L"\x65e5\x672c\x8a9e\x002d\x30c6\x30b9\x30c8\x002e\x0074\x0078\x0074" - }, - { - /* - * Korean Hangul: - * - * U+D55C U+AE00 - U+D14C U+C2A4 U+D2B8 .txt - * - * Meaning: - * first word: Hangul - * second word: test - */ - "\xed\x95\x9c\xea\xb8\x80\x2d\xed\x85\x8c\xec\x8a\xa4\xed\x8a\xb8\x2e\x74\x78\x74", - L"\xd55c\xae00\x002d\xd14c\xc2a4\xd2b8\x002e\x0074\x0078\x0074" - }, - { - /* - * Emoji / non-BMP: - * - * emoji - U+1F600 GRINNING FACE - file.txt - * - * UTF-8: - * F0 9F 98 80 - * - * UTF-16: - * D83D DE00 - */ - "\x65\x6d\x6f\x6a\x69\x2d\xf0\x9f\x98\x80\x2d\x66\x69\x6c\x65\x2e\x74\x78\x74", - L"\x0065\x006d\x006f\x006a\x0069\x002d\xd83d\xde00\x002d\x0066\x0069\x006c\x0065\x002e\x0074\x0078\x0074" - }, - { - /* - * Non-BMP CJK Extension B: - * - * cjk-ext - U+2000B CJK UNIFIED IDEOGRAPH-2000B .txt - * - * UTF-8: - * F0 A0 80 8B - * - * UTF-16: - * D840 DC0B - */ - "\x63\x6a\x6b\x2d\x65\x78\x74\x2d\xf0\xa0\x80\x8b\x2e\x74\x78\x74", - L"\x0063\x006a\x006b\x002d\x0065\x0078\x0074\x002d\xd840\xdc0b\x002e\x0074\x0078\x0074" - }, - { - /* - * Deliberate mojibake sentinel: - * - * mojibake - U+00D0 U+0178 U+00D1 U+20AC - * U+00D0 U+00B8 U+00D0 U+00B2 U+00D0 U+00B5 - * U+00D1 U+201A .txt - * - * These are the literal Unicode characters often produced when UTF-8 - * Cyrillic bytes are interpreted as Windows-1252-ish text. - * - * This testcase verifies that the IDataObject path preserves the - * literal filename and does not try to heuristically repair mojibake. - */ - "\x6d\x6f\x6a\x69\x62\x61\x6b\x65\x2d\xc3\x90\xc5\xb8\xc3\x91\xe2\x82\xac\xc3\x90\xc2\xb8\xc3\x90\xc2\xb2\xc3\x90\xc2\xb5\xc3\x91\xe2\x80\x9a\x2e\x74\x78\x74", - L"\x006d\x006f\x006a\x0069\x0062\x0061\x006b\x0065\x002d\x00d0\x0178\x00d1\x20ac\x00d0\x00b8\x00d0\x00b2\x00d0\x00b5\x00d1\x201a\x002e\x0074\x0078\x0074" - } -}; - - -typedef struct TESTCTX -{ - PSHCLTRANSFER pTransfer; -} TESTCTX; - - -static DECLCALLBACK(int) testTransferBegin(ShClWinDataObject::PCALLBACKCTX pCbCtx) -{ - AssertPtrReturn(pCbCtx, VERR_INVALID_POINTER); - AssertPtrReturn(pCbCtx->pThis, VERR_INVALID_POINTER); - - TESTCTX *pThis = (TESTCTX *)pCbCtx->pvUser; - AssertPtrReturn(pThis, VERR_INVALID_POINTER); - AssertPtrReturn(pThis->pTransfer, VERR_INVALID_POINTER); - - int rc = pCbCtx->pThis->SetTransfer(pThis->pTransfer); - if (RT_SUCCESS(rc)) - rc = pCbCtx->pThis->SetStatus(ShClWinDataObject::Running); - return rc; -} - - -static DECLCALLBACK(int) testTransferEnd(ShClWinDataObject::PCALLBACKCTX pCbCtx, - PSHCLTRANSFER pTransfer, int rcTransfer) -{ - RT_NOREF(pCbCtx, pTransfer, rcTransfer); - return VINF_SUCCESS; -} - - -static size_t testGetExpectedFileContent(unsigned i, char *pszBuf, size_t cbBuf) -{ - AssertReturn(i < RT_ELEMENTS(g_aFiles), 0); - AssertReturn(cbBuf > 0, 0); - - pszBuf[0] = '\0'; - - RTStrPrintf(pszBuf, cbBuf, - "tstClipboardDataObjectWin payload #%u: %s\n", - i, g_aFiles[i].pszNameUtf8); - - pszBuf[cbBuf - 1] = '\0'; - return RTStrNLen(pszBuf, cbBuf); -} - - -static void testReleaseStgMedium(STGMEDIUM *pMedium) -{ - if (!pMedium) - return; - - if (pMedium->pUnkForRelease) - pMedium->pUnkForRelease->Release(); - else if (pMedium->tymed == TYMED_HGLOBAL && pMedium->hGlobal) - GlobalFree(pMedium->hGlobal); - else if (pMedium->tymed == TYMED_ISTREAM && pMedium->pstm) - pMedium->pstm->Release(); - - RT_ZERO(*pMedium); -} - - -static int testCreateTempDir(char *pszTempDir, size_t cbTempDir) -{ - char szTempDir[RTPATH_MAX]; - - int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardDataObjectWin"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirCreate(szTempDir, 0700, 0); - if (rc == VERR_ALREADY_EXISTS) - rc = VINF_SUCCESS; - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "XXXXXX"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirCreateTemp(szTempDir, 0700); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTStrCopy(pszTempDir, cbTempDir, szTempDir); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - return VINF_SUCCESS; -} - - -static int testCreateFile(const char *pszTempDir, unsigned i, - char *pszPath, size_t cbPath) -{ - AssertReturn(i < RT_ELEMENTS(g_aFiles), VERR_INVALID_PARAMETER); - - int rc = RTPathJoin(pszPath, cbPath, pszTempDir, g_aFiles[i].pszNameUtf8); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - RTFILE hFile; - rc = RTFileOpen(&hFile, pszPath, RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - char szPayload[1024]; - size_t const cbPayload = testGetExpectedFileContent(i, szPayload, sizeof(szPayload)); - - rc = RTFileWrite(hFile, szPayload, cbPayload, NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - int rc2 = RTFileClose(hFile); - RTTESTI_CHECK_RC(rc2, VINF_SUCCESS); - - return RT_SUCCESS(rc) ? rc2 : rc; -} - - -static int testCreateTransfer(const char * const *papszRoots, unsigned cRoots, - PSHCLTRANSFER *ppTransfer) -{ - *ppTransfer = NULL; - - PSHCLTRANSFER pTransfer = NULL; - char *pszRoots = NULL; - - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, - NULL /* pCallbacks */, &pTransfer); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - return rc; - } - - for (;;) - { - rc = ShClWinTransferCreate(NULL /* pCtx */, pTransfer); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - SHCLTXPROVIDER Provider; - RT_ZERO(Provider); - if (!ShClTransferProviderLocalQueryInterface(&Provider)) - { - RTTestIFailed("ShClTransferProviderLocalQueryInterface failed"); - rc = VERR_NOT_SUPPORTED; - break; - } - - rc = ShClTransferSetProvider(pTransfer, &Provider); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - for (unsigned i = 0; i < cRoots; i++) - { - rc = RTStrAAppend(&pszRoots, papszRoots[i]); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - rc = RTStrAAppend(&pszRoots, SHCL_TRANSFER_URI_LIST_SEP_STR); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - } - - if (RT_FAILURE(rc)) - break; - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - rc = ShClTransferInit(pTransfer); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - *ppTransfer = pTransfer; - pTransfer = NULL; - break; - } - - RTStrFree(pszRoots); - - if (pTransfer) - { - ShClWinTransferDestroy(NULL /* pCtx */, pTransfer); - - int rc2 = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc2); - if (RT_SUCCESS(rc)) - rc = rc2; - } - - return rc; -} - - -static void testPrintWideNameN(const char *pszWhat, unsigned i, - WCHAR const *pwszName, size_t cwcMax) -{ - WCHAR wszTmp[MAX_PATH + 1]; - - size_t cwcName = 0; - while (cwcName < cwcMax && pwszName[cwcName] != L'\0') - cwcName++; - - size_t const cwcCopy = cwcName < RT_ELEMENTS(wszTmp) - 1 - ? cwcName - : RT_ELEMENTS(wszTmp) - 1; - - memcpy(wszTmp, pwszName, cwcCopy * sizeof(wszTmp[0])); - wszTmp[cwcCopy] = L'\0'; - - char *pszName = NULL; - int rc = RTUtf16ToUtf8((PCRTUTF16)wszTmp, &pszName); - if (RT_SUCCESS(rc)) - { - RTTestIPrintf(RTTESTLVL_ALWAYS, "%s #%u: \"%s\"%s\n", - pszWhat, i, pszName, - cwcName == cwcMax ? " " : ""); - RTStrFree(pszName); - } - else - RTTestIPrintf(RTTESTLVL_ALWAYS, "%s #%u: %s\n", - pszWhat, i, rc, - cwcName == cwcMax ? " " : ""); -} - - -static void testPrintAnsiNameN(const char *pszWhat, unsigned i, - const char *pszName, size_t cchMax) -{ - char szTmp[MAX_PATH + 1]; - - size_t const cchName = RTStrNLen(pszName, cchMax); - size_t const cchCopy = cchName < RT_ELEMENTS(szTmp) - 1 - ? cchName - : RT_ELEMENTS(szTmp) - 1; - - memcpy(szTmp, pszName, cchCopy); - szTmp[cchCopy] = '\0'; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "%s #%u: \"%s\"%s\n", - pszWhat, i, szTmp, - cchName == cchMax ? " " : ""); -} - - -static void testPrintFileUnderTest(unsigned i) -{ - RTTestIPrintf(RTTESTLVL_ALWAYS, "Preparing file #%u, input UTF-8 name: \"%s\"\n", - i, g_aFiles[i].pszNameUtf8); - - testPrintWideNameN("Preparing file expected UTF-16 name", - i, g_aFiles[i].pwszName, MAX_PATH); -} - - -static void testCheckFileGroupDescriptorW(HGLOBAL hGlobal) -{ - SIZE_T const cbActual = GlobalSize(hGlobal); - SIZE_T const cbExpected = sizeof(FILEGROUPDESCRIPTORW) - + (RT_ELEMENTS(g_aFiles) - 1) * sizeof(FILEDESCRIPTORW); - - if (cbActual < cbExpected) - { - RTTestIFailed("FILEGROUPDESCRIPTORW HGLOBAL too small: got %zu bytes, expected at least %zu bytes", - cbActual, cbExpected); - return; - } - - FILEGROUPDESCRIPTORW *pFGD = (FILEGROUPDESCRIPTORW *)GlobalLock(hGlobal); - if (!pFGD) - { - RTTestIFailed("GlobalLock(FILEGROUPDESCRIPTORW) failed, lasterr=%u", GetLastError()); - return; - } - - if (pFGD->cItems != RT_ELEMENTS(g_aFiles)) - { - RTTestIFailed("FILEGROUPDESCRIPTORW cItems=%u, expected %u", - (unsigned)pFGD->cItems, (unsigned)RT_ELEMENTS(g_aFiles)); - GlobalUnlock(hGlobal); - return; - } - - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - FILEDESCRIPTORW const *pFD = &pFGD->fgd[i]; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "Verifying FILEDESCRIPTORW item #%u\n", i); - RTTestIPrintf(RTTESTLVL_ALWAYS, "Verifying file #%u, original UTF-8 name: \"%s\"\n", - i, g_aFiles[i].pszNameUtf8); - - testPrintWideNameN("Verifying file expected UTF-16 name", - i, g_aFiles[i].pwszName, MAX_PATH); - testPrintWideNameN("Verifying file actual FILEDESCRIPTORW name", - i, pFD->cFileName, RT_ELEMENTS(pFD->cFileName)); - - if (!(pFD->dwFlags & FD_ATTRIBUTES)) - RTTestIFailed("file #%u: FD_ATTRIBUTES is not set", i); - - if (!(pFD->dwFlags & FD_FILESIZE)) - RTTestIFailed("file #%u: FD_FILESIZE is not set", i); - - if (pFD->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - RTTestIFailed("file #%u: unexpectedly marked as directory", i); - -#ifdef FD_UNICODE - if (!(pFD->dwFlags & FD_UNICODE)) - RTTestIFailed("file #%u: FD_UNICODE is not set", i); -#endif - - if (RTUtf16NCmp(pFD->cFileName, - g_aFiles[i].pwszName, - RT_ELEMENTS(pFD->cFileName)) != 0) - RTTestIFailed("file #%u: FILEDESCRIPTORW cFileName mismatch", i); - - char szExpected[1024]; - uint64_t const cbExpectedFile = testGetExpectedFileContent(i, szExpected, sizeof(szExpected)); - uint64_t const cbActualFile = ((uint64_t)pFD->nFileSizeHigh << 32) | pFD->nFileSizeLow; - - if (cbActualFile != cbExpectedFile) - RTTestIFailed("file #%u: FILEDESCRIPTORW file size %RU64, expected %RU64", - i, cbActualFile, cbExpectedFile); - } - - GlobalUnlock(hGlobal); -} - - -static void testCheckFileGroupDescriptorA(HGLOBAL hGlobal) -{ - SIZE_T const cbActual = GlobalSize(hGlobal); - SIZE_T const cbExpected = sizeof(FILEGROUPDESCRIPTORA) - + (RT_ELEMENTS(g_aFiles) - 1) * sizeof(FILEDESCRIPTORA); - - if (cbActual < cbExpected) - { - RTTestIFailed("FILEGROUPDESCRIPTORA HGLOBAL too small: got %zu bytes, expected at least %zu bytes", - cbActual, cbExpected); - return; - } - - FILEGROUPDESCRIPTORA *pFGD = (FILEGROUPDESCRIPTORA *)GlobalLock(hGlobal); - if (!pFGD) - { - RTTestIFailed("GlobalLock(FILEGROUPDESCRIPTORA) failed, lasterr=%u", GetLastError()); - return; - } - - if (pFGD->cItems != RT_ELEMENTS(g_aFiles)) - { - RTTestIFailed("FILEGROUPDESCRIPTORA cItems=%u, expected %u", - (unsigned)pFGD->cItems, (unsigned)RT_ELEMENTS(g_aFiles)); - GlobalUnlock(hGlobal); - return; - } - - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - FILEDESCRIPTORA const *pFD = &pFGD->fgd[i]; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "Verifying FILEDESCRIPTORA item #%u\n", i); - RTTestIPrintf(RTTESTLVL_ALWAYS, "Verifying file #%u, original UTF-8 name: \"%s\"\n", - i, g_aFiles[i].pszNameUtf8); - - testPrintAnsiNameN("Verifying file actual FILEDESCRIPTORA name", - i, pFD->cFileName, RT_ELEMENTS(pFD->cFileName)); - - if (RTStrNLen(pFD->cFileName, RT_ELEMENTS(pFD->cFileName)) == RT_ELEMENTS(pFD->cFileName)) - RTTestIFailed("file #%u: FILEDESCRIPTORA cFileName is not NUL-terminated", i); - - /* - * Only assert exact A-name equality for the pure ASCII case. For - * non-ASCII names, the A format is inherently codepage-dependent and - * should not be the Unicode correctness oracle. - */ - if (i == 0 && strcmp(pFD->cFileName, g_aFiles[i].pszNameUtf8) != 0) - RTTestIFailed("file #%u: FILEDESCRIPTORA ASCII cFileName mismatch: got \"%s\", expected \"%s\"", - i, pFD->cFileName, g_aFiles[i].pszNameUtf8); - - if (!(pFD->dwFlags & FD_ATTRIBUTES)) - RTTestIFailed("file #%u: FILEDESCRIPTORA FD_ATTRIBUTES is not set", i); - - if (!(pFD->dwFlags & FD_FILESIZE)) - RTTestIFailed("file #%u: FILEDESCRIPTORA FD_FILESIZE is not set", i); - -#ifdef FD_UNICODE - if (pFD->dwFlags & FD_UNICODE) - RTTestIFailed("file #%u: FILEDESCRIPTORA unexpectedly has FD_UNICODE set", i); -#endif - - if (pFD->dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) - RTTestIFailed("file #%u: FILEDESCRIPTORA unexpectedly marked as directory", i); - - char szExpected[1024]; - uint64_t const cbExpectedFile = testGetExpectedFileContent(i, szExpected, sizeof(szExpected)); - uint64_t const cbActualFile = ((uint64_t)pFD->nFileSizeHigh << 32) | pFD->nFileSizeLow; - - if (cbActualFile != cbExpectedFile) - RTTestIFailed("file #%u: FILEDESCRIPTORA file size %RU64, expected %RU64", - i, cbActualFile, cbExpectedFile); - } - - GlobalUnlock(hGlobal); -} - - -/** - * Creates the expected CF_UNICODETEXT root-name list. - * - * @returns VBox status code. - * @param ppwszText Where to return the allocated UTF-16 text. - */ -static int testCreateExpectedUnicodeText(PRTUTF16 *ppwszText) -{ - AssertPtrReturn(ppwszText, VERR_INVALID_POINTER); - - *ppwszText = NULL; - - size_t cwcText = 1; /* Terminator. */ - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - cwcText += RTUtf16Len((PCRTUTF16)g_aFiles[i].pwszName); - if (i + 1 < RT_ELEMENTS(g_aFiles)) - cwcText += 2; /* CRLF separator. */ - } - - PRTUTF16 pwszText = (PRTUTF16)RTMemAllocZ(cwcText * sizeof(RTUTF16)); - if (!pwszText) - return VERR_NO_MEMORY; - - PRTUTF16 pwszDst = pwszText; - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - size_t const cwcName = RTUtf16Len((PCRTUTF16)g_aFiles[i].pwszName); - memcpy(pwszDst, g_aFiles[i].pwszName, cwcName * sizeof(RTUTF16)); - pwszDst += cwcName; - - if (i + 1 < RT_ELEMENTS(g_aFiles)) - { - *pwszDst++ = '\r'; - *pwszDst++ = '\n'; - } - } - - *pwszDst = '\0'; - *ppwszText = pwszText; - return VINF_SUCCESS; -} - - -/** - * Checks the CF_UNICODETEXT text representation of the transfer roots. - * - * @param pDataObj Data object to check. - */ -static void testCheckUnicodeText(ShClWinDataObject *pDataObj) -{ - AssertPtrReturnVoid(pDataObj); - - RTTestISub("IDataObject / CF_UNICODETEXT"); - - FORMATETC FormatEtc; - RT_ZERO(FormatEtc); - FormatEtc.cfFormat = CF_UNICODETEXT; - FormatEtc.dwAspect = DVASPECT_CONTENT; - FormatEtc.lindex = -1; - FormatEtc.tymed = TYMED_HGLOBAL; - - HRESULT hrc = pDataObj->QueryGetData(&FormatEtc); - if (hrc != S_OK) - { - RTTestIFailed("QueryGetData(CF_UNICODETEXT) returned %Rhrc, expected S_OK", hrc); - return; - } - - STGMEDIUM Medium; - RT_ZERO(Medium); - - hrc = pDataObj->GetData(&FormatEtc, &Medium); - if (hrc != S_OK) - { - RTTestIFailed("GetData(CF_UNICODETEXT) returned %Rhrc, expected S_OK", hrc); - testReleaseStgMedium(&Medium); - return; - } - - if (Medium.tymed != TYMED_HGLOBAL) - RTTestIFailed("CF_UNICODETEXT tymed %#x, expected TYMED_HGLOBAL", Medium.tymed); - - if (!Medium.hGlobal) - RTTestIFailed("CF_UNICODETEXT returned NULL HGLOBAL"); - else - { - PRTUTF16 pwszExpected = NULL; - int rc = testCreateExpectedUnicodeText(&pwszExpected); - if (RT_FAILURE(rc)) - RTTESTI_CHECK_RC_OK(rc); - else - { - PCRTUTF16 pcwszActual = (PCRTUTF16)GlobalLock(Medium.hGlobal); - if (!pcwszActual) - RTTestIFailed("GlobalLock(CF_UNICODETEXT) failed, lasterr=%u", GetLastError()); - else - { - if (RTUtf16Cmp(pcwszActual, pwszExpected) != 0) - { - char *pszActual = NULL; - char *pszExpected = NULL; - RTUtf16ToUtf8(pcwszActual, &pszActual); - RTUtf16ToUtf8(pwszExpected, &pszExpected); - RTTestIFailed("CF_UNICODETEXT mismatch: got \"%s\", expected \"%s\"", - pszActual ? pszActual : "", - pszExpected ? pszExpected : ""); - RTStrFree(pszActual); - RTStrFree(pszExpected); - } - - GlobalUnlock(Medium.hGlobal); - } - - RTMemFree(pwszExpected); - } - } - - testReleaseStgMedium(&Medium); -} - - -static void testCheckFileDescriptorA(ShClWinDataObject *pDataObj) -{ - AssertPtrReturnVoid(pDataObj); - - RTTestISub("IDataObject / CFSTR_FILEDESCRIPTORA"); - - CLIPFORMAT const cfFileDescriptorA = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA); - if (!cfFileDescriptorA) - { - RTTestIFailed("RegisterClipboardFormat(CFSTR_FILEDESCRIPTORA) failed, lasterr=%u", - GetLastError()); - return; - } - - FORMATETC FormatEtc; - RT_ZERO(FormatEtc); - FormatEtc.cfFormat = cfFileDescriptorA; - FormatEtc.dwAspect = DVASPECT_CONTENT; - FormatEtc.lindex = -1; - FormatEtc.tymed = TYMED_HGLOBAL; - - HRESULT hrc = pDataObj->QueryGetData(&FormatEtc); - if (hrc != S_OK) - { - RTTestIFailed("QueryGetData(CFSTR_FILEDESCRIPTORA) returned %Rhrc, expected S_OK", - hrc); - return; - } - - STGMEDIUM Medium; - RT_ZERO(Medium); - - hrc = pDataObj->GetData(&FormatEtc, &Medium); - if (hrc != S_OK) - { - RTTestIFailed("GetData(CFSTR_FILEDESCRIPTORA) returned %Rhrc, expected S_OK", - hrc); - testReleaseStgMedium(&Medium); - return; - } - - if (Medium.tymed != TYMED_HGLOBAL) - RTTestIFailed("CFSTR_FILEDESCRIPTORA tymed %#x, expected TYMED_HGLOBAL", - Medium.tymed); - - if (!Medium.hGlobal) - RTTestIFailed("CFSTR_FILEDESCRIPTORA returned NULL HGLOBAL"); - else - testCheckFileGroupDescriptorA(Medium.hGlobal); - - testReleaseStgMedium(&Medium); -} - - -static void testCheckFileContentsStream(unsigned i, IStream *pStream) -{ - AssertPtrReturnVoid(pStream); - AssertReturnVoid(i < RT_ELEMENTS(g_aFiles)); - - char szExpected[1024]; - size_t const cbExpected = testGetExpectedFileContent(i, szExpected, sizeof(szExpected)); - - STATSTG StatStg; - RT_ZERO(StatStg); - - HRESULT hrc = pStream->Stat(&StatStg, STATFLAG_NONAME); - if (hrc != S_OK) - RTTestIFailed("file #%u: IStream::Stat returned %Rhrc, expected S_OK", i, hrc); - else if ((uint64_t)StatStg.cbSize.QuadPart != cbExpected) - RTTestIFailed("file #%u: IStream size %RU64, expected %zu", - i, (uint64_t)StatStg.cbSize.QuadPart, cbExpected); - - char abBuf[2048]; - RT_ZERO(abBuf); - - if (cbExpected > sizeof(abBuf)) - { - RTTestIFailed("file #%u: expected payload too large for test buffer: %zu > %zu", - i, cbExpected, sizeof(abBuf)); - return; - } - - ULONG cbRead = 0; - hrc = pStream->Read(abBuf, sizeof(abBuf), &cbRead); - if (hrc != S_OK && hrc != S_FALSE) - RTTestIFailed("file #%u: IStream::Read returned %Rhrc, expected S_OK or S_FALSE", - i, hrc); - - RTTestIPrintf(RTTESTLVL_ALWAYS, "CFSTR_FILECONTENTS item #%u read %RU32 bytes: \"%.*s\"\n", - i, (uint32_t)cbRead, (int)cbRead, abBuf); - - if (cbRead != cbExpected) - RTTestIFailed("file #%u: IStream::Read read %RU32 bytes, expected %zu", - i, (uint32_t)cbRead, cbExpected); - else if (memcmp(abBuf, szExpected, cbExpected) != 0) - RTTestIFailed("file #%u: IStream payload mismatch", i); - - ULONG cbReadAgain = ~0U; - hrc = pStream->Read(abBuf, sizeof(abBuf), &cbReadAgain); - if (hrc != S_OK && hrc != S_FALSE) - RTTestIFailed("file #%u: second EOF IStream::Read returned %Rhrc, expected S_OK or S_FALSE", - i, hrc); - - if (cbReadAgain != 0) - RTTestIFailed("file #%u: second EOF IStream::Read returned %RU32 bytes, expected 0", - i, (uint32_t)cbReadAgain); -} - - -static void testCheckFileContents(ShClWinDataObject *pDataObj) -{ - AssertPtrReturnVoid(pDataObj); - - RTTestISub("IDataObject / CFSTR_FILECONTENTS"); - - CLIPFORMAT const cfFileContents = (CLIPFORMAT)RegisterClipboardFormat(CFSTR_FILECONTENTS); - if (!cfFileContents) - { - RTTestIFailed("RegisterClipboardFormat(CFSTR_FILECONTENTS) failed, lasterr=%u", - GetLastError()); - return; - } - - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - RTTestIPrintf(RTTESTLVL_ALWAYS, - "Requesting CFSTR_FILECONTENTS item #%u: \"%s\"\n", - i, g_aFiles[i].pszNameUtf8); - - FORMATETC FormatEtc; - RT_ZERO(FormatEtc); - FormatEtc.cfFormat = cfFileContents; - FormatEtc.dwAspect = DVASPECT_CONTENT; - FormatEtc.lindex = (LONG)i; - FormatEtc.tymed = TYMED_ISTREAM; - - HRESULT hrc = pDataObj->QueryGetData(&FormatEtc); - if (hrc != S_OK) - { - RTTestIFailed("file #%u: QueryGetData(CFSTR_FILECONTENTS) returned %Rhrc, expected S_OK", - i, hrc); - continue; - } - - STGMEDIUM Medium; - RT_ZERO(Medium); - - hrc = pDataObj->GetData(&FormatEtc, &Medium); - if (hrc != S_OK) - { - RTTestIFailed("file #%u: GetData(CFSTR_FILECONTENTS) returned %Rhrc, expected S_OK", - i, hrc); - testReleaseStgMedium(&Medium); - continue; - } - - if (Medium.tymed != TYMED_ISTREAM) - RTTestIFailed("file #%u: CFSTR_FILECONTENTS tymed %#x, expected TYMED_ISTREAM", - i, Medium.tymed); - - if (!Medium.pstm) - RTTestIFailed("file #%u: CFSTR_FILECONTENTS returned NULL IStream", i); - else - testCheckFileContentsStream(i, Medium.pstm); - - testReleaseStgMedium(&Medium); - } -} - - -/** - * Tests Windows clipboard format conversion for file-transfer formats. - */ -static void testClipboardFormatToVBoxFileTransferFormats(void) -{ - RTTestISub("ShClWinClipboardFormatToVBox / file-transfer formats"); - - SHCLFORMAT uFmt = ShClWinClipboardFormatToVBox(CF_HDROP); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_URI_LIST, - ("CF_HDROP: uFmt=%#x expected=%#x\n", uFmt, VBOX_SHCL_FMT_URI_LIST)); - - CLIPFORMAT const cfFileDescriptorA = (CLIPFORMAT)RegisterClipboardFormatA("FileGroupDescriptor"); - if (!cfFileDescriptorA) - RTTestIFailed("RegisterClipboardFormatA(\"FileGroupDescriptor\") failed, lasterr=%u", GetLastError()); - if (cfFileDescriptorA) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileDescriptorA); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileGroupDescriptor via RegisterClipboardFormatA: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } - - CLIPFORMAT const cfFileDescriptorAU = (CLIPFORMAT)RegisterClipboardFormatW(L"FileGroupDescriptor"); - if (!cfFileDescriptorAU) - RTTestIFailed("RegisterClipboardFormatW(L\"FileGroupDescriptor\") failed, lasterr=%u", GetLastError()); - if (cfFileDescriptorAU) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileDescriptorAU); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileGroupDescriptor via RegisterClipboardFormatW: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } - - CLIPFORMAT const cfFileDescriptorW = (CLIPFORMAT)RegisterClipboardFormatW(L"FileGroupDescriptorW"); - if (!cfFileDescriptorW) - RTTestIFailed("RegisterClipboardFormatW(L\"FileGroupDescriptorW\") failed, lasterr=%u", GetLastError()); - if (cfFileDescriptorW) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileDescriptorW); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileGroupDescriptorW via RegisterClipboardFormatW: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } - - CLIPFORMAT const cfFileContents = (CLIPFORMAT)RegisterClipboardFormatA("FileContents"); - if (!cfFileContents) - RTTestIFailed("RegisterClipboardFormatA(\"FileContents\") failed, lasterr=%u", GetLastError()); - if (cfFileContents) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileContents); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileContents via RegisterClipboardFormatA: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } - - CLIPFORMAT const cfFileContentsU = (CLIPFORMAT)RegisterClipboardFormatW(L"FileContents"); - if (!cfFileContentsU) - RTTestIFailed("RegisterClipboardFormatW(L\"FileContents\") failed, lasterr=%u", GetLastError()); - if (cfFileContentsU) - { - uFmt = ShClWinClipboardFormatToVBox(cfFileContentsU); - RTTESTI_CHECK_MSG(uFmt == VBOX_SHCL_FMT_NONE, - ("FileContents via RegisterClipboardFormatW: uFmt=%#x expected=%#x\n", - uFmt, VBOX_SHCL_FMT_NONE)); - } -} - - -static void testFileDescriptorW(void) -{ - RTTestISub("IDataObject / CFSTR_FILEDESCRIPTORW"); - - /* - * Keep Ctx alive until after pDataObj has been released. The data object - * stores the callback context pointer passed to Init(). - */ - TESTCTX Ctx; - RT_ZERO(Ctx); - - char szTempDir[RTPATH_MAX] = ""; - char aaszFilePaths[RT_ELEMENTS(g_aFiles)][RTPATH_MAX]; - const char *apszRoots[RT_ELEMENTS(g_aFiles)]; - RT_ZERO(aaszFilePaths); - RT_ZERO(apszRoots); - - PSHCLTRANSFER pTransfer = NULL; - ShClWinDataObject *pDataObj = NULL; - - STGMEDIUM StgMedium; - RT_ZERO(StgMedium); - - int rc = VINF_SUCCESS; - - for (;;) - { - rc = testCreateTempDir(szTempDir, sizeof(szTempDir)); - if (RT_FAILURE(rc)) - break; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "Temporary test directory: %s\n", szTempDir); - - for (unsigned i = 0; i < RT_ELEMENTS(g_aFiles); i++) - { - testPrintFileUnderTest(i); - - rc = testCreateFile(szTempDir, - i, - aaszFilePaths[i], - sizeof(aaszFilePaths[i])); - if (RT_FAILURE(rc)) - break; - - apszRoots[i] = aaszFilePaths[i]; - - RTTestIPrintf(RTTESTLVL_ALWAYS, "Created test file #%u: %s\n", - i, aaszFilePaths[i]); - } - - if (RT_FAILURE(rc)) - break; - - rc = testCreateTransfer(apszRoots, RT_ELEMENTS(g_aFiles), &pTransfer); - if (RT_FAILURE(rc)) - break; - - Ctx.pTransfer = pTransfer; - - ShClWinDataObject::CALLBACKS Callbacks; - RT_ZERO(Callbacks); - Callbacks.pfnTransferBegin = testTransferBegin; - Callbacks.pfnTransferEnd = testTransferEnd; - - pDataObj = new ShClWinDataObject(); - if (!pDataObj) - { - RTTestIFailed("new ShClWinDataObject failed"); - rc = VERR_NO_MEMORY; - break; - } - - pDataObj->AddRef(); - - rc = pDataObj->Init((PSHCLCONTEXT)&Ctx, &Callbacks); - if (RT_FAILURE(rc)) - { - RTTESTI_CHECK_RC_OK(rc); - break; - } - - testCheckUnicodeText(pDataObj); - - CLIPFORMAT const cfFileDescriptorW = (CLIPFORMAT)RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW); - if (!cfFileDescriptorW) - { - RTTestIFailed("RegisterClipboardFormatW(CFSTR_FILEDESCRIPTORW) failed, lasterr=%u", - GetLastError()); - rc = VERR_INTERNAL_ERROR; - break; - } - - FORMATETC FormatEtc; - RT_ZERO(FormatEtc); - FormatEtc.cfFormat = cfFileDescriptorW; - FormatEtc.dwAspect = DVASPECT_CONTENT; - FormatEtc.lindex = -1; - FormatEtc.tymed = TYMED_HGLOBAL; - - HRESULT hrc = pDataObj->QueryGetData(&FormatEtc); - if (hrc != S_OK) - { - RTTestIFailed("QueryGetData(CFSTR_FILEDESCRIPTORW) returned %Rhrc, expected S_OK", hrc); - break; - } - - hrc = pDataObj->GetData(&FormatEtc, &StgMedium); - if (hrc != S_OK) - { - RTTestIFailed("GetData(CFSTR_FILEDESCRIPTORW) returned %Rhrc, expected S_OK", hrc); - break; - } - - if (StgMedium.tymed != TYMED_HGLOBAL) - RTTestIFailed("GetData returned tymed %#x, expected TYMED_HGLOBAL", - StgMedium.tymed); - - if (!StgMedium.hGlobal) - RTTestIFailed("GetData returned a NULL HGLOBAL"); - - if (StgMedium.tymed == TYMED_HGLOBAL && StgMedium.hGlobal) - testCheckFileGroupDescriptorW(StgMedium.hGlobal); - - testCheckFileDescriptorA(pDataObj); - - /* - * Keep CFSTR_FILECONTENTS last. Consuming the final stream can - * transition the transfer/data-object state to Completed. - */ - testCheckFileContents(pDataObj); - - break; - } - - /* - * Cleanup. - */ - testReleaseStgMedium(&StgMedium); - - if (pDataObj) - { - pDataObj->Release(); - pDataObj = NULL; - } - - if (pTransfer) - { - ShClWinTransferDestroy(NULL /* pCtx */, pTransfer); - - int rc2 = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc2); - - pTransfer = NULL; - Ctx.pTransfer = NULL; - } - - if (szTempDir[0]) - { - int rc2 = RTDirRemoveRecursive(szTempDir, RTDIRRMREC_F_CONTENT_AND_DIR); - RTTESTI_CHECK_RC_OK(rc2); - } -} - - -int main(int argc, char **argv) -{ - RT_NOREF(argc, argv); - - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardDataObjectWin", &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - - RTTestBanner(hTest); - - bool const fMayPanic = RTAssertSetMayPanic(false); - bool const fQuiet = RTAssertSetQuiet(true); - - testClipboardFormatToVBoxFileTransferFormats(); - testFileDescriptorW(); - - RTAssertSetQuiet(fQuiet); - RTAssertSetMayPanic(fMayPanic); - - return RTTestSummaryAndDestroy(hTest); -} diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp new file mode 100644 index 000000000000..5e3316265cde --- /dev/null +++ b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardHostService.cpp @@ -0,0 +1,957 @@ +/* $Id: tstClipboardHostService.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ +/** @file + * Shared Clipboard Host Service testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include + +#include +#include +#include + + +/** @page pg_tstClipboardHostService Shared Clipboard Host Service testcase + * + * This is the Host Service-side unit test for the Shared Clipboard HGCM + * boundary. It runs the production service sources in-process through their + * exported HGCM table. A small extension sink records notifications and uses + * the published service operation table; it does not emulate Main policy. + * + * The test checks connection ownership, host policy and feature negotiation, + * message wakeup/removal, clipboard-data replies, representative malformed + * guest calls, and the transfer control-plane lifecycle. Guest parameters are + * always treated as hostile, and rejected calls must not mutate service or + * extension state. + * + * Guest Additions, native backends, format conversion, filesystem providers, + * HTTP transport, transfer contents and VMM saved-state serialization are out + * of scope for this compact test. + */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** State attached to an HGCM guest-call handle. */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Whether the service completed the call. */ + bool fCompleted; + /** Completion status supplied by the service. */ + int32_t rc; +}; + +/** Minimal stand-in for Main's Shared Clipboard service extension. */ +typedef struct TSTCLEXT +{ + /** Transport received with the latest client connection. */ + SHCLTRANSPORT Transport; + /** Number of connect notifications. */ + uint32_t cConnect; + /** Number of disconnect notifications. */ + uint32_t cDisconnect; + /** Number of synchronization notifications. */ + uint32_t cSync; + /** Number of guest format announcements. */ + uint32_t cGuestFormats; + /** Number of host clipboard data reads requested by the guest. */ + uint32_t cDataReads; + /** Number of guest clipboard data writes forwarded by the service. */ + uint32_t cDataWrites; + /** Last format mask announced by the guest. */ + SHCLFORMATS fGuestFormats; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Number of transfer-status callback-table queries. */ + uint32_t cTransferCallbackQueries; + /** Number of guest transfer-status notifications. */ + uint32_t cTransferStatuses; + /** Session ID of the last transfer-status notification. */ + SHCLSESSIONID idTransferSession; + /** Transfer ID of the last transfer-status notification. */ + SHCLTRANSFERID idTransfer; + /** Generation of the last transfer-status notification. */ + SHCLTRANSFERGEN uTransferGeneration; + /** Direction of the last transfer-status notification. */ + SHCLTRANSFERDIR enmTransferDir; + /** Source of the last transfer-status notification. */ + SHCLSOURCE enmTransferSource; + /** Last transfer status reported by the guest. */ + SHCLTRANSFERSTATUS enmTransferStatus; + /** Last transfer result reported by the guest. */ + int rcTransfer; +#endif +} TSTCLEXT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Test handle. */ +static RTTEST g_hTest; +/** Loaded service function table. */ +static VBOXHGCMSVCFNTABLE g_Table; +/** HGCM helpers supplied to the service. */ +static VBOXHGCMSVCHELPERS g_Helpers; +/** Minimal service extension state. */ +static TSTCLEXT g_Ext; +/** Data returned for VBOX_SHCL_GUEST_FN_DATA_READ. */ +static uint8_t const g_abHostData[] = { 't', 'e', 's', 't', '\0' }; + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable); + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** + * Completes one synthetic guest call. + * + * @returns VINF_SUCCESS. + * @param hCall Synthetic call handle to complete. + * @param rc Guest-call result. + */ +static DECLCALLBACK(int) tstCallComplete(VBOXHGCMCALLHANDLE hCall, int32_t rc) +{ + hCall->fCompleted = true; + hCall->rc = rc; + return VINF_SUCCESS; +} + + +/** + * Dispatches service-extension requests without implementing a native backend. + * + * The test records the transport contract and provides fixed POD clipboard data; + * platform policy and Main behavior deliberately stay outside this testcase. + * + * @returns VBox status code. + * @param pvExtension Test extension state. + * @param uFunction VBOX_CLIPBOARD_EXT_FN_XXX function number. + * @param pvParms Service-extension parameters. + * @param cbParms Size of @a pvParms in bytes. + */ +static DECLCALLBACK(int) tstExtension(void *pvExtension, uint32_t uFunction, void *pvParms, uint32_t cbParms) +{ + TSTCLEXT * const pExt = (TSTCLEXT *)pvExtension; + RTTESTI_CHECK_RET(cbParms == sizeof(SHCLEXTPARMS), VERR_INVALID_PARAMETER); + PSHCLEXTPARMS const pParms = (PSHCLEXTPARMS)pvParms; + + switch (uFunction) + { + case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: + case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: + pExt->Transport = ShClSvcExtGetTransport(pParms); + RTTESTI_CHECK_RET(ShClTransportIsValid(&pExt->Transport), VERR_INVALID_PARAMETER); + pExt->cConnect++; + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + RTTESTI_CHECK_RET(ShClTransportIsEqual(&pExt->Transport, &Transport), VERR_INVALID_PARAMETER); + pExt->cDisconnect++; + return VINF_SUCCESS; + } + + case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + RTTESTI_CHECK_RET(ShClTransportIsEqual(&pExt->Transport, &Transport), VERR_INVALID_PARAMETER); + pExt->cSync++; + return VINF_SUCCESS; + } + + case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: + pExt->cGuestFormats++; + pExt->fGuestFormats = pParms->u.ReportFormats.uFormats; + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_DATA_READ: + pExt->cDataReads++; + pParms->u.ReadWriteData.cbActual = sizeof(g_abHostData); + if (pParms->u.ReadWriteData.cbData >= sizeof(g_abHostData)) + memcpy(pParms->u.ReadWriteData.pvData, g_abHostData, sizeof(g_abHostData)); + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + RTTESTI_CHECK_RET(ShClTransportIsEqual(&pExt->Transport, &Transport), VERR_INVALID_PARAMETER); + pExt->cDataWrites++; + + SHCLGUESTDATATOKEN hToken; + int rc = Transport.pOps->pfnGuestDataBegin(Transport.hClient, pParms->u.ReadWriteData.pCmdCtx, + pParms->u.ReadWriteData.uFormat, &hToken); + if (RT_SUCCESS(rc) && hToken) + rc = Transport.pOps->pfnGuestDataComplete(Transport.hClient, hToken, + pParms->u.ReadWriteData.pvData, + pParms->u.ReadWriteData.cbData); + return rc; + } + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: + RTTESTI_CHECK_RET(pParms->u.TransferCallbacks.pCallbacks != NULL, VERR_INVALID_PARAMETER); + RT_ZERO(*pParms->u.TransferCallbacks.pCallbacks); + pExt->cTransferCallbackQueries++; + return VINF_SUCCESS; + + case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: + { + PSHCLTRANSFER const pTransfer = pParms->u.FileTransferData.pTransfer; + SHCLREPLY const * const pReply = pParms->u.FileTransferData.pReply; + RTTESTI_CHECK_RET(pTransfer != NULL, VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RET(pReply != NULL, VERR_INVALID_PARAMETER); + RTTESTI_CHECK_RET(pReply->uType == VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS, VERR_INVALID_PARAMETER); + + pExt->cTransferStatuses++; + pExt->idTransferSession = ShClTransferGetSessionId(pTransfer); + pExt->idTransfer = ShClTransferGetID(pTransfer); + pExt->uTransferGeneration = ShClTransferGetGeneration(pTransfer); + pExt->enmTransferDir = ShClTransferGetDir(pTransfer); + pExt->enmTransferSource = pParms->u.FileTransferData.enmShClSource; + pExt->enmTransferStatus = pReply->u.TransferStatus.uStatus; + pExt->rcTransfer = pReply->rc; + return VINF_SUCCESS; + } +#endif + + default: + return VERR_NOT_SUPPORTED; + } +} + + +/** + * Starts a guest call and returns its completion state to the caller. + * + * @param pvClient HGCM client state. + * @param uFunction Guest function number. + * @param cParms Number of HGCM parameters. + * @param paParms HGCM parameters. Optional if @a cParms is zero. + * @param pCall Call state which remains valid until completion. + */ +static void tstGuestCallStart(void *pvClient, uint32_t uFunction, uint32_t cParms, + VBOXHGCMSVCPARM *paParms, VBOXHGCMCALLHANDLE_TYPEDEF *pCall) +{ + pCall->fCompleted = false; + pCall->rc = VERR_IPE_UNINITIALIZED_STATUS; + g_Table.pfnCall(g_Table.pvService, pCall, 1 /* idClient */, pvClient, + uFunction, cParms, paParms, 0 /* tsArrival */); +} + + +/** + * Executes a guest call which is expected to complete synchronously. + * + * @returns Guest-call result. + * @param pvClient HGCM client state. + * @param uFunction Guest function number. + * @param cParms Number of HGCM parameters. + * @param paParms HGCM parameters. Optional if @a cParms is zero. + */ +static int tstGuestCall(void *pvClient, uint32_t uFunction, uint32_t cParms, VBOXHGCMSVCPARM *paParms) +{ + VBOXHGCMCALLHANDLE_TYPEDEF Call; + tstGuestCallStart(pvClient, uFunction, cParms, paParms, &Call); + RTTESTI_CHECK_MSG_RET(Call.fCompleted, ("Guest function %RU32 was not completed\n", uFunction), + VERR_INTERNAL_ERROR); + return Call.rc; +} + + +/** + * Executes an intentionally malformed guest call with guest assertions suppressed. + * + * @returns Guest-call result. + * @param pvClient HGCM client state. + * @param uFunction Guest function number. + * @param cParms Number of HGCM parameters. + * @param paParms HGCM parameters. Optional if @a cParms is zero. + */ +static int tstGuestCallUntrusted(void *pvClient, uint32_t uFunction, uint32_t cParms, VBOXHGCMSVCPARM *paParms) +{ + int rc = RTTestIDisableAssertions(); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + int const rcCall = tstGuestCall(pvClient, uFunction, cParms, paParms); + + rc = RTTestIRestoreAssertions(); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + return rcCall; +} + + +/** + * Loads the service and verifies its small HGCM registration contract. + * + * @returns VBox status code. + */ +static int tstLoadService(void) +{ + RTTestISub("HGCM service registration"); + + RT_ZERO(g_Table); + RT_ZERO(g_Helpers); + g_Helpers.pfnCallComplete = tstCallComplete; + g_Table.cbSize = sizeof(g_Table); + g_Table.u32Version = VBOX_HGCM_SVC_VERSION; + g_Table.pHelpers = &g_Helpers; + + int const rc = VBoxHGCMSvcLoad(&g_Table); + RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); + RTTESTI_CHECK(g_Table.cbClient > 0); + RTTESTI_CHECK(g_Table.pfnConnect != NULL); + RTTESTI_CHECK(g_Table.pfnDisconnect != NULL); + RTTESTI_CHECK(g_Table.pfnCall != NULL); + RTTESTI_CHECK(g_Table.pfnHostCall != NULL); + RTTESTI_CHECK(g_Table.pfnRegisterExtension != NULL); + for (uintptr_t i = 0; i < RT_ELEMENTS(g_Table.acMaxClients); i++) + RTTESTI_CHECK(g_Table.acMaxClients[i] == 1); + + RT_ZERO(g_Ext); + return g_Table.pfnRegisterExtension(g_Table.pvService, tstExtension, &g_Ext); +} + + +/** + * Checks connection ownership and the opaque transport supplied to Main. + * + * @param ppvClient Where to return the connected HGCM client state. + */ +static void tstConnection(void **ppvClient) +{ + RTTestISub("Client connection and transport"); + + void *pvClient = RTMemAllocZ(g_Table.cbClient); + void *pvOther = RTMemAllocZ(g_Table.cbClient); + if (!pvClient || !pvOther) + { + RTTestIFailed("Allocating HGCM client state failed"); + RTMemFree(pvOther); + RTMemFree(pvClient); + return; + } + + int rc = g_Table.pfnConnect(g_Table.pvService, 1, pvClient, 0 /* fRequestor */, false /* fRestoring */); + if (RT_FAILURE(rc)) + { + RTTestIFailed("Connecting the first client failed: %Rrc", rc); + RTMemFree(pvOther); + RTMemFree(pvClient); + return; + } + RTTESTI_CHECK(g_Ext.cConnect == 1); + RTTESTI_CHECK(g_Ext.cSync == 1); + + rc = g_Table.pfnConnect(g_Table.pvService, 2, pvOther, 0 /* fRequestor */, false /* fRestoring */); + RTTESTI_CHECK_RC(rc, VERR_RESOURCE_BUSY); + RTTESTI_CHECK(g_Ext.cConnect == 1); + if (RT_SUCCESS(rc)) + { + int const rcDisconnect = g_Table.pfnDisconnect(g_Table.pvService, 2, pvOther); + RTTESTI_CHECK_RC(rcDisconnect, VINF_SUCCESS); + RTMemFree(pvOther); + *ppvClient = pvClient; + return; + } + RTMemFree(pvOther); + + *ppvClient = pvClient; +} + + +/** + * Checks host policy propagation and guest feature negotiation. + * + * @param pvClient Connected HGCM client state. + */ +static void tstPolicyAndFeatures(void *pvClient) +{ + RTTestISub("Policy and feature negotiation"); + + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, VBOX_SHCL_MODE_BIDIRECTIONAL); + int rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + SHCLFORMATS fFiltered = VBOX_SHCL_FMT_NONE; + rc = g_Ext.Transport.pOps->pfnFilterFormats(g_Ext.Transport.hClient, true /* fHostToGuest */, + VBOX_SHCL_FMT_URI_LIST, &fFiltered); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(fFiltered == VBOX_SHCL_FMT_NONE); + + uint32_t const cDataReadsBefore = g_Ext.cDataReads; + uint8_t abData[8]; + VBOXHGCMSVCPARM aRead[3]; + HGCMSvcSetU32(&aRead[0], VBOX_SHCL_FMT_URI_LIST); + HGCMSvcSetPv(&aRead[1], abData, sizeof(abData)); + HGCMSvcSetU32(&aRead[2], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(g_Ext.cDataReads == cDataReadsBefore); + + PSHCLEVENT pEvent = (PSHCLEVENT)(uintptr_t)1; + rc = g_Ext.Transport.pOps->pfnReadDataFromGuestAsync(g_Ext.Transport.hClient, + VBOX_SHCL_FMT_URI_LIST, &pEvent); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(pEvent == NULL); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +#endif + + uint64_t const fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_GF_0_TRANSFERS +#endif + ; + VBOXHGCMSVCPARM aParms[2]; + HGCMSvcSetU64(&aParms[0], fGuestFeatures0); + HGCMSvcSetU64(&aParms[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aParms), aParms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(aParms[0].u.uint64 & VBOX_SHCL_HF_0_CONTEXT_ID); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + fFiltered = VBOX_SHCL_FMT_NONE; + rc = g_Ext.Transport.pOps->pfnFilterFormats(g_Ext.Transport.hClient, true /* fHostToGuest */, + VBOX_SHCL_FMT_URI_LIST, &fFiltered); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(fFiltered == VBOX_SHCL_FMT_URI_LIST); +#endif +} + + +/** + * Checks representative hostile guest values at the HGCM protocol boundary. + * + * @param pvClient Connected HGCM client state. + */ +static void tstUntrustedGuestInput(void *pvClient) +{ + RTTestISub("Untrusted guest input"); + + uint64_t const fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_GF_0_TRANSFERS +#endif + ; + int rc = tstGuestCall(pvClient, UINT32_MAX, 0, NULL); + RTTESTI_CHECK_RC(rc, VERR_NOT_IMPLEMENTED); + + VBOXHGCMSVCPARM aFeatures[2]; + HGCMSvcSetU64(&aFeatures[0], fGuestFeatures0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, 1, aFeatures); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_COUNT); + + HGCMSvcSetU64(&aFeatures[0], fGuestFeatures0); + HGCMSvcSetU32(&aFeatures[1], 0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_TYPE); + + HGCMSvcSetU64(&aFeatures[0], fGuestFeatures0); + HGCMSvcSetU64(&aFeatures[1], 0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + uint32_t const cGuestFormatsBefore = g_Ext.cGuestFormats; + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU64(&Parm, VBOX_SHCL_FMT_UNICODETEXT); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_TYPE); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_FMT_VALID_MASK + 1); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); + RTTESTI_CHECK(g_Ext.cGuestFormats == cGuestFormatsBefore); + + uint8_t abData[8]; + VBOXHGCMSVCPARM aRead[VBOX_SHCL_CPARMS_DATA_READ]; + HGCMSvcSetU32(&aRead[0], VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); + HGCMSvcSetPv(&aRead[1], abData, sizeof(abData)); + HGCMSvcSetU32(&aRead[2], UINT32_MAX); + uint32_t const cDataReadsBefore = g_Ext.cDataReads; + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + RTTESTI_CHECK(aRead[2].u.uint32 == UINT32_MAX); + + HGCMSvcSetU32(&aRead[0], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetU32(&aRead[1], 0); + HGCMSvcSetU32(&aRead[2], UINT32_MAX); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_TYPE); + RTTESTI_CHECK(g_Ext.cDataReads == cDataReadsBefore); + + VBOXHGCMSVCPARM aWrite[VBOX_SHCL_CPARMS_DATA_WRITE]; + HGCMSvcSetU64(&aWrite[0], VBOX_SHCL_CONTEXTID_MAKE(UINT16_MAX, 0, 1)); + HGCMSvcSetU32(&aWrite[1], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetPv(&aWrite[2], abData, sizeof(abData)); + uint32_t const cDataWritesBefore = g_Ext.cDataWrites; + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWrite), aWrite); + RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); + + HGCMSvcSetU32(&aWrite[1], VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWrite), aWrite); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + RTTESTI_CHECK(g_Ext.cDataWrites == cDataWritesBefore); + + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, 0, NULL); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_COUNT); + HGCMSvcSetU32(&Parm, VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, 1, &Parm); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_COUNT); +} + + +/** + * Checks one deferred host-to-guest message from wakeup through removal. + * + * @param pvClient Connected HGCM client state. + */ +static void tstMessageQueue(void *pvClient) +{ + RTTestISub("Deferred message queue"); + + VBOXHGCMSVCPARM aPeek[2]; + HGCMSvcSetU32(&aPeek[0], 0); + HGCMSvcSetU32(&aPeek[1], 0); + VBOXHGCMCALLHANDLE_TYPEDEF Call; + tstGuestCallStart(pvClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT, RT_ELEMENTS(aPeek), aPeek, &Call); + RTTESTI_CHECK(!Call.fCompleted); + + VBOXHGCMSVCPARM aOtherPeek[2]; + HGCMSvcSetU32(&aOtherPeek[0], 0); + HGCMSvcSetU32(&aOtherPeek[1], 0); + VBOXHGCMCALLHANDLE_TYPEDEF OtherCall; + int rc = RTTestIDisableAssertions(); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + tstGuestCallStart(pvClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_WAIT, + RT_ELEMENTS(aOtherPeek), aOtherPeek, &OtherCall); + rc = RTTestIRestoreAssertions(); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(OtherCall.fCompleted); + if (OtherCall.fCompleted) + RTTESTI_CHECK_RC(OtherCall.rc, VERR_RESOURCE_BUSY); + else + { + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_CANCEL, 0, NULL); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + return; + } + + SHCLFORMATS fReported = VBOX_SHCL_FMT_NONE; + rc = g_Ext.Transport.pOps->pfnReportFormatsToGuest(g_Ext.Transport.hClient, + VBOX_SHCL_FMT_UNICODETEXT, &fReported); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(Call.fCompleted); + if (!Call.fCompleted) + { + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_CANCEL, 0, NULL); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(Call.fCompleted); + return; + } + RTTESTI_CHECK_RC(Call.rc, VINF_SUCCESS); + RTTESTI_CHECK(aPeek[0].u.uint32 == VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + RTTESTI_CHECK(aPeek[1].u.uint32 == 2); + RTTESTI_CHECK(fReported == VBOX_SHCL_FMT_UNICODETEXT); + + VBOXHGCMSVCPARM aWrongGet[2]; + HGCMSvcSetU32(&aWrongGet[0], VBOX_SHCL_HOST_MSG_QUIT); + HGCMSvcSetU32(&aWrongGet[1], 0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aWrongGet), aWrongGet); + RTTESTI_CHECK_RC(rc, VERR_MISMATCH); + + HGCMSvcSetU32(&aPeek[0], 0); + HGCMSvcSetU32(&aPeek[1], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT, RT_ELEMENTS(aPeek), aPeek); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(aPeek[0].u.uint32 == VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + RTTESTI_CHECK(aPeek[1].u.uint32 == 2); + + VBOXHGCMSVCPARM aGet[2]; + HGCMSvcSetU32(&aGet[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + HGCMSvcSetU32(&aGet[1], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aGet), aGet); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(aGet[0].u.uint32 == VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + RTTESTI_CHECK(aGet[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + + HGCMSvcSetU32(&aPeek[0], 0); + HGCMSvcSetU32(&aPeek[1], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_PEEK_NOWAIT, RT_ELEMENTS(aPeek), aPeek); + RTTESTI_CHECK_RC(rc, VERR_TRY_AGAIN); +} + + +/** + * Checks service ownership of a valid guest clipboard-data reply context. + * + * @param pvClient Connected HGCM client state. + */ +static void tstGuestDataReply(void *pvClient) +{ + RTTestISub("Guest data reply context"); + + PSHCLEVENT pEvent = NULL; + int rc = g_Ext.Transport.pOps->pfnReadDataFromGuestAsync(g_Ext.Transport.hClient, + VBOX_SHCL_FMT_UNICODETEXT, &pEvent); + RTTESTI_CHECK_RC_RETV(rc, VINF_SUCCESS); + RTTESTI_CHECK_RETV(pEvent != NULL); + + VBOXHGCMSVCPARM aGet[2]; + HGCMSvcSetU64(&aGet[0], VBOX_SHCL_HOST_MSG_READ_DATA_CID); + HGCMSvcSetU32(&aGet[1], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aGet), aGet); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + uint64_t const uContext = aGet[0].u.uint64; + RTTESTI_CHECK(aGet[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + + static uint8_t const s_abGuestData[] = { 'g', 'u', 'e', 's', 't', '\0' }; + VBOXHGCMSVCPARM aWrite[VBOX_SHCL_CPARMS_DATA_WRITE]; + HGCMSvcSetU64(&aWrite[0], uContext); + HGCMSvcSetU32(&aWrite[1], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetPv(&aWrite[2], (void *)s_abGuestData, sizeof(s_abGuestData)); + uint32_t const cDataWritesBefore = g_Ext.cDataWrites; + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWrite), aWrite); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(g_Ext.cDataWrites == cDataWritesBefore + 1); + + PSHCLEVENTPAYLOAD pPayload = NULL; + rc = ShClEventWait(pEvent, RT_MS_1SEC, &pPayload); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK(pPayload != NULL); + if (pPayload) + { + RTTESTI_CHECK(pPayload->cbData == sizeof(s_abGuestData)); + RTTESTI_CHECK(memcmp(pPayload->pvData, s_abGuestData, sizeof(s_abGuestData)) == 0); + ShClPayloadDestroy(pPayload); + } + } + RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); +} + + +/** + * Checks the two central guest-to-host POD calls and mode enforcement. + * + * @param pvClient Connected HGCM client state. + */ +static void tstGuestPodCalls(void *pvClient) +{ + RTTestISub("Guest POD protocol"); + + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, VBOX_SHCL_FMT_UNICODETEXT); + int rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(g_Ext.cGuestFormats == 1); + RTTESTI_CHECK(g_Ext.fGuestFormats == VBOX_SHCL_FMT_UNICODETEXT); + + uint8_t abData[sizeof(g_abHostData)]; + VBOXHGCMSVCPARM aRead[3]; + HGCMSvcSetU32(&aRead[0], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetPv(&aRead[1], abData, sizeof(abData)); + HGCMSvcSetU32(&aRead[2], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(aRead[2].u.uint32 == sizeof(g_abHostData)); + RTTESTI_CHECK(memcmp(abData, g_abHostData, sizeof(g_abHostData)) == 0); + + uint8_t bData = 0; + HGCMSvcSetPv(&aRead[1], &bData, sizeof(bData)); + HGCMSvcSetU32(&aRead[2], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_READ, RT_ELEMENTS(aRead), aRead); + RTTESTI_CHECK_RC(rc, VINF_BUFFER_OVERFLOW); + RTTESTI_CHECK(aRead[2].u.uint32 == sizeof(g_abHostData)); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_MODE_HOST_TO_GUEST); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + HGCMSvcSetU32(&Parm, VBOX_SHCL_FMT_UNICODETEXT); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(g_Ext.cGuestFormats == 1); +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Initializes a guest transfer-status reply. */ +static void tstTransferReplyInit(VBOXHGCMSVCPARM aParms[VBOX_SHCL_CPARMS_REPLY_MIN + 1], + uint64_t uContext, SHCLTRANSFERSTATUS enmStatus) +{ + HGCMSvcSetU64(&aParms[0], uContext); + HGCMSvcSetU32(&aParms[1], VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS); + HGCMSvcSetU32(&aParms[2], VINF_SUCCESS); + HGCMSvcSetPv(&aParms[3], NULL, 0); + HGCMSvcSetU32(&aParms[4], enmStatus); +} + + +/** Gets and checks one service-owned transfer status message. */ +static void tstTransferStatusGet(void *pvClient, SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, + SHCLTRANSFERSTATUS enmStatus, int rcTransfer) +{ + VBOXHGCMSVCPARM aParms[VBOX_SHCL_CPARMS_TRANSFER_STATUS]; + HGCMSvcSetU64(&aParms[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aParms[1], 0); + HGCMSvcSetU32(&aParms[2], 0); + HGCMSvcSetU32(&aParms[3], 0); + HGCMSvcSetU32(&aParms[4], 0); + + int const rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aParms), aParms); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + uint64_t const uContext = aParms[0].u.uint64; + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(uContext) == idSession); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(uContext) == idTransfer); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != 0); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != NIL_SHCLEVENTID); + RTTESTI_CHECK(aParms[1].u.uint32 == SHCLTRANSFERDIR_HOST_TO_GUEST); + RTTESTI_CHECK(aParms[2].u.uint32 == enmStatus); + RTTESTI_CHECK((int32_t)aParms[3].u.uint32 == rcTransfer); + RTTESTI_CHECK(aParms[4].u.uint32 == 0); +} + + +/** + * Checks transfer gates, hostile identifiers and one complete service-owned lifecycle. + * + * @param pvClient Connected HGCM client state. + */ +static void tstTransfers(void *pvClient) +{ + RTTestISub("Clipboard transfer protocol"); + + VBOXHGCMSVCPARM aObjectClose[VBOX_SHCL_CPARMS_OBJ_CLOSE]; + HGCMSvcSetU64(&aObjectClose[0], VBOX_SHCL_CONTEXTID_MAKE(UINT16_MAX, 42, 0)); + HGCMSvcSetU64(&aObjectClose[1], 1); + + VBOXHGCMSVCPARM aFeatures[2]; + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + int rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, + RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + PSHCLTRANSFER pDeniedTransfer = (PSHCLTRANSFER)(uintptr_t)1; + rc = g_Ext.Transport.pOps->pfnTransferCreate(g_Ext.Transport.hClient, SHCLTRANSFERDIR_GUEST_TO_HOST, + SHCLSOURCE_REMOTE, NULL, NIL_SHCLTRANSFERID, &pDeniedTransfer); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(pDeniedTransfer == NULL); + + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, + RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_NONE); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + pDeniedTransfer = (PSHCLTRANSFER)(uintptr_t)1; + rc = g_Ext.Transport.pOps->pfnTransferCreate(g_Ext.Transport.hClient, SHCLTRANSFERDIR_GUEST_TO_HOST, + SHCLSOURCE_REMOTE, NULL, NIL_SHCLTRANSFERID, &pDeniedTransfer); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + RTTESTI_CHECK(pDeniedTransfer == NULL); + + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, 0, NULL); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + HGCMSvcSetU32(&aObjectClose[0], 0); + rc = tstGuestCallUntrusted(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, + RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_WRONG_PARAMETER_TYPE); + + HGCMSvcSetU64(&aObjectClose[0], 0); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); + + VBOXHGCMSVCPARM aReply[VBOX_SHCL_CPARMS_REPLY_MIN + 1]; + tstTransferReplyInit(aReply, 0, SHCLTRANSFERSTATUS_REQUESTED); + uint32_t const cQueriesBefore = g_Ext.cTransferCallbackQueries; + uint32_t const cStatusesBefore = g_Ext.cTransferStatuses; + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPLY, VBOX_SHCL_CPARMS_REPLY_MIN, aReply); + RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); + RTTESTI_CHECK(g_Ext.cTransferCallbackQueries == cQueriesBefore); + RTTESTI_CHECK(g_Ext.cTransferStatuses == cStatusesBefore); + + tstTransferReplyInit(aReply, 0, SHCLTRANSFERSTATUS_REQUESTED); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPLY, RT_ELEMENTS(aReply), aReply); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(g_Ext.cTransferCallbackQueries == cQueriesBefore + 1); + RTTESTI_CHECK(g_Ext.cTransferStatuses == cStatusesBefore + 1); + SHCLSESSIONID const idSession = g_Ext.idTransferSession; + RTTESTI_CHECK(idSession != NIL_SHCLSESSIONID); + RTTESTI_CHECK(g_Ext.idTransfer != NIL_SHCLTRANSFERID); + RTTESTI_CHECK(g_Ext.uTransferGeneration != NIL_SHCLTRANSFERGEN); + RTTESTI_CHECK(g_Ext.enmTransferDir == SHCLTRANSFERDIR_HOST_TO_GUEST); + RTTESTI_CHECK(g_Ext.enmTransferSource == SHCLSOURCE_REMOTE); + RTTESTI_CHECK(g_Ext.enmTransferStatus == SHCLTRANSFERSTATUS_REQUESTED); + RTTESTI_CHECK(g_Ext.rcTransfer == VINF_SUCCESS); + + uint32_t const cQueriesAfterRequest = g_Ext.cTransferCallbackQueries; + uint32_t const cStatusesAfterRequest = g_Ext.cTransferStatuses; + tstTransferReplyInit(aReply, VBOX_SHCL_CONTEXTID_MAKE(idSession, g_Ext.idTransfer + 1, 0), + SHCLTRANSFERSTATUS_REQUESTED); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPLY, RT_ELEMENTS(aReply), aReply); + RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); + RTTESTI_CHECK(g_Ext.cTransferCallbackQueries == cQueriesAfterRequest); + RTTESTI_CHECK(g_Ext.cTransferStatuses == cStatusesAfterRequest); + + HGCMSvcSetU64(&aObjectClose[0], VBOX_SHCL_CONTEXTID_MAKE(idSession + 1, g_Ext.idTransfer, 0)); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); + HGCMSvcSetU64(&aObjectClose[0], VBOX_SHCL_CONTEXTID_MAKE(idSession, g_Ext.idTransfer + 1, 0)); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); + + tstTransferStatusGet(pvClient, idSession, g_Ext.idTransfer, SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS); + + PSHCLTRANSFER pTransfer = g_Ext.Transport.pOps->pfnTransferGetByIdRetained(g_Ext.Transport.hClient, + g_Ext.idTransfer); + RTTESTI_CHECK(pTransfer != NULL); + if (pTransfer) + { + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, + RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + rc = g_Ext.Transport.pOps->pfnTransferInit(g_Ext.Transport.hClient, pTransfer); + RTTESTI_CHECK_RC(rc, VERR_ACCESS_DENIED); + + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, + RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + ShClTransferRelease(pTransfer); + } + + VBOXHGCMSVCPARM aCancel[2]; + HGCMSvcSetU64(&aCancel[0], VBOX_SHCL_CONTEXTID_MAKE(idSession, g_Ext.idTransfer, 0)); + HGCMSvcSetU64(&aCancel[1], g_Ext.uTransferGeneration + 1); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_CANCEL, RT_ELEMENTS(aCancel), aCancel); + RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); + + HGCMSvcSetU64(&aCancel[1], g_Ext.uTransferGeneration); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_CANCEL, RT_ELEMENTS(aCancel), aCancel); + RTTESTI_CHECK_RC_OK(rc); + tstTransferStatusGet(pvClient, idSession, g_Ext.idTransfer, SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + + HGCMSvcSetU64(&aObjectClose[0], VBOX_SHCL_CONTEXTID_MAKE(idSession, g_Ext.idTransfer, 0)); + rc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjectClose), aObjectClose); + RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); + rc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_CANCEL, RT_ELEMENTS(aCancel), aCancel); + RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +/** + * Disconnects the client and unloads the service. + * + * @param pvClient Connected HGCM client state. May be NULL. + */ +static void tstShutdown(void *pvClient) +{ + RTTestISub("Service shutdown"); + + int rc; + if (pvClient) + { + rc = g_Table.pfnDisconnect(g_Table.pvService, 1, pvClient); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + RTTESTI_CHECK(g_Ext.cDisconnect == 1); + RTMemFree(pvClient); + } + + rc = g_Table.pfnRegisterExtension(g_Table.pvService, NULL, NULL); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + rc = g_Table.pfnUnload(g_Table.pvService); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); +} + + +/** Testcase entry point. */ +int main(void) +{ + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardHostService", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + void *pvClient = NULL; + int const rc = tstLoadService(); + if (RT_SUCCESS(rc)) + { + tstConnection(&pvClient); + if (pvClient) + { + tstPolicyAndFeatures(pvClient); + tstUntrustedGuestInput(pvClient); + tstMessageQueue(pvClient); + tstGuestDataReply(pvClient); + tstGuestPodCalls(pvClient); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstTransfers(pvClient); +#endif + } + tstShutdown(pvClient); + } + else + RTTestIFailed("Loading the host service failed: %Rrc", rc); + + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp deleted file mode 100644 index e8c0fa27f25d..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardMockHGCM.cpp +++ /dev/null @@ -1,944 +0,0 @@ -/* $Id: tstClipboardMockHGCM.cpp 114425 2026-06-18 08:30:00Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard host service test case. - */ - -/* - * Copyright (C) 2011-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#include -#include -#include -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -# include -# include -# include -# include -#endif -#ifdef RT_OS_WINDOWS -# include -#endif - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - - -/********************************************************************************************************************************* -* Global Variables * -*********************************************************************************************************************************/ -static RTTEST g_hTest; - - -/********************************************************************************************************************************* -* Shared Clipboard testing * -*********************************************************************************************************************************/ -struct CLIPBOARDTESTDESC; -/** Pointer to a test description. */ -typedef CLIPBOARDTESTDESC *PTESTDESC; - -struct CLIPBOARDTESTCTX; -/** Pointer to a test context. */ -typedef CLIPBOARDTESTCTX *PCLIPBOARDTESTCTX; - -typedef DECLCALLBACKTYPE(int, FNTESTSETUP,(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx)); -/** Pointer to a test setup callback. */ -typedef FNTESTSETUP *PFNTESTSETUP; - -typedef DECLCALLBACKTYPE(int, FNTESTEXEC,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)); -/** Pointer to a test exec callback. */ -typedef FNTESTEXEC *PFNTESTEXEC; - -typedef DECLCALLBACKTYPE(int, FNTESTDESTROY,(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx)); -/** Pointer to a test destroy callback. */ -typedef FNTESTDESTROY *PFNTESTDESTROY; - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -typedef struct CLIPBOARDTESTTASKX11 -{ - /** Thread handle for the X11 "populate clipboard" thread. */ - RTTHREAD hThread; - /** Shutdown indicator flag. */ - volatile bool fShutdown; -} CLIPBOARDTESTTASKX11; -#endif - -/** - * Structure for keeping a clipboard test task. - */ -typedef struct CLIPBOARDTESTTASK -{ - SHCLFORMATS enmFmtHst; - SHCLFORMATS enmFmtGst; - /** For testing chunked reads / writes. */ - size_t cbChunk; - /** Data buffer to read / write for this task. - * Can be NULL if not needed. */ - void *pvData; - /** Size (in bytes) of \a pvData. */ - size_t cbData; - /** Number of bytes read / written from / to \a pvData. */ - size_t cbProcessed; -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) - /** X11-specific data */ - CLIPBOARDTESTTASKX11 X11; -#endif -} CLIPBOARDTESTTASK; -typedef CLIPBOARDTESTTASK *PCLIPBOARDTESTTASK; - -/** - * Structure for keeping a clipboard test context. - */ -typedef struct CLIPBOARDTESTCTX -{ - /** The HGCM Mock utils context. */ - TSTHGCMUTILSCTX HGCM; - /** Clipboard-specific task data. */ - CLIPBOARDTESTTASK Task; - struct - { - /** The VbglR3 Shared Clipboard context to work on. */ - VBGLR3SHCLCMDCTX CmdCtx; - } Guest; -} CLIPBOARDTESTCTX; - -/** The one and only clipboard test context. One at a time. */ -CLIPBOARDTESTCTX g_TstCtx; - -/** - * Structure for keeping a clipboard test description. - */ -typedef struct CLIPBOARDTESTDESC -{ - /** The setup callback. */ - PFNTESTSETUP pfnSetup; - /** The exec callback. */ - PFNTESTEXEC pfnExec; - /** The destruction callback. */ - PFNTESTDESTROY pfnDestroy; -} CLIPBOARDTESTDESC; - -typedef struct SHCLCONTEXT -{ -} SHCLCONTEXT; - - -static int tstSetModeRc(PTSTHGCMMOCKSVC pSvc, uint32_t uMode, int rcExpected) -{ - VBOXHGCMSVCPARM aParms[2]; - HGCMSvcSetU32(&aParms[0], uMode); - int rc2 = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, aParms); - RTTESTI_CHECK_MSG_RET(rcExpected == rc2, ("Expected %Rrc, got %Rrc\n", rcExpected, rc2), rc2); - if (RT_SUCCESS(rcExpected)) - { - uint32_t const uModeRet = ShClSvcGetMode(); - RTTESTI_CHECK_MSG_RET(uMode == uModeRet, ("Expected mode %RU32, got %RU32\n", uMode, uModeRet), VERR_WRONG_TYPE); - } - return rc2; -} - -static int tstClipboardSetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uMode) -{ - return tstSetModeRc(pSvc, uMode, VINF_SUCCESS); -} - -static bool tstClipboardGetMode(PTSTHGCMMOCKSVC pSvc, uint32_t uModeExpected) -{ - RT_NOREF(pSvc); - RTTESTI_CHECK_RET(ShClSvcGetMode() == uModeExpected, false); - return true; -} - -static void tstOperationModes(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - uint32_t u32Mode; - int rc; - - RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE"); - - PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); - - /* Reset global variable which doesn't reset itself. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - u32Mode = ShClSvcGetMode(); - RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); - - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - HGCMSvcSetU64(&parms[0], 99); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_HOST_TO_GUEST); - tstSetModeRc(pSvc, 99, VERR_NOT_SUPPORTED); - tstClipboardGetMode(pSvc, VBOX_SHCL_MODE_OFF); -} - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -static void testSetTransferMode(void) -{ - RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE"); - - PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); - - /* Invalid parameter. */ - VBOXHGCMSVCPARM parms[2]; - HGCMSvcSetU64(&parms[0], 99); - int rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - /* Invalid mode. */ - HGCMSvcSetU32(&parms[0], 99); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); - - /* Enable transfers. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - /* Disable transfers again. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_NONE); - rc = TstHgcmMockSvcHostCall(pSvc, NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - -static void testGuestSimple(void) -{ - RTTestISub("Testing client (guest) API - Simple"); - - PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); - - /* Preparations. */ - VBGLR3SHCLCMDCTX Ctx; - RT_ZERO(Ctx); - - /* - * Multiple connects / disconnects. - */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID)); - RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); - /* Report bogus guest features while connecting. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, 0xdeadbeef)); - RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); - - RTTESTI_CHECK_RC_OK(VbglR3ClipboardConnectEx(&Ctx, VBOX_SHCL_GF_0_CONTEXT_ID)); - - /* - * Feature tests. - */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0x0, NULL /* pfHostFeatures */)); - /* Report bogus features to the host. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFeatures(Ctx.idClient, 0xdeadb33f, NULL /* pfHostFeatures */)); - - /* - * Access denied tests. - */ - tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_OFF); - - /* Try reading data from host. */ - uint8_t abData[32]; uint32_t cbIgnored; - RTTESTI_CHECK_RC(VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, - abData, sizeof(abData), &cbIgnored), VERR_ACCESS_DENIED); - /* Try writing data without reporting formats before (legacy). */ - RTTESTI_CHECK_RC(VbglR3ClipboardWriteData(Ctx.idClient, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED); - /* Try writing data without reporting formats before. */ - RTTESTI_CHECK_RC(VbglR3ClipboardWriteDataEx(&Ctx, 0xdeadb33f, abData, sizeof(abData)), VERR_ACCESS_DENIED); - /* Report bogus formats to the host. */ - RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f), VERR_ACCESS_DENIED); - /* Report supported formats to host. */ - RTTESTI_CHECK_RC(VbglR3ClipboardReportFormats(Ctx.idClient, - VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML), - VERR_ACCESS_DENIED); - /* - * Access allowed tests. - */ - tstClipboardSetMode(pSvc, VBOX_SHCL_MODE_BIDIRECTIONAL); - - /* Try reading data from host. */ - int rc = VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, - abData, sizeof(abData), &cbIgnored); - /* - * The VbglR3ClipboardConnectEx() call above reaches the X11 - * ShClBackendConnect() routine which calls ShClX11ThreadStart() to start the - * "SHCLX11" thread, an Xt thread for handling the Shared Clipboard, which - * sits in clipThreadMain() where it loops calling XtGetSelectionValue(3X11). - * When the "SHCLX11" thread is starting up at connect time, clipThreadMain() - * calls clipQueryX11Targets() which calls XtGetSelectionValue(3X11) to request - * the supported targets of the clipboard selection and then asynchronously calls - * the specified callback, clipQueryX11TargetsCallback(), with the results. - * Meanwhile, clipThreadMain() signals its parent and ShClX11ThreadStart() then - * returns as does ShClBackendConnect(). Attempting to read from the clipboard - * here before clipQueryX11TargetsCallback() returns will fail when - * ShClBackendReadData() -> ShClX11ReadDataFromX11() -> - * shClX11ReadDataFromX11Internal() -> ShClX11ReadDataFromX11Async() -> - * ShClX11ReadDataFromX11Worker() finds the clipboard busy (fXtBusy == true) - * and returns VERR_TRY_AGAIN. - */ - if (rc == VERR_TRY_AGAIN) - { - RTThreadSleep(RT_MS_1SEC); - rc = VbglR3ClipboardReadData(Ctx.idClient, VBOX_SHCL_FMT_UNICODETEXT, - abData, sizeof(abData), &cbIgnored); - } - RTTESTI_CHECK_RC_OK(rc); - /* Report bogus formats to the host. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, 0xdeadb33f)); - /* Report supported formats to host. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardReportFormats(Ctx.idClient, - VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_BITMAP | VBOX_SHCL_FMT_HTML)); - /* Tear down. */ - RTTESTI_CHECK_RC_OK(VbglR3ClipboardDisconnectEx(&Ctx)); -} - -static RTUTF16 tstGetRandUtf8(void) -{ - return RTRandU32Ex(0x20, 0x7A); -} - -static char *tstGenerateUtf8StringA(uint32_t uCch) -{ - char * pszRand = (char *)RTMemAlloc(uCch + 1); - for (uint32_t i = 0; i < uCch; i++) - pszRand[i] = tstGetRandUtf8(); - pszRand[uCch] = 0; - return pszRand; -} - -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) -static RTUTF16 tstGetRandUtf16(void) -{ - RTUTF16 wc; - do - { - wc = (RTUTF16)RTRandU32Ex(1, 0xfffd); - } while (wc >= 0xd800 && wc <= 0xdfff); - return wc; -} - -static PRTUTF16 tstGenerateUtf16StringA(uint32_t uCch) -{ - PRTUTF16 pwszRand = (PRTUTF16)RTMemAlloc((uCch + 1) * sizeof(RTUTF16)); - for (uint32_t i = 0; i < uCch; i++) - pwszRand[i] = tstGetRandUtf16(); - pwszRand[uCch] = 0; - return pwszRand; -} -#endif /* RT_OS_WINDOWS) || RT_OS_OS2 */ - -static void testSetHeadless(void) -{ - RTTestISub("Testing HOST_FN_SET_HEADLESS"); - - PTSTHGCMMOCKSVC pSvc = TstHgcmMockSvcInst(); - - VBOXHGCMSVCPARM parms[2]; - HGCMSvcSetU32(&parms[0], false); - int rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - bool fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless)); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 0, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - HGCMSvcSetU64(&parms[0], 99); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - HGCMSvcSetU32(&parms[0], true); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); - HGCMSvcSetU32(&parms[0], 99); - rc = pSvc->fnTable.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); -} - -static void testHostCall(void) -{ - tstOperationModes(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - testSetTransferMode(); -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - testSetHeadless(); -} - - -/********************************************************************************************************************************* - * Test: Guest reading from host * - ********************************************************************************************************************************/ -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -/* Called from SHCLX11 thread. */ -static DECLCALLBACK(int) tstTestReadFromHost_ReportFormatsCallback(PSHCLCONTEXT pCtx, uint32_t fFormats, void *pvUser) -{ - RT_NOREF(pCtx, fFormats, pvUser); - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "tstTestReadFromHost_SvcReportFormatsCallback: fFormats=%#x\n", fFormats); - return VINF_SUCCESS; -} - -/* Called by the backend, e.g. for X11 in the SHCLX11 thread. */ -static DECLCALLBACK(int) tstTestReadFromHost_OnClipboardReadCallback(PSHCLCONTEXT pCtx, - SHCLFORMAT uFmt, void **ppv, size_t *pcb, void *pvUser) -{ - RT_NOREF(pCtx, uFmt, pvUser); - - PCLIPBOARDTESTTASK pTask = (PCLIPBOARDTESTTASK)TstHGCMUtilsTaskGetCurrent(&g_TstCtx.HGCM)->pvUser; - - void *pvData = NULL; - size_t cbData = pTask->cbData - pTask->cbProcessed; - if (cbData) - { - pvData = RTMemDup((uint8_t *)pTask->pvData + pTask->cbProcessed, cbData); - AssertPtr(pvData); - } - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host reporting back %RU32 bytes of data\n", cbData); - - *ppv = pvData; - *pcb = cbData; - - return VINF_SUCCESS; -} -#endif /* (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */ - -typedef struct TSTUSERMOCK -{ -#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) - SHCLX11CTX X11Ctx; -#endif - PSHCLCONTEXT pCtx; -} TSTUSERMOCK; -typedef TSTUSERMOCK *PTSTUSERMOCK; - -static void tstTestReadFromHost_MockInit(PTSTUSERMOCK pUsrMock, const char *pszName) -{ -#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) - SHCLCALLBACKS Callbacks; - RT_ZERO(Callbacks); - Callbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback; - Callbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback; - - pUsrMock->pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); - AssertPtrReturnVoid(pUsrMock->pCtx); - - ShClX11Init(&pUsrMock->X11Ctx, &Callbacks, pUsrMock->pCtx, false); - ShClX11ThreadStartEx(&pUsrMock->X11Ctx, pszName, false /* fGrab */); - /* Give the clipboard time to synchronise. */ - RTThreadSleep(500); -#else - RT_NOREF(pUsrMock, pszName); -#endif /* RT_OS_LINUX */ -} - -static void tstTestReadFromHost_MockDestroy(PTSTUSERMOCK pUsrMock) -{ -#if defined(RT_OS_LINUX) || defined(RT_OS_SOLARIS) - ShClX11ThreadStop(&pUsrMock->X11Ctx); - ShClX11Term(&pUsrMock->X11Ctx); - RTMemFree(pUsrMock->pCtx); -#else - RT_NOREF(pUsrMock); -#endif -} - -static int tstTestReadFromHost_DoIt(PCLIPBOARDTESTCTX pCtx, PCLIPBOARDTESTTASK pTask) -{ - size_t cbDst = RT_MAX(_64K, pTask->cbData); - uint8_t *pabDst = (uint8_t *)RTMemAllocZ(cbDst); - AssertPtrReturn(pabDst, VERR_NO_MEMORY); - - AssertPtr(pTask->pvData); /* Racing condition with host thread? */ - Assert(pTask->cbChunk); /* Buggy test? */ - Assert(pTask->cbChunk <= pTask->cbData); /* Ditto. */ - - size_t cbToRead = pTask->cbData; - switch (pTask->enmFmtGst) - { - case VBOX_SHCL_FMT_UNICODETEXT: -#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */ - cbToRead *= sizeof(RTUTF16); -#endif - break; - - default: - break; - } - - PVBGLR3SHCLCMDCTX pCmdCtx = &pCtx->Guest.CmdCtx; - - /* Do random chunked reads. */ - uint32_t const cChunkedReads = RTRandU32Ex(1, 16); - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "%RU32 chunked reads\n", cChunkedReads); - for (uint32_t i = 0; i < cChunkedReads; i++) - { - /* Note! VbglR3ClipboardReadData() currently does not support chunked reads! - * It in turn returns VINF_BUFFER_OVERFLOW when the supplied buffer was too small. */ - - uint32_t cbChunk = RTRandU32Ex(1, (uint32_t)(pTask->cbData / cChunkedReads)); - uint32_t cbRead = 0; - RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Guest trying to read %RU32 bytes\n", cbChunk); - int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, cbChunk, &cbRead); - if ( vrc2 == VINF_SUCCESS - && cbRead == 0) /* No data there yet? */ - { - RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "No data (yet) from host\n"); - RTThreadSleep(10); - continue; - } - RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Trying to read host clipboard data with a %RU32 byte buffer -> %Rrc (%RU32)\n", cbChunk, vrc2, cbRead); - RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_BUFFER_OVERFLOW, (g_hTest, "Got %Rrc, expected VINF_BUFFER_OVERFLOW\n", vrc2)); - } - - /* Last read: Read the data with a buffer big enough. This must succeed. */ - RTTestPrintf(g_hTest, RTTESTLVL_ALWAYS, "Reading full data (%zu)\n", pTask->cbData); - uint32_t cbRead = 0; - int vrc2 = VbglR3ClipboardReadData(pCmdCtx->idClient, pTask->enmFmtGst, pabDst, (uint32_t)cbDst, &cbRead); - RTTEST_CHECK_MSG(g_hTest, vrc2 == VINF_SUCCESS, (g_hTest, "Got %Rrc, expected VINF_SUCCESS\n", vrc2)); - RTTEST_CHECK_MSG(g_hTest, cbRead == cbToRead, (g_hTest, "Read %RU32 bytes, expected %zu\n", cbRead, cbToRead)); - - if (pTask->enmFmtGst == VBOX_SHCL_FMT_UNICODETEXT) - RTTEST_CHECK_MSG(g_hTest, RTUtf16ValidateEncoding((PRTUTF16)pabDst) == VINF_SUCCESS, (g_hTest, "Read data is not valid UTF-16\n")); - if (cbRead == cbToRead) - { -#ifndef RT_OS_WINDOWS /** @todo Not sure about OS/2. */ - PRTUTF16 pwszSrc = NULL; - RTTEST_CHECK(g_hTest, RT_SUCCESS(RTStrToUtf16((const char *)pTask->pvData, &pwszSrc))); - RTTEST_CHECK_MSG(g_hTest, memcmp(pwszSrc, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n")); - RTUtf16Free(pwszSrc); -#else - RTTEST_CHECK_MSG(g_hTest, memcmp(pTask->pvData, pabDst, cbRead) == 0, (g_hTest, "Read data does not match host data\n")); -#endif - } - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Read data from host:\n%.*Rhxd\n", cbRead, pabDst); - - RTMemFree(pabDst); - - return VINF_SUCCESS; -} - -static DECLCALLBACK(int) tstTestReadFromHost_ThreadGuest(PTSTHGCMUTILSCTX pCtx, void *pvCtx) -{ - RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */ - - PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvCtx; - AssertPtr(pTstCtx); - - RT_ZERO(pTstCtx->Guest.CmdCtx); - RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardConnectEx(&pTstCtx->Guest.CmdCtx, VBOX_SHCL_GF_0_CONTEXT_ID)); - - RTThreadSleep(1000); /* Fudge; wait until the host has prepared the data for the clipboard. */ - - PCLIPBOARDTESTTASK pTstTask = (PCLIPBOARDTESTTASK)pCtx->Task.pvUser; - AssertPtr(pTstTask); - tstTestReadFromHost_DoIt(pTstCtx, pTstTask); - - RTTEST_CHECK_RC_OK(g_hTest, VbglR3ClipboardDisconnectEx(&pTstCtx->Guest.CmdCtx)); - - /* Signal that the task ended. */ - TstHGCMUtilsTaskSignal(&pCtx->Task, VINF_SUCCESS); - - return VINF_SUCCESS; -} - -static DECLCALLBACK(int) tstTestReadFromHost_ClientConnectedCallback(PTSTHGCMUTILSCTX pCtx, PTSTHGCMMOCKCLIENT pClient, - void *pvUser) -{ - RT_NOREF(pCtx, pClient); - - PCLIPBOARDTESTCTX pTstCtx = (PCLIPBOARDTESTCTX)pvUser; - AssertPtr(pTstCtx); RT_NOREF(pTstCtx); - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Client %RU32 connected\n", pClient->idClient); - return VINF_SUCCESS; -} - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -/** - * This is an X11 clipboard "copy" thread which takes ownership of the - * clipboard, waits for requests of the clipboard selection contents from - * X11 clients, and processes each request by copying the randomly generated - * UTF8 data passed in to the clipboard selection before finally notifying - * the requesting X client when complete. - */ -static DECLCALLBACK(int) tstSetClipboardContents(RTTHREAD hThread, void *pvUser) -{ - PCLIPBOARDTESTTASK pTask = (PCLIPBOARDTESTTASK)pvUser; - - Display *pX11Display = XOpenDisplay(NULL); - if (!pX11Display) - { - RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "XOpenDisplay(3X11) failed\n"); - return VERR_NOT_AVAILABLE; - } - - /* Create an unmapped subwindow which will own the "CLIPBOARD" selection and - * will receive messages from X clients accessing the clipboard. */ - Window window = XCreateSimpleWindow(pX11Display, XDefaultRootWindow(pX11Display), 0, 0, 1, 1, 0, 0, 0); - - /* Populate the atom identifiers needed for our clipboard operations. */ - Atom clipboard = XInternAtom(pX11Display, "CLIPBOARD", False); - Atom targets = XInternAtom(pX11Display, "TARGETS", False); - Atom utf8_string = XInternAtom(pX11Display, "UTF8_STRING", False); - - /* In order to copy data to the clipboard we must first have our window - * become the owner of the "CLIPBOARD" selection. */ - XSetSelectionOwner(pX11Display, clipboard, window, CurrentTime); - if (XGetSelectionOwner(pX11Display, clipboard) != window) - { - RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, - "XSetSelectionOwner(3X11): failed to become owner of clipboard selection\n"); - return VERR_ACCESS_DENIED; - } - - /* Initial setup completed, notify our callee that we are now ready for action. */ - RTThreadUserSignal(hThread); - - while (!ASMAtomicReadBool(&pTask->X11.fShutdown)) - { - /* Check the event queue to see if any events have been received from the X - * server. */ - if (XPending(pX11Display) > 0) - { - /* Copy the first event from the event queue into the specified XEvent - * structure and remove it from the queue. */ - XEvent event; - XNextEvent(pX11Display, &event); - - /* X11 clients which want to paste the contents of the clipboard selection - * call XConvertSelection(3X11) which the X server responds to by sending - * a 'SelectionRequest' event to the X11 client which currently owns the - * clipboard selection. This happens in our case via: shClSvcConnect() -> - * ShClBackendConnect() -> ShClX11ThreadStart() -> ShClX11ThreadStartEx() -> - * clipThreadMain() -> clipQueryX11Targets() -> XtGetSelectionValue(). - * XtGetSelectionValue() calls XConvertSelection() internally. */ - if (event.type == SelectionRequest) - { - /* The 'SelectionRequest' event contains details of the X11 client requestor - * such as their 'window' ('requestor'), the desired format of the clipboard - * contents ('target'), and which property on their window that they would - * like the clipboard contents copied to ('property'). We use this data to - * populate a 'SelectionNotify' event which we send back to the requestor. */ - XSelectionRequestEvent *pReq = &event.xselectionrequest; - XSelectionEvent selectionNotifyEvent = { 0 }; - - selectionNotifyEvent.type = SelectionNotify; - selectionNotifyEvent.display = pReq->display; - selectionNotifyEvent.requestor = pReq->requestor; - selectionNotifyEvent.selection = pReq->selection; - selectionNotifyEvent.target = pReq->target; - selectionNotifyEvent.property = pReq->property; - selectionNotifyEvent.time = pReq->time; - - /* X11 clients typically send an initial 'SelectionRequest' event containing - * a 'target' containing the "TARGETS" atom to request a list of valid - * supported target atoms. After taking ownership of the "CLIPBOARD" - * selection other X11 clients may also send a 'SelectionRequest' event with - * a 'target' of the "TARGETS" atom. */ - if (selectionNotifyEvent.target == targets) - { - Atom supported[] = { targets, utf8_string }; - XChangeProperty(pX11Display, pReq->requestor, pReq->property, XA_ATOM, 32, - PropModeReplace, (unsigned char *)supported, RT_ELEMENTS(supported)); - } - else if (selectionNotifyEvent.target == utf8_string) - { - /* Update the property which the requestor chose to contain the clipboard - * selection contents on the requetor's window with the UTF8-formatted - * contents of the clipboard. */ - XChangeProperty(pX11Display, pReq->requestor, pReq->property, pReq->target, 8, - PropModeReplace, (unsigned char *)pTask->pvData, pTask->cbData); - } - else - { - /* We don't support the requested format. Note that if the size of the - * request to the X server exceeds the maximum request size described in the - * X Consortium's Inter-Client Communication Conventions Manual (ICCM) - * section 2.5 'Large Data Transfers' then the requestor will send a target - * of type 'INCR' meaning the data will be sent incrementally. The Xlib - * Programming Manual describes how the maximum request size can be - * calculated: - * maxsize = XExtendedMaxRequestSize(); - * if (!maxsize) maxsize = XMaxRequestSize(); - * maxsize *= 4 - * On Solaris 11.4 and various Linux distros the lower bound is 64K * 4 which - * is greater than the possible maximum size passed in here of 8K so there is - * no need to include support for the 'INCR' target here. - */ - selectionNotifyEvent.property = None; - } - - /* Send a 'SelectionNotify' event to the requestor of the clipboard - * selection contents with either the list of supported targets or else the - * clipboard contents in their chosen property. */ - XSendEvent(pX11Display, selectionNotifyEvent.requestor, True, 0, (XEvent *)&selectionNotifyEvent); - XFlush(pX11Display); - } - } - else - RTThreadSleep(RT_MS_1SEC / 2); - } - - XDestroyWindow(pX11Display, window); - XCloseDisplay(pX11Display); - - return VINF_SUCCESS; -} -#endif - -static DECLCALLBACK(int) tstTestReadFromHostSetup(PCLIPBOARDTESTCTX pTstCtx, void **ppvCtx) -{ - RT_NOREF(ppvCtx); - int rc = VINF_SUCCESS; - - /* Set the right clipboard mode, so that the guest can read from the host. */ - tstClipboardSetMode(TstHgcmMockSvcInst(), VBOX_SHCL_MODE_BIDIRECTIONAL); - - /* Start the host thread first, so that the guest thread can connect to it later. */ - TSTHGCMUTILSHOSTCALLBACKS HostCallbacks; - RT_ZERO(HostCallbacks); - HostCallbacks.pfnOnClientConnected = tstTestReadFromHost_ClientConnectedCallback; - TstHGCMUtilsHostThreadStart(&pTstCtx->HGCM, &HostCallbacks, pTstCtx /* pvUser */); - - PCLIPBOARDTESTTASK pTask = &pTstCtx->Task; - AssertPtr(pTask); - pTask->enmFmtGst = VBOX_SHCL_FMT_UNICODETEXT; - pTask->enmFmtHst = pTask->enmFmtGst; - pTask->cbChunk = RTRandU32Ex(1, 512); - pTask->cbData = RT_ALIGN_32(pTask->cbChunk * RTRandU32Ex(1, 16), 2); - Assert(pTask->cbData % sizeof(RTUTF16) == 0); -#if !defined(RT_OS_WINDOWS) && !defined(RT_OS_OS2) - pTask->pvData = tstGenerateUtf8StringA(pTask->cbData); - pTask->cbData++; /* Add terminating zero. */ - pTask->X11.fShutdown = false; - rc = RTThreadCreate(&pTask->X11.hThread, tstSetClipboardContents, pTask, 0, RTTHREADTYPE_DEFAULT, - RTTHREADFLAGS_WAITABLE, "X11Copy"); - if (RT_SUCCESS(rc)) - rc = RTThreadUserWait(pTask->X11.hThread, RT_MS_5SEC); - if (RT_FAILURE(rc)) - return VERR_NOT_SUPPORTED; - -#else - pTask->pvData = tstGenerateUtf16StringA((uint32_t)(pTask->cbData /* We use bytes == chars here */)); - pTask->cbData *= sizeof(RTUTF16); - pTask->cbData += sizeof(RTUTF16); /* Add terminating zero. */ -#endif - pTask->cbProcessed = 0; - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) - /* Initialize the Shared Clipboard backend callbacks. */ - PSHCLBACKEND pBackend = ShClSvcGetBackend(); - - SHCLCALLBACKS ShClCallbacks; - RT_ZERO(ShClCallbacks); - ShClCallbacks.pfnReportFormats = tstTestReadFromHost_ReportFormatsCallback; - ShClCallbacks.pfnOnClipboardRead = tstTestReadFromHost_OnClipboardReadCallback; - ShClBackendSetCallbacks(pBackend, &ShClCallbacks); -#elif defined (RT_OS_WINDOWS) - rc = ShClWinOpen(GetDesktopWindow()); - if (RT_SUCCESS(rc)) - { - rc = ShClWinDataWrite(CF_UNICODETEXT, pTask->pvData, (uint32_t)pTask->cbData); - ShClWinClose(); - } -#endif /* defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) */ - - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Host data (%RU32):\n%.*Rhxd\n", pTask->cbData, pTask->cbData, pTask->pvData); - return rc; -} - -static DECLCALLBACK(int) tstTestReadFromHostExec(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx) -{ - RT_NOREF(pvCtx); - - RTTestISub("Testing guest reading from the host clipboard"); - - TstHGCMUtilsGuestThreadStart(&pTstCtx->HGCM, tstTestReadFromHost_ThreadGuest, pTstCtx); - - PTSTHGCMUTILSTASK pHGCMTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(&pTstCtx->HGCM); - - bool fUseMock = false; - TSTUSERMOCK UsrMock; - if (fUseMock) - tstTestReadFromHost_MockInit(&UsrMock, "tstX11Hst"); - - /* Wait until the task has been finished. */ - TstHGCMUtilsTaskWait(pHGCMTask, RT_MS_30SEC); - - if (fUseMock) - tstTestReadFromHost_MockDestroy(&UsrMock); - - return VINF_SUCCESS; -} - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) -/** - * Stops the X11 "copy" thread (tstSetClipboardContents()). - * - * @return VBox status code. - * @param pTstCtx A pointer to a clipboard test task which contains - * the X11 "copy" thread's details. - */ -static int tstClipboardCopyThreadStop(PCLIPBOARDTESTCTX pTstCtx) -{ - PCLIPBOARDTESTTASK pTask = &pTstCtx->Task; - ASMAtomicWriteBool(&pTask->X11.fShutdown, true); - - int rcThread; - int rc = RTThreadWait(pTask->X11.hThread, RT_MS_30SEC, &rcThread); - if (RT_SUCCESS(rc)) - rc = rcThread; - if (RT_SUCCESS(rc)) - pTask->X11.hThread = NIL_RTTHREAD; - - return rc; -} -#endif - -static DECLCALLBACK(int) tstTestReadFromHostDestroy(PCLIPBOARDTESTCTX pTstCtx, void *pvCtx) -{ - RT_NOREF(pvCtx); - int vrc; - -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) - vrc = tstClipboardCopyThreadStop(pTstCtx); - if (RT_FAILURE(vrc)) - RTTestPrintf(g_hTest, RTTESTLVL_FAILURE, "tstSetClipboardContents() failed: rc=%Rrc\n", vrc); -#endif - - vrc = TstHGCMUtilsGuestThreadStop(&pTstCtx->HGCM); - AssertRC(vrc); - - vrc = TstHGCMUtilsHostThreadStop(&pTstCtx->HGCM); - AssertRC(vrc); - - return vrc; -} - - -/********************************************************************************************************************************* -* Main * -*********************************************************************************************************************************/ - -/** Test definition table. */ -CLIPBOARDTESTDESC g_aTests[] = -{ - /* Tests guest reading clipboard data from the host. */ - { tstTestReadFromHostSetup, tstTestReadFromHostExec, tstTestReadFromHostDestroy } -}; -/** Number of tests defined. */ -unsigned g_cTests = RT_ELEMENTS(g_aTests); - -static int tstOne(PTESTDESC pTstDesc) -{ - PCLIPBOARDTESTCTX pTstCtx = &g_TstCtx; - - void *pvCtx; - int rc = pTstDesc->pfnSetup(pTstCtx, &pvCtx); - if (RT_SUCCESS(rc)) - { - rc = pTstDesc->pfnExec(pTstCtx, pvCtx); - - int rc2 = pTstDesc->pfnDestroy(pTstCtx, pvCtx); - if (RT_SUCCESS(rc)) - rc = rc2; - } - - return rc; -} - -int main() -{ - /* - * Init the runtime, test and say hello. - */ - RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardMockHGCM", &g_hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - RTTestBanner(g_hTest); - - PTSTHGCMMOCKSVC const pSvc = TstHgcmMockSvcInst(); - TstHgcmMockSvcCreate(pSvc); - TstHgcmMockSvcStart(pSvc); - - RT_ZERO(g_TstCtx); - - PTSTHGCMUTILSCTX pCtx = &g_TstCtx.HGCM; - TstHGCMUtilsCtxInit(pCtx, pSvc); - - PTSTHGCMUTILSTASK pTask = (PTSTHGCMUTILSTASK)TstHGCMUtilsTaskGetCurrent(pCtx); - TstHGCMUtilsTaskInit(pTask); - pTask->pvUser = &g_TstCtx.Task; - - /* - * Run the tests. The tstOne() and testGuestSimple() tests rely on an X11 - * display on Unix systems so skip them if not applicable. - */ -#if defined (RT_OS_LINUX) || defined (RT_OS_SOLARIS) - VBGHDISPLAYSERVERTYPE const enmDisplayType = VBGHDisplayServerTypeDetect(); - RTTestPrintf(g_hTest, RTTESTLVL_DEBUG, "Display server type = %s\n", VBGHDisplayServerTypeToStr(enmDisplayType)); - if (enmDisplayType == VBGHDISPLAYSERVERTYPE_X11) -#endif - { - for (unsigned i = 0; i < RT_ELEMENTS(g_aTests); i++) - tstOne(&g_aTests[i]); - - testGuestSimple(); - } - - testHostCall(); - - TstHGCMUtilsTaskDestroy(pTask); - - TstHgcmMockSvcStop(pSvc); - TstHgcmMockSvcDestroy(pSvc); - - /* - * Summary - */ - return RTTestSummaryAndDestroy(g_hTest); -} - diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp deleted file mode 100644 index 2fda14eec094..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceHost.cpp +++ /dev/null @@ -1,1057 +0,0 @@ -/* $Id: tstClipboardServiceHost.cpp 114650 2026-07-08 09:14:39Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard host service test case. - */ - -/* - * Copyright (C) 2011-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#define LOG_ENABLED -#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD -#include - -#include -#include -#include -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# include -# include "VBoxSharedClipboardSvc-transfers.h" -#endif - -#include -#include -#include -#include -#include - -extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad (VBOXHGCMSVCFNTABLE *ptable); -/** - * The following no-op functions which correspond to their Shared Clipboard - * backend namesakes (ShClBackend*()) are used by the dispatcher function - * below (tstHgcmMockSvcDispatcher()) for intercepting unused backend calls. - * - * Note: These host service tests exercise the HGCM service layer, - * not the platform clipboard backends! - */ -static bool g_fBackendConnectCalled = false; -static bool g_fBackendConnectHeadless = false; -static int tstShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) { pBackend->pHelpers = pTable->pHelpers; return VINF_SUCCESS; } -static void tstShClBackendDestroy(PSHCLBACKEND) { } -static int tstShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool fHeadless) -{ - g_fBackendConnectCalled = true; - g_fBackendConnectHeadless = fHeadless; - return VINF_SUCCESS; -} -static int tstShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } -static int tstShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } -static int tstShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } -static int tstShClBackendReportFormatsToGuest(PSHCLBACKEND, PSHCLCLIENT, uint32_t) { AssertFailed(); return VINF_SUCCESS; } -static int tstShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } -static int tstShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } - -static SHCLCLIENT g_Client; -static VBOXHGCMSVCHELPERS g_Helpers = { NULL }; - -/** Simple call handle structure for the guest call completion callback */ -struct VBOXHGCMCALLHANDLE_TYPEDEF -{ - /** Where to store the result code */ - int32_t rc; -}; - -/** Call completion callback for guest calls. */ -static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) -{ - callHandle->rc = rc; - return VINF_SUCCESS; -} - -/** - * A copy of the GuestShCl::hgcmDispatcher() dispatcher routine which - * handles callbacks from the Shared Clipboard host service. For the - * variety of tests here we only need ShClBackendInit() to get called to - * setup the shared clipboard for the tests and the remainder of the - * backend routines are routed to no-op equivalent routines. - */ -DECLCALLBACK(int) tstHgcmMockSvcDispatcher(void *pvExtension, uint32_t u32Function, - void *pvParms, uint32_t cbParms) -{ - NOREF(pvExtension); - int rc = VINF_SUCCESS; - PSHCLEXTPARMS pParms = (PSHCLEXTPARMS)pvParms; /* pParms might be NULL, depending on the message. */ - - LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); - - switch (u32Function) - { - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: // via VBOX_SHCL_GUEST_FN_REPORT_FORMATS in the guest - { - PSHCLCLIENT pClient = pParms->u.ReportFormats.pClient; - SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; - - rc = tstShClBackendReportFormats(pClient->pBackend, pClient, fFormats); - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Reporting guest clipboard formats to the host failed with %Rrc\n", rc)); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - { - PSHCLCLIENT pClient = pParms->u.ReportFormats.pClient; - SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; - - rc = tstShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: // via VBOX_SHCL_GUEST_FN_DATA_READ in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - SHCLCLIENTCMDCTX cmdCtx; - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - rc = tstShClBackendReadData(pClient->pBackend, pClient, &cmdCtx, fFormats, pvData, cbData, - &pParms->u.ReadWriteData.cbActual); - if (RT_SUCCESS(rc)) - LogRel2(("Shared Clipboard: Read host clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, - pParms->u.ReadWriteData.cbActual)); - else - LogRel(("Shared Clipboard: Reading host clipboard data failed with %Rrc\n", rc)); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: // via VBOX_SHCL_GUEST_FN_DATA_WRITE in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - PSHCLCLIENTCMDCTX pCmdCtx = pParms->u.ReadWriteData.pCmdCtx; - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - rc = tstShClBackendWriteData(pClient->pBackend, pClient, pCmdCtx, fFormats, pvData, cbData); - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Writing guest clipboard data to the host failed with %Rrc\n", rc)); - /* Complete any pending events. */ - int rc2 = ShClSvcGuestDataSignal(pClient, pCmdCtx, fFormats, pvData, cbData); - if (RT_FAILURE(rc2)) - LogRel(("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", rc2)); - AssertRC(rc2); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - VBOXHGCMSVCFNTABLE *pTable = pParms->u.ReadWriteData.pTable; - rc = tstShClBackendInit(pBackend, pTable); - break; - } - - // via VbglR3HGCMDisconnect()->...HGCMService::DisconnectClient()->...HGCMService::instanceDestroy()->...shClSvcUnload() - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - tstShClBackendDestroy(pBackend); - rc = VINF_SUCCESS; - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: // via VbglR3ClipboardConnect()->VbglR3HGCMConnect() in the guest - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - bool fHeadless = pParms->u.ReadWriteData.fHeadless; - rc = tstShClBackendConnect(pBackend, pClient, fHeadless); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: // via VbglR3ClipboardDisconnect()->VbglR3HGCMDisconnect() in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = tstShClBackendDisconnect(pClient->pBackend, pClient); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = tstShClBackendSync(pBackend, pClient); - break; - } - - default: - break; - } - - return rc; -} - -static int setupTable(VBOXHGCMSVCFNTABLE *pTable) -{ - pTable->cbSize = sizeof(*pTable); - pTable->u32Version = VBOX_HGCM_SVC_VERSION; - g_Helpers.pfnCallComplete = callComplete; - pTable->pHelpers = &g_Helpers; - int rc = VBoxHGCMSvcLoad(pTable); - RTTESTI_CHECK_MSG_RET(RT_SUCCESS(rc), ("rc=%Rrc\n", rc), rc); - return pTable->pfnRegisterExtension(pTable->pvService, tstHgcmMockSvcDispatcher, NULL); -} - -static void testSetMode(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - uint32_t u32Mode; - int rc; - - RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_MODE"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - /* Reset global variable which doesn't reset itself. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_OFF); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - u32Mode = ShClSvcGetMode(); - RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); - - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 0, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - HGCMSvcSetU64(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_HOST_TO_GUEST); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - u32Mode = ShClSvcGetMode(); - RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_HOST_TO_GUEST, ("u32Mode=%u\n", (unsigned) u32Mode)); - - HGCMSvcSetU32(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_NOT_SUPPORTED); - - u32Mode = ShClSvcGetMode(); - RTTESTI_CHECK_MSG(u32Mode == VBOX_SHCL_MODE_OFF, ("u32Mode=%u\n", (unsigned) u32Mode)); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -static void testSetTransferMode(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - - RTTestISub("Testing VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE"); - int rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - /* Invalid parameter. */ - HGCMSvcSetU64(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - /* Invalid mode. */ - HGCMSvcSetU32(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); - - /* Enable transfers. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - /* Disable transfers again. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_NONE); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -static void testGetTransferStatusMessage(VBOXHGCMSVCFNTABLE *pTable, SHCLSESSIONID idSessionExpected, - SHCLTRANSFERID idTransferExpected, SHCLTRANSFERSTATUS enmStatusExpected, - int rcTransferExpected) -{ - struct VBOXHGCMSVCPARM aStatusParms[VBOX_SHCL_CPARMS_TRANSFER_STATUS]; - VBOXHGCMCALLHANDLE_TYPEDEF call; - - HGCMSvcSetU64(&aStatusParms[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); - HGCMSvcSetU32(&aStatusParms[1], 0); - HGCMSvcSetU32(&aStatusParms[2], 0); - HGCMSvcSetU32(&aStatusParms[3], 0); - HGCMSvcSetU32(&aStatusParms[4], 0); - - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - pTable->pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatusParms), aStatusParms, 0); - RTTESTI_CHECK_RC_OK(call.rc); - uint64_t const uContext = aStatusParms[0].u.uint64; - RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(uContext) == idSessionExpected); - RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(uContext) == idTransferExpected); - RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != 0); - RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_EVENT(uContext) != NIL_SHCLEVENTID); - RTTESTI_CHECK(aStatusParms[2].u.uint32 == enmStatusExpected); - RTTESTI_CHECK((int32_t)aStatusParms[3].u.uint32 == rcTransferExpected); -} - -static void testSetTransferKeyParms(VBOXHGCMSVCPARM aParms[], SHCLSESSIONID idSession, - SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration) -{ - HGCMSvcSetU64(&aParms[0], VBOX_SHCL_CONTEXTID_MAKE(idSession, idTransfer, 0)); - HGCMSvcSetU64(&aParms[1], uGeneration); -} - -/** - * Tests transfer format filtering for disabled or unsupported transfers. - */ -static void testTransferFormatFiltering(void) -{ - RTTestISub("Testing transfer format filtering"); - - static const struct - { - const char *pszName; - uint32_t fTransferMode; - uint64_t fGuestFeatures0; - SHCLFORMATS fExpectedHostToGuest; - SHCLFORMATS fExpectedGuestToHost; - } s_aTests[] = - { - { "disabled", VBOX_SHCL_TRANSFER_MODE_F_NONE, - VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS, - VBOX_SHCL_FMT_UNICODETEXT, VBOX_SHCL_FMT_UNICODETEXT }, - { "no features", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, VBOX_SHCL_GF_NONE, - VBOX_SHCL_FMT_UNICODETEXT, VBOX_SHCL_FMT_UNICODETEXT }, - { "transfers", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, VBOX_SHCL_GF_0_TRANSFERS, - VBOX_SHCL_FMT_UNICODETEXT, VBOX_SHCL_FMT_UNICODETEXT }, - { "context-id", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, VBOX_SHCL_GF_0_CONTEXT_ID, - VBOX_SHCL_FMT_UNICODETEXT, VBOX_SHCL_FMT_UNICODETEXT }, - { "7.2 transfer", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, - VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS, - VBOX_SHCL_FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST | VBOX_SHCL_FMT_UNICODETEXT }, - { "frontend", VBOX_SHCL_TRANSFER_MODE_F_ENABLED, - VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS | VBOX_SHCL_GF_0_TRANSFERS_FRONTEND, - VBOX_SHCL_FMT_URI_LIST, VBOX_SHCL_FMT_URI_LIST | VBOX_SHCL_FMT_UNICODETEXT } - }; - - SHCLFORMATS const fInput = VBOX_SHCL_FMT_URI_LIST | VBOX_SHCL_FMT_UNICODETEXT; - for (size_t i = 0; i < RT_ELEMENTS(s_aTests); i++) - { - RT_ZERO(g_Client); - g_Client.State.Transfers.uTransferMode = s_aTests[i].fTransferMode; - g_Client.State.fGuestFeatures0 = s_aTests[i].fGuestFeatures0; - for (unsigned iDirection = 0; iDirection < 2; iDirection++) - { - bool const fHostToGuest = iDirection == 0; - SHCLFORMATS const fExpected = fHostToGuest ? s_aTests[i].fExpectedHostToGuest - : s_aTests[i].fExpectedGuestToHost; - SHCLFORMATS const fFiltered = shClSvcHandleFormats(fHostToGuest, &g_Client, fInput); - RTTESTI_CHECK_MSG(fFiltered == fExpected, - ("%s/%s fFiltered=%#x expected=%#x\n", s_aTests[i].pszName, - fHostToGuest ? "H2G" : "G2H", fFiltered, fExpected)); - } - } -} - - -/** - * Tests that transfer messages require both guest feature bits. - */ -static void testTransferGuestFeatures(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - - RTTestISub("Testing transfer guest features"); - int rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU64(&parms[0], VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, 42, 0)); - HGCMSvcSetU64(&parms[1], 1); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(parms), parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_NOT_IMPLEMENTED); - - g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_0_TRANSFERS; - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(parms), parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_NOT_IMPLEMENTED); - - g_Client.State.fGuestFeatures0 = VBOX_SHCL_GF_0_CONTEXT_ID; - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(parms), parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_ACCESS_DENIED); - - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_TRANSFERS; - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(parms), parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -static void testTransferHostCancelError(void) -{ - struct VBOXHGCMSVCPARM parms[3]; - struct VBOXHGCMSVCPARM aObjCloseParms[VBOX_SHCL_CPARMS_OBJ_CLOSE]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - - RTTestISub("Testing transfer host cancel/error"); - int rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_ENABLED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - RTTESTI_CHECK(g_Client.State.uSessionID != 0); - RTTESTI_CHECK(g_Client.State.uSessionID != NIL_SHCLSESSIONID); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - - struct VBOXHGCMSVCPARM aReplyParms[VBOX_SHCL_CPARMS_REPLY_MIN + 1]; - HGCMSvcSetU64(&aReplyParms[0], 0 /* no transfer context for SHCLTRANSFERSTATUS_REQUESTED */); - HGCMSvcSetU32(&aReplyParms[1], VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS); - HGCMSvcSetU32(&aReplyParms[2], VINF_SUCCESS); - HGCMSvcSetPv(&aReplyParms[3], NULL, 0); - HGCMSvcSetU32(&aReplyParms[4], SHCLTRANSFERSTATUS_REQUESTED); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_REPLY, RT_ELEMENTS(aReplyParms), aReplyParms, 0); - RTTESTI_CHECK_RC_OK(call.rc); - PSHCLTRANSFER pTransferRequested = ShClTransferCtxGetTransferByIndex(&g_Client.Transfers.Ctx, 0); - RTTESTI_CHECK(pTransferRequested != NULL); - SHCLTRANSFERID const idTransferRequested = ShClTransferGetID(pTransferRequested); - testGetTransferStatusMessage(&table, g_Client.State.uSessionID, idTransferRequested, SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS); - ShClSvcTransferDestroy(&g_Client, pTransferRequested); - - SHCLSESSIONID const idSessionBeforeReset = g_Client.State.uSessionID; - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 0, parms); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(g_Client.State.uSessionID != idSessionBeforeReset); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS; - - HGCMSvcSetU32(&parms[0], 42); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - testSetTransferKeyParms(parms, g_Client.State.uSessionID, 42, 1); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 3, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - PSHCLTRANSFER pTransfer; - rc = ShClSvcTransferCreate(&g_Client, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, - NIL_SHCLTRANSFERID, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - SHCLSESSIONID const idSessionCancel = ShClTransferGetSessionId(pTransfer); - SHCLTRANSFERID const idTransferCancel = ShClTransferGetID(pTransfer); - SHCLTRANSFERGEN const uGenerationCancel = ShClTransferGetGeneration(pTransfer); - RTTESTI_CHECK(idSessionCancel == g_Client.State.uSessionID); - RTTESTI_CHECK(uGenerationCancel != 0); - RTTESTI_CHECK(uGenerationCancel != NIL_SHCLTRANSFERGEN); - - testSetTransferKeyParms(parms, idSessionCancel + 1, idTransferCancel, uGenerationCancel); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferCancel) != NULL); - - testSetTransferKeyParms(parms, idSessionCancel, idTransferCancel, uGenerationCancel + 1); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferCancel) != NULL); - - HGCMSvcSetU64(&aObjCloseParms[0], VBOX_SHCL_CONTEXTID_MAKE(idSessionCancel + 1, idTransferCancel, 0)); - HGCMSvcSetU64(&aObjCloseParms[1], 1); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjCloseParms), aObjCloseParms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_INVALID_CONTEXT); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferCancel) != NULL); - - testSetTransferKeyParms(parms, idSessionCancel, idTransferCancel, uGenerationCancel); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_CANCEL, 2, parms); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferCancel) == NULL); - testGetTransferStatusMessage(&table, idSessionCancel, idTransferCancel, SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); - - HGCMSvcSetU64(&aObjCloseParms[0], VBOX_SHCL_CONTEXTID_MAKE(idSessionCancel, idTransferCancel, 0)); - HGCMSvcSetU64(&aObjCloseParms[1], 1); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_OBJ_CLOSE, RT_ELEMENTS(aObjCloseParms), aObjCloseParms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - - testSetTransferKeyParms(parms, g_Client.State.uSessionID, 42, 1); - HGCMSvcSetU32(&parms[2], (uint32_t)VERR_ACCESS_DENIED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 3, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - - rc = ShClSvcTransferCreate(&g_Client, SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, - NIL_SHCLTRANSFERID, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - SHCLSESSIONID const idSessionError = ShClTransferGetSessionId(pTransfer); - SHCLTRANSFERID const idTransferError = ShClTransferGetID(pTransfer); - SHCLTRANSFERGEN const uGenerationError = ShClTransferGetGeneration(pTransfer); - - testSetTransferKeyParms(parms, idSessionError, idTransferError, uGenerationError); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - HGCMSvcSetU32(&parms[2], (uint32_t)VERR_CANCELLED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 3, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferError) != NULL); - - testSetTransferKeyParms(parms, idSessionError, idTransferError, uGenerationError + 1); - HGCMSvcSetU32(&parms[2], (uint32_t)VERR_ACCESS_DENIED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 3, parms); - RTTESTI_CHECK_RC(rc, VERR_SHCLPB_TRANSFER_ID_NOT_FOUND); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferError) != NULL); - - testSetTransferKeyParms(parms, idSessionError, idTransferError, uGenerationError); - HGCMSvcSetU32(&parms[2], (uint32_t)VERR_ACCESS_DENIED); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_ERROR, 3, parms); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClTransferCtxGetTransferById(&g_Client.Transfers.Ctx, idTransferError) == NULL); - testGetTransferStatusMessage(&table, idSessionError, idTransferError, SHCLTRANSFERSTATUS_ERROR, VERR_ACCESS_DENIED); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_TRANSFER_MODE_F_NONE); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - -/* Adds a host data read request message to the client's message queue. */ -static void testMsgAddReadData(PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - int rc = ShClSvcReadDataFromGuestAsync(pClient, fFormats, NULL /* ppEvent */); - RTTESTI_CHECK_RC_OK(rc); -} - -/* Does testing of VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, needed for providing compatibility to older Guest Additions clients. */ -static void testGetHostMsgOld(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - int rc; - - RTTestISub("Setting up VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT test"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - /* Unless we are bidirectional the host message requests will be dropped. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - - RTTestISub("Testing one format, waiting guest call."); - RT_ZERO(g_Client); - HGCMSvcSetU32(&parms[0], 0); - HGCMSvcSetU32(&parms[1], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */ - testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ - table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - - RTTestISub("Testing one format, no waiting guest calls."); - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_HTML); - HGCMSvcSetU32(&parms[0], 0); - HGCMSvcSetU32(&parms[1], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ - table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - - RTTestISub("Testing two formats, waiting guest call."); - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - HGCMSvcSetU32(&parms[0], 0); - HGCMSvcSetU32(&parms[1], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This should get updated only when the guest call completes. */ - testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ - table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - - RTTestISub("Testing two formats, no waiting guest calls."); - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - testMsgAddReadData(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML); - HGCMSvcSetU32(&parms[0], 0); - HGCMSvcSetU32(&parms[1], 0); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK(parms[0].u.uint32 == VBOX_SHCL_HOST_MSG_READ_DATA); - RTTESTI_CHECK(parms[1].u.uint32 == VBOX_SHCL_FMT_HTML); - RTTESTI_CHECK_RC_OK(call.rc); - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, VBOX_SHCL_GUEST_FN_MSG_OLD_GET_WAIT, 2, parms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_IPE_UNINITIALIZED_STATUS); /* This call should not complete yet. */ - table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - table.pfnUnload(NULL); -} - -/** - * Tests late guest data replies for an expired host wait event. - * - * The test creates a context-ID capable client, queues an asynchronous - * read-data request, drains the resulting READ_DATA_CID host message, and then - * releases the associated event to simulate the host-side read timing out. It - * finally calls ShClSvcGuestDataSignal() with the stale context ID, as a guest - * would do when its DATA_WRITE reply arrives after the host has stopped waiting. - * - * This is needed because guest response timing is guest-controlled and cannot - * be trusted. The expected behavior is to drop the - * stale payload and report success to the service dispatcher, because there is - * no remaining host waiter that can consume the data. - */ -static void testGuestDataSignalExpiredEvent(void) -{ - struct VBOXHGCMSVCPARM parms[1]; - struct VBOXHGCMSVCPARM aMsgParms[2]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - int rc; - - RTTestISub("Testing late guest data for expired event"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID; - - PSHCLEVENT pEvent = NULL; - rc = ShClSvcReadDataFromGuestAsync(&g_Client, VBOX_SHCL_FMT_UNICODETEXT, &pEvent); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK_RETV(pEvent != NULL); - - uint64_t const uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID, pEvent->idEvent); - - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - HGCMSvcSetU64(&aMsgParms[0], VBOX_SHCL_HOST_MSG_READ_DATA_CID); - HGCMSvcSetU32(&aMsgParms[1], 0); - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_MSG_GET, 2, aMsgParms, 0); - RTTESTI_CHECK_RC_OK(call.rc); - RTTESTI_CHECK(aMsgParms[0].u.uint64 == uContextID); - RTTESTI_CHECK(aMsgParms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); - - SHCLCLIENTCMDCTX cmdCtx; - RT_ZERO(cmdCtx); - cmdCtx.uContextID = uContextID; - - char szData[] = "late data"; - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC_OK(rc); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -/** - * Tests guest data reply validation for still pending host wait events. - */ -static void testGuestDataSignalRejectsMismatches(void) -{ - struct VBOXHGCMSVCPARM parms[1]; - struct VBOXHGCMSVCPARM aMsgParms[2]; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - int rc; - - RTTestISub("Testing guest data context validation"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID; - - PSHCLEVENT pEvent = NULL; - rc = ShClSvcReadDataFromGuestAsync(&g_Client, VBOX_SHCL_FMT_UNICODETEXT, &pEvent); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK_RETV(pEvent != NULL); - - uint64_t const uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID, pEvent->idEvent); - - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - HGCMSvcSetU64(&aMsgParms[0], VBOX_SHCL_HOST_MSG_READ_DATA_CID); - HGCMSvcSetU32(&aMsgParms[1], 0); - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_MSG_GET, 2, aMsgParms, 0); - RTTESTI_CHECK_RC_OK(call.rc); - RTTESTI_CHECK(aMsgParms[0].u.uint64 == uContextID); - RTTESTI_CHECK(aMsgParms[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); - - SHCLCLIENTCMDCTX cmdCtx; - RT_ZERO(cmdCtx); - char szData[] = "pending data"; - - cmdCtx.uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID, 0); - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC(rc, VERR_WRONG_ORDER); - - cmdCtx.uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID + 1, - g_Client.EventSrc.uID, pEvent->idEvent); - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); - - cmdCtx.uContextID = VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID + 1, pEvent->idEvent); - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); - - cmdCtx.uContextID = uContextID; - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_HTML, szData, sizeof(szData)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_CONTEXT); - - rc = ShClSvcGuestDataSignal(&g_Client, &cmdCtx, VBOX_SHCL_FMT_UNICODETEXT, szData, sizeof(szData)); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); - - PSHCLEVENT pMultiEvent = NULL; - rc = ShClSvcReadDataFromGuestAsync(&g_Client, VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML, &pMultiEvent); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - RTTESTI_CHECK(pMultiEvent == NULL); - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -/** - * Tests guest DATA_WRITE format validation before forwarding data to the backend. - */ -static void testGuestDataWriteRejectsInvalidFormats(void) -{ - struct VBOXHGCMSVCPARM parms[1]; - struct VBOXHGCMSVCPARM aWriteParms[VBOX_SHCL_CPARMS_DATA_WRITE]; - static const SHCLFORMAT s_aInvalidFormats[] = - { - VBOX_SHCL_FMT_NONE, - VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML, - VBOX_SHCL_FMT_VALID_MASK + 1 - }; - VBOXHGCMSVCFNTABLE table; - VBOXHGCMCALLHANDLE_TYPEDEF call; - char szData[] = "invalid format data"; - int rc; - - RTTestISub("Testing guest DATA_WRITE invalid format rejection"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - g_Client.State.fGuestFeatures0 |= VBOX_SHCL_GF_0_CONTEXT_ID; - - for (size_t i = 0; i < RT_ELEMENTS(s_aInvalidFormats); i++) - { - RTTestISubF("Testing guest DATA_WRITE invalid format %#x", s_aInvalidFormats[i]); - HGCMSvcSetU64(&aWriteParms[0], VBOX_SHCL_CONTEXTID_MAKE(g_Client.State.uSessionID, - g_Client.EventSrc.uID, 1)); - HGCMSvcSetU32(&aWriteParms[1], s_aInvalidFormats[i]); - aWriteParms[2].type = VBOX_HGCM_SVC_PARM_PTR; - aWriteParms[2].u.pointer.addr = szData; - aWriteParms[2].u.pointer.size = sizeof(szData); - - call.rc = VERR_IPE_UNINITIALIZED_STATUS; - table.pfnCall(NULL, &call, 1 /* clientId */, &g_Client, - VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWriteParms), aWriteParms, 0); - RTTESTI_CHECK_RC(call.rc, VERR_INVALID_PARAMETER); - } - - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -/** - * Tests VBOX_SHCL_HOST_FN_SET_HEADLESS host calls. - * - * This host function is deprecated but intentionally kept for older Guest - * Additions and compatibility with the historic service protocol. - */ -static void testSetHeadless(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - bool fHeadless; - int rc; - - RTTestISub("Testing HOST_FN_SET_HEADLESS"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - RT_ZERO(g_Client); - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - /* Reset global variable which doesn't reset itself. */ - HGCMSvcSetU32(&parms[0], false); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == false, ("fHeadless=%RTbool\n", fHeadless)); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 0, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 2, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - HGCMSvcSetU64(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 1, parms); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - HGCMSvcSetU32(&parms[0], true); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); - HGCMSvcSetU32(&parms[0], 99); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, - 1, parms); - RTTESTI_CHECK_RC_OK(rc); - fHeadless = ShClSvcGetHeadless(); - RTTESTI_CHECK_MSG(fHeadless == true, ("fHeadless=%RTbool\n", fHeadless)); - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -static void testHeadlessBackendConnect(void) -{ - struct VBOXHGCMSVCPARM parms[1]; - VBOXHGCMSVCFNTABLE table; - int rc; - - RTTestISub("Testing HOST_FN_SET_HEADLESS backend connect propagation"); - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - - HGCMSvcSetU32(&parms[0], false); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - RT_ZERO(g_Client); - g_fBackendConnectCalled = false; - g_fBackendConnectHeadless = true; - rc = table.pfnConnect(NULL, 1 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(g_fBackendConnectCalled); - RTTESTI_CHECK_MSG(g_fBackendConnectHeadless == false, - ("g_fBackendConnectHeadless=%RTbool\n", g_fBackendConnectHeadless)); - rc = table.pfnDisconnect(NULL, 1 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - HGCMSvcSetU32(&parms[0], true); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - RT_ZERO(g_Client); - g_fBackendConnectCalled = false; - g_fBackendConnectHeadless = false; - rc = table.pfnConnect(NULL, 2 /* clientId */, &g_Client, 0, 0); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(g_fBackendConnectCalled); - RTTESTI_CHECK_MSG(g_fBackendConnectHeadless == true, - ("g_fBackendConnectHeadless=%RTbool\n", g_fBackendConnectHeadless)); - rc = table.pfnDisconnect(NULL, 2 /* clientId */, &g_Client); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - HGCMSvcSetU32(&parms[0], false); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - - rc = table.pfnUnload(NULL); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); -} - -static void testHostCall(void) -{ - testSetMode(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - testSetTransferMode(); - testTransferFormatFiltering(); - testTransferGuestFeatures(); - testTransferHostCancelError(); -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - testSetHeadless(); - testHeadlessBackendConnect(); - testGuestDataSignalExpiredEvent(); - testGuestDataSignalRejectsMismatches(); - testGuestDataWriteRejectsInvalidFormats(); -} - -int main(int argc, char *argv[]) -{ - /* - * Init the runtime, test and say hello. - */ - const char *pcszExecName; - NOREF(argc); - pcszExecName = strrchr(argv[0], '/'); - pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - RTTestBanner(hTest); - - /* Don't let assertions in the host service panic (core dump) the test cases. */ - RTAssertSetMayPanic(false); - - /* - * Run the tests. - */ - testHostCall(); - testGetHostMsgOld(); - - /* - * Summary - */ - return RTTestSummaryAndDestroy(hTest); -} - - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND, PSHCLCLIENT, PSHCLTRANSFER, SHCLSOURCE, SHCLTRANSFERSTATUS, int) { return VINF_SUCCESS; } -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp deleted file mode 100644 index f4e43ed4030a..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardServiceImpl.cpp +++ /dev/null @@ -1,204 +0,0 @@ -/* $Id: tstClipboardServiceImpl.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ -/** @file - * Shared Clipboard host service implementation (backend) test case. - */ - -/* - * Copyright (C) 2020-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include -#include -#ifdef RT_OS_WINDOWS -# include -#endif - -#include -#include -#include - -extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *ptable); - -static SHCLCLIENT g_Client; -static VBOXHGCMSVCHELPERS g_Helpers = { NULL }; - -/** Simple call handle structure for the guest call completion callback */ -struct VBOXHGCMCALLHANDLE_TYPEDEF -{ - /** Where to store the result code */ - int32_t rc; -}; - -/** Call completion callback for guest calls. */ -static DECLCALLBACK(int) callComplete(VBOXHGCMCALLHANDLE callHandle, int32_t rc) -{ - callHandle->rc = rc; - return VINF_SUCCESS; -} - -static int setupTable(VBOXHGCMSVCFNTABLE *pTable) -{ - pTable->cbSize = sizeof(*pTable); - pTable->u32Version = VBOX_HGCM_SVC_VERSION; - g_Helpers.pfnCallComplete = callComplete; - pTable->pHelpers = &g_Helpers; - return VBoxHGCMSvcLoad(pTable); -} - -int ShClBackendInit(PSHCLBACKEND, VBOXHGCMSVCFNTABLE *) { return VINF_SUCCESS; } -void ShClBackendDestroy(PSHCLBACKEND) { } -int ShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } -int ShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; } -int ShClBackendReportFormats(PSHCLBACKEND, PSHCLCLIENT, SHCLFORMATS) { AssertFailed(); return VINF_SUCCESS; } -int ShClBackendReadData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t, unsigned int *) { AssertFailed(); return VERR_WRONG_ORDER; } -int ShClBackendWriteData(PSHCLBACKEND, PSHCLCLIENT, PSHCLCLIENTCMDCTX, SHCLFORMAT, void *, uint32_t) { AssertFailed(); return VINF_SUCCESS; } -int ShClBackendSync(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } - -static void testAnnounceAndReadData(void) -{ - struct VBOXHGCMSVCPARM parms[2]; - VBOXHGCMSVCFNTABLE table; - int rc; - - RTTestISub("Setting up client ..."); - RTTestIDisableAssertions(); - - rc = setupTable(&table); - RTTESTI_CHECK_MSG_RETV(RT_SUCCESS(rc), ("rc=%Rrc\n", rc)); - /* Unless we are bidirectional the host message requests will be dropped. */ - HGCMSvcSetU32(&parms[0], VBOX_SHCL_MODE_BIDIRECTIONAL); - rc = table.pfnHostCall(NULL, VBOX_SHCL_HOST_FN_SET_MODE, 1, parms); - RTTESTI_CHECK_RC_OK(rc); - rc = ShClSvcClientInit(&g_Client, 1 /* clientId */); - RTTESTI_CHECK_RC_OK(rc); - - RTTestIRestoreAssertions(); -} - -#ifdef RT_OS_WINDOWS -# include "VBoxOrgCfHtml1.h" /* From chrome 97.0.4692.71 */ -# include "VBoxOrgMimeHtml1.h" - -static void testHtmlCf(void) -{ - RTTestISub("CF_HTML"); - - char *pszOutput = NULL; - uint32_t cbOutput = UINT32_MAX/2; - RTTestIDisableAssertions(); - RTTESTI_CHECK_RC(ShClWinConvertCFHTMLToMIME("", 0, &pszOutput, &cbOutput), VERR_INVALID_PARAMETER); - RTTestIRestoreAssertions(); - - pszOutput = NULL; - cbOutput = UINT32_MAX/2; - RTTESTI_CHECK_RC(ShClWinConvertCFHTMLToMIME((char *)&g_abVBoxOrgCfHtml1[0], g_cbVBoxOrgCfHtml1, - &pszOutput, &cbOutput), VINF_SUCCESS); - RTTESTI_CHECK(cbOutput == g_cbVBoxOrgMimeHtml1); - RTTESTI_CHECK(memcmp(pszOutput, g_abVBoxOrgMimeHtml1, cbOutput) == 0); - RTMemFree(pszOutput); - - - static RTSTRTUPLE const s_aRoundTrips[] = - { - { RT_STR_TUPLE("") }, - { RT_STR_TUPLE("1") }, - { RT_STR_TUPLE("12") }, - { RT_STR_TUPLE("123") }, - { RT_STR_TUPLE("1234") }, - { RT_STR_TUPLE("12345") }, - { RT_STR_TUPLE("123456") }, - { RT_STR_TUPLE("1234567") }, - { RT_STR_TUPLE("12345678") }, - { RT_STR_TUPLE("123456789") }, - { RT_STR_TUPLE("1234567890") }, - { RT_STR_TUPLE("

asdfkjhasdflhj

") }, - { RT_STR_TUPLE("

asdfkjhasdflhj

\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0") }, - { (const char *)g_abVBoxOrgMimeHtml1, sizeof(g_abVBoxOrgMimeHtml1) }, - }; - - for (size_t i = 0; i < RT_ELEMENTS(s_aRoundTrips); i++) - { - int rc; - char *pszCfHtml = NULL; - uint32_t cbCfHtml = UINT32_MAX/2; - rc = ShClWinConvertMIMEToCFHTML(s_aRoundTrips[i].psz, s_aRoundTrips[i].cch + 1, &pszCfHtml, &cbCfHtml); - if (rc == VINF_SUCCESS) - { - if (strlen(pszCfHtml) + 1 != cbCfHtml) - RTTestIFailed("#%u: ShClWinConvertMIMEToCFHTML(%s, %#zx,,) returned incorrect length: %#x, actual %#zx", - i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, cbCfHtml, strlen(pszCfHtml) + 1); - - char *pszHtml = NULL; - uint32_t cbHtml = UINT32_MAX/4; - rc = ShClWinConvertCFHTMLToMIME(pszCfHtml, (uint32_t)strlen(pszCfHtml), &pszHtml, &cbHtml); - if (rc == VINF_SUCCESS) - { - if (strlen(pszHtml) + 1 != cbHtml) - RTTestIFailed("#%u: ShClWinConvertCFHTMLToMIME(%s, %#zx,,) returned incorrect length: %#x, actual %#zx", - i, pszHtml, strlen(pszHtml), cbHtml, strlen(pszHtml) + 1); - if (strcmp(pszHtml, s_aRoundTrips[i].psz) != 0) - RTTestIFailed("#%u: roundtrip for '%s' LB %#zx failed, ended up with '%s'", - i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, pszHtml); - RTMemFree(pszHtml); - } - else - RTTestIFailed("#%u: ShClWinConvertCFHTMLToMIME(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS", - i, pszCfHtml, strlen(pszCfHtml), rc); - RTMemFree(pszCfHtml); - } - else - RTTestIFailed("#%u: ShClWinConvertMIMEToCFHTML(%s, %#zx,,) returned %Rrc, expected VINF_SUCCESS", - i, s_aRoundTrips[i].psz, s_aRoundTrips[i].cch, rc); - } -} - -#endif /* RT_OS_WINDOWS */ - - -int main(int argc, char *argv[]) -{ - /* - * Init the runtime, test and say hello. - */ - const char *pcszExecName; - NOREF(argc); - pcszExecName = strrchr(argv[0], '/'); - pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - RTTestBanner(hTest); - - /* - * Run the tests. - */ - testAnnounceAndReadData(); -#ifdef RT_OS_WINDOWS - testHtmlCf(); -#endif - - /* - * Summary - */ - return RTTestSummaryAndDestroy(hTest); -} - diff --git a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp b/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp deleted file mode 100644 index 11eb901e505e..000000000000 --- a/src/VBox/HostServices/SharedClipboard/testcase/tstClipboardTransfers.cpp +++ /dev/null @@ -1,1037 +0,0 @@ -/* $Id: tstClipboardTransfers.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard transfers test case. - */ - -/* - * Copyright (C) 2019-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include - - -static int testCreateTempDir(RTTEST hTest, const char *pszTestcase, char *pszTempDir, size_t cbTempDir) -{ - char szTempDir[RTPATH_MAX]; - int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirCreate(szTempDir, 0700, 0); - if (rc == VERR_ALREADY_EXISTS) - rc = VINF_SUCCESS; - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "XXXXX"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirCreateTemp(szTempDir, 0700); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathJoin(pszTempDir, cbTempDir, szTempDir, pszTestcase); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Created temporary directory: %s\n", pszTempDir); - - return rc; -} - -static int testRemoveTempDir(RTTEST hTest) -{ - char szTempDir[RTPATH_MAX]; - int rc = RTPathTemp(szTempDir, sizeof(szTempDir)); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTPathAppend(szTempDir, sizeof(szTempDir), "tstClipboardTransfers"); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - rc = RTDirRemoveRecursive(szTempDir, RTDIRRMREC_F_CONTENT_AND_DIR); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Removed temporary directory: %s\n", szTempDir); - - return rc; -} - -static int testCreateDir(RTTEST hTest, const char *pszPathToCreate) -{ - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating directory: %s\n", pszPathToCreate); - - int rc = RTDirCreateFullPath(pszPathToCreate, 0700); - if (rc == VERR_ALREADY_EXISTS) - rc = VINF_SUCCESS; - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - - return rc; -} - -static int testCreateFile(RTTEST hTest, const char *pszTempDir, const char *pszFileName, uint32_t fOpen, size_t cbSize, - char **ppszFilePathAbs) -{ - char szFilePath[RTPATH_MAX]; - - int rc = RTStrCopy(szFilePath, sizeof(szFilePath), pszTempDir); - RTTESTI_CHECK_RC_OK_RET(rc, rc); - - rc = RTPathAppend(szFilePath, sizeof(szFilePath), pszFileName); - RTTESTI_CHECK_RC_OK_RET(rc, rc); - - char *pszDirToCreate = RTStrDup(szFilePath); - RTTESTI_CHECK_RET(pszDirToCreate, VERR_NO_MEMORY); - - RTPathStripFilename(pszDirToCreate); - - rc = testCreateDir(hTest, pszDirToCreate); - RTTESTI_CHECK_RC_OK_RET(rc, rc); - - RTStrFree(pszDirToCreate); - pszDirToCreate = NULL; - - if (!fOpen) - fOpen = RTFILE_O_OPEN_CREATE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE; - - RTTestPrintf(hTest, RTTESTLVL_DEBUG, "Creating file: %s\n", szFilePath); - - RTFILE hFile; - rc = RTFileOpen(&hFile, szFilePath, fOpen); - if (RT_SUCCESS(rc)) - { - if (cbSize) - { - /** @todo Fill in some random stuff. */ - } - - rc = RTFileClose(hFile); - RTTESTI_CHECK_RC_RET(rc, VINF_SUCCESS, rc); - } - - if (ppszFilePathAbs) - *ppszFilePathAbs = RTStrDup(szFilePath); - - return rc; -} - -typedef struct TESTTRANSFERROOTENTRY -{ - TESTTRANSFERROOTENTRY(const RTCString &a_strPath) - : strPath(a_strPath) { } - - RTCString strPath; -} TESTTRANSFERROOTENTRY; - -static int testAddRootEntry(RTTEST hTest, const char *pszTempDir, - const TESTTRANSFERROOTENTRY &rootEntry, char **ppszRoots) -{ - char *pszRoots = NULL; - - const char *pszPath = rootEntry.strPath.c_str(); - - char *pszPathAbs; - int rc = testCreateFile(hTest, pszTempDir, pszPath, 0, 0, &pszPathAbs); - RTTESTI_CHECK_RC_OK_RET(rc, rc); - - rc = RTStrAAppend(&pszRoots, pszPathAbs); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszPathAbs); - - *ppszRoots = pszRoots; - - return rc; -} - -static int testAddRootEntries(RTTEST hTest, const char *pszTempDir, - RTCList &lstBase, RTCList lstToExtend, - char **ppszRoots) -{ - int rc = VINF_SUCCESS; - - char *pszRoots = NULL; - - for (size_t i = 0; i < lstBase.size(); ++i) - { - char *pszEntry = NULL; - rc = testAddRootEntry(hTest, pszTempDir, lstBase.at(i), &pszEntry); - RTTESTI_CHECK_RC_OK_BREAK(rc); - rc = RTStrAAppend(&pszRoots, pszEntry); - RTTESTI_CHECK_RC_OK_BREAK(rc); - RTStrFree(pszEntry); - } - - for (size_t i = 0; i < lstToExtend.size(); ++i) - { - char *pszEntry = NULL; - rc = testAddRootEntry(hTest, pszTempDir, lstToExtend.at(i), &pszEntry); - RTTESTI_CHECK_RC_OK_BREAK(rc); - rc = RTStrAAppend(&pszRoots, pszEntry); - RTTESTI_CHECK_RC_OK_BREAK(rc); - RTStrFree(pszEntry); - } - - if (RT_SUCCESS(rc)) - *ppszRoots = pszRoots; - - return rc; -} - -static void testTransferRootsSetSingle(RTTEST hTest, - RTCList &lstBase, RTCList lstToExtend, - int rcExpected) -{ - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - SHCLTXPROVIDER Provider; - RTTESTI_CHECK(ShClTransferProviderLocalQueryInterface(&Provider) != NULL); - RTTESTI_CHECK_RC_OK(ShClTransferSetProvider(pTransfer, &Provider)); - - char szTestTransferRootsSetDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferRootsSet", szTestTransferRootsSetDir, sizeof(szTestTransferRootsSetDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - /* This is the file we're trying to access (but not supposed to). */ - rc = testCreateFile(hTest, szTestTransferRootsSetDir, "must-not-access-this", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - char *pszRoots; - rc = testAddRootEntries(hTest, szTestTransferRootsSetDir, lstBase, lstToExtend, &pszRoots); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC(rc, rcExpected); - - RTStrFree(pszRoots); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -static void testTransferObjOpenSingle(RTTEST hTest, - RTCList &lstRoots, const char *pszObjPath, int rcExpected) -{ - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferObjOpenDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferObjOpen", szTestTransferObjOpenDir, sizeof(szTestTransferObjOpenDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - /* This is the file we're trying to access (but not supposed to). */ - rc = testCreateFile(hTest, szTestTransferObjOpenDir, "file1.txt", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - RTCList lstToExtendEmpty; - - char *pszRoots; - rc = testAddRootEntries(hTest, szTestTransferObjOpenDir, lstRoots, lstToExtendEmpty, &pszRoots); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLOBJOPENCREATEPARMS openCreateParms; - rc = ShClTransferObjOpenParmsInit(&openCreateParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, pszObjPath); - RTTESTI_CHECK_RC_OK(rc); - - SHCLOBJHANDLE hObj; - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC(rc, rcExpected); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferObjClose(pTransfer, hObj); - RTTESTI_CHECK_RC_OK(rc); - } - - ShClTransferObjOpenParmsDestroy(&openCreateParms); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -static void testPathSanitize(void) -{ - RTTestISub("Testing path sanitizing"); - - /* Valid dotted names: expect success and no path changes. */ - char szValid[] = "dir.with.dots/file...txt"; - int rc = ShClPathSanitize(szValid, sizeof(szValid)); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - RTTESTI_CHECK(!strcmp(szValid, "dir.with.dots/file...txt")); - - /* Current-directory components are not transferable. */ - char szDot[] = "dir/./file.txt"; - rc = ShClPathSanitize(szDot, sizeof(szDot)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - /* Parent-directory components are not transferable. */ - char szDotDot[] = "dir/../file.txt"; - rc = ShClPathSanitize(szDotDot, sizeof(szDotDot)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - - /* Malformed UTF-8 paths are rejected before use. */ - uint8_t abInvalidUtf8[] = { 0xc0, 0xaf, 0 }; - rc = ShClPathSanitize((char *)abInvalidUtf8, sizeof(abInvalidUtf8)); - RTTESTI_CHECK_RC(rc, VERR_INVALID_UTF8_ENCODING); - - /* Unterminated bounded path buffers are rejected. */ - char szUnterminated[] = { 'a', 'b' }; - rc = ShClPathSanitize(szUnterminated, sizeof(szUnterminated)); - RTTESTI_CHECK_RC(rc, VERR_BUFFER_OVERFLOW); -} - -static void testEvents(void) -{ - RTTestISub("Testing events"); - - SHCLEVENTSOURCE Source; - RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 0)); - RTTESTI_CHECK(ShClEventSourceGetLast(&Source) == NULL); /* Should be empty. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); /* Destroying a second time, intentional. */ - - RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); - PSHCLEVENT pEvent; - RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); - ShClEventSourceReset(&Source); - RTTESTI_CHECK(ShClEventSourceGetLast(&Source) == NULL); /* Event still valid, but removed from the source. */ - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); /* Free'd event, as ref count is 0. */ - RTTESTI_CHECK(ShClEventSourceGetLast(&Source) == NULL); /* Now it should be empty. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); - - /* Test delayed destruction of the event by retaining it. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceInit(&Source, 42)); - RTTESTI_CHECK_RC_OK(ShClEventSourceGenerateAndRegisterEvent(&Source, &pEvent)); - RTTESTI_CHECK_RC_OK(ShClEventRetain(pEvent)); - RTTESTI_CHECK(ShClEventGetRefs(pEvent) == 2); - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); - RTTESTI_CHECK(ShClEventGetRefs(pEvent) == 2); /* Make sure the ref count didn't drop due to ShClEventSourceDestroy(). */ - RTTESTI_CHECK(ShClEventRelease(pEvent) == 1); - RTTESTI_CHECK(ShClEventGetRefs(pEvent) == 1); - RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); /* Free'd event, as ref count is 0. */ - RTTESTI_CHECK_RC_OK(ShClEventSourceTerm(&Source)); /* Try to destruct again. */ -} - -static void testTransferBasics(void) -{ - RTTestISub("Testing transfer basics"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClTransferGetID(pTransfer) == NIL_SHCLTRANSFERID); - RTTESTI_CHECK(ShClTransferGetSessionId(pTransfer) == NIL_SHCLSESSIONID); - RTTESTI_CHECK(ShClTransferGetGeneration(pTransfer) == NIL_SHCLTRANSFERGEN); - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - pTransfer = NULL; /* Was free'd above. */ - rc = ShClTransferDestroy(pTransfer); /* Second time, intentional. */ - RTTESTI_CHECK_RC_OK(rc); - - PSHCLLIST pList = ShClTransferListAlloc(); - RTTESTI_CHECK(pList != NULL); - rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK(rc); - ShClTransferListFree(pList); - pList = NULL; - ShClTransferListFree(pList); /* Second time, intentional. */ - - SHCLLISTENTRY Entry; - RTTESTI_CHECK_RC_OK(ShClTransferListEntryInit(&Entry)); - ShClTransferListEntryDestroy(&Entry); - ShClTransferListEntryDestroy(&Entry); /* Second time, intentional. */ - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -/** - * Tests zero-length object data chunk duplication. - */ -static void testTransferObjDataChunkDupZeroLength(void) -{ - RTTestISub("Testing zero-length transfer object data chunk duplication"); - - SHCLOBJDATACHUNK Chunk; - RT_ZERO(Chunk); - Chunk.uHandle = 42; - Chunk.pvData = NULL; - Chunk.cbData = 0; - - PSHCLOBJDATACHUNK pDup = ShClTransferObjDataChunkDup(&Chunk); - RTTESTI_CHECK_RETV(pDup != NULL); - RTTESTI_CHECK(pDup->uHandle == Chunk.uHandle); - RTTESTI_CHECK(pDup->pvData == NULL); - RTTESTI_CHECK(pDup->cbData == 0); - ShClTransferObjDataChunkFree(pDup); - - Chunk.cbData = 1; - pDup = ShClTransferObjDataChunkDup(&Chunk); - RTTESTI_CHECK(pDup == NULL); -} - - -static void testTransferContextIdentity(void) -{ - RTTestISub("Testing transfer context identity"); - - SHCLTRANSFERCTX Ctx; - int rc = ShClTransferCtxInit(&Ctx); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferCtxBeginSession(&Ctx, 7); - RTTESTI_CHECK_RC_OK(rc); - - PSHCLTRANSFER pTransfer; - rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; - rc = ShClTransferCtxRegister(&Ctx, pTransfer, &idTransfer); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(idTransfer != NIL_SHCLTRANSFERID); - RTTESTI_CHECK(ShClTransferGetSessionId(pTransfer) == 7); - RTTESTI_CHECK(ShClTransferGetGeneration(pTransfer) != 0); - RTTESTI_CHECK(ShClTransferGetGeneration(pTransfer) != NIL_SHCLTRANSFERGEN); - - SHCLTRANSFERGEN const uGeneration = ShClTransferGetGeneration(pTransfer); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 7, idTransfer, uGeneration) == pTransfer); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 8, idTransfer, uGeneration) == NULL); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 7, idTransfer, uGeneration + 1) == NULL); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 7, idTransfer + 1, uGeneration) == NULL); - - SHCLTRANSFERID idDuplicate = NIL_SHCLTRANSFERID; - rc = ShClTransferCtxRegister(&Ctx, pTransfer, &idDuplicate); - RTTESTI_CHECK_RC(rc, VERR_ALREADY_EXISTS); - rc = ShClTransferCtxRegisterById(&Ctx, pTransfer, idTransfer + 1); - RTTESTI_CHECK_RC(rc, VERR_ALREADY_EXISTS); - - rc = ShClTransferCtxUnregisterById(&Ctx, idTransfer); - RTTESTI_CHECK_RC_OK(rc); - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - PSHCLTRANSFER pTransferReuse; - rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransferReuse); - RTTESTI_CHECK_RC_OK_RETV(rc); - rc = ShClTransferCtxRegisterById(&Ctx, pTransferReuse, idTransfer); - RTTESTI_CHECK_RC(rc, VERR_ALREADY_EXISTS); - rc = ShClTransferDestroy(pTransferReuse); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferCtxBeginSession(&Ctx, 8); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransferReuse); - RTTESTI_CHECK_RC_OK_RETV(rc); - rc = ShClTransferCtxRegisterById(&Ctx, pTransferReuse, idTransfer); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(ShClTransferGetSessionId(pTransferReuse) == 8); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 7, idTransfer, uGeneration) == NULL); - RTTESTI_CHECK(ShClTransferCtxGetTransferByKey(&Ctx, 8, idTransfer, - ShClTransferGetGeneration(pTransferReuse)) == pTransferReuse); - - rc = ShClTransferCtxUnregisterById(&Ctx, idTransfer); - RTTESTI_CHECK_RC_OK(rc); - rc = ShClTransferDestroy(pTransferReuse); - RTTESTI_CHECK_RC_OK(rc); - - ShClTransferCtxDestroy(&Ctx); -} - -static void testTransferRootsSet(RTTEST hTest) -{ - RTTestISub("Testing setting transfer roots"); - - /* Define the (valid) transfer root set. */ - RTCList lstBase; - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt")); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt")); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt")); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt")); - - RTCList lstBreakout; - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VINF_SUCCESS); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("../must-not-access-this")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("does-not-exist/file1.txt")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/../must-not-access-this")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/./../must-not-access-this")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - lstBreakout.clear(); - lstBase.append(TESTTRANSFERROOTENTRY("../does-not-exist")); - testTransferRootsSetSingle(hTest, lstBase, lstBreakout, VERR_INVALID_PARAMETER); - - RTCList lstBoundaryBase; - lstBoundaryBase.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); - - RTCList lstBoundaryBreakout; - lstBoundaryBreakout.append(TESTTRANSFERROOTENTRY("my-transfer-10/file2.txt")); - /* Sibling prefix confusion is rejected (my-transfer-1 vs my-transfer-10). */ - testTransferRootsSetSingle(hTest, lstBoundaryBase, lstBoundaryBreakout, VERR_PATH_DOES_NOT_START_WITH_ROOT); -} - -static void testTransferObjOpenPrefixBoundary(RTTEST hTest) -{ - RTTestISub("Testing transfer object root prefix boundaries"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferObjOpenDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferObjOpenPrefixBoundary", szTestTransferObjOpenDir, - sizeof(szTestTransferObjOpenDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - RTCList lstRoots; - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/foo")); - - RTCList lstToExtendEmpty; - - char *pszRoots; - rc = testAddRootEntries(hTest, szTestTransferObjOpenDir, lstRoots, lstToExtendEmpty, &pszRoots); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = testCreateFile(hTest, szTestTransferObjOpenDir, "my-transfer-1/foobar", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLOBJOPENCREATEPARMS openCreateParms; - rc = ShClTransferObjOpenParmsInit(&openCreateParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, "foo"); - RTTESTI_CHECK_RC_OK(rc); - - /* Exact authorized root entry opens successfully. */ - SHCLOBJHANDLE hObj; - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferObjClose(pTransfer, hObj); - RTTESTI_CHECK_RC_OK(rc); - } - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, "foobar"); - RTTESTI_CHECK_RC_OK(rc); - - /* Sibling path sharing the same string prefix is not authorized. */ - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC(rc, VERR_PATH_NOT_FOUND); - - ShClTransferObjOpenParmsDestroy(&openCreateParms); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -/** - * Tests that resetting a transfer closes active object handles. - * - * @param hTest The test handle. - */ -static void testTransferResetClosesObjectHandles(RTTEST hTest) -{ - RTTestISub("Testing transfer reset closes object handles"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreateEx(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, SHCL_TRANSFER_DEFAULT_MAX_CHUNK_SIZE, - SHCL_TRANSFER_DEFAULT_MAX_LIST_HANDLES, 1 /* cMaxObjHandles */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferResetDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferResetHandleAccounting", szTestTransferResetDir, - sizeof(szTestTransferResetDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char *pszFilePathAbs = NULL; - rc = testCreateFile(hTest, szTestTransferResetDir, "file1.txt", 0, 0, &pszFilePathAbs); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char *pszRoots = NULL; - rc = RTStrAAppend(&pszRoots, pszFilePathAbs); - RTTESTI_CHECK_RC_OK(rc); - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - SHCLOBJOPENCREATEPARMS OpenParms; - rc = ShClTransferObjOpenParmsInit(&OpenParms); - RTTESTI_CHECK_RC_OK(rc); - rc = RTStrCopy(OpenParms.pszPath, OpenParms.cbPath, "file1.txt"); - RTTESTI_CHECK_RC_OK(rc); - - SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; - rc = ShClTransferObjOpen(pTransfer, &OpenParms, &hObj); - RTTESTI_CHECK_RC_OK(rc); - - ShClTransferReset(pTransfer); - - rc = ShClTransferObjClose(pTransfer, hObj); - RTTESTI_CHECK_RC(rc, VERR_NOT_FOUND); - - ShClTransferObjOpenParmsDestroy(&OpenParms); - RTStrFree(pszRoots); - RTStrFree(pszFilePathAbs); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - - -static void testTransferObjReadWriteValidation(void) -{ - RTTestISub("Testing transfer object read/write validation"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreateEx(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, 4 /* cbMaxChunkSize */, - SHCL_TRANSFER_DEFAULT_MAX_LIST_HANDLES, SHCL_TRANSFER_DEFAULT_MAX_OBJ_HANDLES, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char abBuf[8]; - uint32_t cbActual = 0; - - /* Invalid read handles, pointers, sizes, chunks and flags are rejected with their precise status codes. */ - rc = ShClTransferObjRead(pTransfer, NIL_SHCLOBJHANDLE, abBuf, 1, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_HANDLE); - rc = ShClTransferObjRead(pTransfer, 1, NULL, 1, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_POINTER); - rc = ShClTransferObjRead(pTransfer, 1, abBuf, 0, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - rc = ShClTransferObjRead(pTransfer, 1, abBuf, sizeof(abBuf), 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_BUFFER_OVERFLOW); - rc = ShClTransferObjRead(pTransfer, 1, abBuf, 1, 1 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); - - /* Invalid write handles, pointers, sizes, chunks and flags are rejected with their precise status codes. */ - rc = ShClTransferObjWrite(pTransfer, NIL_SHCLOBJHANDLE, abBuf, 1, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_HANDLE); - rc = ShClTransferObjWrite(pTransfer, 1, NULL, 1, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_POINTER); - rc = ShClTransferObjWrite(pTransfer, 1, abBuf, 0, 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_PARAMETER); - rc = ShClTransferObjWrite(pTransfer, 1, abBuf, sizeof(abBuf), 0 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_BUFFER_OVERFLOW); - rc = ShClTransferObjWrite(pTransfer, 1, abBuf, 1, 1 /* fFlags */, &cbActual); - RTTESTI_CHECK_RC(rc, VERR_INVALID_FLAGS); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -static void testTransferListOpenEmptyPath(RTTEST hTest) -{ - RTTestISub("Testing transfer list empty-path rejection"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferListOpenDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferListOpenEmptyPath", szTestTransferListOpenDir, - sizeof(szTestTransferListOpenDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char szRootDir[RTPATH_MAX]; - rc = RTPathJoin(szRootDir, sizeof(szRootDir), szTestTransferListOpenDir, "my-transfer-1/dir"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = testCreateDir(hTest, szRootDir); - RTTESTI_CHECK_RC_OK(rc); - rc = testCreateFile(hTest, szTestTransferListOpenDir, "my-transfer-1/dir/file1.txt", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - char *pszRoots = RTStrDup(szRootDir); - RTTESTI_CHECK_RETV(pszRoots); - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLLISTOPENPARMS openParms; - rc = ShClTransferListOpenParmsInit(&openParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(openParms.pszPath, openParms.cbPath, ""); - RTTESTI_CHECK_RC_OK(rc); - rc = RTStrCopy(openParms.pszFilter, openParms.cbFilter, ""); - RTTESTI_CHECK_RC_OK(rc); - - /* Empty list-open paths must not enumerate the common root. */ - SHCLLISTHANDLE hList; - rc = ShClTransferListOpen(pTransfer, &openParms, &hList); - RTTESTI_CHECK_RC(rc, VERR_PATH_NOT_FOUND); - - ShClTransferListOpenParmsDestroy(&openParms); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -/** - * Tests local-provider list handling for a single file root. - * - * @param hTest Test handle. - */ -static void testTransferListSingleFileRoot(RTTEST hTest) -{ - RTTestISub("Testing single-file transfer list handling"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferListDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferListSingleFileRoot", szTestTransferListDir, sizeof(szTestTransferListDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char *pszRootFileAbs = NULL; - rc = testCreateFile(hTest, szTestTransferListDir, "my-transfer-1/file1.txt", 0, 0, &pszRootFileAbs); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char *pszRoots = RTStrDup(pszRootFileAbs); - RTTESTI_CHECK_RETV(pszRoots); - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLLISTOPENPARMS OpenParms; - rc = ShClTransferListOpenParmsInit(&OpenParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(OpenParms.pszPath, OpenParms.cbPath, "file1.txt"); - RTTESTI_CHECK_RC_OK(rc); - rc = RTStrCopy(OpenParms.pszFilter, OpenParms.cbFilter, ""); - RTTESTI_CHECK_RC_OK(rc); - - SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; - rc = ShClTransferListOpen(pTransfer, &OpenParms, &hList); - RTTESTI_CHECK_RC_OK(rc); - - if (RT_SUCCESS(rc)) - { - SHCLLISTHDR Hdr; - rc = ShClTransferListGetHeader(pTransfer, hList, &Hdr); - RTTESTI_CHECK_RC_OK(rc); - RTTESTI_CHECK(Hdr.cEntries == 1); - - SHCLLISTENTRY Entry; - rc = ShClTransferListEntryInit(&Entry); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferListRead(pTransfer, hList, &Entry); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - RTTESTI_CHECK(strcmp(Entry.pszName, "file1.txt") == 0); - ShClTransferListEntryDestroy(&Entry); - } - - SHCLLISTENTRY WriteEntry; - rc = ShClTransferListEntryInit(&WriteEntry); - RTTESTI_CHECK_RC_OK(rc); - if (RT_SUCCESS(rc)) - { - rc = ShClTransferListWrite(pTransfer, hList, &WriteEntry); - RTTESTI_CHECK_RC(rc, VERR_NOT_SUPPORTED); - ShClTransferListEntryDestroy(&WriteEntry); - } - - rc = ShClTransferListClose(pTransfer, hList); - RTTESTI_CHECK_RC_OK(rc); - } - - ShClTransferListOpenParmsDestroy(&OpenParms); - RTStrFree(pszRootFileAbs); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - - -static void testTransferSymlinkRejection(RTTEST hTest) -{ - RTTestISub("Testing transfer symlink rejection"); - - PSHCLTRANSFER pTransfer; - int rc = ShClTransferCreate(SHCLTRANSFERDIR_TO_REMOTE, SHCLSOURCE_LOCAL, NULL /* Callbacks */, &pTransfer); - RTTESTI_CHECK_RC_OK_RETV(rc); - - SHCLTXPROVIDER Provider; - ShClTransferProviderLocalQueryInterface(&Provider); - - rc = ShClTransferSetProvider(pTransfer, &Provider); - RTTESTI_CHECK_RC_OK(rc); - - char szTestTransferSymlinkDir[RTPATH_MAX]; - rc = testCreateTempDir(hTest, "testTransferSymlinkRejection", szTestTransferSymlinkDir, - sizeof(szTestTransferSymlinkDir)); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char szRootDir[RTPATH_MAX]; - rc = RTPathJoin(szRootDir, sizeof(szRootDir), szTestTransferSymlinkDir, "my-transfer-1/dir"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = testCreateDir(hTest, szRootDir); - RTTESTI_CHECK_RC_OK(rc); - - char *pszTargetFileAbs = NULL; - rc = testCreateFile(hTest, szTestTransferSymlinkDir, "my-transfer-1/target.txt", 0, 0, &pszTargetFileAbs); - RTTESTI_CHECK_RC_OK_RETV(rc); - - char szLinkFile[RTPATH_MAX]; - rc = RTPathJoin(szLinkFile, sizeof(szLinkFile), szRootDir, "link-file"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = RTSymlinkCreate(szLinkFile, pszTargetFileAbs, RTSYMLINKTYPE_FILE, 0 /* fCreate */); - if (RT_FAILURE(rc)) - { - RTTestSkipped(hTest, "RTSymlinkCreate(file) failed: %Rrc", rc); - RTStrFree(pszTargetFileAbs); - RTTESTI_CHECK_RC_OK(ShClTransferDestroy(pTransfer)); - return; - } - - char szEscapeDir[RTPATH_MAX]; - rc = RTPathJoin(szEscapeDir, sizeof(szEscapeDir), szTestTransferSymlinkDir, "escape"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = testCreateDir(hTest, szEscapeDir); - RTTESTI_CHECK_RC_OK(rc); - rc = testCreateFile(hTest, szTestTransferSymlinkDir, "escape/secret.txt", 0, 0, NULL); - RTTESTI_CHECK_RC_OK(rc); - - char szLinkDir[RTPATH_MAX]; - rc = RTPathJoin(szLinkDir, sizeof(szLinkDir), szRootDir, "link-out"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = RTSymlinkCreate(szLinkDir, szEscapeDir, RTSYMLINKTYPE_DIR, 0 /* fCreate */); - if (RT_FAILURE(rc)) - { - RTTestSkipped(hTest, "RTSymlinkCreate(dir) failed: %Rrc", rc); - RTTESTI_CHECK_RC_OK(RTSymlinkDelete(szLinkFile, 0 /* fDelete */)); - RTStrFree(pszTargetFileAbs); - RTTESTI_CHECK_RC_OK(ShClTransferDestroy(pTransfer)); - return; - } - - char *pszRoots = RTStrDup(szRootDir); - RTTESTI_CHECK_RETV(pszRoots); - rc = RTStrAAppend(&pszRoots, "\r\n"); - RTTESTI_CHECK_RC_OK_RETV(rc); - - rc = ShClTransferRootsSetFromStringList(pTransfer, pszRoots, strlen(pszRoots) + 1); - RTTESTI_CHECK_RC_OK(rc); - - rc = ShClTransferInit(pTransfer); - RTTESTI_CHECK_RC_OK(rc); - - RTStrFree(pszRoots); - - SHCLOBJOPENCREATEPARMS openCreateParms; - rc = ShClTransferObjOpenParmsInit(&openCreateParms); - RTTESTI_CHECK_RC_OK(rc); - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, "dir/link-file"); - RTTESTI_CHECK_RC_OK(rc); - - /* Final-component symlinks are rejected before file open. */ - SHCLOBJHANDLE hObj; - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC(rc, VERR_IS_A_SYMLINK); - - rc = RTStrCopy(openCreateParms.pszPath, openCreateParms.cbPath, "dir/link-out/secret.txt"); - RTTESTI_CHECK_RC_OK(rc); - - /* Intermediate directory symlink breakout is rejected. */ - rc = ShClTransferObjOpen(pTransfer, &openCreateParms, &hObj); - RTTESTI_CHECK_RC(rc, VERR_IS_A_SYMLINK); - - ShClTransferObjOpenParmsDestroy(&openCreateParms); - RTStrFree(pszTargetFileAbs); - RTTESTI_CHECK_RC_OK(RTSymlinkDelete(szLinkFile, 0 /* fDelete */)); - RTTESTI_CHECK_RC_OK(RTSymlinkDelete(szLinkDir, 0 /* fDelete */)); - - rc = ShClTransferDestroy(pTransfer); - RTTESTI_CHECK_RC_OK(rc); -} - -static void testTransferObjOpen(RTTEST hTest) -{ - RTTestISub("Testing setting transfer object open"); - - /* Define the (valid) transfer root set. */ - RTCList lstRoots; - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/file2..txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/file2...txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir1/sub1/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file1.txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file2..txt")); - lstRoots.append(TESTTRANSFERROOTENTRY("my-transfer-1/dir2/sub1/file2...txt")); - - testTransferObjOpenSingle(hTest, lstRoots, "file1.txt", VINF_SUCCESS); - testTransferObjOpenSingle(hTest, lstRoots, "file2..txt", VINF_SUCCESS); - testTransferObjOpenSingle(hTest, lstRoots, "file2...txt", VINF_SUCCESS); - testTransferObjOpenSingle(hTest, lstRoots, "dir2/sub1/file2...txt", VINF_SUCCESS); - testTransferObjOpenSingle(hTest, lstRoots, "does-not-exist.txt", VERR_PATH_NOT_FOUND); - testTransferObjOpenSingle(hTest, lstRoots, "dir1/does-not-exist.txt", VERR_PATH_NOT_FOUND); - testTransferObjOpenSingle(hTest, lstRoots, "../must-not-access-this.txt", VERR_INVALID_PARAMETER); - testTransferObjOpenSingle(hTest, lstRoots, "dir1/../../must-not-access-this.txt", VERR_INVALID_PARAMETER); -} - -int main(int argc, char *argv[]) -{ - /* - * Init the runtime, test and say hello. - */ - const char *pcszExecName; - NOREF(argc); - pcszExecName = strrchr(argv[0], '/'); - pcszExecName = pcszExecName ? pcszExecName + 1 : argv[0]; - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate(pcszExecName, &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - RTTestBanner(hTest); - - /* For negative stuff that may assert: */ - bool const fMayPanic = RTAssertSetMayPanic(false); - bool const fQuiet = RTAssertSetQuiet(true); - - testPathSanitize(); - testEvents(); - testTransferBasics(); - testTransferObjDataChunkDupZeroLength(); - testTransferContextIdentity(); - testTransferResetClosesObjectHandles(hTest); - testTransferRootsSet(hTest); - testTransferObjOpenPrefixBoundary(hTest); - testTransferObjReadWriteValidation(); - testTransferListOpenEmptyPath(hTest); - testTransferListSingleFileRoot(hTest); - testTransferSymlinkRejection(hTest); - testTransferObjOpen(hTest); - - int rc = testRemoveTempDir(hTest); - RTTESTI_CHECK_RC(rc, VINF_SUCCESS); - - RTAssertSetMayPanic(fMayPanic); - RTAssertSetQuiet(fQuiet); - - /* - * Summary - */ - return RTTestSummaryAndDestroy(hTest); -} diff --git a/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp index 039dfc3c7bad..5bf6574d13ff 100644 --- a/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp +++ b/src/VBox/HostServices/SharedFolders/VBoxSharedFoldersSvc.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxSharedFoldersSvc.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxSharedFoldersSvc.cpp 114904 2026-08-10 08:28:37Z andreas.loeffler@oracle.com $ */ /** @file * Shared Folders - Host service entry points. */ @@ -1064,12 +1064,6 @@ static DECLCALLBACK(void) svcCall(void *, VBOXHGCMCALLHANDLE callHandle, uint32_ pStat = &g_StatMapFolder; pStatFail = &g_StatMapFolderFail; Log(("SharedFolders host service: svcCall: SHFL_FN_MAP_FOLDER\n")); - if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) - Log(("SharedFolders host service: request to map folder '%s'\n", - ((PSHFLSTRING)paParms[0].u.pointer.addr)->String.utf8)); - else - Log(("SharedFolders host service: request to map folder '%ls'\n", - ((PSHFLSTRING)paParms[0].u.pointer.addr)->String.utf16)); /* Verify parameter count and types. */ if (cParms != SHFL_CPARMS_MAP_FOLDER) @@ -1101,11 +1095,11 @@ static DECLCALLBACK(void) svcCall(void *, VBOXHGCMCALLHANDLE callHandle, uint32_ { rc = VERR_INVALID_PARAMETER; - /* Fudge for windows GAs getting the length wrong by one char. */ + /* Fudge for windows GAs getting the length wrong by one char. Let the + validator check the adjusted length and terminator before accessing it. */ if ( !(pClient->fu32Flags & SHFL_CF_UTF8) && paParms[0].u.pointer.size >= sizeof(SHFLSTRING) - && pszMapName->u16Length >= 2 - && pszMapName->String.utf16[pszMapName->u16Length / 2 - 1] == 0x0000) + && pszMapName->u16Length >= 2) { pszMapName->u16Length -= 2; if (ShflStringIsValidIn(pszMapName, paParms[0].u.pointer.size, false /*fUtf8Not16*/)) @@ -1117,7 +1111,14 @@ static DECLCALLBACK(void) svcCall(void *, VBOXHGCMCALLHANDLE callHandle, uint32_ /* Execute the function. */ if (RT_SUCCESS(rc)) + { + if (BIT_FLAG(pClient->fu32Flags, SHFL_CF_UTF8)) + Log(("SharedFolders host service: request to map folder '%s'\n", pszMapName->String.utf8)); + else + Log(("SharedFolders host service: request to map folder '%ls'\n", pszMapName->String.utf16)); + rc = vbsfMapFolder(pClient, pszMapName, delimiter, fCaseSensitive, &root); + } if (RT_SUCCESS(rc)) { @@ -1973,4 +1974,3 @@ extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pta return rc; } - diff --git a/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp index 6fc13b45d2b7..63e3e79a1101 100644 --- a/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp +++ b/src/VBox/HostServices/SharedFolders/testcase/tstSharedFolderService.cpp @@ -1,4 +1,4 @@ -/* $Id: tstSharedFolderService.cpp 114418 2026-06-18 06:56:36Z andreas.loeffler@oracle.com $ */ +/* $Id: tstSharedFolderService.cpp 114904 2026-08-10 08:28:37Z andreas.loeffler@oracle.com $ */ /** @file * Testcase for the shared folder service vbsf API. * @@ -630,7 +630,72 @@ void testMapFolderTwice(RTTEST hTest) { RT_NOREF1(hTest); } void testMapFolderDelimiter(RTTEST hTest) { RT_NOREF1(hTest); } void testMapFolderCaseSensitive(RTTEST hTest) { RT_NOREF1(hTest); } void testMapFolderCaseInsensitive(RTTEST hTest) { RT_NOREF1(hTest); } -void testMapFolderBadParameters(RTTEST hTest) { RT_NOREF1(hTest); } +void testMapFolderBadParameters(RTTEST hTest) +{ + RTTestSub(hTest, "Map folder string validation"); + + VBOXHGCMSVCFNTABLE SvcTable; + VBOXHGCMSVCHELPERS SvcHelpers; + RT_ZERO(SvcTable); + RT_ZERO(SvcHelpers); + initTable(&SvcTable, &SvcHelpers); + int rc = VBoxHGCMSvcLoad(&SvcTable); + RTTEST_CHECK_RC_RETV(hTest, rc, VINF_SUCCESS); + + AssertRelease(SvcTable.pvService = RTTestGuardedAllocTail(hTest, SvcTable.cbClient)); + RT_BZERO(SvcTable.pvService, SvcTable.cbClient); + + /* Place the supplied six-byte buffer directly before a guard page. A length of four + used to make the compatibility workaround read the first two bytes beyond it. */ + PSHFLSTRING pMalformed = (PSHFLSTRING)RTTestGuardedAllocTail(hTest, sizeof(*pMalformed)); + AssertRelease(pMalformed); + pMalformed->u16Size = sizeof(RTUTF16); + pMalformed->u16Length = sizeof(RTUTF16) * 2; + pMalformed->String.utf16[0] = 0; + + VBOXHGCMSVCPARM aParms[SHFL_CPARMS_MAP_FOLDER]; + HGCMSvcSetPv(&aParms[0], pMalformed, sizeof(*pMalformed)); + HGCMSvcSetU32(&aParms[1], 0); /* root */ + HGCMSvcSetU32(&aParms[2], '/'); /* delimiter */ + HGCMSvcSetU32(&aParms[3], true); /* fCaseSensitive */ + + VBOXHGCMCALLHANDLE_TYPEDEF CallHandle = { VERR_INTERNAL_ERROR }; + SvcTable.pfnCall(SvcTable.pvService, &CallHandle, 0, SvcTable.pvService, SHFL_FN_MAP_FOLDER, + SHFL_CPARMS_MAP_FOLDER, aParms, 0); + RTTEST_CHECK_RC(hTest, CallHandle.rc, VERR_INVALID_PARAMETER); + RTTEST_CHECK(hTest, pMalformed->u16Length == sizeof(RTUTF16) * 2); + + /* Also cover the largest even length accepted by the wire format. */ + pMalformed->u16Length = UINT16_MAX - 1; + CallHandle.rc = VERR_INTERNAL_ERROR; + SvcTable.pfnCall(SvcTable.pvService, &CallHandle, 0, SvcTable.pvService, SHFL_FN_MAP_FOLDER, + SHFL_CPARMS_MAP_FOLDER, aParms, 0); + RTTEST_CHECK_RC(hTest, CallHandle.rc, VERR_INVALID_PARAMETER); + RTTEST_CHECK(hTest, pMalformed->u16Length == UINT16_MAX - 1); + + /* Preserve the legacy case: old Windows clients included the terminator in u16Length. */ + PSHFLSTRING pLegacy = (PSHFLSTRING)RTTestGuardedAllocTail(hTest, + SHFLSTRING_HEADER_SIZE + sizeof(RTUTF16) * 2); + AssertRelease(pLegacy); + pLegacy->u16Size = sizeof(RTUTF16) * 2; + pLegacy->u16Length = sizeof(RTUTF16) * 2; + pLegacy->String.utf16[0] = 'x'; + pLegacy->String.utf16[1] = 0; + HGCMSvcSetPv(&aParms[0], pLegacy, SHFLSTRING_HEADER_SIZE + pLegacy->u16Size); + CallHandle.rc = VERR_INTERNAL_ERROR; + SvcTable.pfnCall(SvcTable.pvService, &CallHandle, 0, SvcTable.pvService, SHFL_FN_MAP_FOLDER, + SHFL_CPARMS_MAP_FOLDER, aParms, 0); + RTTEST_CHECK_RC(hTest, CallHandle.rc, VERR_FILE_NOT_FOUND); + RTTEST_CHECK(hTest, pLegacy->u16Length == sizeof(RTUTF16)); + + rc = SvcTable.pfnDisconnect(NULL, 0, SvcTable.pvService); + RTTEST_CHECK_RC(hTest, rc, VINF_SUCCESS); + rc = SvcTable.pfnUnload(NULL); + RTTEST_CHECK_RC(hTest, rc, VINF_SUCCESS); + RTTEST_CHECK_RC(hTest, RTTestGuardedFree(hTest, pLegacy), VINF_SUCCESS); + RTTEST_CHECK_RC(hTest, RTTestGuardedFree(hTest, pMalformed), VINF_SUCCESS); + RTTEST_CHECK_RC(hTest, RTTestGuardedFree(hTest, SvcTable.pvService), VINF_SUCCESS); +} /* Sub-tests for testUnmapFolder(). */ void testUnmapFolderValid(RTTEST hTest) { RT_NOREF1(hTest); } diff --git a/src/VBox/HostServices/testcase/TstHGCMMock.cpp b/src/VBox/HostServices/testcase/TstHGCMMock.cpp index d7efd9d2aba4..8fade5e16874 100644 --- a/src/VBox/HostServices/testcase/TstHGCMMock.cpp +++ b/src/VBox/HostServices/testcase/TstHGCMMock.cpp @@ -1,4 +1,4 @@ -/* $Id: TstHGCMMock.cpp 114158 2026-05-20 15:21:01Z andreas.loeffler@oracle.com $ */ +/* $Id: TstHGCMMock.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ /** @file * TstHGCMMock.cpp - Mocking framework for testing HGCM-based host services. * @@ -53,12 +53,6 @@ #include #include -#ifdef VBOX_WITH_SHARED_CLIPBOARD -#include -#include -#include -#endif - /********************************************************************************************************************************* * Global Variables * @@ -289,126 +283,6 @@ static DECLCALLBACK(int) tstHgcmMockSvcCallComplete(VBOXHGCMCALLHANDLE callHandl return VERR_NOT_FOUND; } -#ifdef VBOX_WITH_SHARED_CLIPBOARD -//int tstShClBackendConnect(PSHCLBACKEND, PSHCLCLIENT, bool) { return VINF_SUCCESS; } -//int tstShClBackendDisconnect(PSHCLBACKEND, PSHCLCLIENT) { return VINF_SUCCESS; } - -DECLCALLBACK(int) tstHgcmMockSvcDispatcher(void *pvExtension, uint32_t u32Function, - void *pvParms, uint32_t cbParms) -{ - RT_NOREF(pvExtension, cbParms); - int rc = VINF_SUCCESS; - - PSHCLEXTPARMS pParms = (PSHCLEXTPARMS)pvParms; /* pParms might be NULL, depending on the message. */ - - LogFlowFunc(("u32Function=%RU32, pvParms=%p, cbParms=%RU32\n", u32Function, pvParms, cbParms)); - - switch (u32Function) - { - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: // via VBOX_SHCL_GUEST_FN_REPORT_FORMATS in the guest - { - PSHCLCLIENT pClient = pParms->u.ReportFormats.pClient; - SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; - - rc = ShClBackendReportFormats(pClient->pBackend, pClient, fFormats); - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Reporting guest clipboard formats to the host failed with %Rrc\n", rc)); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - { - PSHCLCLIENT pClient = pParms->u.ReportFormats.pClient; - SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; - - rc = ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: // via VBOX_SHCL_GUEST_FN_DATA_READ in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - SHCLCLIENTCMDCTX cmdCtx; - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - rc = ShClBackendReadData(pClient->pBackend, pClient, &cmdCtx, fFormats, pvData, cbData, - &pParms->u.ReadWriteData.cbActual); - if (RT_SUCCESS(rc)) - LogRel2(("Shared Clipboard: Read host clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, - pParms->u.ReadWriteData.cbActual)); - else - LogRel(("Shared Clipboard: Reading host clipboard data failed with %Rrc\n", rc)); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: // via VBOX_SHCL_GUEST_FN_DATA_WRITE in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - PSHCLCLIENTCMDCTX pCmdCtx = pParms->u.ReadWriteData.pCmdCtx; - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - rc = ShClBackendWriteData(pClient->pBackend, pClient, pCmdCtx, fFormats, pvData, cbData); - if (RT_FAILURE(rc)) - LogRel(("Shared Clipboard: Writing guest clipboard data to the host failed with %Rrc\n", rc)); - /* Complete any pending events. */ - int rc2 = ShClSvcGuestDataSignal(pClient, pCmdCtx, fFormats, pvData, cbData); - if (RT_FAILURE(rc2)) - LogRel(("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", rc2)); - AssertRC(rc2); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - VBOXHGCMSVCFNTABLE *pTable = pParms->u.ReadWriteData.pTable; - rc = ShClBackendInit(pBackend, pTable); - break; - } - - // via VbglR3HGCMDisconnect()->...HGCMService::DisconnectClient()->...HGCMService::instanceDestroy()->...svcUnload() - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - ShClBackendDestroy(pBackend); - rc = VINF_SUCCESS; - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: // via VbglR3ClipboardConnect()->VbglR3HGCMConnect() in the guest - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - bool fHeadless = pParms->u.ReadWriteData.fHeadless; - rc = ShClBackendConnect(pBackend, pClient, fHeadless); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: // via VbglR3ClipboardDisconnect()->VbglR3HGCMDisconnect() in the guest - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = ShClBackendDisconnect(pClient->pBackend, pClient); - break; - } - - case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - rc = ShClBackendSync(pBackend, pClient); - break; - } - - default: - break; - } - - return rc; -} -#endif - /** * Main thread of HGCM mock service. * @@ -434,9 +308,6 @@ static DECLCALLBACK(int) tstHgcmMockSvcThread(RTTHREAD hThread, void *pvUser) if (RT_FAILURE(rc)) return rc; -#ifdef VBOX_WITH_SHARED_CLIPBOARD - rc = pSvc->fnTable.pfnRegisterExtension(pSvc->fnTable.pvService, tstHgcmMockSvcDispatcher, NULL); -#endif if (RT_SUCCESS(rc)) { RTThreadUserSignal(hThread); diff --git a/src/VBox/Main/Makefile.kmk b/src/VBox/Main/Makefile.kmk index 07516b936e65..3ae79339a47e 100644 --- a/src/VBox/Main/Makefile.kmk +++ b/src/VBox/Main/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114613 2026-07-03 15:57:50Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ ## @file # Makefile for the VBox Main module. # @@ -1303,12 +1303,12 @@ if !defined(VBOX_ONLY_SDK) && !defined(VBOX_ONLY_EXTPACKS) # Note this goes on f src-client/GuestSessionImpl.cpp endif ifdef VBOX_WITH_SHARED_CLIPBOARD - VBoxC_DEFS += VBOX_WITH_SHARED_CLIPBOARD_HOST VBoxC_SOURCES += \ + src-client/GuestShClBackend.cpp \ + src-client/GuestShClConn.cpp \ src-client/GuestShClHelpers.cpp \ src-client/GuestShClPrivate.cpp \ src-client/GuestShClSvcExt.cpp \ - src-client/VBoxSharedClipboardSvc-utils.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp if1of ($(KBUILD_TARGET), linux solaris freebsd) diff --git a/src/VBox/Main/idl/VirtualBox.xidl b/src/VBox/Main/idl/VirtualBox.xidl index 6fc7c6558893..3b54d2f58e74 100644 --- a/src/VBox/Main/idl/VirtualBox.xidl +++ b/src/VBox/Main/idl/VirtualBox.xidl @@ -1763,7 +1763,10 @@ - Include clipboard payload items in session data-change events when payload data is available. + + Include clipboard payload items in session data-change and data-requested + events when payload data is available. + @@ -15853,14 +15856,14 @@ - Unique identifier used to distinguish clipboard transfers. + Unique identifier used to distinguish clipboard items. - Source clipboard identifier. For X11 based clipboards, values can be used to distinguish PRIMARY, - SECONDARY and CLIPBOARD selections. + Clipboard source which owns or supplied the item, such as the host, + guest, a remote clipboard source, or a custom source. @@ -16261,13 +16264,16 @@ Examples:
    -
  • Calling setSourcePaths(["/home/user/report.txt"]) publishes - the local file as a transfer root such as report.txt.
  • -
  • Calling setSourcePaths(["/home/user/photos"]) publishes the - local directory as a root such as photos; directory contents are - then addressed as transfer-relative paths below that root.
  • -
  • Calling setSourcePaths(["/tmp/a.txt", "/tmp/b.txt"]) creates - one transfer with two roots, not two independent transfers.
  • +
  • Calling setSourcePaths(["/home/user/report.txt"]) configures + the local file as a root such as report.txt in this + manager-owned transfer tree.
  • +
  • Calling setSourcePaths(["/home/user/photos"]) configures the + local directory as a root such as photos in this manager-owned + transfer tree; directory contents are then addressed as + transfer-relative paths below that root.
  • +
  • Calling setSourcePaths(["/tmp/a.txt", "/tmp/b.txt"]) + configures one transfer with two roots, not two independent + transfers.
Empty path entries, entries containing transfer-list separators, and unsupported @@ -16286,7 +16292,7 @@ The returned values are the same kind of local paths accepted by . They are useful for a local producer or management UI that wants to show or verify what local objects - were offered. They are not the portable transfer names used by the receiver; + were configured. They are not the portable transfer names used by the receiver; use to discover those names. For example, a local host-to-guest transfer may return @@ -16304,12 +16310,19 @@ Clipboard transfer manager. + + API clients obtain clipboard transfers from + or + . The manager's + control methods accept only transfers owned by that manager; + IClipboardTransfer is not a caller-implemented extension point. + @@ -16329,48 +16342,97 @@ - + - Adds a clipboard transfer to the manager. + Creates a Main-owned clipboard transfer and immediately tracks it in + this manager. The returned transfer can be configured, for example by + calling , and is visible + through . + + Manager ownership and tracking do not publish the transfer to the + active Shared Clipboard service client or platform backend. This + method does not currently offer a Main-created transfer to the guest. + - - Clipboard transfer to add. + + Transfer direction. Use ToGuest for host-local source paths. + + + Clipboard source owning the new transfer. + + + Clipboard action associated with the new transfer. + + + Created manager-owned transfer. Removes a clipboard transfer from the manager. + + The transfer is not owned by this manager or is no longer tracked. + + + The transfer is backed by an active Shared Clipboard service + operation. Cancel it before removing it. + - Clipboard transfer to remove. + Manager-owned clipboard transfer to remove. Cancels a clipboard transfer. + + The transfer is not owned by this manager or is no longer tracked. + - Clipboard transfer to cancel. + Manager-owned clipboard transfer to cancel. - Approves a transfer waiting for client approval. + Reserved for approving a transfer waiting for client approval. + + Currently not implemented. No production interaction-response + transport exists. For a transfer owned by this manager, this method + returns E_NOTIMPL. + + + Responding to transfer interactions is not implemented. + + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer to approve. - Reserved approval flags. Must currently be zero. + Reserved approval flags for a future implementation. - Denies a transfer waiting for client approval. + Reserved for denying a transfer waiting for client approval. + + Currently not implemented. No production interaction-response + transport exists. For a transfer owned by this manager, this method + returns E_NOTIMPL. + + + Responding to transfer interactions is not implemented. + + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer to deny. @@ -16382,7 +16444,18 @@ - Supplies a response to a transfer interaction request. + Reserved for supplying a response to a transfer interaction request. + + Currently not implemented. No production interaction-response + transport exists. For a transfer owned by this manager, this method + returns E_NOTIMPL. + + + Responding to transfer interactions is not implemented. + + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer waiting for interaction. @@ -16404,7 +16477,7 @@ - Reserved response flags. Must currently be zero. + Reserved response flags for a future implementation. @@ -16417,6 +16490,9 @@ The method is not implemented yet. + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer to pause. @@ -16432,6 +16508,9 @@ The method is not implemented yet. + + The transfer is not owned by this manager or is no longer tracked. + Clipboard transfer to resume. @@ -16440,32 +16519,19 @@ - Resets all clipboard transfers tracked by the manager. + Resets all Main-created clipboard transfers tracked by the manager only + when no service transfer is active. If a service transfer is active, + this method returns VBOX_E_OBJECT_IN_USE without modifying the + manager because removing Main records without first canceling the + host-service transfer would desynchronize the public and service + lifecycles. + + A service transfer is active. Cancel that transfer before resetting + the manager. + - - - Creates an unpublished clipboard transfer owned by Main. The returned - transfer can be configured, for example by calling - , and then published with - . The transfer is not visible - through until it is - added. - - - Transfer direction. Use ToGuest for host-local source paths. - - - Clipboard source owning the new transfer. - - - Clipboard action associated with the new transfer. - - - Created unpublished transfer. - - for the Main clipboard API overview and the role of this endpoint. When returned by , operations are associated with that session's client identifier. + + Calls to , + , and + are best-effort when the Shared + Clipboard service exists but no active native clipboard client or backend + is connected. In that state, they can return success without changing the + visible operating-system clipboard. @@ -16487,8 +16560,10 @@ Reports clipboard formats from the specified source to the native host clipboard. This also makes those formats the current generic VM clipboard offer for the supplied source, so a later native host data request can be matched - with and completed through - . + with and correlated with + . Because the request event is + non-waitable, a response dispatched later by a passive listener is not + guaranteed to satisfy the original native host read. Clipboard action associated with the reported formats. @@ -16513,6 +16588,9 @@ request; for a session endpoint, the session client identifier must match as well. The supplied action, source and MIME type must match the request and the currently advertised native host clipboard formats. + Because the matching data-request event is non-waitable, a response + dispatched later by a passive listener is not guaranteed to satisfy the + original native host read. Identifier from the corresponding clipboard data request event. @@ -16760,9 +16838,11 @@ , which also updates the generic VM clipboard offer for the guest source. When a native host application later requests one of those formats, Main emits an - and the client completes - the request with . For eager - publication, a client that already has the payload can call + whose request identifier can + be correlated with . The event + is non-waitable, so a later passive response is not guaranteed to + satisfy the original read. For eager publication, a client that + already has the payload can call directly. @@ -17012,7 +17092,7 @@ Identifier of the pending clipboard data request. + + + + Sets a Shared Clipboard service transfer status on the live console + clipboard. This is an internal testcase seam only. + + + Shared Clipboard service session identifier. + + + Shared Clipboard transfer identifier. + + + Host-private transfer generation. + + + + Data source associated with the status. A backing transfer, when + present, must record the same source and remains authoritative for the + transfer direction. + + + + Internal Shared Clipboard transfer status value. + + + IPRT result associated with the status. + + Clipboard item associated with the event, or NULL when no payload item is - included. Session data-change events include this item only when the - session was created with IncludePayload. + included. Session data-change and data-requested events include this item + only when the session was created with IncludePayload. Data-requested + events on the main clipboard event source do not include a payload item. @@ -30725,13 +30835,28 @@ Snapshot 1 (B.vdi) Snapshot 1 (B.vdi) by the native host clipboard backend. An API client may use this event to decide whether and how to supply the requested clipboard data. - The inherited item attribute may be NULL or may contain implementation - specific request context. The request details are provided by the - attributes below. + This event is non-waitable. The requesting backend does not wait for + , and a response dispatched later + by a passive listener is not guaranteed to satisfy the original read. + Native host requests use a one-shot request identifier that can be + correlated with . + + For a host-text request delivered on a session event source created with + IncludePayload, the inherited item attribute contains complete host text + data available to Main when the session event is generated. This can be a + native host snapshot or the current Main clipboard item. It is NULL when + no complete data is available, on the main clipboard event source, and on + session event sources without IncludePayload. An event handler can still + supply replacement data, so this item does not necessarily contain the + bytes ultimately returned to the guest. The request details are provided + by the attributes below. - Unique identifier for this clipboard data request. + Unique identifier for this clipboard data request. For a native host + request, this is a non-zero, one-shot identifier. Supply it through the + host clipboard endpoint that owns the request; it cannot be reused after + the matching request is completed or becomes stale. diff --git a/src/VBox/Main/include/ClipboardImpl.h b/src/VBox/Main/include/ClipboardImpl.h index 3eda1ef6b7d2..9bda72ad47d8 100644 --- a/src/VBox/Main/include/ClipboardImpl.h +++ b/src/VBox/Main/include/ClipboardImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardImpl.h 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Console clipboard API. */ @@ -97,14 +97,28 @@ class ATL_NO_VTABLE Clipboard : const std::vector > &aFormats); HRESULT i_getCurrentStateForEvent(ClipboardSource_T *aSource, std::vector > &aFormats); + HRESULT i_getCurrentSource(ClipboardSource_T *aSource); LONG64 i_nextEventRevision(); HRESULT i_reset(); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS HRESULT i_transferCancel(ULONG aTransferId); HRESULT i_transferCancel(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration); + /** + * Handles a Shared Clipboard transfer lifecycle status delivered by the host service. + * + * @returns COM status code. + * @param aServiceSessionId Shared Clipboard service session identifier. + * @param aTransferId Shared Clipboard transfer identifier. + * @param aGeneration Host-private transfer generation. + * @param aTransfer Borrowed service transfer backing the data plane. + * @param enmShClSource Data source recorded by the backing transfer. + * @param enmStatus Transfer lifecycle status. + * @param vrcTransfer Transfer status result code. + */ HRESULT i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration, + PSHCLTRANSFER aTransfer, SHCLSOURCE enmShClSource, SHCLTRANSFERSTATUS enmStatus, int vrcTransfer); @@ -201,6 +215,23 @@ class ATL_NO_VTABLE Clipboard : * @{ */ HRESULT requestData(const com::Utf8Str &aMimeType, ULONG *aRequestId); + /** + * Sets a Shared Clipboard transfer status for Main testcase coverage. + * + * @returns COM status code. + * @param aServiceSessionId Shared Clipboard service session identifier. + * @param aTransferId Shared Clipboard transfer identifier. + * @param aGeneration Host-private transfer generation. + * @param aSource Data source associated with the status. + * @param aStatus Internal Shared Clipboard transfer status value. + * @param aResult IPRT result associated with the status. + */ + HRESULT setTransferStatus(ULONG aServiceSessionId, + ULONG aTransferId, + LONG64 aGeneration, + ClipboardSource_T aSource, + ULONG aStatus, + LONG aResult); /** @} */ HRESULT i_createFormat(const com::Utf8Str &aMimeType, ComPtr &aFormat); @@ -233,7 +264,8 @@ class ATL_NO_VTABLE Clipboard : ULONG i_fireDataRequested(VBOXSHCLMAINCLIENTID aClientId, ClipboardAction_T aAction, ClipboardSource_T aSource, - uint32_t uFormat); + uint32_t uFormat, + const std::vector *pResolvedBuffer = NULL); bool i_isClientIdRegisteredLocked(VBOXSHCLMAINCLIENTID aClientId) const; bool i_isClientFormatOwnerLocked(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFormats, ClipboardSource_T aSource) const; VBOXSHCLMAINCLIENTID i_allocateClientId(); diff --git a/src/VBox/Main/include/ClipboardSessionImpl.h b/src/VBox/Main/include/ClipboardSessionImpl.h index dbe6c7893707..6c8ea9fbd3eb 100644 --- a/src/VBox/Main/include/ClipboardSessionImpl.h +++ b/src/VBox/Main/include/ClipboardSessionImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardSessionImpl.h 114560 2026-06-29 08:32:23Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardSessionImpl.h 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard session API object. */ @@ -55,6 +55,16 @@ class ATL_NO_VTABLE ClipboardSession : void FinalRelease(); HRESULT init(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFlags, Clipboard *aParent); +#ifdef UNIT_TEST + /** + * Initializes a parentless session for testing the public session object's own state. + * + * @returns COM status code. + * @param aClientId Main clipboard client ID represented by the session. + * @param fFlags IClipboardSessionFlag mask. + */ + HRESULT initForTesting(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFlags); +#endif void uninit(); HRESULT i_onEventSourceChanged(const ComPtr &aListener, BOOL fAdded); diff --git a/src/VBox/Main/include/ClipboardTransferDataImpl.h b/src/VBox/Main/include/ClipboardTransferDataImpl.h index 1ec2e579f7ef..0bb6873dcc27 100644 --- a/src/VBox/Main/include/ClipboardTransferDataImpl.h +++ b/src/VBox/Main/include/ClipboardTransferDataImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferDataImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferDataImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer data plane object. */ @@ -95,10 +95,10 @@ class ATL_NO_VTABLE ClipboardTransferData : #endif { } - /** Parent transfer object used to keep the backing transfer alive. */ + /** Parent transfer object used to preserve the public object relationship. */ ComPtr mParent; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /** Shared Clipboard transfer backing data-plane operations. */ + /** Parent-owned Shared Clipboard transfer backing data-plane operations. */ PSHCLTRANSFER mTransfer; #endif } mData; diff --git a/src/VBox/Main/include/ClipboardTransferDirectoryImpl.h b/src/VBox/Main/include/ClipboardTransferDirectoryImpl.h index 9347f326c611..dcaeea989d62 100644 --- a/src/VBox/Main/include/ClipboardTransferDirectoryImpl.h +++ b/src/VBox/Main/include/ClipboardTransferDirectoryImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferDirectoryImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferDirectoryImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer directory handle. */ @@ -35,6 +35,8 @@ #include +class ClipboardTransfer; + /** * Clipboard transfer directory handle. */ @@ -49,7 +51,7 @@ class ATL_NO_VTABLE ClipboardTransferDirectory : void FinalRelease(); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - HRESULT init(const ComPtr &aParent, + HRESULT init(const ComObjPtr &aParent, PSHCLTRANSFER aTransfer, const com::Utf8Str &aPath, SHCLLISTHANDLE aHandle); @@ -81,11 +83,13 @@ class ATL_NO_VTABLE ClipboardTransferDirectory : , mStatus(DirectoryStatus_Undefined) { } - ComPtr mParent; - PSHCLTRANSFER mTransfer; - SHCLLISTHANDLE mHandle; - com::Utf8Str mPath; - DirectoryStatus_T mStatus; + /** Concrete parent transfer object keeping the borrowed transfer alive. */ + ComObjPtr mParent; + /** Parent-owned Shared Clipboard transfer owning mHandle. */ + PSHCLTRANSFER mTransfer; + SHCLLISTHANDLE mHandle; + com::Utf8Str mPath; + DirectoryStatus_T mStatus; } mData; }; diff --git a/src/VBox/Main/include/ClipboardTransferFileImpl.h b/src/VBox/Main/include/ClipboardTransferFileImpl.h index 6b2ae1a6e168..f6e3d067fbab 100644 --- a/src/VBox/Main/include/ClipboardTransferFileImpl.h +++ b/src/VBox/Main/include/ClipboardTransferFileImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferFileImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferFileImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer file handle. */ @@ -114,7 +114,9 @@ class ATL_NO_VTABLE ClipboardTransferFile : , mCreationMode(0) { RT_ZERO(mInfo); } + /** Parent transfer object. */ ComPtr mParent; + /** Retained Shared Clipboard transfer owning mHandle. */ PSHCLTRANSFER mTransfer; SHCLOBJHANDLE mHandle; com::Utf8Str mPath; diff --git a/src/VBox/Main/include/ClipboardTransferImpl.h b/src/VBox/Main/include/ClipboardTransferImpl.h index 570574c66322..5147e8b98f4f 100644 --- a/src/VBox/Main/include/ClipboardTransferImpl.h +++ b/src/VBox/Main/include/ClipboardTransferImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer object. */ @@ -66,8 +66,32 @@ class ATL_NO_VTABLE ClipboardTransfer : void uninit(); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /** Returns the backing Shared Clipboard transfer. */ + /** Returns the parent-owned backing Shared Clipboard transfer. */ PSHCLTRANSFER i_getTransfer() const; + /** + * Lists nodes from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aPath Transfer-relative directory path, or empty for roots. + * @param aFlags ClipboardTransferListFlag mask. + * @param aNodes Where to return listed nodes. + */ + HRESULT i_list(PSHCLTRANSFER pTransfer, + const com::Utf8Str &aPath, + ULONG aFlags, + std::vector > &aNodes); + /** + * Queries one node from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aPath Transfer-relative path. + * @param aNode Where to return the node information. + */ + HRESULT i_query(PSHCLTRANSFER pTransfer, + const com::Utf8Str &aPath, + ComPtr &aNode); #endif /** Updates the public transfer state. */ void i_setState(ClipboardTransferState_T aState, @@ -108,6 +132,18 @@ class ATL_NO_VTABLE ClipboardTransfer : HRESULT createDirectory(const com::Utf8Str &aPath); /** @} */ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** + * Returns root nodes from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aNodes Where to return the root nodes. + */ + HRESULT i_roots(PSHCLTRANSFER pTransfer, + std::vector > &aNodes); +#endif + struct Data { /** Unique transfer identifier. */ @@ -131,9 +167,12 @@ class ATL_NO_VTABLE ClipboardTransfer : #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** Source-side local paths explicitly configured for this transfer. */ std::vector mSourcePaths; - /** Shared Clipboard transfer backing data-plane operations. Optional. */ + /** Parent-owned Shared Clipboard transfer backing data-plane operations. Optional. */ PSHCLTRANSFER mTransfer; - /** Whether this object owns and destroys mTransfer. */ + /** + * Whether this object owns and destroys mTransfer. Otherwise the + * object keeps its parent alive while using this borrowed transfer. + */ bool mfOwnTransfer; #endif } mData; diff --git a/src/VBox/Main/include/ClipboardTransferManagerImpl.h b/src/VBox/Main/include/ClipboardTransferManagerImpl.h index 2c0c8d54a8a3..ac23f64b0e55 100644 --- a/src/VBox/Main/include/ClipboardTransferManagerImpl.h +++ b/src/VBox/Main/include/ClipboardTransferManagerImpl.h @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferManagerImpl.h 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferManagerImpl.h 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer manager object. */ @@ -32,6 +32,7 @@ #endif #include "ClipboardTransferManagerWrap.h" +#include "ClipboardTransferImpl.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include @@ -57,12 +58,25 @@ class ATL_NO_VTABLE ClipboardTransferManager : HRESULT init(IEventSource *aEventSource = NULL, Clipboard *aParent = NULL); void uninit(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** Resets the internally tracked transfer list. */ void i_reset(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** + * Handles a Shared Clipboard transfer lifecycle status delivered by the host service. + * + * @returns COM status code. + * @param aServiceSessionId Service session that owns the transfer. + * @param aTransferId Shared Clipboard transfer identifier. + * @param aGeneration Host-private transfer generation. + * @param aTransfer Borrowed service transfer used to validate status metadata. + * @param enmShClSource Data source recorded by the backing transfer. + * @param enmStatus Transfer lifecycle status. + * @param vrcTransfer Transfer result code associated with the status. + */ HRESULT i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration, + PSHCLTRANSFER aTransfer, SHCLSOURCE enmShClSource, SHCLTRANSFERSTATUS enmStatus, int vrcTransfer); @@ -71,23 +85,33 @@ class ATL_NO_VTABLE ClipboardTransferManager : private: - void i_fireTransferEvent(IClipboardTransfer *aTransfer, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + void i_fireTransferEvent(const ComObjPtr &aTransfer, ClipboardTransferState_T aState, ClipboardTransferInteraction_T aInteraction, const com::Utf8Str &aPath, const com::Utf8Str &aMessage, ClipboardError_T aError); +#endif /** @name Wrapped IClipboardTransferManager properties and methods * @{ */ HRESULT getTransfers(ClipboardTransferDirection_T aDirection, ULONG aFlags, std::vector > &aTransfers); - HRESULT createTransfer(ClipboardTransferDirection_T aDirection, - ClipboardSource_T aSource, - ClipboardAction_T aAction, - ComPtr &aTransfer); - HRESULT add(const ComPtr &aTransfer); + /** + * Creates and tracks a Main-owned clipboard transfer. + * + * @returns COM status code. + * @param aDirection Transfer direction. + * @param aSource Clipboard source owning the transfer. + * @param aAction Clipboard transfer action. + * @param aTransfer Where to return the transfer object. + */ + HRESULT create(ClipboardTransferDirection_T aDirection, + ClipboardSource_T aSource, + ClipboardAction_T aAction, + ComPtr &aTransfer); HRESULT remove(const ComPtr &aTransfer); HRESULT cancel(const ComPtr &aTransfer); HRESULT approve(const ComPtr &aTransfer, @@ -121,11 +145,12 @@ class ATL_NO_VTABLE ClipboardTransferManager : : mServiceSessionId(NIL_SHCLSESSIONID) , mTransferId(0) , mGeneration(NIL_SHCLTRANSFERGEN) + , mDirection(SHCLTRANSFERDIR_UNKNOWN) + , mSource(SHCLSOURCE_INVALID) , mStatus(SHCLTRANSFERSTATUS_NONE) , mState(ClipboardTransferState_Added) , mfTerminal(false) , mfCancelRequested(false) - , mfPublished(false) #endif { } @@ -133,27 +158,85 @@ class ATL_NO_VTABLE ClipboardTransferManager : SHCLSESSIONID mServiceSessionId; ULONG mTransferId; SHCLTRANSFERGEN mGeneration; + /** Shared Clipboard data-plane direction. */ + SHCLTRANSFERDIR mDirection; + /** Shared Clipboard data source recorded by the backing transfer. */ + SHCLSOURCE mSource; SHCLTRANSFERSTATUS mStatus; ClipboardTransferState_T mState; bool mfTerminal; bool mfCancelRequested; #endif - ComPtr mTransfer; + /** Concrete transfer owned by this manager. */ + ComObjPtr mTransfer; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /** Whether the transfer is visible through getTransfers(). */ - bool mfPublished; ComPtr mProgress; ComPtr mProgressControl; + + /** Returns whether the public Main interface matches this record. */ + bool matches(IClipboardTransfer *aTransfer) const + { + ClipboardTransfer *pTransfer = mTransfer; + return static_cast(pTransfer) == aTransfer; + } + + /** Returns whether the service identity matches this record. */ + bool matches(SHCLSESSIONID aServiceSessionId, ULONG aTransferId, + SHCLTRANSFERGEN aGeneration) const + { + return mServiceSessionId == aServiceSessionId + && mTransferId == aTransferId + && mGeneration == aGeneration; + } + + /** Returns whether the concrete Main object matches this record. */ + bool matches(ClipboardTransfer *aTransfer) const + { + ClipboardTransfer *pTransfer = mTransfer; + return pTransfer == aTransfer; + } + + /** Returns whether both the Main object and service identity match this record. */ + bool matches(ClipboardTransfer *aTransfer, + SHCLSESSIONID aServiceSessionId, ULONG aTransferId, + SHCLTRANSFERGEN aGeneration) const + { + return matches(aTransfer) + && matches(aServiceSessionId, aTransferId, aGeneration); + } #endif }; + /** Transfer record container type. */ + typedef std::vector TransferRecords; + /** Parent clipboard object. */ Clipboard *mParent; /** Clipboard event source. */ ComPtr mEventSource; /** Current clipboard transfer records. */ - std::vector mTransfers; + TransferRecords mTransfers; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Finds a record by public Main interface while the caller owns the manager lock. */ + TransferRecords::iterator findTransferRecord(IClipboardTransfer *aTransfer) + { + for (TransferRecords::iterator it = mTransfers.begin(); it != mTransfers.end(); ++it) + if (it->matches(aTransfer)) + return it; + return mTransfers.end(); + } + + /** Finds an exact transfer record while the caller owns the manager lock. */ + TransferRecords::iterator findTransferRecord(ClipboardTransfer *aTransfer, + SHCLSESSIONID aServiceSessionId, ULONG aTransferId, + SHCLTRANSFERGEN aGeneration) + { + for (TransferRecords::iterator it = mTransfers.begin(); it != mTransfers.end(); ++it) + if (it->matches(aTransfer, aServiceSessionId, aTransferId, aGeneration)) + return it; + return mTransfers.end(); + } + /** Next Main-created transfer identifier. */ ULONG mNextTransferId; #endif diff --git a/src/VBox/Main/include/ConsoleVRDPServer.h b/src/VBox/Main/include/ConsoleVRDPServer.h index 88448bc7dfbf..fe596a9c105d 100644 --- a/src/VBox/Main/include/ConsoleVRDPServer.h +++ b/src/VBox/Main/include/ConsoleVRDPServer.h @@ -1,4 +1,4 @@ -/* $Id: ConsoleVRDPServer.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: ConsoleVRDPServer.h 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console VRDE Server Helper class and implementation of IVRDEServerInfo */ @@ -45,8 +45,9 @@ #include #include -#include +#include #include +#include #include "SchemaDefs.h" @@ -137,8 +138,10 @@ class ConsoleVRDPServer bool isRemoteUSBThreadRunning (void); void waitRemoteUSBThreadEvent (RTMSINTERVAL cMillies); - void ClipboardCreate (uint32_t u32ClientId); - void ClipboardDelete (uint32_t u32ClientId); + int ClipboardReportGuestFormats(SHCLFORMATS fFormats) const; + int ClipboardReadRemoteData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) const; + int ClipboardWriteGuestData(SHCLFORMAT uFormat, const void *pvData, uint32_t cbData) const; + void ClipboardSetGuestShClAvailable(bool fAvailable); /* * Forwarders to VRDP server library. @@ -227,14 +230,17 @@ class ConsoleVRDPServer RTCRITSECT mCritSect; + /** Whether direct clipboard callbacks may acquire the GuestShCl singleton. */ + bool mfClipboardGuestShClAvailable; + /** Number of direct clipboard callbacks currently using GuestShCl. */ + uint32_t mcClipboardGuestShClCalls; + /** Signalled while no direct clipboard callback is using GuestShCl. */ + RTSEMEVENTMULTI mhClipboardGuestShClCallsDone; + int lockConsoleVRDPServer (void); void unlockConsoleVRDPServer (void); - int mcClipboardRefs; - PFNSHCLEXTCALLBACK mpfnClipboardCallback; - static DECLCALLBACK(int) ClipboardCallback (void *pvCallback, uint32_t u32ClientId, uint32_t u32Function, uint32_t u32Format, const void *pvData, uint32_t cbData); - static DECLCALLBACK(int) ClipboardServiceExtension(void *pvExtension, uint32_t u32Function, void *pvParms, uint32_t cbParms); #ifdef VBOX_WITH_USB RemoteUSBBackend *usbBackendFindByUUID (const Guid *pGuid); diff --git a/src/VBox/Main/include/GuestShClBackend.h b/src/VBox/Main/include/GuestShClBackend.h new file mode 100644 index 000000000000..675c94ecad6d --- /dev/null +++ b/src/VBox/Main/include/GuestShClBackend.h @@ -0,0 +1,160 @@ +/* $Id: GuestShClBackend.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Native backend dispatcher. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef MAIN_INCLUDED_GuestShClBackend_h +#define MAIN_INCLUDED_GuestShClBackend_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include + +class GuestShClConn; +struct SHCLBACKENDOPS; +/** Pointer to a native Shared Clipboard backend operation table. */ +typedef struct SHCLBACKENDOPS const *PCSHCLBACKENDOPS; + +/** + * Dispatches Shared Clipboard operations to a native platform backend. + * + * The selected operation table is fixed when the object is constructed. + * Platform implementations and their entry points remain private to VBoxC. + */ +class ShClBackend +{ +public: + /** Creates a dispatcher using the native backend selected for this host. */ + ShClBackend(void); + + /** Destroys the dispatcher. The backend must be disconnected first. */ + ~ShClBackend(void); + + /** + * Initializes the selected native backend. + * + * @returns VBox status code. + */ + int init(void); + + /** Destroys the selected native backend. */ + void destroy(void); + + /** + * Replaces the native backend callback table for test purposes. + * + * Does nothing when the selected backend does not implement callback + * replacement. + * + * @param pCallbacks Callback table, or NULL for backend defaults. + */ + void setCallbacks(PSHCLCALLBACKS pCallbacks); + + /** + * Connects a Main service connection to the selected native backend. + * + * @returns VBox status code. + * @param pConn Main connection owning the service endpoint. + */ + int connect(GuestShClConn *pConn); + + /** + * Disconnects the current native backend context. + * + * @returns VBox status code. + */ + int disconnect(void); + + /** + * Reports guest clipboard formats to the selected native backend. + * + * @returns VBox status code. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ + int reportFormats(SHCLFORMATS fFormats); + + /** + * Reads native clipboard data for the guest. + * + * @returns VBox status code. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the required or actual byte count. + */ + int readData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); + + /** + * Writes guest clipboard data to the selected native backend. + * + * @returns VBox status code. + * @param uFormat Clipboard format to write. + * @param pvData Clipboard data to write. + * @param cbData Clipboard data size in bytes. + */ + int writeData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData); + + /** + * Synchronizes the selected native backend with the guest. + * + * @returns VBox status code. + */ + int sync(void); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** + * Gets the selected native backend's callbacks for a new transfer. + * + * @param pCallbacks Where to return the callback table. + */ + void transferGetCallbacks(PSHCLTRANSFERCALLBACKS pCallbacks); + + /** + * Handles a transfer status reply in the selected native backend. + * + * @returns VBox status code. + * @param pTransfer Transfer whose status changed. + * @param enmSource Source issuing the reply. + * @param enmStatus New transfer status. + * @param rcStatus Status-specific VBox status code. + */ + int transferHandleStatusReply(PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus); +#endif + +private: + /** No copy construction. */ + ShClBackend(ShClBackend const &rThat); + /** No assignment. */ + ShClBackend &operator=(ShClBackend const &rThat); + + /** Selected native backend operation table; immutable. */ + PCSHCLBACKENDOPS m_pOps; + /** Opaque connection context owned by the selected native backend. */ + PSHCLCONTEXT m_pCtx; +}; + +#endif /* !MAIN_INCLUDED_GuestShClBackend_h */ diff --git a/src/VBox/Main/include/GuestShClConn.h b/src/VBox/Main/include/GuestShClConn.h new file mode 100644 index 000000000000..70df4fd98862 --- /dev/null +++ b/src/VBox/Main/include/GuestShClConn.h @@ -0,0 +1,363 @@ +/* $Id: GuestShClConn.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Service connection management. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef MAIN_INCLUDED_GuestShClConn_h +#define MAIN_INCLUDED_GuestShClConn_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "GuestShClBackend.h" + +#include +#include + +class GuestShCl; + +/** + * Represents Main's connection to one Shared Clipboard HGCM client. + * + * The HGCM service owns the opaque client referenced by the service endpoint, + * whereas this object owns the corresponding native backend context. Public + * operations pin both values internally so that service disconnect can wait + * for callers without exposing an ownership guard to users of this class. + */ +class GuestShClConn +{ +public: + /** + * Creates an empty Shared Clipboard connection. + * + * @param pOwner GuestShCl instance owning this connection. May be + * NULL in an out-of-process testcase. + * @throws VBox status code if internal synchronization cannot be created. + */ + GuestShClConn(GuestShCl *pOwner); + + /** + * Destroys the connection object. + * + * The native backend and service client must have been disconnected first. + */ + ~GuestShClConn(void); + + /** + * Initializes the process-wide native clipboard backend. + * + * @returns VBox status code. + */ + int initBackend(void); + + /** + * Disconnects the current client, if any, and destroys the native backend. + * + * @returns VBox status code from disconnecting the active backend context. + */ + int destroyBackend(void); + + /** + * Replaces the native backend callback table for test purposes. + * + * @param pCallbacks Callback table, or NULL for backend defaults. + */ + void setBackendCallbacks(PSHCLCALLBACKS pCallbacks); + + /** + * Connects a service endpoint to the native clipboard backend. + * + * @returns VBox status code. + * @param pTransport Service endpoint supplied by the HGCM service. + */ + int connect(PCSHCLTRANSPORT pTransport); + + /** + * Disconnects the service endpoint from the native clipboard backend. + * + * @returns VBox status code. + * @param pTransport Service endpoint being disconnected. + */ + int disconnect(PCSHCLTRANSPORT pTransport); + + /** + * Checks whether a service endpoint identifies the active connection. + * + * @returns true if @a pTransport identifies this connection, otherwise false. + * @param pTransport Service endpoint to compare. + */ + bool matches(PCSHCLTRANSPORT pTransport) const; + + /** + * Checks whether a service client is connected. + * + * @returns true if connected, otherwise false. + */ + bool isConnected(void) const; + + /** + * Queues a clipboard format announcement for the guest. + * + * @returns VBox status code. + * @param fFormats Formats to report, VBOX_SHCL_FMT_XXX. + * @param pfReported Where to return the filtered formats. Optional. + */ + int reportFormatsToGuest(SHCLFORMATS fFormats, SHCLFORMATS *pfReported = NULL); + + /** + * Publishes formats discovered by the native host clipboard backend. + * + * @returns VBox status code. + * @param fFormats Formats to publish, VBOX_SHCL_FMT_XXX. + */ + int reportLocalFormats(SHCLFORMATS fFormats); + + /** + * Requests clipboard data from the guest without waiting for a reply. + * + * @returns VBox status code. + * @param fFormats Requested formats, VBOX_SHCL_FMT_XXX. + * @param ppEvent Where to return the reply event. Optional. + */ + int readDataFromGuestAsync(SHCLFORMATS fFormats, PSHCLEVENT *ppEvent); + + /** + * Requests and waits for clipboard data from the guest. + * + * @returns VBox status code. + * @param uFormat Requested format, VBOX_SHCL_FMT_XXX. + * @param ppvData Where to return the allocated data buffer. + * @param pcbData Where to return the data size. + */ + int readDataFromGuest(SHCLFORMAT uFormat, void **ppvData, uint32_t *pcbData); + + /** + * Validates and retains a pending guest-data reply. + * + * @returns VBox status code. + * @retval VINF_SUCCESS if the reply was retained or if its event already + * expired. In the latter case @a phToken is set to NULL. + * @param pCmdCtx Command context identifying the pending reply. + * @param uFormat Format carried by the reply. + * @param phToken Where to return the retained reply token, or + * NULL if the event already expired. A returned + * token pins the connection until it is passed to + * guestDataComplete() or guestDataCancel(). + */ + int guestDataBegin(PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken); + + /** + * Signals and releases a retained guest-data reply. + * + * @returns VBox status code. + * @param hToken Retained reply token returned by guestDataBegin(). + * @param pvData Reply data. Optional when @a cbData is zero. + * @param cbData Reply data size in bytes. + */ + int guestDataComplete(SHCLGUESTDATATOKEN hToken, void const *pvData, uint32_t cbData); + + /** + * Releases a retained guest-data reply without signalling it. + * + * @param hToken Retained reply token returned by guestDataBegin(). + */ + void guestDataCancel(SHCLGUESTDATATOKEN hToken); + + /** + * Synchronizes the native clipboard backend with the guest. + * + * @returns VBox status code. + */ + int syncBackend(void); + + /** + * Reports guest formats to the native clipboard backend. + * + * @returns VBox status code. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ + int reportFormatsToBackend(SHCLFORMATS fFormats); + + /** + * Reads data from the native clipboard backend. + * + * @returns VBox status code. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size. + * @param pcbActual Where to return the actual or required size. + */ + int readDataFromBackend(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); + + /** + * Writes guest data to the native clipboard backend. + * + * @returns VBox status code. + * @param uFormat Clipboard format to write. + * @param pvData Data buffer. Optional when @a cbData is zero. + * @param cbData Data size in bytes. + */ + int writeDataToBackend(SHCLFORMAT uFormat, void *pvData, uint32_t cbData); + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** + * Returns the native backend callbacks for a new transfer. + * + * @returns VBox status code. + * @param pCallbacks Where to return the callback table. + */ + int transferGetCallbacks(PSHCLTRANSFERCALLBACKS pCallbacks); + + /** + * Handles a transfer status reply in the native backend. + * + * @returns VBox status code. + * @param pTransfer Transfer whose status changed. + * @param enmSource Endpoint issuing the reply. + * @param enmStatus New transfer status. + * @param rcStatus Status-specific VBox status code. + */ + int transferHandleStatusReply(PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus); + + /** + * Retains a service transfer selected by ID. + * + * @returns Retained transfer on success, or NULL if not found or disconnected. + * @param idTransfer Transfer ID to look up. + */ + PSHCLTRANSFER transferGetByIdRetained(SHCLTRANSFERID idTransfer); + + /** + * Retains a service transfer selected by its complete generation key. + * + * @returns Retained transfer on success, or NULL if not found or disconnected. + * @param idSession Service session ID. + * @param idTransfer Transfer ID. + * @param uGeneration Transfer generation. + */ + PSHCLTRANSFER transferGetByKeyRetained(SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration); + + /** + * Creates and retains a service-owned transfer. + * + * @returns VBox status code. + * @param enmDir Transfer direction. + * @param enmSource Transfer source. + * @param pCallbacks Transfer callback table. + * @param idTransfer Requested transfer ID. + * @param ppTransfer Where to return the retained transfer. + */ + int transferCreate(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, + SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer); + + /** + * Initializes a service-owned transfer. + * + * @returns VBox status code. + * @param pTransfer Transfer to initialize. + */ + int transferInit(PSHCLTRANSFER pTransfer); + + /** + * Destroys a service-owned transfer selected by ID. + * + * @param idTransfer Transfer ID to destroy. + */ + void transferDestroyById(SHCLTRANSFERID idTransfer); + + /** + * Destroys every transfer owned by the connected service client. + * + * This teardown operation remains available while disconnect is closing + * the connection because transfer callbacks can retain backend state. + */ + void transferDestroyAll(void); + + /** + * Initializes a provider which reads transfer data from the guest. + * + * @returns VBox status code. + * @param pProvider Provider to initialize. + */ + int transferProviderInitGuest(PSHCLTXPROVIDER pProvider); + +#endif + +private: + /** Internal connection lifecycle states. */ + enum State + { + /** No service client is associated with the object. */ + State_Disconnected = 0, + /** The native backend is establishing its per-client context. */ + State_Connecting, + /** The service client and native backend context are usable. */ + State_Connected, + /** New calls are blocked while existing calls and backend workers drain. */ + State_Closing + }; + + /** No copy construction. */ + GuestShClConn(GuestShClConn const &rThat); + /** No assignment. */ + GuestShClConn &operator=(GuestShClConn const &rThat); + + /** + * Begins an operation using the current connection. + * + * @returns VBox status code. + * @param pTransport Where to return the stable service endpoint. + */ + int i_callBegin(PSHCLTRANSPORT pTransport); + /** + * Finishes an operation begun by i_callBegin(). + */ + void i_callEnd(void); + /** + * Waits for every operation begun by i_callBegin() to finish. + */ + void i_waitForCalls(void); + + /** GuestShCl instance owning this object for the full object lifetime; immutable. */ + GuestShCl *m_pOwner; + /** Serializes connection state and active-call accounting. */ + mutable RTCRITSECT m_CritSect; + /** Signalled while no service or backend calls are active. */ + RTSEMEVENTMULTI m_hCallsDone; + /** Current lifecycle state; protected by m_CritSect. */ + State m_enmState; + /** Whether the process-wide native clipboard backend is initialized; protected by m_CritSect. */ + bool m_fBackendInitialized; + /** Number of operations currently using m_Transport or m_Backend; protected by m_CritSect. */ + uint32_t m_cCalls; + /** Non-owning endpoint for the service-owned HGCM client; protected by m_CritSect. */ + SHCLTRANSPORT m_Transport; + /** Native backend dispatcher and its platform connection context. */ + ShClBackend m_Backend; +}; + +#endif /* !MAIN_INCLUDED_GuestShClConn_h */ diff --git a/src/VBox/Main/include/GuestShClPrivate.h b/src/VBox/Main/include/GuestShClPrivate.h index 7f3040231405..3afda7b6b75e 100644 --- a/src/VBox/Main/include/GuestShClPrivate.h +++ b/src/VBox/Main/include/GuestShClPrivate.h @@ -1,4 +1,4 @@ -/* $Id: GuestShClPrivate.h 114584 2026-07-01 13:24:25Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClPrivate.h 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Private Shared Clipboard code for the Main API. */ @@ -32,31 +32,13 @@ #endif #include -#include - -#include +#include /** * Forward prototype declarations. */ class Console; - -/** - * Struct for keeping a Shared Clipboard service extension. - */ -struct SHCLSVCEXT -{ - /** Service extension callback function. - * Setting this to NULL deactivates the extension. */ - PFNHGCMSVCEXT pfnExt; - /** User-supplied service extension data. Might be NULL if not being used. */ - void * pvExt; - /** Pointer to an optional extension callback. - * Might be NULL if not being used. */ - PFNSHCLEXTCALLBACK pfnExtCallback; -}; -/** Pointer to a Shared Clipboard service extension. */ -typedef SHCLSVCEXT *PSHCLSVCEXT; +class GuestShClConn; /** * Private singleton class for managing the Shared Clipboard implementation within Main. @@ -134,9 +116,7 @@ class GuestShCl bool i_isHostDataSeqCurrentLocked(uint64_t uSeq); uint64_t i_getGuestDataSeq(void); bool i_isGuestDataSeqCurrent(uint64_t uSeq); - int i_beginGuestRead(PSHCLCLIENT *ppClient); - void i_endGuestRead(void); - void i_waitForGuestReads(void); + int i_reportRemoteFormatsToGuestNow(SHCLFORMATS fFormats); /** @} */ public: @@ -149,10 +129,9 @@ class GuestShCl int ReportFormatsToHost(SHCLFORMATS fFormats); int WriteDataToHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbData); int ReportFormatsToGuest(SHCLFORMATS fFormats); - int ReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource); + int ReportRemoteFormatsToGuest(SHCLFORMATS fFormats); + int ReportFormatsToGuest(GuestShClConn *pConn, SHCLFORMATS fFormats, SHCLSOURCE enmSource); int ReportError(const char *pcszId, int vrc, const char *pcszMsgFmt, ...); - int RegisterServiceExtension(PFNHGCMSVCEXT pfnExtension, void *pvExtension); - int UnregisterServiceExtension(PFNHGCMSVCEXT pfnExtension); /** @} */ public: @@ -164,30 +143,24 @@ class GuestShCl protected: - /** @name Service extension callback helpers. - * @{ */ - int i_forwardToSvcExt(uint32_t u32Function, void *pvParms, uint32_t cbParms); - int i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32_t cbParms); - /** @} */ + int i_svcExtParmsValidate(uint32_t u32Function, void *pvParms, uint32_t cbParms); protected: /** @name Service extension callback handlers. * @{ */ - int i_handleSvcExtSetCallback(PSHCLEXTPARMS pParms); - int i_handleSvcExtReportFormatsToHost(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtReportFormatsToGuest(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtDataRead(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtDataReadVrde(PSHCLEXTPARMS pParms); - int i_handleSvcExtDataWrite(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendInit(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendDestroy(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendConnect(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendDisconnect(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtBackendSync(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); - int i_handleSvcExtError(PSHCLEXTPARMS pParms); + int i_svcExtReportFormatsToHostCallback(PSHCLEXTPARMS pParms); + int i_svcExtDataReadCallback(PSHCLEXTPARMS pParms); + int i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms); + int i_svcExtBackendInitCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtBackendDestroyCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtBackendConnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtBackendDisconnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtBackendSyncCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtErrorCallback(PSHCLEXTPARMS pParms); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - int i_handleSvcExtFileTransfer(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms); + int i_svcExtTransferGetCallbacksCallback(PSHCLEXTPARMS pParms); + int i_svcExtFileTransferCallback(PSHCLEXTPARMS pParms); #endif /** @} */ @@ -198,24 +171,16 @@ class GuestShCl Console *m_pConsole; /** Critical section to serialize access. */ RTCRITSECT m_CritSect; - /** Pointer an additional service extension handle to serve (daisy chaining). - * - * This currently only is being used by the Console VRDP server helper class (historical reasons). - * We might want to transform this into a map later if we (ever) need more than one service extension, - * or drop this concept althogether when we move the service stuff out of the VM process (later). */ - SHCLSVCEXT m_SvcExtVRDP; - /** Pointer to an optional extension callback. - * Might be NULL if not being used. */ - PFNSHCLEXTCALLBACK m_pfnExtCallback; - /** Active guest clipboard client, if any. - * Weak pointer owned by the HGCM service and protected by m_CritSect. */ - PSHCLCLIENT m_pClient; - /** Whether new guest data reads using m_pClient are blocked. */ - bool m_fGuestReadsBlocked; - /** Number of active guest data reads using m_pClient outside m_CritSect. */ - uint32_t m_cGuestReads; - /** Signalled when no guest data reads are active. */ - RTSEMEVENTMULTI m_hGuestReadsDone; + /** Serializes remote format publication and the handoff after a remote data read. */ + RTCRITSECT m_RemoteFormatsCritSect; + /** Main-owned connection encapsulating the service endpoint and native backend context. */ + GuestShClConn *m_pConn; + /** Whether Main is synchronously reading from the remote clipboard provider. */ + bool m_fRemoteDataReadActive; + /** Whether a remote format announcement arrived during that read. */ + bool m_fRemoteFormatsPending; + /** Latest remote formats deferred until the active read completes. */ + SHCLFORMATS m_fPendingRemoteFormats; /** Host data sequence counter, protected by m_CritSect. */ uint64_t m_uHostDataSeq; /** Guest data sequence counter, protected by m_CritSect. */ @@ -232,4 +197,3 @@ class GuestShCl #define GuestShClInst() GuestShCl::GetInst() #endif /* !MAIN_INCLUDED_GuestShClPrivate_h */ - diff --git a/src/VBox/Main/include/darwin-pasteboard.h b/src/VBox/Main/include/darwin-pasteboard.h index a41caa897879..87613ce509bf 100644 --- a/src/VBox/Main/include/darwin-pasteboard.h +++ b/src/VBox/Main/include/darwin-pasteboard.h @@ -1,4 +1,4 @@ -/* $Id: darwin-pasteboard.h 114509 2026-06-24 14:35:45Z andreas.loeffler@oracle.com $ */ +/* $Id: darwin-pasteboard.h 114987 2026-08-11 13:50:56Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host implementation. */ @@ -39,7 +39,10 @@ DECLHIDDEN(int) initPasteboard(PasteboardRef *pPasteboardRef); DECLHIDDEN(void) destroyPasteboard(PasteboardRef *pPasteboardRef); DECLHIDDEN(int) queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, - uint32_t *pfFormats, bool *pfChanged); + bool fForce, uint32_t *pfFormats, bool *pfChanged); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +DECLHIDDEN(int) readFileURLsFromPasteboard(PasteboardRef hPasteboard, char **ppszRoots, size_t *pcbRoots); +#endif DECLHIDDEN(int) readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, void *pv, uint32_t cb, uint32_t *pcbActual); DECLHIDDEN(int) takePasteboardOwnership(PasteboardRef pPasteboard, uint64_t idOwnership, const char *pszOwnershipFlavor, const char *pszOwnershipValue, void **phStrOwnershipFlavor); diff --git a/src/VBox/Main/src-all/VBoxLibSsh.def b/src/VBox/Main/src-all/VBoxLibSsh.def index 6b2e91a6b01d..7f26f2144d33 100644 --- a/src/VBox/Main/src-all/VBoxLibSsh.def +++ b/src/VBox/Main/src-all/VBoxLibSsh.def @@ -1,4 +1,4 @@ -; $Id: VBoxLibSsh.def 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VBoxLibSsh.def 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ ;; @file ; VBoxLibSsh - Definition file for lazy import generation for VBoxC. ; @@ -28,7 +28,10 @@ LIBRARY VBoxLibSsh EXPORTS ssh_key_free - ssh_pki_generate + ssh_pki_ctx_free + ssh_pki_ctx_new + ssh_pki_ctx_options_set + ssh_pki_generate_key ssh_pki_export_privkey_base64 ssh_pki_export_privkey_file ssh_pki_export_pubkey_base64 diff --git a/src/VBox/Main/src-client/ClipboardImpl.cpp b/src/VBox/Main/src-client/ClipboardImpl.cpp index de09b1307280..cea6fc50fbba 100644 --- a/src/VBox/Main/src-client/ClipboardImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardImpl.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardImpl.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Console clipboard API. */ @@ -1688,8 +1688,10 @@ HRESULT Clipboard::reset() i_clearPendingDataRequestsLocked(); ptrTransfers = mData->mTransfers; } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if (!ptrTransfers.isNull()) ptrTransfers->i_reset(); +#endif return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD */ } @@ -1922,7 +1924,7 @@ HRESULT Clipboard::i_readDataForFormat(ClipboardAction_T aAction, { LogFunc(("Guest clipboard payload too large: format=%#x, cb=%RU32, max=%RU32\n", uFormat, cbData, s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Guest clipboard data is too large: format %#x, %RU32 bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Guest clipboard data is too large: format %#x, %RU32 bytes (limit %RU32 bytes)\n", uFormat, cbData, s_cbClipboardReadMax)); RTMemFree(pvData); return mData->mParent->setErrorBoth(VBOX_E_SHCL_TOO_MUCH_DATA, VERR_TOO_MUCH_DATA, @@ -1934,7 +1936,7 @@ HRESULT Clipboard::i_readDataForFormat(ClipboardAction_T aAction, if (RT_FAILURE(vrc2)) { LogFunc(("Converting guest clipboard data failed: format=%#x, cb=%RU32, vrc=%Rrc\n", uFormat, cbData, vrc2)); - LogRelMax2(16, ("Shared Clipboard: Failed to convert guest clipboard data: format %#x, %RU32 bytes, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to convert guest clipboard data: format %#x, %RU32 bytes, vrc=%Rrc\n", uFormat, cbData, vrc2)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_GUEST_ERROR, vrc2, Console::tr("Converting shared clipboard data failed with %Rrc"), vrc2); @@ -1948,13 +1950,13 @@ HRESULT Clipboard::i_readDataForFormat(ClipboardAction_T aAction, if (vrc == VERR_NOT_AVAILABLE) { LogFunc(("No guest clipboard client connected for read: format=%#x\n", uFormat)); - LogRelMax2(16, ("Shared Clipboard: Cannot read guest clipboard data, no guest clipboard client is connected (format %#x)\n", + LogRelMax(16, ("Shared Clipboard: Cannot read guest clipboard data, no guest clipboard client is connected (format %#x)\n", uFormat)); return mData->mParent->setError(VBOX_E_SHCL_NO_DATA, Console::tr("No guest clipboard client is currently connected")); } LogFunc(("Reading guest clipboard data failed: format=%#x, vrc=%Rrc\n", uFormat, vrc)); - LogRelMax2(16, ("Shared Clipboard: Reading guest clipboard data failed: format %#x, vrc=%Rrc\n", uFormat, vrc)); + LogRelMax(16, ("Shared Clipboard: Reading guest clipboard data failed: format %#x, vrc=%Rrc\n", uFormat, vrc)); aBuffer.clear(); return mData->mParent->setErrorBoth(VBOX_E_SHCL_GUEST_ERROR, vrc, Console::tr("Reading shared clipboard data failed with %Rrc"), vrc); @@ -2009,6 +2011,24 @@ HRESULT Clipboard::i_getCurrentStateForEvent(ClipboardSource_T *aSource, } +/** + * Returns the current clipboard source. + * + * @returns COM status code. + * @param aSource Where to return the current clipboard source. + */ +HRESULT Clipboard::i_getCurrentSource(ClipboardSource_T *aSource) +{ + AssertPtrReturn(aSource, E_POINTER); + + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + AssertPtrReturn(mData, E_FAIL); + + *aSource = mData->mSource; + return S_OK; +} + + /** * Allocates the next clipboard event revision. * @@ -2408,7 +2428,7 @@ HRESULT Clipboard::i_writeData(VBOXSHCLMAINCLIENTID aClientId, { LogFunc(("Rejecting oversized clipboard write: mime=%s, cb=%zu, max=%RU32\n", aMimeType.c_str(), aBuffer.size(), s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Refusing to write too much clipboard data: MIME '%s', %zu bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Refusing to write too much clipboard data: MIME '%s', %zu bytes (limit %RU32 bytes)\n", aMimeType.c_str(), aBuffer.size(), s_cbClipboardReadMax)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_TOO_MUCH_DATA, VERR_TOO_MUCH_DATA, Console::tr("Writing shared clipboard data exceeded the supported size (%RU32 bytes)"), @@ -2429,7 +2449,7 @@ HRESULT Clipboard::i_writeData(VBOXSHCLMAINCLIENTID aClientId, { LogFunc(("Converting Main clipboard data failed: format=%#x, mime=%s, cb=%zu, vrc=%Rrc\n", uFormat, aMimeType.c_str(), aBuffer.size(), vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to convert clipboard data for guest: MIME '%s', format %#x, %zu bytes, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to convert clipboard data for guest: MIME '%s', format %#x, %zu bytes, vrc=%Rrc\n", aMimeType.c_str(), uFormat, aBuffer.size(), vrc)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_ERROR, vrc, Console::tr("Converting shared clipboard data failed with %Rrc"), vrc); @@ -2440,7 +2460,7 @@ HRESULT Clipboard::i_writeData(VBOXSHCLMAINCLIENTID aClientId, { LogFunc(("Converted clipboard write is too large: format=%#x, cb=%zu, max=%RU32\n", uFormat, abProtocolBuffer.size(), s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Converted clipboard data is too large for the guest: format %#x, %zu bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Converted clipboard data is too large for the guest: format %#x, %zu bytes (limit %RU32 bytes)\n", uFormat, abProtocolBuffer.size(), s_cbClipboardReadMax)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_TOO_MUCH_DATA, VERR_TOO_MUCH_DATA, Console::tr("Writing shared clipboard data exceeded the supported size (%RU32 bytes)"), @@ -2481,7 +2501,7 @@ HRESULT Clipboard::i_writeData(VBOXSHCLMAINCLIENTID aClientId, if (RT_FAILURE(vrc)) { LogFunc(("Reporting write format to guest failed: format=%#x, vrc=%Rrc\n", uFormat, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to report clipboard format %#x to the guest, vrc=%Rrc\n", uFormat, vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to report clipboard format %#x to the guest, vrc=%Rrc\n", uFormat, vrc)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_GUEST_ERROR, vrc, Console::tr("Writing shared clipboard data failed with %Rrc"), vrc); } @@ -2559,7 +2579,7 @@ HRESULT Clipboard::i_writeFormats(VBOXSHCLMAINCLIENTID aClientId, if (RT_FAILURE(vrc)) { LogFunc(("Reporting formats to guest failed: fFormats=%#x, vrc=%Rrc\n", fFormats, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to report clipboard formats %#x to the guest, vrc=%Rrc\n", fFormats, vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to report clipboard formats %#x to the guest, vrc=%Rrc\n", fFormats, vrc)); return mData->mParent->setErrorBoth(VBOX_E_SHCL_GUEST_ERROR, vrc, Console::tr("Writing shared clipboard formats failed with %Rrc"), vrc); } @@ -2697,7 +2717,7 @@ HRESULT Clipboard::i_hostClipboardReportFormats(VBOXSHCLMAINCLIENTID aClientId, if (RT_FAILURE(vrc)) { LogFunc(("Reporting formats to native host clipboard failed: fFormats=%#x, vrc=%Rrc\n", fFormats, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to report clipboard formats %#x to the native host clipboard, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to report clipboard formats %#x to the native host clipboard, vrc=%Rrc\n", fFormats, vrc)); return setErrorBoth(VBOX_E_SHCL_ERROR, vrc, tr("Reporting shared clipboard formats to the host failed with %Rrc"), vrc); @@ -3025,7 +3045,7 @@ HRESULT Clipboard::i_hostClipboardSetData(VBOXSHCLMAINCLIENTID aClientId, if (RT_FAILURE(vrc)) { LogFunc(("Reporting setData format to native host clipboard failed: format=%#x, vrc=%Rrc\n", uFormat, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to report clipboard format %#x to the native host clipboard, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to report clipboard format %#x to the native host clipboard, vrc=%Rrc\n", uFormat, vrc)); return setErrorBoth(VBOX_E_SHCL_ERROR, vrc, tr("Reporting shared clipboard formats to the host failed with %Rrc"), vrc); @@ -3037,7 +3057,7 @@ HRESULT Clipboard::i_hostClipboardSetData(VBOXSHCLMAINCLIENTID aClientId, { LogFunc(("Writing setData payload to native host clipboard failed: format=%#x, cb=%zu, vrc=%Rrc\n", uFormat, abProtocolBuffer.size(), vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to write clipboard data to the native host clipboard, format %#x, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to write clipboard data to the native host clipboard, format %#x, vrc=%Rrc\n", uFormat, vrc)); return setErrorBoth(VBOX_E_SHCL_ERROR, vrc, tr("Writing shared clipboard data to the host failed with %Rrc"), vrc); @@ -3097,7 +3117,7 @@ HRESULT Clipboard::i_hostClipboardClear(VBOXSHCLMAINCLIENTID aClientId) if (RT_FAILURE(vrc)) { LogFunc(("Clearing native host clipboard failed: vrc=%Rrc\n", vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to clear the native host clipboard, vrc=%Rrc\n", vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to clear the native host clipboard, vrc=%Rrc\n", vrc)); return setErrorBoth(VBOX_E_SHCL_ERROR, vrc, tr("Clearing the shared clipboard on the host failed with %Rrc"), vrc); } @@ -3130,7 +3150,7 @@ HRESULT Clipboard::i_reset() if (RT_FAILURE(vrc)) { LogFunc(("Reset HGCM host call failed: vrc=%Rrc\n", vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to reset service state, vrc=%Rrc\n", vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to reset service state, vrc=%Rrc\n", vrc)); return mData->mParent->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, Console::tr("Resetting shared clipboard state failed with %Rrc"), vrc); } @@ -3207,7 +3227,7 @@ HRESULT Clipboard::i_transferCancel(SHCLSESSIONID aServiceSessionId, SHCLTRANSFE { LogFunc(("Cancel transfer HGCM host call failed: session=%RU16, id=%RU16, generation=%RU64, vrc=%Rrc\n", aServiceSessionId, aTransferId, aGeneration, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to cancel transfer %RU16, vrc=%Rrc\n", aTransferId, vrc)); + LogRelMax(16, ("Shared Clipboard: Failed to cancel transfer %RU16, vrc=%Rrc\n", aTransferId, vrc)); return mData->mParent->setErrorBoth(VBOX_E_IPRT_ERROR, vrc, Console::tr("Canceling shared clipboard transfer failed with %Rrc"), vrc); } @@ -3223,13 +3243,15 @@ HRESULT Clipboard::i_transferCancel(SHCLSESSIONID aServiceSessionId, SHCLTRANSFE * @param aServiceSessionId Service session that owns the transfer. * @param aTransferId Transfer ID that produced the status. * @param aGeneration Host-private transfer generation. - * @param enmShClSource Shared Clipboard status source. + * @param aTransfer Borrowed service transfer backing the data plane. + * @param enmShClSource Data source recorded by the backing transfer. * @param enmStatus Transfer lifecycle status. * @param vrcTransfer Transfer status result code. */ HRESULT Clipboard::i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration, + PSHCLTRANSFER aTransfer, SHCLSOURCE enmShClSource, SHCLTRANSFERSTATUS enmStatus, int vrcTransfer) @@ -3245,7 +3267,7 @@ HRESULT Clipboard::i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, if (ptrTransfers.isNull()) return E_FAIL; - return ptrTransfers->i_handleTransferStatus(aServiceSessionId, aTransferId, aGeneration, + return ptrTransfers->i_handleTransferStatus(aServiceSessionId, aTransferId, aGeneration, aTransfer, enmShClSource, enmStatus, vrcTransfer); } # endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -3273,7 +3295,7 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c if (!pszMimeType) { LogFunc(("Service requested unsupported format: uFormat=%#x\n", uFormat)); - LogRelMax2(16, ("Shared Clipboard: Service requested unsupported clipboard format %#x\n", uFormat)); + LogRelMax(16, ("Shared Clipboard: Service requested unsupported clipboard format %#x\n", uFormat)); return E_INVALIDARG; } @@ -3282,6 +3304,10 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c VBOXSHCLMAINCLIENTID idRequestClient; uint32_t fCurrentFormats; uint64_t uLastItemSerial; +#ifdef RT_OS_DARWIN + bool fHadMatchingHostData; + bool fWantRequestPayload; +#endif { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); AssertPtrReturn(mData, E_FAIL); @@ -3289,6 +3315,21 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c idRequestClient = mData->mCurrentClientId; fCurrentFormats = m_fFormats; uLastItemSerial = mData->mLastItemSerial; +#ifdef RT_OS_DARWIN + fHadMatchingHostData = enmRequestSource == ClipboardSource_Host + && mData->mfHaveLastItem + && mData->mLastItemSource == enmRequestSource + && mData->mLastItemAction == enmRequestAction + && consoleClipboardMimeTypeToFormat(mData->mLastItemMimeType) == uFormat; + fWantRequestPayload = false; + for (std::vector::const_iterator it = mData->mSessions.begin(); + it != mData->mSessions.end(); ++it) + if (it->mEventSource.isNotNull() && (it->mfFlags & IClipboardSessionFlag_IncludePayload)) + { + fWantRequestPayload = true; + break; + } +#endif } if ( enmRequestSource != ClipboardSource_Host && enmRequestSource != ClipboardSource_Guest) @@ -3303,7 +3344,43 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c uFormat, fCurrentFormats)); return E_FAIL; } - ULONG const idRequest = i_fireDataRequested(idRequestClient, enmRequestAction, enmRequestSource, uFormat); + GuestShCl *pShCl = NULL; + bool fHostReadAttempted = false; + int vrcHostRead = VERR_NO_DATA; + const std::vector *pResolvedHostData = NULL; +#ifdef RT_OS_DARWIN + std::vector abResolvedHostData; + /* Keep the established provider-first order unless a macOS session explicitly requested the payload. */ + if ( enmRequestSource == ClipboardSource_Host + && uFormat == VBOX_SHCL_FMT_UNICODETEXT + && !fHadMatchingHostData + && fWantRequestPayload + && cbData) + { + pShCl = GuestShCl::TryGetInst(); + if (pShCl) + { + fHostReadAttempted = true; + vrcHostRead = pShCl->ReadDataFromHost((SHCLFORMAT)uFormat, pvData, cbData, pcbActual); + if ( RT_SUCCESS(vrcHostRead) + && *pcbActual > 0 + && *pcbActual <= cbData + && *pcbActual <= s_cbClipboardReadMax) + { + int const vrc2 = clipboardProtocolToMainData((SHCLFORMAT)uFormat, pvData, *pcbActual, + abResolvedHostData); + if (RT_SUCCESS(vrc2) && abResolvedHostData.size() <= s_cbClipboardReadMax) + pResolvedHostData = &abResolvedHostData; + else + Log2Func(("Not attaching native host text to request event: format=%#x, cbActual=%RU32, vrc=%Rrc\n", + uFormat, *pcbActual, vrc2)); + } + } + } +#endif + + ULONG const idRequest = i_fireDataRequested(idRequestClient, enmRequestAction, enmRequestSource, uFormat, + pResolvedHostData); if (idRequest == 0) Log2Func(("No pending data request was registered for service request: format=%#x\n", uFormat)); @@ -3348,7 +3425,8 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c return E_FAIL; } - GuestShCl *pShCl = GuestShCl::TryGetInst(); + if (!pShCl) + pShCl = GuestShCl::TryGetInst(); if (!pShCl) { LogFunc(("Cannot read native host data for guest request without Shared Clipboard service: format=%#x\n", @@ -3356,10 +3434,15 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c i_removePendingDataRequest(idRequest); return E_FAIL; } - int vrc = pShCl->ReadDataFromHost((SHCLFORMAT)uFormat, pvData, cbData, pcbActual); - if (RT_FAILURE(vrc)) + if (!fHostReadAttempted) + { + fHostReadAttempted = true; + vrcHostRead = pShCl->ReadDataFromHost((SHCLFORMAT)uFormat, pvData, cbData, pcbActual); + } + if (RT_FAILURE(vrcHostRead)) { - Log2Func(("Reading native host data for guest request failed: format=%#x, vrc=%Rrc\n", uFormat, vrc)); + Log2Func(("Reading native host data for guest request failed: format=%#x, vrc=%Rrc\n", + uFormat, vrcHostRead)); i_removePendingDataRequest(idRequest); return E_FAIL; } @@ -3378,7 +3461,7 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c { LogFunc(("Converting cached data for service request failed: format=%#x, cbMain=%zu, vrc=%Rrc\n", uFormat, abMainBuffer.size(), vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to convert cached clipboard data for service request: format %#x, %zu bytes, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to convert cached clipboard data for service request: format %#x, %zu bytes, vrc=%Rrc\n", uFormat, abMainBuffer.size(), vrc)); return E_FAIL; } @@ -3387,7 +3470,7 @@ HRESULT Clipboard::i_readDataForGuest(uint32_t uFormat, void *pvData, uint32_t c if (cbActual > UINT32_MAX) { LogFunc(("Cached data for service request is too large: format=%#x, cb=%zu\n", uFormat, cbActual)); - LogRelMax2(16, ("Shared Clipboard: Cached clipboard data is too large for service request: format %#x, %zu bytes\n", + LogRelMax(16, ("Shared Clipboard: Cached clipboard data is too large for service request: format %#x, %zu bytes\n", uFormat, cbActual)); return E_FAIL; } @@ -3415,6 +3498,60 @@ HRESULT Clipboard::requestData(const com::Utf8Str &aMimeType, ULONG *aRequestId) } +/** + * Sets a Shared Clipboard transfer status for Main testcase coverage. + * + * @returns COM status code. + * @param aServiceSessionId Shared Clipboard service session identifier. + * @param aTransferId Shared Clipboard transfer identifier. + * @param aGeneration Host-private transfer generation. + * @param aSource Data source associated with the status. + * @param aStatus Internal Shared Clipboard transfer status value. + * @param aResult IPRT result associated with the status. + */ +HRESULT Clipboard::setTransferStatus(ULONG aServiceSessionId, + ULONG aTransferId, + LONG64 aGeneration, + ClipboardSource_T aSource, + ULONG aStatus, + LONG aResult) +{ +#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RT_NOREF(aServiceSessionId, aTransferId, aGeneration, aSource, aStatus, aResult); + ReturnComNotImplemented(); +#else + if ( aServiceSessionId == 0 + || aServiceSessionId >= NIL_SHCLSESSIONID + || aTransferId == 0 + || aTransferId >= NIL_SHCLTRANSFERID + || aGeneration <= 0) + return E_INVALIDARG; + + SHCLSOURCE enmShClSource; + switch (aSource) + { + case ClipboardSource_Host: + enmShClSource = SHCLSOURCE_LOCAL; + break; + case ClipboardSource_Guest: + enmShClSource = SHCLSOURCE_REMOTE; + break; + default: + return E_INVALIDARG; + } + + HRESULT const hrc = i_handleTransferStatus((SHCLSESSIONID)aServiceSessionId, + (SHCLTRANSFERID)aTransferId, + (SHCLTRANSFERGEN)aGeneration, + NULL /* pTransfer */, + enmShClSource, + (SHCLTRANSFERSTATUS)aStatus, + (int)aResult); + return hrc; +#endif +} + + /** * Requests clipboard data from the current source and registers a pending request. * @@ -3498,7 +3635,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo if (cbData && !pvData) { LogFunc(("Reported data has invalid pointer: fFormat=%#x, cbData=%RU32\n", fFormat, cbData)); - LogRelMax2(16, ("Shared Clipboard: Service reported clipboard data without a buffer: formats %#x, %RU32 bytes\n", + LogRelMax(16, ("Shared Clipboard: Service reported clipboard data without a buffer: formats %#x, %RU32 bytes\n", fFormat, cbData)); return E_POINTER; } @@ -3506,7 +3643,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo { LogFunc(("Reported data is too large: fFormat=%#x, cbData=%RU32, max=%RU32\n", fFormat, cbData, s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Service reported too much clipboard data: formats %#x, %RU32 bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Service reported too much clipboard data: formats %#x, %RU32 bytes (limit %RU32 bytes)\n", fFormat, cbData, s_cbClipboardReadMax)); return E_FAIL; } @@ -3515,7 +3652,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo if (uFormat == VBOX_SHCL_FMT_NONE) { LogFunc(("Reported data has no supported format: fFormat=%#x\n", fFormat)); - LogRelMax2(16, ("Shared Clipboard: Service reported unsupported clipboard formats %#x\n", fFormat)); + LogRelMax(16, ("Shared Clipboard: Service reported unsupported clipboard formats %#x\n", fFormat)); return E_INVALIDARG; } @@ -3527,7 +3664,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo if (RT_FAILURE(vrc)) { LogFunc(("Converting reported data failed: uFormat=%#x, cbData=%RU32, vrc=%Rrc\n", uFormat, cbData, vrc)); - LogRelMax2(16, ("Shared Clipboard: Failed to convert reported clipboard data: format %#x, %RU32 bytes, vrc=%Rrc\n", + LogRelMax(16, ("Shared Clipboard: Failed to convert reported clipboard data: format %#x, %RU32 bytes, vrc=%Rrc\n", uFormat, cbData, vrc)); return E_FAIL; } @@ -3535,7 +3672,7 @@ HRESULT Clipboard::i_reportData(ClipboardAction_T aAction, ClipboardSource_T aSo { LogFunc(("Converted reported data is too large: uFormat=%#x, cb=%zu, max=%RU32\n", uFormat, abBuffer.size(), s_cbClipboardReadMax)); - LogRelMax2(16, ("Shared Clipboard: Converted reported clipboard data is too large: format %#x, %zu bytes (limit %RU32 bytes)\n", + LogRelMax(16, ("Shared Clipboard: Converted reported clipboard data is too large: format %#x, %zu bytes (limit %RU32 bytes)\n", uFormat, abBuffer.size(), s_cbClipboardReadMax)); return E_FAIL; } @@ -3760,11 +3897,13 @@ void Clipboard::i_fireDataChanged(VBOXSHCLMAINCLIENTID aClientId, * @param aAction Clipboard action associated with the request. * @param aSource Clipboard source from which data is requested. * @param uFormat Shared Clipboard format requested. + * @param pResolvedBuffer Optional canonical Main payload already read for the request. */ ULONG Clipboard::i_fireDataRequested(VBOXSHCLMAINCLIENTID aClientId, ClipboardAction_T aAction, ClipboardSource_T aSource, - uint32_t uFormat) + uint32_t uFormat, + const std::vector *pResolvedBuffer) { const char *pszMimeType = consoleClipboardFormatToMimeType((SHCLFORMAT)uFormat); if (!pszMimeType) @@ -3836,10 +3975,9 @@ ULONG Clipboard::i_fireDataRequested(VBOXSHCLMAINCLIENTID aClientId, LONG64 const i64Revision = i_nextEventRevision(); Log2Func(("Firing data requested event: requestId=%RU32, action=%RU32, source=%RU32, mime=%s, revision=%RI64, clientId=%RU32\n", (uint32_t)idRequest, (uint32_t)aAction, (uint32_t)aSource, strMimeType.c_str(), i64Revision, aClientId)); - ComPtr ptrItem; ComPtr ptrEvent; hrc = ::CreateClipboardDataRequestedEvent(ptrEvent.asOutParam(), ptrEventSource, i64Revision, aClientId, - com::Utf8Str(), ptrItem, FALSE /* aVeto */, + com::Utf8Str(), NULL /* aItem */, FALSE /* aVeto */, idRequest, aAction, aSource, ptrFormat); if (FAILED(hrc)) { @@ -3862,9 +4000,69 @@ ULONG Clipboard::i_fireDataRequested(VBOXSHCLMAINCLIENTID aClientId, std::vector vecTargets; i_getSessionEventTargets(vecTargets, aClientId, false /* fPassive */, false /* fCheckReflection */, VBOX_SHCL_FMT_NONE, aSource); + bool fIncludePayload = false; for (std::vector::const_iterator it = vecTargets.begin(); it != vecTargets.end(); ++it) - ::FireClipboardDataRequestedEvent(it->mEventSource, i64Revision, aClientId, com::Utf8Str(), ptrItem, + if (it->mfFlags & IClipboardSessionFlag_IncludePayload) + { + fIncludePayload = true; + break; + } + + std::vector abRequestBuffer; + const std::vector *pRequestBuffer = NULL; + if (fIncludePayload) + { + if (pResolvedBuffer) + pRequestBuffer = pResolvedBuffer; + else if ( aSource == ClipboardSource_Host + && uFormat == VBOX_SHCL_FMT_UNICODETEXT) + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + if ( mData + && mData->mfHaveLastItem + && mData->mLastItemSource == aSource + && mData->mLastItemAction == aAction + && consoleClipboardMimeTypeToFormat(mData->mLastItemMimeType) == uFormat) + { + try + { + abRequestBuffer = mData->mLastItemBuffer; + pRequestBuffer = &abRequestBuffer; + } + catch (std::bad_alloc &) + { + LogFunc(("Out of memory copying cached data-requested payload\n")); + } + } + } + } + + ComPtr ptrItem; + if (pRequestBuffer) + { + try + { + hrc = i_createItem(aSource, strMimeType, *pRequestBuffer, ptrItem); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + if (FAILED(hrc)) + { + LogFunc(("Creating data-requested item failed: requestId=%RU32, source=%RU32, mime=%s, cb=%zu, hrc=%#x\n", + (uint32_t)idRequest, (uint32_t)aSource, strMimeType.c_str(), pRequestBuffer->size(), hrc)); + ptrItem.setNull(); + } + } + for (std::vector::const_iterator it = vecTargets.begin(); it != vecTargets.end(); ++it) + { + ComPtr ptrSessionItem; + if (it->mfFlags & IClipboardSessionFlag_IncludePayload) + ptrSessionItem = ptrItem; + ::FireClipboardDataRequestedEvent(it->mEventSource, i64Revision, aClientId, com::Utf8Str(), ptrSessionItem, FALSE /* aVeto */, idRequest, aAction, aSource, ptrFormat); + } return idRequest; } diff --git a/src/VBox/Main/src-client/ClipboardSessionImpl.cpp b/src/VBox/Main/src-client/ClipboardSessionImpl.cpp index 6c110182c683..a7b0f6ce91e6 100644 --- a/src/VBox/Main/src-client/ClipboardSessionImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardSessionImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardSessionImpl.cpp 114560 2026-06-29 08:32:23Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardSessionImpl.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard session API object. */ @@ -188,6 +188,37 @@ HRESULT ClipboardSession::init(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFlags, } +#ifdef UNIT_TEST +/** + * Initializes a parentless clipboard session for unit testing. + * + * @returns COM status code. + * @param aClientId Main clipboard client ID this session represents. + * @param fFlags IClipboardSessionFlag mask. + */ +HRESULT ClipboardSession::initForTesting(VBOXSHCLMAINCLIENTID aClientId, uint32_t fFlags) +{ + AssertReturn(aClientId != VBOX_SHCL_MAIN_CLIENT_NONE, E_INVALIDARG); + + AutoInitSpan autoInitSpan(this); + AssertReturn(autoInitSpan.isOk(), E_FAIL); + + mData.mClientId = aClientId; + mData.mfFlags = fFlags; + mData.mParent = NULL; + mData.mfInitialStateDelivered = false; + + HRESULT hrc = mData.mEventSource.createObject(); + AssertComRCReturnRC(hrc); + hrc = mData.mEventSource->init(); + AssertComRCReturnRC(hrc); + + autoInitSpan.setSucceeded(); + return S_OK; +} +#endif + + /** * Uninitializes a clipboard session object. */ @@ -509,4 +540,3 @@ HRESULT ClipboardSession::close() ptrEventSource->uninit(); return S_OK; } - diff --git a/src/VBox/Main/src-client/ClipboardTransferDataImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferDataImpl.cpp index 3aa7f0bcba96..724728540b62 100644 --- a/src/VBox/Main/src-client/ClipboardTransferDataImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferDataImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferDataImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferDataImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer data plane object. */ @@ -64,6 +64,9 @@ static HRESULT clipboardTransferDataRcToHrc(int vrc) if ( vrc == VERR_INVALID_PARAMETER || vrc == VERR_INVALID_POINTER || vrc == VERR_INVALID_HANDLE + || vrc == VERR_INVALID_NAME + || vrc == VERR_INVALID_UTF8_ENCODING + || vrc == VERR_PATH_IS_NOT_RELATIVE || vrc == VERR_WRONG_ORDER || vrc == VERR_BUFFER_OVERFLOW || vrc == VERR_TOO_MUCH_DATA) @@ -74,43 +77,6 @@ static HRESULT clipboardTransferDataRcToHrc(int vrc) } -/** - * Validates a transfer-relative path supplied through the low-level data API. - * - * @returns COM status code. - * @param aPath Path to validate. - * @param fAllowEmpty Whether the empty path is accepted. - */ -static HRESULT clipboardTransferDataValidatePath(const com::Utf8Str &aPath, bool fAllowEmpty) -{ - if (aPath.isEmpty()) - return fAllowEmpty ? S_OK : E_INVALIDARG; - - const char *pszPath = aPath.c_str(); - if ( pszPath[0] == '/' - || pszPath[0] == '\\' - || strchr(pszPath, '\\') - || strchr(pszPath, ':')) - return E_INVALIDARG; - - const char *pszCur = pszPath; - while (*pszCur) - { - const char *pszNext = strchr(pszCur, '/'); - size_t const cch = pszNext ? (size_t)(pszNext - pszCur) : strlen(pszCur); - if ( cch == 0 - || (cch == 1 && pszCur[0] == '.') - || (cch == 2 && pszCur[0] == '.' && pszCur[1] == '.')) - return E_INVALIDARG; - if (!pszNext) - break; - pszCur = pszNext + 1; - } - - return S_OK; -} - - /** * Copies a Shared Clipboard list entry to Main API output values. * @@ -127,22 +93,26 @@ static HRESULT clipboardTransferDataListEntryToMain(PCSHCLLISTENTRY pEntry, { AssertPtrReturn(pEntry, E_POINTER); AssertPtrReturn(aInfoFlags, E_POINTER); + if (!ShClTransferListEntryIsValid((PSHCLLISTENTRY)pEntry)) + return E_INVALIDARG; - aName = pEntry->pszName ? pEntry->pszName : ""; - *aInfoFlags = pEntry->fInfo; + com::Utf8Str Name; + std::vector Info; try { - aInfo.resize(pEntry->cbInfo); + Name = pEntry->pszName; + Info.resize(pEntry->cbInfo); if (pEntry->cbInfo) - { - AssertPtrReturn(pEntry->pvInfo, E_POINTER); - memcpy(&aInfo[0], pEntry->pvInfo, pEntry->cbInfo); - } + memcpy(&Info[0], pEntry->pvInfo, pEntry->cbInfo); } catch (std::bad_alloc &) { return E_OUTOFMEMORY; } + + aName.swap(Name); + aInfo.swap(Info); + *aInfoFlags = pEntry->fInfo; return S_OK; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -174,7 +144,7 @@ void ClipboardTransferData::FinalRelease() * Initializes a clipboard transfer data plane object. * * @returns COM status code. - * @param aParent Parent transfer object used to keep the backing transfer alive. + * @param aParent Parent transfer object. * @param aTransfer Backing Shared Clipboard transfer. */ HRESULT ClipboardTransferData::init(const ComPtr &aParent, PSHCLTRANSFER aTransfer) @@ -234,11 +204,8 @@ HRESULT ClipboardTransferData::open(ClipboardTransferDataType_T aType, return setError(E_POINTER, tr("The clipboard transfer data handle output argument must not be NULL")); *aHandle = 0; - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -256,9 +223,12 @@ HRESULT ClipboardTransferData::open(ClipboardTransferDataType_T aType, case ClipboardTransferDataType_List: { - HRESULT hrc = clipboardTransferDataValidatePath(aPath, true /* fAllowEmpty */); - if (FAILED(hrc)) - return setError(hrc, tr("Invalid clipboard transfer list path")); + vrc = ShClTransferValidatePath(aPath.c_str(), false /* fMustExist */); + if (RT_FAILURE(vrc)) + { + HRESULT const hrc = clipboardTransferDataRcToHrc(vrc); + return setErrorBoth(hrc, vrc, tr("Invalid clipboard transfer list path: %Rrc"), vrc); + } SHCLLISTOPENPARMS OpenParms; vrc = ShClTransferListOpenParmsInit(&OpenParms); @@ -284,9 +254,14 @@ HRESULT ClipboardTransferData::open(ClipboardTransferDataType_T aType, case ClipboardTransferDataType_Object: { - HRESULT hrc = clipboardTransferDataValidatePath(aPath, false /* fAllowEmpty */); - if (FAILED(hrc)) - return setError(hrc, tr("Invalid clipboard transfer object path")); + if (aPath.isEmpty()) + return setError(E_INVALIDARG, tr("Clipboard transfer object paths must not be empty")); + vrc = ShClTransferValidatePath(aPath.c_str(), false /* fMustExist */); + if (RT_FAILURE(vrc)) + { + HRESULT const hrc = clipboardTransferDataRcToHrc(vrc); + return setErrorBoth(hrc, vrc, tr("Invalid clipboard transfer object path: %Rrc"), vrc); + } SHCLOBJOPENCREATEPARMS OpenParms; vrc = ShClTransferObjOpenParmsInit(&OpenParms); @@ -332,11 +307,8 @@ HRESULT ClipboardTransferData::close(ClipboardTransferDataType_T aType, LONG64 a ReturnComNotImplemented(); #else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); if (aHandle < 0) @@ -401,11 +373,8 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, if (aHandle < 0) return setError(E_INVALIDARG, tr("Clipboard transfer data handle must not be negative")); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -416,9 +385,12 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, { if (aSize != 0 || aFlags != 0) return setError(E_INVALIDARG, tr("Root-list clipboard transfer reads require size 0 and flags 0")); - PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTransfer, (uint64_t)aHandle); + + PCSHCLLISTENTRY const pEntry = ShClTransferRootsEntryGet(pTransfer, (uint64_t)aHandle); if (!pEntry) - return setError(VBOX_E_SHCL_NO_DATA, tr("No clipboard transfer root entry exists at index %RI64"), aHandle); + return setErrorBoth(clipboardTransferDataRcToHrc(VERR_NOT_FOUND), VERR_NOT_FOUND, + tr("No clipboard transfer root entry exists at index %RI64"), aHandle); + HRESULT hrc = clipboardTransferDataListEntryToMain(pEntry, aName, aInfoFlags, aInfo); if (FAILED(hrc)) return setError(hrc, tr("Copying clipboard transfer root entry metadata failed")); @@ -435,8 +407,14 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, { vrc = ShClTransferListRead(pTransfer, (SHCLLISTHANDLE)aHandle, &Entry); if (RT_SUCCESS(vrc)) - vrc = SUCCEEDED(clipboardTransferDataListEntryToMain(&Entry, aName, aInfoFlags, aInfo)) - ? VINF_SUCCESS : VERR_NO_MEMORY; + { + HRESULT const hrc = clipboardTransferDataListEntryToMain(&Entry, aName, aInfoFlags, aInfo); + if (FAILED(hrc)) + { + ShClTransferListEntryDestroy(&Entry); + return setError(hrc, tr("Copying clipboard transfer list entry metadata failed")); + } + } ShClTransferListEntryDestroy(&Entry); } break; @@ -446,6 +424,8 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, { if (!aSize || aFlags != 0) return setError(E_INVALIDARG, tr("Clipboard transfer object reads require a non-zero size and flags 0")); + if (aSize > pTransfer->cbMaxChunkSize) + return setError(E_INVALIDARG, tr("Clipboard transfer object read size exceeds the backend chunk limit")); try { aData.resize(aSize); @@ -457,9 +437,15 @@ HRESULT ClipboardTransferData::read(ClipboardTransferDataType_T aType, uint32_t cbRead = 0; vrc = ShClTransferObjRead(pTransfer, (SHCLOBJHANDLE)aHandle, aData.empty() ? NULL : &aData[0], aSize, aFlags, &cbRead); - if (RT_SUCCESS(vrc)) + if ( RT_SUCCESS(vrc) + && cbRead <= aSize) aData.resize(cbRead); - else + else if (RT_SUCCESS(vrc)) + { + aData.clear(); + return setError(E_INVALIDARG, tr("Clipboard transfer provider returned an invalid read size")); + } + if (RT_FAILURE(vrc)) aData.clear(); break; } @@ -509,11 +495,8 @@ HRESULT ClipboardTransferData::write(ClipboardTransferDataType_T aType, if (aHandle < 0) return setError(E_INVALIDARG, tr("Clipboard transfer data handle must not be negative")); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -524,6 +507,14 @@ HRESULT ClipboardTransferData::write(ClipboardTransferDataType_T aType, { if (aFlags != 0 || aName.isEmpty()) return setError(E_INVALIDARG, tr("Clipboard transfer list writes require flags 0 and a non-empty entry name")); + if ( aInfo.size() > UINT32_MAX + || ( aInfoFlags == VBOX_SHCL_INFO_F_NONE + && !aInfo.empty()) + || ( aInfoFlags == VBOX_SHCL_INFO_F_FSOBJINFO + && aInfo.size() != sizeof(SHCLFSOBJINFO)) + || ( aInfoFlags != VBOX_SHCL_INFO_F_NONE + && aInfoFlags != VBOX_SHCL_INFO_F_FSOBJINFO)) + return setError(E_INVALIDARG, tr("Clipboard transfer list information flags and payload do not match")); void *pvInfo = NULL; if (!aInfo.empty()) { @@ -540,7 +531,10 @@ HRESULT ClipboardTransferData::write(ClipboardTransferDataType_T aType, if (RT_SUCCESS(vrc)) { pvInfo = NULL; /* Ownership transferred to Entry. */ - vrc = ShClTransferListWrite(pTransfer, (SHCLLISTHANDLE)aHandle, &Entry); + if (!ShClTransferListEntryIsValid(&Entry)) + vrc = VERR_INVALID_PARAMETER; + else + vrc = ShClTransferListWrite(pTransfer, (SHCLLISTHANDLE)aHandle, &Entry); ShClTransferListEntryDestroy(&Entry); } if (pvInfo) @@ -557,8 +551,11 @@ HRESULT ClipboardTransferData::write(ClipboardTransferDataType_T aType, uint32_t cbWritten = 0; vrc = ShClTransferObjWrite(pTransfer, (SHCLOBJHANDLE)aHandle, (void *)&aData[0], (uint32_t)aData.size(), aFlags, &cbWritten); - if (RT_SUCCESS(vrc)) + if ( RT_SUCCESS(vrc) + && cbWritten <= aData.size()) *aWritten = cbWritten; + else if (RT_SUCCESS(vrc)) + return setError(E_INVALIDARG, tr("Clipboard transfer provider returned an invalid write size")); break; } diff --git a/src/VBox/Main/src-client/ClipboardTransferDirectoryImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferDirectoryImpl.cpp index 4e6744527e92..08fb26b99c3e 100644 --- a/src/VBox/Main/src-client/ClipboardTransferDirectoryImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferDirectoryImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferDirectoryImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferDirectoryImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer directory handle. */ @@ -30,6 +30,7 @@ #include "VirtualBoxBase.h" #include "AutoCaller.h" +#include "ClipboardTransferImpl.h" #include "ClipboardTransferDirectoryImpl.h" #include "ClipboardTransferFsObjInfoImpl.h" @@ -38,6 +39,8 @@ #include +#include + DEFINE_EMPTY_CTOR_DTOR(ClipboardTransferDirectory) @@ -117,9 +120,8 @@ static HRESULT clipboardTransferDirectoryEntryToInfo(const com::Utf8Str &aDirect const SHCLLISTENTRY &Entry, ComPtr &aInfo) { - if ( !(Entry.fInfo & VBOX_SHCL_INFO_F_FSOBJINFO) - || !Entry.pvInfo - || Entry.cbInfo != sizeof(SHCLFSOBJINFO)) + if ( Entry.fInfo != VBOX_SHCL_INFO_F_FSOBJINFO + || !ShClTransferListEntryIsValid((PSHCLLISTENTRY)&Entry)) return E_INVALIDARG; ComObjPtr ptrInfo; @@ -127,11 +129,18 @@ static HRESULT clipboardTransferDirectoryEntryToInfo(const com::Utf8Str &aDirect if (FAILED(hrc)) return hrc; - com::Utf8Str const strName(Entry.pszName ? Entry.pszName : ""); - com::Utf8Str const strPath = clipboardTransferDirectoryMakeChildPath(aDirectoryPath, Entry.pszName); - hrc = ptrInfo->init(strPath, strName, (PCSHCLFSOBJINFO)Entry.pvInfo); - if (FAILED(hrc)) - return hrc; + try + { + com::Utf8Str const strName(Entry.pszName); + com::Utf8Str const strPath = clipboardTransferDirectoryMakeChildPath(aDirectoryPath, Entry.pszName); + hrc = ptrInfo->init(strPath, strName, (PCSHCLFSOBJINFO)Entry.pvInfo); + if (FAILED(hrc)) + return hrc; + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } return ptrInfo.queryInterfaceTo(aInfo.asOutParam()); } @@ -164,25 +173,33 @@ void ClipboardTransferDirectory::FinalRelease() * Initializes a clipboard transfer directory handle. * * @returns COM status code. - * @param aParent Parent transfer object used to keep the backing transfer alive. + * @param aParent Concrete parent transfer object. * @param aTransfer Backing Shared Clipboard transfer. * @param aPath Transfer-relative directory path. * @param aHandle Open Shared Clipboard list handle. */ -HRESULT ClipboardTransferDirectory::init(const ComPtr &aParent, +HRESULT ClipboardTransferDirectory::init(const ComObjPtr &aParent, PSHCLTRANSFER aTransfer, const com::Utf8Str &aPath, SHCLLISTHANDLE aHandle) { + AssertReturn(aParent.isNotNull(), E_POINTER); AssertPtrReturn(aTransfer, E_POINTER); AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); + try + { + mData.mPath = aPath; + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } mData.mParent = aParent; - mData.mTransfer = aTransfer; mData.mHandle = aHandle; - mData.mPath = aPath; mData.mStatus = DirectoryStatus_Open; + mData.mTransfer = aTransfer; autoInitSpan.setSucceeded(); return S_OK; @@ -200,10 +217,12 @@ void ClipboardTransferDirectory::uninit() return; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ComObjPtr ptrParent; PSHCLTRANSFER pTransfer = NULL; SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrParent = mData.mParent; pTransfer = mData.mTransfer; hList = mData.mHandle; mData.mTransfer = NULL; @@ -263,19 +282,22 @@ HRESULT ClipboardTransferDirectory::close() #ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS ReturnComNotImplemented(); #else + ComObjPtr ptrParent; PSHCLTRANSFER pTransfer = NULL; SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrParent = mData.mParent; pTransfer = mData.mTransfer; hList = mData.mHandle; mData.mTransfer = NULL; mData.mHandle = NIL_SHCLLISTHANDLE; mData.mStatus = DirectoryStatus_Close; + mData.mParent.setNull(); } - if (!pTransfer || hList == NIL_SHCLLISTHANDLE) + if (!pTransfer) return S_OK; - int vrc = ShClTransferListClose(pTransfer, hList); + int const vrc = hList == NIL_SHCLLISTHANDLE ? VINF_SUCCESS : ShClTransferListClose(pTransfer, hList); HRESULT hrc = clipboardTransferDirectoryRcToHrc(vrc); if (FAILED(hrc)) return setErrorBoth(hrc, vrc, tr("Closing clipboard transfer directory failed with %Rrc"), vrc); @@ -294,12 +316,22 @@ HRESULT ClipboardTransferDirectory::list(ULONG aMaxEntries, std::vector >::const_iterator it = vecEntries.begin(); it != vecEntries.end(); ++it) + std::vector > ObjInfo; + try { - ComPtr ptrInfo(*it); - aObjInfo.push_back(ptrInfo); + ObjInfo.reserve(vecEntries.size()); + for (std::vector >::const_iterator it = vecEntries.begin(); + it != vecEntries.end(); ++it) + { + ComPtr ptrInfo(*it); + ObjInfo.push_back(ptrInfo); + } } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer directory result failed")); + } + aObjInfo.swap(ObjInfo); return S_OK; #endif } @@ -328,15 +360,10 @@ HRESULT ClipboardTransferDirectory::rewind() #ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS ReturnComNotImplemented(); #else - PSHCLTRANSFER pTransfer = NULL; - SHCLLISTHANDLE hOld = NIL_SHCLLISTHANDLE; - com::Utf8Str strPath; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hOld = mData.mHandle; - strPath = mData.mPath; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLLISTHANDLE const hOld = mData.mHandle; + com::Utf8Str const strPath = mData.mPath; if (!pTransfer || hOld == NIL_SHCLLISTHANDLE) return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer directory is closed")); @@ -348,12 +375,8 @@ HRESULT ClipboardTransferDirectory::rewind() return setErrorBoth(hrc, vrc, tr("Rewinding clipboard transfer directory failed with %Rrc"), vrc); } - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - hOld = mData.mHandle; - mData.mHandle = hNew; - mData.mStatus = DirectoryStatus_Open; - } + mData.mHandle = hNew; + mData.mStatus = DirectoryStatus_Open; if (hOld != NIL_SHCLLISTHANDLE) ShClTransferListClose(pTransfer, hOld); return S_OK; @@ -382,47 +405,65 @@ HRESULT ClipboardTransferDirectory::listEx(ULONG aMaxEntries, if (aFlags & ~(ClipboardTransferListFlag_NoRecursion | ClipboardTransferListFlag_IncludeRoot | ClipboardTransferListFlag_NoFollowSymlinks)) return setError(E_INVALIDARG, tr("Invalid clipboard transfer directory-list flags %RU32"), aFlags); - aEntries.clear(); + std::vector > Entries; + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLLISTHANDLE const hList = mData.mHandle; + com::Utf8Str const strPath = mData.mPath; + ComObjPtr const ptrParent = mData.mParent; + if (!pTransfer || hList == NIL_SHCLLISTHANDLE) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer directory is closed")); + if (!(aFlags & ClipboardTransferListFlag_NoRecursion)) { - ComPtr ptrParent; - com::Utf8Str strPath; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - ptrParent = mData.mParent; - strPath = mData.mPath; - } if (ptrParent.isNull()) return setError(E_FAIL, tr("Clipboard transfer directory has no parent transfer for recursive listing")); - SafeIfaceArray aSafeEntries; - HRESULT hrc = ptrParent->List(Bstr(strPath).raw(), aFlags, ComSafeArrayAsOutParam(aSafeEntries)); + + std::vector > vecEntries; + HRESULT hrc = ptrParent->i_list(pTransfer, strPath, aFlags, vecEntries); if (FAILED(hrc)) return setError(hrc, tr("Recursively listing clipboard transfer directory failed")); - for (size_t i = 0; i < aSafeEntries.size(); ++i) + for (std::vector >::const_iterator it = vecEntries.begin(); + it != vecEntries.end(); ++it) { - if (aMaxEntries && aEntries.size() >= aMaxEntries) + if (aMaxEntries && Entries.size() >= aMaxEntries) break; - aEntries.push_back(ComPtr(aSafeEntries[i])); + try + { + Entries.push_back(*it); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the recursive clipboard transfer directory result failed")); + } } + aEntries.swap(Entries); return S_OK; } - PSHCLTRANSFER pTransfer = NULL; - SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; - com::Utf8Str strPath; + if ( (aFlags & ClipboardTransferListFlag_IncludeRoot) + && !strPath.isEmpty()) { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hList = mData.mHandle; - strPath = mData.mPath; + if (ptrParent.isNull()) + return setError(E_FAIL, tr("Clipboard transfer directory has no parent transfer for querying its root")); + + ComPtr ptrRoot; + HRESULT hrc = ptrParent->i_query(pTransfer, strPath, ptrRoot); + if (FAILED(hrc)) + return setError(hrc, tr("Querying the clipboard transfer directory root failed")); + try + { + Entries.push_back(ptrRoot); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer directory root result failed")); + } } - if (!pTransfer || hList == NIL_SHCLLISTHANDLE) - return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer directory is closed")); - ULONG cEntries = 0; for (;;) { - if (aMaxEntries && cEntries >= aMaxEntries) + if (aMaxEntries && Entries.size() >= aMaxEntries) break; SHCLLISTENTRY Entry; @@ -447,9 +488,16 @@ HRESULT ClipboardTransferDirectory::listEx(ULONG aMaxEntries, ShClTransferListEntryDestroy(&Entry); if (FAILED(hrc)) return setError(hrc, tr("Creating clipboard transfer directory entry information failed")); - aEntries.push_back(ptrInfo); - ++cEntries; + try + { + Entries.push_back(ptrInfo); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer directory result failed")); + } } + aEntries.swap(Entries); return S_OK; #endif } diff --git a/src/VBox/Main/src-client/ClipboardTransferFileImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferFileImpl.cpp index 4d1daf9df62f..8feff86d5140 100644 --- a/src/VBox/Main/src-client/ClipboardTransferFileImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferFileImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferFileImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferFileImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer file handle. */ @@ -112,15 +112,21 @@ static int clipboardTransferFileOpenObject(PSHCLTRANSFER pTransfer, */ static int clipboardTransferFileSkip(PSHCLTRANSFER pTransfer, SHCLOBJHANDLE hObj, uint64_t cbSkip) { + uint32_t const cbMaxChunk = pTransfer->cbMaxChunkSize; + if (!cbMaxChunk) + return VERR_INVALID_STATE; + uint8_t abBuf[_64K]; while (cbSkip > 0) { - uint32_t const cbToRead = (uint32_t)RT_MIN(cbSkip, (uint64_t)sizeof(abBuf)); + uint32_t const cbToRead = (uint32_t)RT_MIN(cbSkip, (uint64_t)RT_MIN(sizeof(abBuf), (size_t)cbMaxChunk)); uint32_t cbRead = 0; int vrc = ShClTransferObjRead(pTransfer, hObj, abBuf, cbToRead, 0, &cbRead); if (RT_FAILURE(vrc)) return vrc; - if (!cbRead) + if ( !cbRead + || cbRead > cbToRead + || cbRead > cbSkip) return VERR_EOF; cbSkip -= cbRead; } @@ -157,7 +163,7 @@ void ClipboardTransferFile::FinalRelease() * Initializes a clipboard transfer file handle. * * @returns COM status code. - * @param aParent Parent transfer object used to keep the backing transfer alive. + * @param aParent Parent transfer object. * @param aTransfer Backing Shared Clipboard transfer. * @param aHandle Open Shared Clipboard object handle. * @param aPath Transfer-relative file path. @@ -178,13 +184,21 @@ HRESULT ClipboardTransferFile::init(const ComPtr &aParent, ULONG aCreationMode) { AssertPtrReturn(aTransfer, E_POINTER); + if (aInfo.cbObject < 0) + return E_INVALIDARG; AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); + try + { + mData.mPath = aPath; + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } mData.mParent = aParent; - mData.mTransfer = aTransfer; mData.mHandle = aHandle; - mData.mPath = aPath; mData.mInfo = aInfo; mData.mOffset = 0; mData.mStatus = FileStatus_Open; @@ -192,6 +206,7 @@ HRESULT ClipboardTransferFile::init(const ComPtr &aParent, mData.mOpenAction = aOpenAction; mData.mSharingMode = aSharingMode; mData.mCreationMode = aCreationMode; + mData.mTransfer = aTransfer; autoInitSpan.setSucceeded(); return S_OK; @@ -209,10 +224,12 @@ void ClipboardTransferFile::uninit() return; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + ComPtr ptrParent; PSHCLTRANSFER pTransfer = NULL; SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrParent = mData.mParent; pTransfer = mData.mTransfer; hObj = mData.mHandle; mData.mTransfer = NULL; @@ -305,19 +322,22 @@ HRESULT ClipboardTransferFile::close() #ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS ReturnComNotImplemented(); #else + ComPtr ptrParent; PSHCLTRANSFER pTransfer = NULL; SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrParent = mData.mParent; pTransfer = mData.mTransfer; hObj = mData.mHandle; mData.mTransfer = NULL; mData.mHandle = NIL_SHCLOBJHANDLE; mData.mStatus = FileStatus_Closed; + mData.mParent.setNull(); } - if (!pTransfer || hObj == NIL_SHCLOBJHANDLE) + if (!pTransfer) return S_OK; - int vrc = ShClTransferObjClose(pTransfer, hObj); + int const vrc = hObj == NIL_SHCLOBJHANDLE ? VINF_SUCCESS : ShClTransferObjClose(pTransfer, hObj); HRESULT hrc = clipboardTransferFileRcToHrc(vrc); if (FAILED(hrc)) return setErrorBoth(hrc, vrc, tr("Closing clipboard transfer file failed with %Rrc"), vrc); @@ -373,15 +393,13 @@ HRESULT ClipboardTransferFile::read(ULONG aToRead, ULONG aTimeoutMS, std::vector if (!aToRead) return setError(E_INVALIDARG, tr("Clipboard transfer file read size must be non-zero")); - PSHCLTRANSFER pTransfer = NULL; - SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hObj = mData.mHandle; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLOBJHANDLE const hObj = mData.mHandle; if (!pTransfer || hObj == NIL_SHCLOBJHANDLE) return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer file is closed")); + if (aToRead > pTransfer->cbMaxChunkSize) + return setError(E_INVALIDARG, tr("Clipboard transfer file read size exceeds the backend chunk limit")); try { @@ -399,11 +417,14 @@ HRESULT ClipboardTransferFile::read(ULONG aToRead, ULONG aTimeoutMS, std::vector HRESULT hrc = clipboardTransferFileRcToHrc(vrc); return setErrorBoth(hrc, vrc, tr("Reading clipboard transfer file failed with %Rrc"), vrc); } - aData.resize(cbRead); + if ( cbRead > aToRead + || mData.mOffset > INT64_MAX - cbRead) { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - mData.mOffset += cbRead; + aData.clear(); + return setError(E_INVALIDARG, tr("Clipboard transfer provider returned an invalid read size")); } + aData.resize(cbRead); + mData.mOffset += cbRead; return S_OK; #endif } @@ -447,6 +468,10 @@ HRESULT ClipboardTransferFile::seek(LONG64 aOffset, FileSeekOrigin_T aWhence, LO return setError(E_INVALIDARG, tr("Invalid clipboard transfer file seek origin %RU32"), (uint32_t)aWhence); } + if ( (aOffset > 0 && offBase > INT64_MAX - aOffset) + || (aOffset < 0 && offBase < INT64_MIN - aOffset)) + return setError(E_INVALIDARG, tr("Clipboard transfer file seek offset is out of range")); + LONG64 const offNew = offBase + aOffset; if (offNew < 0) return setError(E_INVALIDARG, tr("Clipboard transfer file seek would move before the start of the file")); @@ -468,15 +493,10 @@ HRESULT ClipboardTransferFile::seek(LONG64 aOffset, FileSeekOrigin_T aWhence, LO */ HRESULT ClipboardTransferFile::i_reopenAt(uint64_t offNew) { - PSHCLTRANSFER pTransfer = NULL; - SHCLOBJHANDLE hOld = NIL_SHCLOBJHANDLE; - com::Utf8Str strPath; - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hOld = mData.mHandle; - strPath = mData.mPath; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLOBJHANDLE const hOld = mData.mHandle; + com::Utf8Str const strPath = mData.mPath; if (!pTransfer) return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer file is closed")); @@ -484,6 +504,9 @@ HRESULT ClipboardTransferFile::i_reopenAt(uint64_t offNew) SHCLFSOBJINFO Info; RT_ZERO(Info); int vrc = clipboardTransferFileOpenObject(pTransfer, strPath, &hNew, &Info); + if ( RT_SUCCESS(vrc) + && Info.cbObject < 0) + vrc = VERR_OUT_OF_RANGE; if (RT_SUCCESS(vrc)) vrc = clipboardTransferFileSkip(pTransfer, hNew, offNew); if (RT_FAILURE(vrc)) @@ -494,15 +517,11 @@ HRESULT ClipboardTransferFile::i_reopenAt(uint64_t offNew) return setErrorBoth(hrc, vrc, tr("Seeking clipboard transfer file failed with %Rrc"), vrc); } + mData.mHandle = hNew; + mData.mInfo = Info; + mData.mOffset = (LONG64)offNew; if (hOld != NIL_SHCLOBJHANDLE) ShClTransferObjClose(pTransfer, hOld); - - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - mData.mHandle = hNew; - mData.mInfo = Info; - mData.mOffset = (LONG64)offNew; - } return S_OK; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -535,19 +554,17 @@ HRESULT ClipboardTransferFile::write(const std::vector &aData, ULONG aTime if (aData.empty()) return setError(E_INVALIDARG, tr("Clipboard transfer file write data must not be empty")); - PSHCLTRANSFER pTransfer = NULL; - SHCLOBJHANDLE hObj = NIL_SHCLOBJHANDLE; - FileAccessMode_T enmAccessMode = FileAccessMode_ReadOnly; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - hObj = mData.mHandle; - enmAccessMode = mData.mAccessMode; - } + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + PSHCLTRANSFER const pTransfer = mData.mTransfer; + SHCLOBJHANDLE const hObj = mData.mHandle; + FileAccessMode_T const enmAccessMode = mData.mAccessMode; if (enmAccessMode == FileAccessMode_ReadOnly) return setError(E_NOTIMPL, tr("Writing to read-only clipboard transfer files is not implemented")); if (!pTransfer || hObj == NIL_SHCLOBJHANDLE) return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer file is closed")); + if ( aData.size() > UINT32_MAX + || aData.size() > pTransfer->cbMaxChunkSize) + return setError(E_INVALIDARG, tr("Clipboard transfer file write size exceeds the backend chunk limit")); uint32_t cbWritten = 0; int vrc = ShClTransferObjWrite(pTransfer, hObj, (void *)&aData[0], (uint32_t)aData.size(), 0, &cbWritten); @@ -556,10 +573,10 @@ HRESULT ClipboardTransferFile::write(const std::vector &aData, ULONG aTime HRESULT hrc = clipboardTransferFileRcToHrc(vrc); return setErrorBoth(hrc, vrc, tr("Writing clipboard transfer file failed with %Rrc"), vrc); } - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - mData.mOffset += cbWritten; - } + if ( cbWritten > aData.size() + || mData.mOffset > INT64_MAX - cbWritten) + return setError(E_INVALIDARG, tr("Clipboard transfer provider returned an invalid write size")); + mData.mOffset += cbWritten; *aWritten = cbWritten; return S_OK; #endif @@ -590,4 +607,3 @@ HRESULT ClipboardTransferFile::getPath(com::Utf8Str &aPath) aPath = mData.mPath; return S_OK; } - diff --git a/src/VBox/Main/src-client/ClipboardTransferFsObjInfoImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferFsObjInfoImpl.cpp index c45d3fad270d..90eb03a0fd5d 100644 --- a/src/VBox/Main/src-client/ClipboardTransferFsObjInfoImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferFsObjInfoImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferFsObjInfoImpl.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferFsObjInfoImpl.cpp 114858 2026-08-05 15:08:05Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer file system object information. */ @@ -34,6 +34,8 @@ #include +#include + DEFINE_EMPTY_CTOR_DTOR(ClipboardTransferFsObjInfo) @@ -125,13 +127,26 @@ HRESULT ClipboardTransferFsObjInfo::init(const com::Utf8Str &aPath, PCSHCLFSOBJINFO aInfo) { AssertPtrReturn(aInfo, E_POINTER); + if ( aInfo->cbObject < 0 + || aInfo->cbAllocated < 0 + || aInfo->Attr.enmAdditional < SHCLFSOBJATTRADD_NOTHING + || aInfo->Attr.enmAdditional > SHCLFSOBJATTRADD_LAST) + return E_INVALIDARG; + AutoInitSpan autoInitSpan(this); AssertReturn(autoInitSpan.isOk(), E_FAIL); - mData.mPath = aPath; - mData.mName = aName; + try + { + mData.mPath = aPath; + mData.mName = aName; + mData.mFileAttributes = clipboardTransferFsObjInfoModeToAttrs(aInfo->Attr.fMode); + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } mData.mType = clipboardTransferFsObjInfoModeToType(aInfo->Attr.fMode); - mData.mFileAttributes = clipboardTransferFsObjInfoModeToAttrs(aInfo->Attr.fMode); mData.mObjectSize = aInfo->cbObject; mData.mAllocatedSize = aInfo->cbAllocated; mData.mAccessTime = aInfo->AccessTime.i64NanosecondsRelativeToUnixEpoch; diff --git a/src/VBox/Main/src-client/ClipboardTransferImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferImpl.cpp index 6818a39b4f36..da6fab345400 100644 --- a/src/VBox/Main/src-client/ClipboardTransferImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferImpl.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer object. */ @@ -45,6 +45,15 @@ #include +/** Maximum number of active directory levels, including the initial directory, + * while recursively listing a transfer. This bounds stack consumption for + * externally supplied directory trees. */ +#define VBOX_SHCL_MAIN_MAX_RECURSION_DEPTH 128 +/** Maximum number of result nodes accumulated while recursively listing a + * transfer. This bounds memory consumption and traversal work for externally + * supplied directory trees. */ +#define VBOX_SHCL_MAIN_MAX_RECURSIVE_NODES _64K + // constructor / destructor ///////////////////////////////////////////////////////////////////////////// @@ -92,9 +101,9 @@ static ClipboardTransferDirection_T clipboardTransferDirectionFromShCl(SHCLTRANS { switch (enmDir) { - case SHCLTRANSFERDIR_TO_REMOTE: return ClipboardTransferDirection_ToGuest; - case SHCLTRANSFERDIR_FROM_REMOTE: return ClipboardTransferDirection_ToHost; - default: return ClipboardTransferDirection_Any; + case SHCLTRANSFERDIR_HOST_TO_GUEST: return ClipboardTransferDirection_ToGuest; + case SHCLTRANSFERDIR_GUEST_TO_HOST: return ClipboardTransferDirection_ToHost; + default: return ClipboardTransferDirection_Any; } } @@ -146,6 +155,7 @@ static HRESULT clipboardTransferValidatePath(const com::Utf8Str &aPath, bool fAl const char *pszPath = aPath.c_str(); if ( pszPath[0] == '/' || pszPath[0] == '\\' + || pszPath[aPath.length() - 1] == '/' || strchr(pszPath, '\\') || strchr(pszPath, ':')) return E_INVALIDARG; @@ -201,7 +211,6 @@ static HRESULT clipboardTransferCreateLocalProviderBackend(const std::vector &aNode) { AssertPtrReturn(pEntry, E_POINTER); - if ( !(pEntry->fInfo & VBOX_SHCL_INFO_F_FSOBJINFO) - || !pEntry->pvInfo - || pEntry->cbInfo != sizeof(SHCLFSOBJINFO)) + if ( pEntry->fInfo != VBOX_SHCL_INFO_F_FSOBJINFO + || !ShClTransferListEntryIsValid((PSHCLLISTENTRY)pEntry)) return E_INVALIDARG; ComObjPtr ptrInfo; @@ -273,11 +281,18 @@ static HRESULT clipboardTransferCreateFsObjInfoFromEntry(const com::Utf8Str &aPa if (FAILED(hrc)) return hrc; - com::Utf8Str const strName(pEntry->pszName ? pEntry->pszName : ""); - com::Utf8Str const strPath = clipboardTransferMakeChildPath(aParent, pEntry->pszName); - hrc = ptrInfo->init(strPath, strName, (PCSHCLFSOBJINFO)pEntry->pvInfo); - if (FAILED(hrc)) - return hrc; + try + { + com::Utf8Str const strName(pEntry->pszName); + com::Utf8Str const strPath = clipboardTransferMakeChildPath(aParent, pEntry->pszName); + hrc = ptrInfo->init(strPath, strName, (PCSHCLFSOBJINFO)pEntry->pvInfo); + if (FAILED(hrc)) + return hrc; + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } return ptrInfo.queryInterfaceTo(aNode.asOutParam()); } @@ -315,12 +330,17 @@ static HRESULT clipboardTransferOpenList(PSHCLTRANSFER pTransfer, const com::Utf * @param aPath Transfer-relative directory path. * @param aFlags ClipboardTransferListFlag mask. * @param aNodes Where to append listed nodes. + * @param cDepth Current recursion depth. */ static HRESULT clipboardTransferListRecursive(PSHCLTRANSFER pTransfer, const com::Utf8Str &aPath, ULONG aFlags, - std::vector > &aNodes) + std::vector > &aNodes, + uint32_t cDepth) { + if (cDepth >= VBOX_SHCL_MAIN_MAX_RECURSION_DEPTH) + return E_INVALIDARG; + SHCLLISTHANDLE hList = NIL_SHCLLISTHANDLE; HRESULT hrc = clipboardTransferOpenList(pTransfer, aPath, &hList); if (FAILED(hrc)) @@ -354,9 +374,22 @@ static HRESULT clipboardTransferListRecursive(PSHCLTRANSFER pTransfer, if (SUCCEEDED(hrc)) { Bstr bstrPath; - ptrInfo->COMGETTER(Path)(bstrPath.asOutParam()); - strChild = bstrPath; - aNodes.push_back(ptrInfo); + hrc = ptrInfo->COMGETTER(Path)(bstrPath.asOutParam()); + if (SUCCEEDED(hrc)) + { + try + { + strChild = bstrPath; + if (aNodes.size() >= VBOX_SHCL_MAIN_MAX_RECURSIVE_NODES) + hrc = E_INVALIDARG; + else + aNodes.push_back(ptrInfo); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + } } ShClTransferListEntryDestroy(&Entry); if (FAILED(hrc)) @@ -367,7 +400,7 @@ static HRESULT clipboardTransferListRecursive(PSHCLTRANSFER pTransfer, if ( fIsDirectory && !(aFlags & ClipboardTransferListFlag_NoRecursion)) { - hrc = clipboardTransferListRecursive(pTransfer, strChild, aFlags, aNodes); + hrc = clipboardTransferListRecursive(pTransfer, strChild, aFlags, aNodes, cDepth + 1); if (FAILED(hrc)) { ShClTransferListClose(pTransfer, hList); @@ -729,11 +762,7 @@ HRESULT ClipboardTransfer::getData(ComPtr &aData) RT_NOREF(aData); ReturnComNotImplemented(); #else - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -766,8 +795,19 @@ HRESULT ClipboardTransfer::getSourcePaths(std::vector &aSourcePath RT_NOREF(aSourcePaths); ReturnComNotImplemented(); #else - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - aSourcePaths = mData.mSourcePaths; + std::vector SourcePaths; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + try + { + SourcePaths = mData.mSourcePaths; + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer source-path result failed")); + } + } + aSourcePaths.swap(SourcePaths); return S_OK; #endif } @@ -822,13 +862,37 @@ HRESULT ClipboardTransfer::setSourcePaths(const std::vector &aSour PSHCLTRANSFER pOldTransfer = NULL; bool fDestroyOldTransfer = false; + bool fForeignBackend = false; + bool fWrongDirection = false; + bool fWrongState = false; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - pOldTransfer = mData.mTransfer; - fDestroyOldTransfer = mData.mfOwnTransfer && pOldTransfer; - mData.mTransfer = pNewTransfer; - mData.mfOwnTransfer = pNewTransfer != NULL; - mData.mSourcePaths.swap(vecSourcePaths); + fForeignBackend = mData.mTransfer && !mData.mfOwnTransfer; + fWrongDirection = mData.mDirection != ClipboardTransferDirection_ToGuest + || mData.mSource != ClipboardSource_Host; + fWrongState = mData.mState != ClipboardTransferState_Added; + if (!fForeignBackend && !fWrongDirection && !fWrongState) + { + pOldTransfer = mData.mTransfer; + fDestroyOldTransfer = mData.mfOwnTransfer && pOldTransfer; + mData.mTransfer = pNewTransfer; + mData.mfOwnTransfer = pNewTransfer != NULL; + mData.mSourcePaths.swap(vecSourcePaths); + } + } + + if (fForeignBackend || fWrongDirection || fWrongState) + { + if (pNewTransfer) + { + int vrc = ShClTransferDestroy(pNewTransfer); + AssertRC(vrc); + } + if (fForeignBackend) + return setError(E_NOTIMPL, tr("Clipboard transfer source paths cannot replace a foreign data-plane backend")); + if (fWrongDirection) + return setError(E_NOTIMPL, tr("Clipboard transfer source paths currently require a host-to-guest transfer")); + return setError(E_FAIL, tr("Clipboard transfer source paths can only be changed while the transfer is pending")); } if (fDestroyOldTransfer) @@ -853,30 +917,56 @@ HRESULT ClipboardTransfer::roots(std::vector RT_NOREF(aNodes); ReturnComNotImplemented(); #else - aNodes.clear(); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); + std::vector > Nodes; + HRESULT const hrc = i_roots(pTransfer, Nodes); + if (SUCCEEDED(hrc)) + aNodes.swap(Nodes); + return hrc; +#endif +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Returns root nodes from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aNodes Where to return the root nodes. + */ +HRESULT ClipboardTransfer::i_roots(PSHCLTRANSFER pTransfer, + std::vector > &aNodes) +{ + AssertPtrReturn(pTransfer, E_POINTER); + aNodes.clear(); uint64_t const cRoots = ShClTransferRootsCount(pTransfer); for (uint64_t i = 0; i < cRoots; ++i) { - PCSHCLLISTENTRY pEntry = ShClTransferRootsEntryGet(pTransfer, i); + PCSHCLLISTENTRY const pEntry = ShClTransferRootsEntryGet(pTransfer, i); if (!pEntry) - return setError(VBOX_E_SHCL_NO_DATA, tr("No clipboard transfer root entry exists at index %RU64"), i); + return setErrorBoth(clipboardTransferDataPlaneRcToHrc(VERR_NOT_FOUND), VERR_NOT_FOUND, + tr("No clipboard transfer root entry exists at index %RU64"), i); + ComPtr ptrInfo; HRESULT hrc = clipboardTransferCreateFsObjInfoFromEntry(com::Utf8Str(), pEntry, ptrInfo); if (FAILED(hrc)) return setError(hrc, tr("Creating clipboard transfer root entry information failed")); - aNodes.push_back(ptrInfo); + try + { + aNodes.push_back(ptrInfo); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer root result failed")); + } } return S_OK; -#endif } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /** @@ -897,8 +987,30 @@ HRESULT ClipboardTransfer::query(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Invalid clipboard transfer query path")); + PSHCLTRANSFER const pTransfer = i_getTransfer(); + if (!pTransfer) + return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); + return i_query(pTransfer, aPath, aNode); +#endif +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Queries a node from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aPath Transfer-relative path. + * @param aNode Where to return the node information. + */ +HRESULT ClipboardTransfer::i_query(PSHCLTRANSFER pTransfer, + const com::Utf8Str &aPath, + ComPtr &aNode) +{ + AssertPtrReturn(pTransfer, E_POINTER); std::vector > vecNodes; - hrc = list(com::Utf8Str(), ClipboardTransferListFlag_None, vecNodes); + HRESULT hrc = i_list(pTransfer, com::Utf8Str(), ClipboardTransferListFlag_None, vecNodes); if (FAILED(hrc)) return setError(hrc, tr("Listing clipboard transfer roots for query failed")); for (std::vector >::const_iterator it = vecNodes.begin(); it != vecNodes.end(); ++it) @@ -907,15 +1019,22 @@ HRESULT ClipboardTransfer::query(const com::Utf8Str &aPath, hrc = (*it)->COMGETTER(Path)(bstrPath.asOutParam()); if (FAILED(hrc)) return setError(hrc, tr("Querying clipboard transfer node path failed")); - if (com::Utf8Str(bstrPath) == aPath) + try { - aNode = *it; - return S_OK; + if (com::Utf8Str(bstrPath) == aPath) + { + aNode = *it; + return S_OK; + } + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer query path failed")); } } return setError(VBOX_E_SHCL_NO_DATA, tr("Clipboard transfer path '%s' was not found"), aPath.c_str()); -#endif } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /** @@ -940,24 +1059,53 @@ HRESULT ClipboardTransfer::list(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Invalid clipboard transfer list path")); - aNodes.clear(); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); + std::vector > Nodes; + hrc = i_list(pTransfer, aPath, aFlags, Nodes); + if (SUCCEEDED(hrc)) + aNodes.swap(Nodes); + return hrc; +#endif +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Lists nodes from one parent-owned backing transfer. + * + * @returns COM status code. + * @param pTransfer Retained backing transfer. + * @param aPath Transfer-relative directory path, or empty for roots. + * @param aFlags ClipboardTransferListFlag mask. + * @param aNodes Where to return listed nodes. + */ +HRESULT ClipboardTransfer::i_list(PSHCLTRANSFER pTransfer, + const com::Utf8Str &aPath, + ULONG aFlags, + std::vector > &aNodes) +{ + AssertPtrReturn(pTransfer, E_POINTER); + aNodes.clear(); if (aPath.isEmpty()) { - hrc = roots(aNodes); + HRESULT hrc = i_roots(pTransfer, aNodes); if (FAILED(hrc)) return setError(hrc, tr("Listing clipboard transfer roots failed")); if (aFlags & ClipboardTransferListFlag_NoRecursion) return S_OK; - std::vector > vecRoots = aNodes; + std::vector > vecRoots; + try + { + vecRoots = aNodes; + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer root traversal list failed")); + } for (std::vector >::const_iterator it = vecRoots.begin(); it != vecRoots.end(); ++it) { FsObjType_T enmType = FsObjType_Unknown; @@ -970,7 +1118,14 @@ HRESULT ClipboardTransfer::list(const com::Utf8Str &aPath, hrc2 = (*it)->COMGETTER(Path)(bstrPath.asOutParam()); if (FAILED(hrc2)) return setError(hrc2, tr("Querying clipboard transfer node path failed")); - hrc2 = clipboardTransferListRecursive(pTransfer, com::Utf8Str(bstrPath), aFlags, aNodes); + try + { + hrc2 = clipboardTransferListRecursive(pTransfer, com::Utf8Str(bstrPath), aFlags, aNodes, 0); + } + catch (std::bad_alloc &) + { + hrc2 = E_OUTOFMEMORY; + } if (FAILED(hrc2)) return setError(hrc2, tr("Recursively listing clipboard transfer directory failed")); } @@ -981,17 +1136,24 @@ HRESULT ClipboardTransfer::list(const com::Utf8Str &aPath, if (aFlags & ClipboardTransferListFlag_IncludeRoot) { ComPtr ptrRoot; - hrc = query(aPath, ptrRoot); + HRESULT hrc = i_query(pTransfer, aPath, ptrRoot); if (FAILED(hrc)) return setError(hrc, tr("Querying clipboard transfer list root failed")); - aNodes.push_back(ptrRoot); + try + { + aNodes.push_back(ptrRoot); + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer list result failed")); + } } - hrc = clipboardTransferListRecursive(pTransfer, aPath, aFlags, aNodes); + HRESULT hrc = clipboardTransferListRecursive(pTransfer, aPath, aFlags, aNodes, 0); if (FAILED(hrc)) return setError(hrc, tr("Listing clipboard transfer directory failed")); return S_OK; -#endif } +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ /** @@ -1016,11 +1178,7 @@ HRESULT ClipboardTransfer::openDirectory(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Invalid clipboard transfer directory path")); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); @@ -1029,7 +1187,7 @@ HRESULT ClipboardTransfer::openDirectory(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Opening clipboard transfer directory failed")); - ComPtr ptrSelf(this); + ComObjPtr ptrSelf(this); ComObjPtr ptrDirectory; hrc = ptrDirectory.createObject(); @@ -1088,11 +1246,7 @@ HRESULT ClipboardTransfer::openFile(const com::Utf8Str &aPath, if (FAILED(hrc)) return setError(hrc, tr("Invalid clipboard transfer file path")); - PSHCLTRANSFER pTransfer; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pTransfer = mData.mTransfer; - } + PSHCLTRANSFER const pTransfer = i_getTransfer(); if (!pTransfer) return setError(E_NOTIMPL, tr("Clipboard transfer has no data-plane backend")); diff --git a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp index 4750bf351cc3..1bd688b4f1e9 100644 --- a/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp +++ b/src/VBox/Main/src-client/ClipboardTransferManagerImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardTransferManagerImpl.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardTransferManagerImpl.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * VirtualBox Main - Clipboard transfer manager object. */ @@ -35,6 +35,7 @@ #include "ClipboardTransferManagerImpl.h" #include "GuestShClHelpers.h" #include "ProgressImpl.h" +#include "VirtualBoxErrorInfoImpl.h" #include "VBoxEvents.h" #include @@ -44,6 +45,8 @@ #include #include +#include + // constructor / destructor ///////////////////////////////////////////////////////////////////////////// @@ -73,41 +76,6 @@ void ClipboardTransferManager::FinalRelease() #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** - * Checks whether a transfer key is usable for Main-side lifecycle tracking. - * - * @returns true if the key identifies a non-nil service transfer, false otherwise. - * @param idSession Service session ID. - * @param idTransfer Service transfer ID. - * @param uGeneration Service transfer generation. - */ -static bool clipboardTransferManagerKeyIsValid(SHCLSESSIONID idSession, ULONG idTransfer, SHCLTRANSFERGEN uGeneration) -{ - return idSession != 0 - && idSession != NIL_SHCLSESSIONID - && idTransfer > 0 - && idTransfer < VBOX_SHCL_MAX_TRANSFERS - 1 - && uGeneration != 0 - && uGeneration != NIL_SHCLTRANSFERGEN; -} - - -/** - * Checks whether a transfer status ends the Main transfer record lifecycle. - * - * @returns true if the status is terminal, false otherwise. - * @param enmStatus Transfer status to classify. - */ -static bool clipboardTransferManagerStatusIsTerminal(SHCLTRANSFERSTATUS enmStatus) -{ - return enmStatus == SHCLTRANSFERSTATUS_COMPLETED - || enmStatus == SHCLTRANSFERSTATUS_CANCELED - || enmStatus == SHCLTRANSFERSTATUS_KILLED - || enmStatus == SHCLTRANSFERSTATUS_ERROR - || enmStatus == SHCLTRANSFERSTATUS_UNINITIALIZED; -} - - /** * Converts a transfer status to the corresponding public Main transfer state. * @@ -199,46 +167,41 @@ static void clipboardTransferManagerCompleteProgress(const ComPtrNotifyComplete((LONG)clipboardTransferManagerStatusToProgressHrc(enmStatus, vrcTransfer), - NULL /* aErrorInfo */); - AssertComRC(hrc); -} - - -/** - * Validates an optional transfer-relative interaction path. - * - * @returns COM status code. - * @param aPath Path to validate. - * @param fAllowEmpty Whether the empty path is accepted. - */ -static HRESULT clipboardTransferManagerValidatePath(const com::Utf8Str &aPath, bool fAllowEmpty) -{ - if (aPath.isEmpty()) - return fAllowEmpty ? S_OK : E_INVALIDARG; - - const char *pszPath = aPath.c_str(); - if ( pszPath[0] == '/' - || pszPath[0] == '\\' - || strchr(pszPath, '\\') - || strchr(pszPath, ':')) - return E_INVALIDARG; - - const char *psz = pszPath; - while (*psz) + HRESULT const hrcProgress = clipboardTransferManagerStatusToProgressHrc(enmStatus, vrcTransfer); + ComPtr ptrErrorInfo; + if (FAILED(hrcProgress)) { - const char *pszSlash = strchr(psz, '/'); - size_t const cchComponent = pszSlash ? (size_t)(pszSlash - psz) : strlen(psz); - if ( cchComponent == 0 - || (cchComponent == 1 && psz[0] == '.') - || (cchComponent == 2 && psz[0] == '.' && psz[1] == '.')) - return E_INVALIDARG; - if (!pszSlash) - break; - psz = pszSlash + 1; + ComObjPtr ptrErrorInfoImpl; + HRESULT hrc = ptrErrorInfoImpl.createObject(); + if (SUCCEEDED(hrc)) + { + const char *pszText; + if (enmStatus == SHCLTRANSFERSTATUS_CANCELED) + pszText = ClipboardTransferManager::tr("Shared Clipboard transfer was canceled"); + else if (enmStatus == SHCLTRANSFERSTATUS_UNINITIALIZED) + pszText = ClipboardTransferManager::tr("Shared Clipboard transfer was removed before completion"); + else + pszText = ClipboardTransferManager::tr("Shared Clipboard transfer failed"); + try + { + hrc = ptrErrorInfoImpl->initEx(hrcProgress, (LONG)vrcTransfer, COM_IIDOF(IClipboardTransferManager), + "ClipboardTransferManager", com::Utf8Str(pszText)); + } + catch (std::bad_alloc &) + { + hrc = E_OUTOFMEMORY; + } + if (SUCCEEDED(hrc)) + ptrErrorInfo = ptrErrorInfoImpl; + } + if (FAILED(hrc)) + AssertComRC(hrc); } - return S_OK; + + HRESULT hrc = ptrProgressControl->NotifyComplete((LONG)hrcProgress, ptrErrorInfo); + AssertComRC(hrc); } + #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -279,109 +242,19 @@ void ClipboardTransferManager::uninit() if (autoUninitSpan.uninitDone()) return; - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - mData.mTransfers.clear(); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - mData.mNextTransferId = 1; -#endif - mData.mEventSource.setNull(); - mData.mParent = NULL; -} - - -/** - * Resets the internally tracked transfer list. - */ -void ClipboardTransferManager::i_reset() -{ - LogFunc(("Resetting transfer manager\n")); + std::vector DetachedTransfers; ComPtr ptrEventSource; - std::vector > aTransfers; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - std::vector > aProgressControls; -#endif { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - ptrEventSource = mData.mEventSource; - for (std::vector::const_iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - { - aTransfers.push_back(it->mTransfer); + DetachedTransfers.swap(mData.mTransfers); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - if (it->mProgressControl.isNotNull()) - aProgressControls.push_back(it->mProgressControl); + mData.mNextTransferId = 1; #endif - } - mData.mTransfers.clear(); - Log2Func(("Detached %zu transfers during reset\n", aTransfers.size())); + ptrEventSource = mData.mEventSource; + mData.mEventSource.setNull(); + mData.mParent = NULL; } - RT_NOREF(ptrEventSource); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - for (std::vector >::const_iterator it = aProgressControls.begin(); - it != aProgressControls.end(); ++it) - clipboardTransferManagerCompleteProgress(*it, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); -#endif - for (std::vector >::const_iterator it = aTransfers.begin(); - it != aTransfers.end(); ++it) - { - Log2Func(("Firing transfer removed event during reset: transfer=%p\n", (void *)*it)); - i_fireTransferEvent(*it, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, - com::Utf8Str(), com::Utf8Str(), ClipboardError_None); - } -} - - -/** - * Fires a clipboard transfer event through the parent clipboard object when available. - * If there is no parent clipboard object, emits an anonymous event directly on - * the stored event source. - * - * @param aTransfer Transfer associated with the event. - * @param aState Transfer state. - * @param aInteraction Transfer interaction type. - * @param aPath Transfer-relative path associated with the event, if any. - * @param aMessage Optional event message. - * @param aError Clipboard transfer error code. - */ -void ClipboardTransferManager::i_fireTransferEvent(IClipboardTransfer *aTransfer, - ClipboardTransferState_T aState, - ClipboardTransferInteraction_T aInteraction, - const com::Utf8Str &aPath, - const com::Utf8Str &aMessage, - ClipboardError_T aError) -{ - Clipboard *pParent = NULL; - ComPtr ptrEventSource; - AutoCaller autoCaller; - { - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - pParent = mData.mParent; - if (pParent) - autoCaller.attach(pParent); - else - ptrEventSource = mData.mEventSource; - } - - if (pParent) - { - if (SUCCEEDED(autoCaller.hrc())) - pParent->i_fireClipboardTransferEvent(VBOX_SHCL_MAIN_CLIENT_NONE, aTransfer, aState, aInteraction, - aPath, aMessage, aError); - else - LogFunc(("Cannot fire clipboard transfer event through parent: hrc=%#x\n", autoCaller.hrc())); - return; - } - - if (ptrEventSource.isNotNull()) - { - /* - * No parent Clipboard is available to supply a live revision or session - * fan-out context. Keep this legacy event anonymous. - */ - ::FireClipboardTransferEvent(ptrEventSource, 0 /* anonymous revision */, VBOX_SHCL_MAIN_CLIENT_NONE, - aTransfer, aState, aInteraction, Bstr(aPath).raw(), aMessage, aError); - } } @@ -409,43 +282,45 @@ HRESULT ClipboardTransferManager::getTransfers(ClipboardTransferDirection_T aDir && aDirection != ClipboardTransferDirection_ToHost) return setError(E_INVALIDARG, tr("Invalid clipboard transfer direction %RU32"), (uint32_t)aDirection); - AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - aTransfers.clear(); - for (std::vector::const_iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) + std::vector > Transfers; { - if (!it->mfPublished) - continue; - if (aDirection != ClipboardTransferDirection_Any) + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + try { - ClipboardTransferDirection_T enmDirection = ClipboardTransferDirection_Any; - HRESULT hrc = it->mTransfer->COMGETTER(Direction)(&enmDirection); - if (FAILED(hrc)) - return setError(hrc, tr("Querying clipboard transfer direction failed")); - if (enmDirection != aDirection) - continue; + Transfers.reserve(mData.mTransfers.size()); + for (std::vector::const_iterator it = mData.mTransfers.begin(); + it != mData.mTransfers.end(); ++it) + { + if (aDirection != ClipboardTransferDirection_Any) + { + ClipboardTransferDirection_T const enmDirection + = it->mDirection == SHCLTRANSFERDIR_GUEST_TO_HOST + ? ClipboardTransferDirection_ToHost : ClipboardTransferDirection_ToGuest; + if (enmDirection != aDirection) + continue; + } + ComPtr ptrTransfer; + HRESULT const hrc = it->mTransfer.queryInterfaceTo(ptrTransfer.asOutParam()); + AssertComRCReturn(hrc, hrc); + Transfers.push_back(ptrTransfer); + } + } + catch (std::bad_alloc &) + { + return setError(E_OUTOFMEMORY, tr("Allocating the clipboard transfer result list failed")); } - aTransfers.push_back(it->mTransfer); } + aTransfers.swap(Transfers); Log3Func(("cTransfers=%zu\n", aTransfers.size())); return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ } -/** - * Creates an unpublished Main-owned clipboard transfer. - * - * @returns COM status code. - * @param aDirection Transfer direction. - * @param aSource Clipboard source owning the transfer. - * @param aAction Clipboard transfer action. - * @param aTransfer Where to return the transfer object. - */ -HRESULT ClipboardTransferManager::createTransfer(ClipboardTransferDirection_T aDirection, - ClipboardSource_T aSource, - ClipboardAction_T aAction, - ComPtr &aTransfer) +HRESULT ClipboardTransferManager::create(ClipboardTransferDirection_T aDirection, + ClipboardSource_T aSource, + ClipboardAction_T aAction, + ComPtr &aTransfer) { #ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS RT_NOREF(aDirection, aSource, aAction, aTransfer); @@ -500,76 +375,26 @@ HRESULT ClipboardTransferManager::createTransfer(ClipboardTransferDirection_T aD AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); Data::TransferRecord Record; Record.mTransferId = idTransfer; - Record.mTransfer = ptrTransfer; - Record.mfPublished = false; - mData.mTransfers.push_back(Record); - } - - aTransfer = ptrTransfer; - return S_OK; -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ -} - - -/** - * Adds a clipboard transfer. - * - * @returns COM status code. - * @param aTransfer Transfer to add. - */ -HRESULT ClipboardTransferManager::add(const ComPtr &aTransfer) -{ -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer); - ReturnComNotImplemented(); -#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - - Log2Func(("aTransfer=%p\n", (void *)aTransfer)); - if (aTransfer.isNull()) - { - LogFunc(("Rejecting NULL transfer add\n")); - return setError(E_INVALIDARG, tr("Clipboard transfer to add must not be NULL")); - } - - ULONG idTransfer = 0; - HRESULT hrc = aTransfer->COMGETTER(Id)(&idTransfer); - if (FAILED(hrc)) - return setError(hrc, tr("Querying clipboard transfer ID failed")); - - bool fFireEvent = false; - { - AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - bool fKnown = false; - for (std::vector::iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) - { - if (it->mfPublished) - return S_OK; - it->mfPublished = true; - fKnown = true; - Log2Func(("Published existing transfer: cTransfers=%zu\n", mData.mTransfers.size())); - break; - } - - if (!fKnown) + Record.mDirection = aDirection == ClipboardTransferDirection_ToHost + ? SHCLTRANSFERDIR_GUEST_TO_HOST : SHCLTRANSFERDIR_HOST_TO_GUEST; + Record.mSource = aSource == ClipboardSource_Host + ? SHCLSOURCE_LOCAL + : aSource == ClipboardSource_Guest ? SHCLSOURCE_REMOTE : SHCLSOURCE_INVALID; + Record.mTransfer = ptrTransferObj; + try { - Data::TransferRecord Record; - Record.mTransferId = idTransfer; - Record.mTransfer = aTransfer; - Record.mfPublished = true; mData.mTransfers.push_back(Record); - Log2Func(("Added transfer: cTransfers=%zu\n", mData.mTransfers.size())); } - fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } } - if (fFireEvent) - { - Log2Func(("Firing transfer added event: transfer=%p\n", (void *)aTransfer)); - i_fireTransferEvent(aTransfer, ClipboardTransferState_Added, ClipboardTransferInteraction_None, - com::Utf8Str(), com::Utf8Str(), ClipboardError_None); - } + aTransfer = ptrTransfer; + Log2Func(("Firing transfer added event: transfer=%p\n", (void *)(ClipboardTransfer *)ptrTransferObj)); + i_fireTransferEvent(ptrTransferObj, ClipboardTransferState_Added, ClipboardTransferInteraction_None, + com::Utf8Str(), com::Utf8Str(), ClipboardError_None); return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ } @@ -583,11 +408,6 @@ HRESULT ClipboardTransferManager::add(const ComPtr &aTransfe */ HRESULT ClipboardTransferManager::remove(const ComPtr &aTransfer) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer); - ReturnComNotImplemented(); -#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - Log2Func(("aTransfer=%p\n", (void *)aTransfer)); if (aTransfer.isNull()) { @@ -595,34 +415,49 @@ HRESULT ClipboardTransferManager::remove(const ComPtr &aTran return setError(E_INVALIDARG, tr("Clipboard transfer to remove must not be NULL")); } +#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RT_NOREF(aTransfer); + ReturnComNotImplemented(); +#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + ComObjPtr ptrTransfer; bool fRemoved = false; bool fFireEvent = false; + bool fServiceTransfer = false; ComPtr ptrProgressControl; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - for (std::vector::iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) + Data::TransferRecords::iterator it = mData.findTransferRecord(aTransfer); + if (it != mData.mTransfers.end()) + { + ptrTransfer = it->mTransfer; + if (ShClTransferKeyIsValid(it->mServiceSessionId, it->mTransferId, it->mGeneration)) + fServiceTransfer = true; + else { ptrProgressControl = it->mProgressControl; mData.mTransfers.erase(it); Log2Func(("Removed transfer: cTransfers=%zu\n", mData.mTransfers.size())); fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); fRemoved = true; - break; } + } } - if (fRemoved) - clipboardTransferManagerCompleteProgress(ptrProgressControl, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); - if (fRemoved && fFireEvent) + if (fServiceTransfer) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot remove an active service transfer; cancel it first")); + if (!fRemoved) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is no longer owned by this manager")); + + ptrTransfer->i_setState(ClipboardTransferState_Removed, com::Utf8Str(), ClipboardError_None); + clipboardTransferManagerCompleteProgress(ptrProgressControl, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); + if (fFireEvent) { - Log2Func(("Firing transfer removed event: transfer=%p\n", (void *)aTransfer)); - i_fireTransferEvent(aTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, + Log2Func(("Firing transfer removed event: transfer=%p\n", (void *)(ClipboardTransfer *)ptrTransfer)); + i_fireTransferEvent(ptrTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); } - else if (!fRemoved) - Log2Func(("Transfer not found for remove: transfer=%p\n", (void *)aTransfer)); return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ } @@ -636,11 +471,6 @@ HRESULT ClipboardTransferManager::remove(const ComPtr &aTran */ HRESULT ClipboardTransferManager::cancel(const ComPtr &aTransfer) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer); - ReturnComNotImplemented(); -#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - Log2Func(("aTransfer=%p\n", (void *)aTransfer)); if (aTransfer.isNull()) { @@ -648,37 +478,54 @@ HRESULT ClipboardTransferManager::cancel(const ComPtr &aTran return setError(E_INVALIDARG, tr("Clipboard transfer to cancel must not be NULL")); } +#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RT_NOREF(aTransfer); + ReturnComNotImplemented(); +#else /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + ComObjPtr ptrTransfer; bool fCanceled = false; bool fFireEvent = false; bool fNeedsHostCancel = false; + bool fCancelAlreadyRequested = false; SHCLSESSIONID idSession = NIL_SHCLSESSIONID; ULONG idTransfer = 0; SHCLTRANSFERGEN uGeneration = NIL_SHCLTRANSFERGEN; ComPtr ptrProgressControl; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - for (std::vector::iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) + Data::TransferRecords::iterator it = mData.findTransferRecord(aTransfer); + if (it != mData.mTransfers.end()) + { + ptrTransfer = it->mTransfer; + idSession = it->mServiceSessionId; + idTransfer = it->mTransferId; + uGeneration = it->mGeneration; + fNeedsHostCancel = ShClTransferKeyIsValid(idSession, idTransfer, uGeneration); + if (!fNeedsHostCancel) { - idSession = it->mServiceSessionId; - idTransfer = it->mTransferId; - uGeneration = it->mGeneration; - fNeedsHostCancel = clipboardTransferManagerKeyIsValid(idSession, idTransfer, uGeneration); - if (!fNeedsHostCancel) - { - ptrProgressControl = it->mProgressControl; - mData.mTransfers.erase(it); - Log2Func(("Canceled transfer: cTransfers=%zu\n", mData.mTransfers.size())); - fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); - fCanceled = true; - } + ptrProgressControl = it->mProgressControl; + mData.mTransfers.erase(it); + Log2Func(("Canceled transfer: cTransfers=%zu\n", mData.mTransfers.size())); + fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); + fCanceled = true; + } + else + { + if (it->mfCancelRequested) + fCancelAlreadyRequested = true; else it->mfCancelRequested = true; - break; } + } } + if (fCancelAlreadyRequested) + return setError(VBOX_E_OBJECT_IN_USE, tr("Clipboard transfer cancellation is already in progress")); + if (!fCanceled && !fNeedsHostCancel) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is no longer owned by this manager")); + + ClipboardTransfer *pTransfer = ptrTransfer; if (fNeedsHostCancel) { Clipboard *pParent = NULL; @@ -690,40 +537,63 @@ HRESULT ClipboardTransferManager::cancel(const ComPtr &aTran autoCaller.attach(pParent); } if (!pParent) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Data::TransferRecords::iterator it = mData.findTransferRecord(pTransfer, idSession, + idTransfer, uGeneration); + if (it != mData.mTransfers.end()) + it->mfCancelRequested = false; return setError(E_FAIL, tr("Clipboard transfer cannot be canceled because no clipboard backend is available")); + } if (FAILED(autoCaller.hrc())) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Data::TransferRecords::iterator it = mData.findTransferRecord(pTransfer, idSession, + idTransfer, uGeneration); + if (it != mData.mTransfers.end()) + it->mfCancelRequested = false; return setError(autoCaller.hrc(), tr("Clipboard backend is not ready for canceling clipboard transfers")); + } - HRESULT hrc = pParent->i_transferCancel(idSession, (SHCLTRANSFERID)idTransfer, uGeneration); + HRESULT const hrc = pParent->i_transferCancel(idSession, (SHCLTRANSFERID)idTransfer, uGeneration); if (FAILED(hrc)) + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + Data::TransferRecords::iterator it = mData.findTransferRecord(pTransfer, idSession, + idTransfer, uGeneration); + if (it != mData.mTransfers.end()) + it->mfCancelRequested = false; return setError(hrc, tr("Canceling clipboard transfer through the backend failed")); + } { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - for (std::vector::iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) - { - ptrProgressControl = it->mProgressControl; - mData.mTransfers.erase(it); - Log2Func(("Canceled transfer after host request: cTransfers=%zu\n", mData.mTransfers.size())); - fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); - fCanceled = true; - break; - } + Data::TransferRecords::iterator it = mData.findTransferRecord(pTransfer, idSession, + idTransfer, uGeneration); + if (it != mData.mTransfers.end()) + { + ptrProgressControl = it->mProgressControl; + mData.mTransfers.erase(it); + Log2Func(("Canceled transfer after host request: cTransfers=%zu\n", mData.mTransfers.size())); + fFireEvent = mData.mParent != NULL || mData.mEventSource.isNotNull(); + fCanceled = true; + } } } if (fCanceled) + { + ptrTransfer->i_setState(ClipboardTransferState_Canceled, com::Utf8Str(), ClipboardError_None); clipboardTransferManagerCompleteProgress(ptrProgressControl, SHCLTRANSFERSTATUS_CANCELED, VERR_CANCELLED); + } if (fCanceled && fFireEvent) { - Log2Func(("Firing transfer canceled event: transfer=%p\n", (void *)aTransfer)); - i_fireTransferEvent(aTransfer, ClipboardTransferState_Canceled, ClipboardTransferInteraction_None, + Log2Func(("Firing transfer canceled event: transfer=%p\n", (void *)pTransfer)); + i_fireTransferEvent(ptrTransfer, ClipboardTransferState_Canceled, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); } else if (!fCanceled) - Log2Func(("Transfer not found for cancel: transfer=%p\n", (void *)aTransfer)); + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is no longer owned by this manager")); return S_OK; #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ } @@ -738,15 +608,19 @@ HRESULT ClipboardTransferManager::cancel(const ComPtr &aTran */ HRESULT ClipboardTransferManager::approve(const ComPtr &aTransfer, ULONG aFlags) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer, aFlags); - ReturnComNotImplemented(); -#else - if (aFlags != 0) - return setError(E_INVALIDARG, tr("Invalid clipboard transfer approval flags %RU32"), aFlags); - return respond(aTransfer, ClipboardTransferInteraction_Approval, com::Utf8Str(), ClipboardTransferResponse_Accept, - com::Utf8Str(), 0); + if (aTransfer.isNull()) + return setError(E_INVALIDARG, tr("Clipboard transfer to approve must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); + } + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); #endif + RT_NOREF(aFlags); + ReturnComNotImplemented(); } @@ -759,17 +633,19 @@ HRESULT ClipboardTransferManager::approve(const ComPtr &aTra */ HRESULT ClipboardTransferManager::deny(const ComPtr &aTransfer, const com::Utf8Str &aReason) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer, aReason); - ReturnComNotImplemented(); -#else - HRESULT hrc = respond(aTransfer, ClipboardTransferInteraction_Approval, com::Utf8Str(), ClipboardTransferResponse_Reject, - com::Utf8Str(), 0); - if (SUCCEEDED(hrc) && aReason.isNotEmpty()) - i_fireTransferEvent(aTransfer, ClipboardTransferState_Canceled, ClipboardTransferInteraction_Approval, - com::Utf8Str(), aReason, ClipboardError_AccessDenied); - return hrc; + if (aTransfer.isNull()) + return setError(E_INVALIDARG, tr("Clipboard transfer to deny must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); + } + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); #endif + RT_NOREF(aReason); + ReturnComNotImplemented(); } @@ -791,51 +667,19 @@ HRESULT ClipboardTransferManager::respond(const ComPtr &aTra const com::Utf8Str &aResponsePath, ULONG aFlags) { -#ifndef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - RT_NOREF(aTransfer, aInteraction, aPath, aResponse, aResponsePath, aFlags); - ReturnComNotImplemented(); -#else if (aTransfer.isNull()) - return setError(E_INVALIDARG, tr("Clipboard transfer response requires a transfer object")); - if (aInteraction == ClipboardTransferInteraction_None) - return setError(E_INVALIDARG, tr("Invalid clipboard transfer interaction %RU32"), (uint32_t)aInteraction); - if (aResponse == ClipboardTransferResponse_None) - return setError(E_INVALIDARG, tr("Invalid clipboard transfer response %RU32"), (uint32_t)aResponse); - if (aFlags != 0) - return setError(E_INVALIDARG, tr("Invalid clipboard transfer response flags %RU32"), aFlags); - if ( aResponsePath.isNotEmpty() - && aInteraction != ClipboardTransferInteraction_Destination - && aInteraction != ClipboardTransferInteraction_Rename) - return setError(E_INVALIDARG, tr("Clipboard transfer response path is only valid for destination or rename interactions")); - HRESULT hrc = clipboardTransferManagerValidatePath(aPath, true /* fAllowEmpty */); - if (FAILED(hrc)) - return setError(hrc, tr("Invalid clipboard transfer interaction path")); - hrc = clipboardTransferManagerValidatePath(aResponsePath, true /* fAllowEmpty */); - if (FAILED(hrc)) - return setError(hrc, tr("Invalid clipboard transfer response path")); - - bool fKnown = false; + return setError(E_INVALIDARG, tr("Clipboard transfer awaiting a response must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); - for (std::vector::const_iterator it = mData.mTransfers.begin(); - it != mData.mTransfers.end(); ++it) - if (it->mTransfer == aTransfer) - { - fKnown = true; - break; - } + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); } - if (!fKnown) - return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer object is not known to this manager")); - - ClipboardTransferState_T const enmState = aResponse == ClipboardTransferResponse_Reject - || aResponse == ClipboardTransferResponse_Cancel - ? ClipboardTransferState_Canceled : ClipboardTransferState_InProgress; - ClipboardError_T const enmError = enmState == ClipboardTransferState_Canceled - ? ClipboardError_AccessDenied : ClipboardError_None; - i_fireTransferEvent(aTransfer, enmState, aInteraction, aPath, com::Utf8Str(), enmError); - return S_OK; + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); #endif + RT_NOREF(aInteraction, aPath, aResponse, aResponsePath, aFlags); + ReturnComNotImplemented(); } @@ -847,7 +691,17 @@ HRESULT ClipboardTransferManager::respond(const ComPtr &aTra */ HRESULT ClipboardTransferManager::pause(const ComPtr &aTransfer) { - RT_NOREF(aTransfer); + if (aTransfer.isNull()) + return setError(E_INVALIDARG, tr("Clipboard transfer to pause must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); + } + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); +#endif ReturnComNotImplemented(); } @@ -860,7 +714,17 @@ HRESULT ClipboardTransferManager::pause(const ComPtr &aTrans */ HRESULT ClipboardTransferManager::resume(const ComPtr &aTransfer) { - RT_NOREF(aTransfer); + if (aTransfer.isNull()) + return setError(E_INVALIDARG, tr("Clipboard transfer to resume must not be NULL")); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + bool fOwned; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + fOwned = mData.findTransferRecord(aTransfer) != mData.mTransfers.end(); + } + if (!fOwned) + return setError(VBOX_E_OBJECT_NOT_FOUND, tr("Clipboard transfer is not owned by this manager")); +#endif ReturnComNotImplemented(); } @@ -878,31 +742,40 @@ HRESULT ClipboardTransferManager::reset() LogFunc(("Resetting transfer manager via public API\n")); ComPtr ptrEventSource; - std::vector > aTransfers; - std::vector > aProgressControls; + std::vector DetachedTransfers; + bool fHasServiceTransfers = false; { AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); - ptrEventSource = mData.mEventSource; for (std::vector::const_iterator it = mData.mTransfers.begin(); it != mData.mTransfers.end(); ++it) + if (ShClTransferKeyIsValid(it->mServiceSessionId, it->mTransferId, it->mGeneration)) + { + fHasServiceTransfers = true; + break; + } + + if (!fHasServiceTransfers) { - aTransfers.push_back(it->mTransfer); - if (it->mProgressControl.isNotNull()) - aProgressControls.push_back(it->mProgressControl); + ptrEventSource = mData.mEventSource; + DetachedTransfers.swap(mData.mTransfers); + Log2Func(("Detached %zu transfers during public reset\n", DetachedTransfers.size())); } - mData.mTransfers.clear(); - Log2Func(("Detached %zu transfers during public reset\n", aTransfers.size())); } + if (fHasServiceTransfers) + return setError(VBOX_E_OBJECT_IN_USE, + tr("Cannot reset clipboard transfers while a service transfer is active; cancel it first")); + RT_NOREF(ptrEventSource); - for (std::vector >::const_iterator it = aProgressControls.begin(); - it != aProgressControls.end(); ++it) - clipboardTransferManagerCompleteProgress(*it, SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); - for (std::vector >::const_iterator it = aTransfers.begin(); - it != aTransfers.end(); ++it) + for (std::vector::const_iterator it = DetachedTransfers.begin(); + it != DetachedTransfers.end(); ++it) { - Log2Func(("Firing transfer removed event during public reset: transfer=%p\n", (void *)*it)); - i_fireTransferEvent(*it, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, + it->mTransfer->i_setState(ClipboardTransferState_Removed, com::Utf8Str(), ClipboardError_None); + clipboardTransferManagerCompleteProgress(it->mProgressControl, + SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); + Log2Func(("Firing transfer removed event during public reset: transfer=%p\n", + (void *)(ClipboardTransfer *)it->mTransfer)); + i_fireTransferEvent(it->mTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); } return S_OK; @@ -911,6 +784,101 @@ HRESULT ClipboardTransferManager::reset() #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Resets the internally tracked transfer list. + */ +void ClipboardTransferManager::i_reset() +{ + LogFunc(("Resetting transfer manager\n")); + ComPtr ptrEventSource; + std::vector DetachedTransfers; + { + AutoWriteLock alock(this COMMA_LOCKVAL_SRC_POS); + ptrEventSource = mData.mEventSource; + DetachedTransfers.swap(mData.mTransfers); + Log2Func(("Detached %zu transfers during reset\n", DetachedTransfers.size())); + } + + RT_NOREF(ptrEventSource); + for (std::vector::const_iterator it = DetachedTransfers.begin(); + it != DetachedTransfers.end(); ++it) + { + it->mTransfer->i_setState(ClipboardTransferState_Removed, com::Utf8Str(), ClipboardError_None); + clipboardTransferManagerCompleteProgress(it->mProgressControl, + SHCLTRANSFERSTATUS_UNINITIALIZED, VERR_CANCELLED); + } + for (std::vector::const_iterator it = DetachedTransfers.begin(); + it != DetachedTransfers.end(); ++it) + { + Log2Func(("Firing transfer removed event during reset: transfer=%p\n", + (void *)(ClipboardTransfer *)it->mTransfer)); + i_fireTransferEvent(it->mTransfer, ClipboardTransferState_Removed, ClipboardTransferInteraction_None, + com::Utf8Str(), com::Utf8Str(), ClipboardError_None); + } +} + + +/** + * Fires a clipboard transfer event through the parent clipboard object when available. + * If there is no parent clipboard object, emits an anonymous event directly on + * the stored event source. + * + * @param aTransfer Transfer associated with the event. + * @param aState Transfer state. + * @param aInteraction Transfer interaction type. + * @param aPath Transfer-relative path associated with the event, if any. + * @param aMessage Optional event message. + * @param aError Clipboard transfer error code. + */ +void ClipboardTransferManager::i_fireTransferEvent(const ComObjPtr &aTransfer, + ClipboardTransferState_T aState, + ClipboardTransferInteraction_T aInteraction, + const com::Utf8Str &aPath, + const com::Utf8Str &aMessage, + ClipboardError_T aError) +{ + ComPtr ptrTransfer; + HRESULT const hrc = aTransfer.queryInterfaceTo(ptrTransfer.asOutParam()); + if (FAILED(hrc)) + { + AssertComRC(hrc); + return; + } + + Clipboard *pParent = NULL; + ComPtr ptrEventSource; + AutoCaller autoCaller; + { + AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); + pParent = mData.mParent; + if (pParent) + autoCaller.attach(pParent); + else + ptrEventSource = mData.mEventSource; + } + + if (pParent) + { + if (SUCCEEDED(autoCaller.hrc())) + pParent->i_fireClipboardTransferEvent(VBOX_SHCL_MAIN_CLIENT_NONE, ptrTransfer, aState, aInteraction, + aPath, aMessage, aError); + else + LogFunc(("Cannot fire clipboard transfer event through parent: hrc=%#x\n", autoCaller.hrc())); + return; + } + + if (ptrEventSource.isNotNull()) + { + /* + * No parent Clipboard is available to supply a live revision or session + * fan-out context. Keep this legacy event anonymous. + */ + ::FireClipboardTransferEvent(ptrEventSource, 0 /* anonymous revision */, VBOX_SHCL_MAIN_CLIENT_NONE, + ptrTransfer, aState, aInteraction, Bstr(aPath).raw(), aMessage, aError); + } +} + + /** * Handles a Shared Clipboard transfer status from the host service. * @@ -918,13 +886,15 @@ HRESULT ClipboardTransferManager::reset() * @param aServiceSessionId Service session that owns the transfer. * @param aTransferId Shared Clipboard transfer ID. * @param aGeneration Host-private transfer generation. - * @param enmShClSource Shared Clipboard status source. + * @param aTransfer Borrowed service transfer used to validate status metadata. + * @param enmShClSource Data source recorded by the backing transfer. * @param enmStatus Transfer lifecycle status. * @param vrcTransfer Transfer result code associated with the status. */ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceSessionId, SHCLTRANSFERID aTransferId, SHCLTRANSFERGEN aGeneration, + PSHCLTRANSFER aTransfer, SHCLSOURCE enmShClSource, SHCLTRANSFERSTATUS enmStatus, int vrcTransfer) @@ -932,16 +902,48 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS SHCLSESSIONID const idSession = aServiceSessionId; SHCLTRANSFERID const idTransfer = aTransferId; SHCLTRANSFERGEN const uGeneration = aGeneration; - if (!clipboardTransferManagerKeyIsValid(idSession, idTransfer, uGeneration)) + if (!ShClTransferKeyIsValid(idSession, idTransfer, uGeneration)) + return E_INVALIDARG; + if ( enmShClSource != SHCLSOURCE_LOCAL + && enmShClSource != SHCLSOURCE_REMOTE) + return E_INVALIDARG; + if (!ShClTransferStatusIsValid(enmStatus)) + return E_INVALIDARG; + if (!ShClTransferStatusResultIsValid(enmStatus, vrcTransfer)) + return E_INVALIDARG; + + SHCLTRANSFERDIR enmTransferDirection; + SHCLSOURCE enmTransferSource; + if (aTransfer) + { + if (ShClTransferGetSource(aTransfer) != enmShClSource) + return E_INVALIDARG; + enmTransferDirection = ShClTransferGetDir(aTransfer); + enmTransferSource = ShClTransferGetSource(aTransfer); + } + else + { + enmTransferDirection = enmShClSource == SHCLSOURCE_REMOTE + ? SHCLTRANSFERDIR_GUEST_TO_HOST : SHCLTRANSFERDIR_HOST_TO_GUEST; + enmTransferSource = enmShClSource; + } + if ( ( enmTransferDirection != SHCLTRANSFERDIR_GUEST_TO_HOST + && enmTransferDirection != SHCLTRANSFERDIR_HOST_TO_GUEST) + || ( enmTransferSource != SHCLSOURCE_LOCAL + && enmTransferSource != SHCLSOURCE_REMOTE) + || ( enmTransferSource == SHCLSOURCE_LOCAL + && enmTransferDirection != SHCLTRANSFERDIR_HOST_TO_GUEST) + || ( enmTransferSource == SHCLSOURCE_REMOTE + && enmTransferDirection != SHCLTRANSFERDIR_GUEST_TO_HOST)) return E_INVALIDARG; if (enmStatus == SHCLTRANSFERSTATUS_NONE) return S_OK; ClipboardTransferState_T const enmState = clipboardTransferManagerStatusToState(enmStatus); ClipboardError_T const enmError = clipboardTransferManagerStatusToError(enmStatus, vrcTransfer); - bool const fTerminal = clipboardTransferManagerStatusIsTerminal(enmStatus); + bool const fTerminal = ShClTransferStatusIsTerminal(enmStatus); - ComPtr ptrTransfer; + ComObjPtr ptrTransfer; ComPtr ptrProgress; ComPtr ptrProgressControl; bool fFireAdded = false; @@ -952,9 +954,7 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS size_t idxRecord = mData.mTransfers.size(); for (size_t i = 0; i < mData.mTransfers.size(); ++i) - if ( mData.mTransfers[i].mServiceSessionId == idSession - && mData.mTransfers[i].mTransferId == idTransfer - && mData.mTransfers[i].mGeneration == uGeneration) + if (mData.mTransfers[i].matches(idSession, idTransfer, uGeneration)) { idxRecord = i; break; @@ -968,13 +968,19 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS idSession, idTransfer, uGeneration, (uint32_t)enmStatus)); return S_OK; } + if ( enmStatus != SHCLTRANSFERSTATUS_REQUESTED + && enmStatus != SHCLTRANSFERSTATUS_INITIALIZED) + return E_INVALIDARG; ComObjPtr ptrNewProgress; HRESULT hrc = ptrNewProgress.createObject(); if (FAILED(hrc)) return hrc; - hrc = ptrNewProgress->init(FALSE /* aCancelable */, 1 /* aOperationCount */, - com::Utf8Str("Shared Clipboard transfer")); + ComPtr ptrProgressInitiator = mData.mEventSource; + if (ptrProgressInitiator.isNull()) + return E_FAIL; + hrc = ptrNewProgress->init(ptrProgressInitiator, + com::Utf8Str("Shared Clipboard transfer"), FALSE /* aCancelable */); if (FAILED(hrc)) return hrc; @@ -992,19 +998,15 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS if (FAILED(hrc)) return hrc; - ClipboardTransferDirection_T const enmDirection = enmShClSource == SHCLSOURCE_REMOTE + ClipboardTransferDirection_T const enmDirection = enmTransferDirection == SHCLTRANSFERDIR_GUEST_TO_HOST ? ClipboardTransferDirection_ToHost : ClipboardTransferDirection_ToGuest; - ClipboardSource_T const enmSource = enmShClSource == SHCLSOURCE_REMOTE + ClipboardSource_T const enmSource = enmTransferSource == SHCLSOURCE_REMOTE ? ClipboardSource_Guest : ClipboardSource_Host; ComPtr ptrItem; - hrc = ptrNewTransfer->init(idTransfer, enmDirection, enmSource, ClipboardAction_Copy, ptrItem, ptrIProgress); - if (FAILED(hrc)) - return hrc; - - ComPtr ptrITransfer; - hrc = ptrNewTransfer.queryInterfaceTo(ptrITransfer.asOutParam()); + hrc = ptrNewTransfer->init(idTransfer, enmDirection, enmSource, ClipboardAction_Copy, ptrItem, ptrIProgress, + NULL /* aTransfer */, false /* fOwnTransfer */); if (FAILED(hrc)) return hrc; @@ -1012,18 +1014,31 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS Record.mServiceSessionId = idSession; Record.mTransferId = idTransfer; Record.mGeneration = uGeneration; + Record.mDirection = enmTransferDirection; + Record.mSource = enmTransferSource; Record.mStatus = enmStatus; Record.mState = ClipboardTransferState_Added; - Record.mTransfer = ptrITransfer; - Record.mfPublished = true; + Record.mTransfer = ptrNewTransfer; Record.mProgress = ptrIProgress; Record.mProgressControl = ptrIProgressControl; - mData.mTransfers.push_back(Record); + try + { + mData.mTransfers.push_back(Record); + } + catch (std::bad_alloc &) + { + return E_OUTOFMEMORY; + } idxRecord = mData.mTransfers.size() - 1; fFireAdded = true; } Data::TransferRecord &Record = mData.mTransfers[idxRecord]; + if ( Record.mDirection != enmTransferDirection + || Record.mSource != enmTransferSource + || !ShClTransferStatusTransitionIsValid(Record.mStatus, enmStatus)) + return E_INVALIDARG; + ptrTransfer = Record.mTransfer; ptrProgress = Record.mProgress; ptrProgressControl = Record.mProgressControl; @@ -1047,15 +1062,20 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS RT_NOREF(ptrProgress); - if (fTerminal) - clipboardTransferManagerCompleteProgress(ptrProgressControl, enmStatus, vrcTransfer); - if (fFireAdded) + { + ptrTransfer->i_setState(ClipboardTransferState_Added, com::Utf8Str(), ClipboardError_None); i_fireTransferEvent(ptrTransfer, ClipboardTransferState_Added, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), ClipboardError_None); + } if (fFireState) + { + ptrTransfer->i_setState(enmState, com::Utf8Str(), enmError); + if (fTerminal) + clipboardTransferManagerCompleteProgress(ptrProgressControl, enmStatus, vrcTransfer); i_fireTransferEvent(ptrTransfer, enmState, ClipboardTransferInteraction_None, com::Utf8Str(), com::Utf8Str(), enmError); + } return S_OK; } @@ -1069,7 +1089,7 @@ HRESULT ClipboardTransferManager::i_handleTransferStatus(SHCLSESSIONID aServiceS */ HRESULT ClipboardTransferManager::i_cancelTransferById(ULONG aTransferId) { - ComPtr ptrTransfer; + ComObjPtr ptrTransfer; { AutoReadLock alock(this COMMA_LOCKVAL_SRC_POS); for (std::vector::const_iterator it = mData.mTransfers.begin(); @@ -1085,6 +1105,9 @@ HRESULT ClipboardTransferManager::i_cancelTransferById(ULONG aTransferId) if (ptrTransfer.isNull()) return E_INVALIDARG; - return cancel(ptrTransfer); + ComPtr ptrITransfer; + HRESULT const hrc = ptrTransfer.queryInterfaceTo(ptrITransfer.asOutParam()); + AssertComRCReturn(hrc, hrc); + return cancel(ptrITransfer); } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ diff --git a/src/VBox/Main/src-client/CloudGateway.cpp b/src/VBox/Main/src-client/CloudGateway.cpp index 866eeb242ce8..cd58d8fbcb0b 100644 --- a/src/VBox/Main/src-client/CloudGateway.cpp +++ b/src/VBox/Main/src-client/CloudGateway.cpp @@ -1,4 +1,4 @@ -/* $Id: CloudGateway.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: CloudGateway.cpp 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ */ /** @file * Implementation of local and cloud gateway management. */ @@ -252,8 +252,17 @@ HRESULT generateKeys(GatewayInfo& gateway) RT_NOREF(gateway); return E_NOTIMPL; #else /* VBOX_WITH_LIBSSH */ + int iKeySize = 2048; ssh_key single_use_key; - int iRcSsh = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &single_use_key); + ssh_pki_ctx ctx = ssh_pki_ctx_new(); + if (ctx == NULL) + { + LogRel(("Failed to allocate a PKI context.\n")); + return E_FAIL; + } + int iRcSsh = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &iKeySize); + iRcSsh = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &single_use_key); + ssh_pki_ctx_free(ctx); if (iRcSsh != SSH_OK) { LogRel(("Failed to generate a key pair. iRcSsh = %d\n", iRcSsh)); diff --git a/src/VBox/Main/src-client/ConsoleImpl.cpp b/src/VBox/Main/src-client/ConsoleImpl.cpp index 43d2aba13dec..6106fe847ce6 100644 --- a/src/VBox/Main/src-client/ConsoleImpl.cpp +++ b/src/VBox/Main/src-client/ConsoleImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: ConsoleImpl.cpp 114560 2026-06-29 08:32:23Z andreas.loeffler@oracle.com $ */ +/* $Id: ConsoleImpl.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console COM Class implementation */ @@ -1657,11 +1657,6 @@ void Console::i_VRDPClientDisconnect(uint32_t u32ClientId, mConsoleVRDPServer->USBBackendDelete(u32ClientId); } - if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_CLIPBOARD) - { - mConsoleVRDPServer->ClipboardDelete(u32ClientId); - } - #ifdef VBOX_WITH_AUDIO_VRDE if (fu32Intercepted & VRDE_CLIENT_INTERCEPT_AUDIO) { @@ -1731,9 +1726,7 @@ void Console::i_VRDPInterceptClipboard(uint32_t u32ClientId) AutoCaller autoCaller(this); AssertComRCReturnVoid(autoCaller.hrc()); - AssertReturnVoid(mConsoleVRDPServer); - - mConsoleVRDPServer->ClipboardCreate(u32ClientId); + RT_NOREF(u32ClientId); LogFlowFuncLeave(); return; @@ -9272,10 +9265,13 @@ HRESULT Console::i_powerDown(IProgress *aProgress /*= NULL*/) # ifdef VBOX_WITH_SHARED_CLIPBOARD if (m_hHgcmSvcExtShCl) { - HGCMHostUnregisterServiceExtension(m_hHgcmSvcExtShCl); - m_hHgcmSvcExtShCl = NULL; + i_consoleVRDPServer()->ClipboardSetGuestShClAvailable(false); + int const vrcUnregister = HGCMHostUnregisterServiceExtension(m_hHgcmSvcExtShCl); + if (RT_SUCCESS(vrcUnregister)) + m_hHgcmSvcExtShCl = NULL; + else + LogRel(("Shared Clipboard: Unregistering the HGCM service extension failed with %Rrc\n", vrcUnregister)); } - GuestShCl::DestroyInstance(); #endif # ifdef VBOX_WITH_DRAG_AND_DROP @@ -9288,6 +9284,13 @@ HRESULT Console::i_powerDown(IProgress *aProgress /*= NULL*/) m_pVMMDev->hgcmShutdown(); +# ifdef VBOX_WITH_SHARED_CLIPBOARD + m_hHgcmSvcExtShCl = NULL; + /* Keep the extension target alive through HGCM shutdown. Service + * unloading may perform the final synchronous teardown callback. */ + GuestShCl::DestroyInstance(); +# endif + alock.acquire(); } @@ -11667,7 +11670,23 @@ void Console::i_powerUpThreadTask(VMPowerUpTask *pTask) if (pConsole->m_pVMMDev) { alock.release(); /* just to be on the safe side... */ +#ifdef VBOX_WITH_SHARED_CLIPBOARD + if (pConsole->m_hHgcmSvcExtShCl) + { + pConsole->i_consoleVRDPServer()->ClipboardSetGuestShClAvailable(false); + int const vrcUnregister = HGCMHostUnregisterServiceExtension(pConsole->m_hHgcmSvcExtShCl); + if (RT_SUCCESS(vrcUnregister)) + pConsole->m_hHgcmSvcExtShCl = NULL; + else + LogRel(("Shared Clipboard: Unregistering the HGCM service extension after VM creation failure" + " failed with %Rrc\n", vrcUnregister)); + } +#endif pConsole->m_pVMMDev->hgcmShutdown(true /*fUvmIsInvalid*/); +#ifdef VBOX_WITH_SHARED_CLIPBOARD + pConsole->m_hHgcmSvcExtShCl = NULL; + GuestShCl::DestroyInstance(); +#endif alock.acquire(); } pVMM->pfnVMR3ReleaseUVM(pConsole->mpUVM); @@ -12410,4 +12429,3 @@ const PDMDRVREG Console::DrvStatusReg = /* u32EndVersion */ PDM_DRVREG_VERSION }; - diff --git a/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp b/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp index 53a7657fc301..a276f1f177ea 100644 --- a/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp +++ b/src/VBox/Main/src-client/ConsoleImplConfigCommon.cpp @@ -1,4 +1,4 @@ -/* $Id: ConsoleImplConfigCommon.cpp 114532 2026-06-25 11:46:49Z andreas.loeffler@oracle.com $ */ +/* $Id: ConsoleImplConfigCommon.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console COM Class implementation - VM Configuration Bits. * @@ -50,6 +50,7 @@ #include "BusAssignmentManager.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD # include "GuestShClPrivate.h" +# include "ConsoleVRDPServer.h" #endif #ifdef VBOX_WITH_DRAG_AND_DROP # include "GuestImpl.h" @@ -4001,17 +4002,6 @@ int Console::i_configVmmDev(ComPtr pMachine, BusAssignmentManager *pBu { LogRel(("Shared Clipboard: Service loaded\n")); - /* Let the service avoid touching the host clipboard/X11 for headless frontends. - * This is legacy behavior and needs to be removed. See @bugref{4697}. */ - Bstr bstrSessionName; - hrc = pMachine->COMGETTER(SessionName)(bstrSessionName.asOutParam()); H(); - const bool fHeadless = bstrSessionName.compare(Bstr("headless").raw()) == 0; - VBOXHGCMSVCPARM parmHeadless; - HGCMSvcSetU32(&parmHeadless, fHeadless); - vrc = pVMMDev->hgcmHostCall("VBoxSharedClipboard", VBOX_SHCL_HOST_FN_SET_HEADLESS, 1, &parmHeadless); - AssertLogRelMsg(RT_SUCCESS(vrc), ("Shared Clipboard: Failed to set headless mode (%RTbool): vrc=%Rrc\n", - fHeadless, vrc)); - /* Set initial clipboard mode. */ AssertPtrReturn(i_getClipboard(), VERR_INVALID_POINTER); vrc = i_getClipboard()->i_changeMode(enmClipboardMode); @@ -4029,7 +4019,9 @@ int Console::i_configVmmDev(ComPtr pMachine, BusAssignmentManager *pBu vrc = HGCMHostRegisterServiceExtension(&m_hHgcmSvcExtShCl, "VBoxSharedClipboard", &GuestShCl::s_HgcmDispatcher, pGuestShCl); - if (RT_FAILURE(vrc)) + if (RT_SUCCESS(vrc)) + i_consoleVRDPServer()->ClipboardSetGuestShClAvailable(true); + else Log(("Cannot register VBoxSharedClipboard extension, vrc=%Rrc\n", vrc)); } else diff --git a/src/VBox/Main/src-client/ConsoleVRDPServer.cpp b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp index c315942a59ad..a25d76caef22 100644 --- a/src/VBox/Main/src-client/ConsoleVRDPServer.cpp +++ b/src/VBox/Main/src-client/ConsoleVRDPServer.cpp @@ -1,4 +1,4 @@ -/* $Id: ConsoleVRDPServer.cpp 114604 2026-07-03 12:17:31Z andreas.loeffler@oracle.com $ */ +/* $Id: ConsoleVRDPServer.cpp 115061 2026-08-17 17:42:59Z andreas.loeffler@oracle.com $ */ /** @file * VBox Console VRDP helper class. */ @@ -32,7 +32,7 @@ #include "ConsoleImpl.h" #include "DisplayImpl.h" #ifdef VBOX_WITH_SHARED_CLIPBOARD -# include "GuestShClPrivate.h" /* For (un-)registering the service extension. */ +# include "GuestShClPrivate.h" #endif #include "KeyboardImpl.h" #include "MouseImpl.h" @@ -1356,10 +1356,14 @@ ConsoleVRDPServer::ConsoleVRDPServer(Console *console) mConsole = console; int vrc = RTCritSectInit(&mCritSect); - AssertRC(vrc); - - mcClipboardRefs = 0; - mpfnClipboardCallback = NULL; + AssertFatalMsgRC(vrc, ("Initializing the VRDE server critical section failed with %Rrc\n", vrc)); + mfClipboardGuestShClAvailable = false; + mcClipboardGuestShClCalls = 0; + mhClipboardGuestShClCallsDone = NIL_RTSEMEVENTMULTI; + vrc = RTSemEventMultiCreate(&mhClipboardGuestShClCallsDone); + AssertFatalMsgRC(vrc, ("Creating the VRDE clipboard callback event failed with %Rrc\n", vrc)); + vrc = RTSemEventMultiSignal(mhClipboardGuestShClCallsDone); + AssertFatalMsgRC(vrc, ("Signalling the VRDE clipboard callback event failed with %Rrc\n", vrc)); #ifdef VBOX_WITH_USB mUSBBackends.pHead = NULL; mUSBBackends.pTail = NULL; @@ -1437,8 +1441,15 @@ ConsoleVRDPServer::ConsoleVRDPServer(Console *console) ConsoleVRDPServer::~ConsoleVRDPServer() { + ClipboardSetGuestShClAvailable(false); Stop(); + if (mhClipboardGuestShClCallsDone != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(mhClipboardGuestShClCallsDone); + mhClipboardGuestShClCallsDone = NIL_RTSEMEVENTMULTI; + } + if (mConsoleListener) { ComPtr es; @@ -3286,6 +3297,19 @@ void ConsoleVRDPServer::unlockConsoleVRDPServer(void) RTCritSectLeave(&mCritSect); } +/** + * Handles a clipboard request received from a remote-desktop client. + * + * @returns VBox status code. + * @param pvCallback ConsoleVRDPServer instance receiving the request. + * @param u32ClientId Remote client ID. + * @param u32Function VRDE_CLIPBOARD_FUNCTION_XXX request number. + * @param u32Format Clipboard format associated with the request. + * @param pvData Request data. Optional if @a cbData is zero. + * @param cbData Request data size in bytes. + * + * @thread VRDE callback thread. + */ DECLCALLBACK(int) ConsoleVRDPServer::ClipboardCallback(void *pvCallback, uint32_t u32ClientId, uint32_t u32Function, @@ -3293,181 +3317,166 @@ DECLCALLBACK(int) ConsoleVRDPServer::ClipboardCallback(void *pvCallback, const void *pvData, uint32_t cbData) { + RT_NOREF(u32Format); + LogFlowFunc(("pvCallback = %p, u32ClientId = %d, u32Function = %d, u32Format = 0x%08X, pvData = %p, cbData = %d\n", pvCallback, u32ClientId, u32Function, u32Format, pvData, cbData)); - int vrc = VINF_SUCCESS; + ConsoleVRDPServer *pServer = static_cast(pvCallback); + AssertPtrReturn(pServer, VERR_INVALID_POINTER); - ConsoleVRDPServer *pServer = static_cast (pvCallback); + RT_NOREF(u32ClientId, pvData, cbData); - RT_NOREF(u32ClientId); + int vrc = pServer->lockConsoleVRDPServer(); + AssertRCReturn(vrc, vrc); + bool const fCallGuestShCl = pServer->mfClipboardGuestShClAvailable; + if (fCallGuestShCl && pServer->mcClipboardGuestShClCalls++ == 0) + { + int const vrcReset = RTSemEventMultiReset(pServer->mhClipboardGuestShClCallsDone); + AssertFatalMsgRC(vrcReset, ("Resetting the VRDE clipboard callback event failed with %Rrc\n", vrcReset)); + } + pServer->unlockConsoleVRDPServer(); + + if (!fCallGuestShCl) + return VERR_NOT_AVAILABLE; switch (u32Function) { case VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE: { - if (pServer->mpfnClipboardCallback) +#ifdef VBOX_WITH_SHARED_CLIPBOARD + if (!ShClFormatsAreValid(u32Format)) + vrc = VERR_INVALID_PARAMETER; + else { - vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, - u32Format, - (void *)pvData, - cbData); + GuestShCl *pGuestShCl = GuestShCl::TryGetInst(); + vrc = pGuestShCl ? pGuestShCl->ReportRemoteFormatsToGuest(u32Format) : VERR_NOT_AVAILABLE; } +#else + vrc = VERR_NOT_SUPPORTED; +#endif } break; case VRDE_CLIPBOARD_FUNCTION_DATA_READ: { - if (pServer->mpfnClipboardCallback) +#ifdef VBOX_WITH_SHARED_CLIPBOARD + GuestShCl *pGuestShCl = GuestShCl::TryGetInst(); + if (!pGuestShCl) + vrc = VERR_NOT_AVAILABLE; + else { - vrc = pServer->mpfnClipboardCallback(VBOX_CLIPBOARD_EXT_FN_DATA_READ, - u32Format, - (void *)pvData, - cbData); + void *pvGuestData = NULL; + uint32_t cbGuestData = 0; + vrc = pGuestShCl->ReadDataFromGuest(u32Format, &pvGuestData, &cbGuestData); + /* GuestShCl mirrors a successful guest reply to VRDE before + * completing the read operation. */ + RTMemFree(pvGuestData); } +#else + vrc = VERR_NOT_SUPPORTED; +#endif } break; default: - { vrc = VERR_NOT_SUPPORTED; - } break; + break; } + int const vrcLock = pServer->lockConsoleVRDPServer(); + AssertFatalMsgRC(vrcLock, ("Locking the VRDE server after a clipboard callback failed with %Rrc\n", vrcLock)); + Assert(pServer->mcClipboardGuestShClCalls > 0); + if (pServer->mcClipboardGuestShClCalls > 0 && --pServer->mcClipboardGuestShClCalls == 0) + { + int const vrcSignal = RTSemEventMultiSignal(pServer->mhClipboardGuestShClCallsDone); + AssertFatalMsgRC(vrcSignal, ("Signalling the VRDE clipboard callback event failed with %Rrc\n", vrcSignal)); + } + pServer->unlockConsoleVRDPServer(); + return vrc; } /** - * Service extension callback called by GuestShCl::s_HgcmDispatcher(). + * Publishes or withdraws the GuestShCl singleton from direct VRDE callbacks. * - * @returns VBox status code. - * @retval VERR_NOT_SUPPORTED if the extension didn't handle the requested function. This will invoke the regular backend then. - * @param pvExtension Pointer to service extension. - * @param u32Function Callback HGCM message ID. - * @param pvParms Pointer to optional data provided for a particular message. Optional. - * @param cbParms Size (in bytes) of \a pvParms. + * Withdrawing it waits until every callback which previously acquired it has + * returned, so the caller may safely destroy GuestShCl afterwards. + * + * @param fAvailable Whether GuestShCl may be acquired. */ -/* static */ -DECLCALLBACK(int) ConsoleVRDPServer::ClipboardServiceExtension(void *pvExtension, - uint32_t u32Function, void *pvParms, uint32_t cbParms) +void ConsoleVRDPServer::ClipboardSetGuestShClAvailable(bool fAvailable) { - RT_NOREF(cbParms); - LogFlowFunc(("pvExtension = %p, u32Function = %d, pvParms = %p, cbParms = %d\n", - pvExtension, u32Function, pvParms, cbParms)); - - int vrc = VINF_SUCCESS; - - ConsoleVRDPServer *pServer = static_cast (pvExtension); - AssertPtrReturn(pServer, VERR_INVALID_POINTER); - - SHCLEXTPARMS *pParms = (SHCLEXTPARMS *)pvParms; - AssertPtrReturn(pParms, VERR_INVALID_POINTER); + int const vrc = lockConsoleVRDPServer(); + AssertFatalMsgRC(vrc, ("Locking the VRDE server for clipboard publication failed with %Rrc\n", vrc)); + mfClipboardGuestShClAvailable = fAvailable; + unlockConsoleVRDPServer(); - switch (u32Function) + if (!fAvailable) { - case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: - { - pServer->mpfnClipboardCallback = pParms->u.SetCallback.pfnCallback; - } break; - - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - { - /* The guest announces clipboard formats to the host. This must be delivered to all clients. */ - if (mpEntryPoints && pServer->mhServer) - { - mpEntryPoints->VRDEClipboard(pServer->mhServer, - VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE, - pParms->u.ReportFormats.uFormats, - NULL, - 0, - NULL); - } - } break; - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - { - /* The clipboard service expects that the pvData buffer will be filled - * with clipboard data. The server returns the data from the client that - * announced the requested format most recently. - */ - if (mpEntryPoints && pServer->mhServer) - { - mpEntryPoints->VRDEClipboard(pServer->mhServer, - VRDE_CLIPBOARD_FUNCTION_DATA_READ, - pParms->u.ReadWriteData.uFormat, - pParms->u.ReadWriteData.pvData, - pParms->u.ReadWriteData.cbData, - &pParms->u.ReadWriteData.cbActual); - } - } break; - - case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: - { - if (mpEntryPoints && pServer->mhServer) - { - mpEntryPoints->VRDEClipboard(pServer->mhServer, - VRDE_CLIPBOARD_FUNCTION_DATA_WRITE, - pParms->u.ReadWriteData.uFormat, - pParms->u.ReadWriteData.pvData, - pParms->u.ReadWriteData.cbData, - NULL); - } - /* - * VRDE only mirrors the data here. Return VERR_NOT_SUPPORTED so the - * regular Main path still signals the pending guest-read event exactly - * once; returning success would consume the reply and leave Main waiters - * blocked. - */ - vrc = VERR_NOT_SUPPORTED; - } break; - - default: - vrc = VERR_NOT_SUPPORTED; + int const vrcWait = RTSemEventMultiWait(mhClipboardGuestShClCallsDone, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(vrcWait, ("Waiting for direct VRDE clipboard callbacks failed with %Rrc\n", vrcWait)); } - - return vrc; } -void ConsoleVRDPServer::ClipboardCreate(uint32_t u32ClientId) +/** + * Reports guest clipboard formats to all connected remote-desktop clients. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if the active VRDE server has no clipboard interface. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ +int ConsoleVRDPServer::ClipboardReportGuestFormats(SHCLFORMATS fFormats) const { - RT_NOREF(u32ClientId); - - int vrc = lockConsoleVRDPServer(); - if (RT_SUCCESS(vrc)) + if (mpEntryPoints && mhServer && mpEntryPoints->VRDEClipboard) { - if (mcClipboardRefs == 0) - { -#ifdef VBOX_WITH_SHARED_CLIPBOARD - vrc = GuestShClInst()->RegisterServiceExtension(ClipboardServiceExtension, this /* pvExtension */); - AssertRC(vrc); -#endif /* VBOX_WITH_SHARED_CLIPBOARD */ - } - - mcClipboardRefs++; - unlockConsoleVRDPServer(); + mpEntryPoints->VRDEClipboard(mhServer, VRDE_CLIPBOARD_FUNCTION_FORMAT_ANNOUNCE, + fFormats, NULL, 0, NULL); + return VINF_SUCCESS; } + return VERR_NOT_SUPPORTED; } -void ConsoleVRDPServer::ClipboardDelete(uint32_t u32ClientId) +/** + * Reads data from the remote clipboard provider selected by the VRDE server. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if the active VRDE server has no clipboard interface. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. Optional if @a cbData is zero. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required byte count. + */ +int ConsoleVRDPServer::ClipboardReadRemoteData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, + uint32_t *pcbActual) const { - RT_NOREF(u32ClientId); - - int vrc = lockConsoleVRDPServer(); - if (RT_SUCCESS(vrc)) + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); + *pcbActual = 0; + if (mpEntryPoints && mhServer && mpEntryPoints->VRDEClipboard) { - Assert(mcClipboardRefs); - if (mcClipboardRefs > 0) - { - mcClipboardRefs--; - - if (mcClipboardRefs == 0) - { -#ifdef VBOX_WITH_SHARED_CLIPBOARD - GuestShClInst()->UnregisterServiceExtension(ClipboardServiceExtension); -#endif /* VBOX_WITH_SHARED_CLIPBOARD */ - } - } + mpEntryPoints->VRDEClipboard(mhServer, VRDE_CLIPBOARD_FUNCTION_DATA_READ, + uFormat, pvData, cbData, pcbActual); + return VINF_SUCCESS; + } + return VERR_NOT_SUPPORTED; +} - unlockConsoleVRDPServer(); +/** + * Sends guest clipboard data to the remote client which requested it. + * + * @returns VBox status code. + * @retval VERR_NOT_SUPPORTED if the active VRDE server has no clipboard interface. + * @param uFormat Clipboard format of the data. + * @param pvData Data buffer. Optional if @a cbData is zero. + * @param cbData Data size in bytes. + */ +int ConsoleVRDPServer::ClipboardWriteGuestData(SHCLFORMAT uFormat, const void *pvData, uint32_t cbData) const +{ + if (mpEntryPoints && mhServer && mpEntryPoints->VRDEClipboard) + { + mpEntryPoints->VRDEClipboard(mhServer, VRDE_CLIPBOARD_FUNCTION_DATA_WRITE, + uFormat, (void *)pvData, cbData, NULL); + return VINF_SUCCESS; } + return VERR_NOT_SUPPORTED; } /* That is called on INPUT thread of the VRDP server. diff --git a/src/VBox/Main/src-client/DisplayImpl.cpp b/src/VBox/Main/src-client/DisplayImpl.cpp index 95e3727a2a18..b0fab7019ab7 100644 --- a/src/VBox/Main/src-client/DisplayImpl.cpp +++ b/src/VBox/Main/src-client/DisplayImpl.cpp @@ -1,4 +1,4 @@ -/* $Id: DisplayImpl.cpp 114707 2026-07-14 13:40:18Z vitali.pelenjow@oracle.com $ */ +/* $Id: DisplayImpl.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox COM class implementation */ @@ -3576,8 +3576,10 @@ DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pIn struct VBVACMDHDR const RT_UNTRUSTED_VOLATILE_GUEST *pCmd, size_t cbCmd) { LogFlowFunc(("uScreenId %d pCmd %p cbCmd %d, @%d,%d %dx%d\n", uScreenId, pCmd, cbCmd, pCmd->x, pCmd->y, pCmd->w, pCmd->h)); - VBVACMDHDR hdrSaved; - RT_COPY_VOLATILE(hdrSaved, *pCmd); + AssertReturnVoid(cbCmd >= sizeof(VBVACMDHDR)); + + VBVACMDHDR hdr; + RT_COPY_VOLATILE(hdr, *pCmd); RT_UNTRUSTED_NONVOLATILE_COPY_FENCE(); PDRVMAINDISPLAY pDrv = PDMIDISPLAYCONNECTOR_2_MAINDISPLAY(pInterface); @@ -3593,7 +3595,7 @@ DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pIn if ( uScreenId == VBOX_VIDEO_PRIMARY_SCREEN && !pFBInfo->fDisabled) { - pDrv->pUpPort->pfnUpdateDisplayRect(pDrv->pUpPort, hdrSaved.x, hdrSaved.y, hdrSaved.w, hdrSaved.h); + pDrv->pUpPort->pfnUpdateDisplayRect(pDrv->pUpPort, hdr.x, hdr.y, hdr.w, hdr.h); } else if ( !pFBInfo->pSourceBitmap.isNull() && !pFBInfo->fDisabled @@ -3615,12 +3617,12 @@ DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pIn &bitmapFormat); if (SUCCEEDED(hrc)) { - uint32_t width = hdrSaved.w; - uint32_t height = hdrSaved.h; + uint32_t width = hdr.w; + uint32_t height = hdr.h; const uint8_t *pu8Src = pFBInfo->pu8FramebufferVRAM; - int32_t xSrc = hdrSaved.x - pFBInfo->xOrigin; - int32_t ySrc = hdrSaved.y - pFBInfo->yOrigin; + int32_t xSrc = hdr.x - pFBInfo->xOrigin; + int32_t ySrc = hdr.y - pFBInfo->yOrigin; uint32_t u32SrcWidth = pFBInfo->w; uint32_t u32SrcHeight = pFBInfo->h; uint32_t u32SrcLineSize = pFBInfo->u32LineSize; @@ -3648,18 +3650,11 @@ DECLCALLBACK(void) Display::i_displayVBVAUpdateProcess(PPDMIDISPLAYCONNECTOR pIn } } - /* - * Here is your classic 'temporary' solution. - */ - /** @todo New SendUpdate entry which can get a separate cmd header or coords. */ - VBVACMDHDR *pHdrUnconst = (VBVACMDHDR *)pCmd; - - pHdrUnconst->x -= (int16_t)pFBInfo->xOrigin; - pHdrUnconst->y -= (int16_t)pFBInfo->yOrigin; - - pThis->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, pHdrUnconst, (uint32_t)cbCmd); + /* Always send just a bitmap update to the VRDP server. */ + hdr.x -= (int16_t)pFBInfo->xOrigin; + hdr.y -= (int16_t)pFBInfo->yOrigin; - *pHdrUnconst = hdrSaved; + pThis->mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, &hdr, sizeof(hdr)); } DECLCALLBACK(void) Display::i_displayVBVAUpdateEnd(PPDMIDISPLAYCONNECTOR pInterface, unsigned uScreenId, int32_t x, int32_t y, diff --git a/src/VBox/Main/src-client/DisplayImplLegacy.cpp b/src/VBox/Main/src-client/DisplayImplLegacy.cpp index 402f7a6369e9..d7a8aaead193 100644 --- a/src/VBox/Main/src-client/DisplayImplLegacy.cpp +++ b/src/VBox/Main/src-client/DisplayImplLegacy.cpp @@ -1,4 +1,4 @@ -/* $Id: DisplayImplLegacy.cpp 114707 2026-07-14 13:40:18Z vitali.pelenjow@oracle.com $ */ +/* $Id: DisplayImplLegacy.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VirtualBox IDisplay implementation, helpers for legacy GAs. * @@ -390,7 +390,8 @@ static bool i_vbvaFetchBytes(uint8_t RT_UNTRUSTED_VOLATILE_GUEST const *pu8RingB static bool i_vbvaPartialRead(uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, - uint8_t RT_UNTRUSTED_VOLATILE_GUEST const *pu8RingBuffer, uint32_t off32Data) + uint8_t RT_UNTRUSTED_VOLATILE_GUEST const *pu8RingBuffer, uint32_t off32Data, + uint32_t RT_UNTRUSTED_VOLATILE_GUEST *poff32Data) { uint8_t *pu8New; @@ -426,7 +427,8 @@ static bool i_vbvaPartialRead(uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, } /* Fetch data from the ring buffer. */ - if (!i_vbvaFetchBytes(pu8RingBuffer, off32Data, pu8New + *pcb, cbRecord - *pcb)) + uint32_t const cbFetch = cbRecord - *pcb; + if (!i_vbvaFetchBytes(pu8RingBuffer, off32Data, pu8New + *pcb, cbFetch)) { RTMemFree(pu8New); @@ -436,6 +438,9 @@ static bool i_vbvaPartialRead(uint8_t **ppu8, uint32_t *pcb, uint32_t cbRecord, return false; } + /* Advance data offset. */ + *poff32Data = (off32Data + cbFetch) % VMMDEV_VBVA_RING_BUFFER_SIZE; + *ppu8 = pu8New; *pcb = cbRecord; @@ -490,7 +495,8 @@ static bool i_vbvaFetchCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR **ppHdr, uint32_t if (cbRecord > pVideoAccel->cbVbvaPartial) { /* New data has been added to the record. */ - if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, &pVbvaMemory->au8RingBuffer[0], off32Data)) + if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, + &pVbvaMemory->au8RingBuffer[0], off32Data, &pVbvaMemory->off32Data)) { return false; } @@ -524,7 +530,8 @@ static bool i_vbvaFetchCmd(VIDEOACCEL *pVideoAccel, VBVACMDHDR **ppHdr, uint32_t if (cbRecord >= VMMDEV_VBVA_RING_BUFFER_SIZE - VMMDEV_VBVA_RING_BUFFER_THRESHOLD) { /* Partial read must be started. */ - if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, &pVbvaMemory->au8RingBuffer[0], off32Data)) + if (!i_vbvaPartialRead(&pVideoAccel->pu8VbvaPartial, &pVideoAccel->cbVbvaPartial, cbRecord, + &pVbvaMemory->au8RingBuffer[0], off32Data, &pVbvaMemory->off32Data)) { return false; } @@ -671,19 +678,18 @@ int Display::i_videoAccelFlush(PPDMIDISPLAYPORT pUpPort) cbCmd, phdr->x, phdr->y, phdr->w, phdr->h)); #endif /* DEBUG_sunlover */ - VBVACMDHDR hdrSaved = *phdr; - - int x = phdr->x; - int y = phdr->y; - int w = phdr->w; - int h = phdr->h; + VBVACMDHDR hdr = *phdr; + int x = hdr.x; + int y = hdr.y; + int w = hdr.w; + int h = hdr.h; uScreenId = mapCoordsToScreen(maFramebuffers, mcMonitors, &x, &y, &w, &h); - phdr->x = (int16_t)x; - phdr->y = (int16_t)y; - phdr->w = (uint16_t)w; - phdr->h = (uint16_t)h; + hdr.x = (int16_t)x; + hdr.y = (int16_t)y; + hdr.w = (uint16_t)w; + hdr.h = (uint16_t)h; /* Handle the command. * @@ -698,12 +704,10 @@ int Display::i_videoAccelFlush(PPDMIDISPLAYPORT pUpPort) */ /* Accumulate the update. */ - vbvaRgnDirtyRect(&rgn, uScreenId, phdr); - - /* Forward the command to VRDP server. */ - mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, phdr, cbCmd); + vbvaRgnDirtyRect(&rgn, uScreenId, &hdr); - *phdr = hdrSaved; + /* Forward the command to VRDP server as a bitmap update. */ + mParent->i_consoleVRDPServer()->SendUpdate(uScreenId, &hdr, sizeof(hdr)); } i_vbvaReleaseCmd(pVideoAccel, phdr, cbCmd); diff --git a/src/VBox/Main/src-client/GuestShClBackend.cpp b/src/VBox/Main/src-client/GuestShClBackend.cpp new file mode 100644 index 000000000000..5ea32bb0e7bc --- /dev/null +++ b/src/VBox/Main/src-client/GuestShClBackend.cpp @@ -0,0 +1,152 @@ +/* $Id: GuestShClBackend.cpp 115052 2026-08-17 15:35:37Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Native backend dispatcher implementation. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include "GuestShClBackend.h" +#include "GuestShClBackendPrivate.h" + +#include + +#include +#include + + +ShClBackend::ShClBackend(void) + : m_pOps(ShClBackendGetOps()) + , m_pCtx(NULL) +{ + AssertPtr(m_pOps); +} + + +ShClBackend::~ShClBackend(void) +{ + Assert(m_pCtx == NULL); +} + + +int ShClBackend::init(void) +{ + AssertPtrReturn(m_pOps, VERR_INVALID_STATE); + AssertReturn(m_pOps->pfnInit != NULL, VERR_INVALID_STATE); + return m_pOps->pfnInit(); +} + + +void ShClBackend::destroy(void) +{ + AssertPtrReturnVoid(m_pOps); + AssertReturnVoid(m_pOps->pfnDestroy != NULL); + m_pOps->pfnDestroy(); +} + + +void ShClBackend::setCallbacks(PSHCLCALLBACKS pCallbacks) +{ + AssertPtrReturnVoid(m_pOps); + if (m_pOps->pfnSetCallbacks) + m_pOps->pfnSetCallbacks(pCallbacks); +} + + +int ShClBackend::connect(GuestShClConn *pConn) +{ + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(m_pOps, VERR_INVALID_STATE); + AssertReturn(m_pOps->pfnConnect != NULL, VERR_INVALID_STATE); + AssertReturn(m_pCtx == NULL, VERR_RESOURCE_BUSY); + + PSHCLCONTEXT pCtx = NULL; + int vrc = m_pOps->pfnConnect(pConn, &pCtx); + if (RT_SUCCESS(vrc)) + { + if (pCtx) + m_pCtx = pCtx; + else + vrc = VERR_INTERNAL_ERROR; + } + return vrc; +} + + +int ShClBackend::disconnect(void) +{ + AssertPtrReturn(m_pOps, VERR_INVALID_STATE); + AssertReturn(m_pOps->pfnDisconnect != NULL, VERR_INVALID_STATE); + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + + PSHCLCONTEXT const pCtx = m_pCtx; + int const vrc = m_pOps->pfnDisconnect(pCtx); + m_pCtx = NULL; + return vrc; +} + + +int ShClBackend::reportFormats(SHCLFORMATS fFormats) +{ + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnReportFormats(m_pCtx, fFormats); +} + + +int ShClBackend::readData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnReadData(m_pCtx, uFormat, pvData, cbData, pcbActual); +} + + +int ShClBackend::writeData(SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnWriteData(m_pCtx, uFormat, pvData, cbData); +} + + +int ShClBackend::sync(void) +{ + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnSync(m_pCtx); +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +void ShClBackend::transferGetCallbacks(PSHCLTRANSFERCALLBACKS pCallbacks) +{ + AssertPtrReturnVoid(pCallbacks); + AssertPtrReturnVoid(m_pCtx); + m_pOps->pfnTransferGetCallbacks(m_pCtx, pCallbacks); +} + + +int ShClBackend::transferHandleStatusReply(PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) +{ + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + AssertPtrReturn(m_pCtx, VERR_INVALID_STATE); + return m_pOps->pfnTransferHandleStatusReply(m_pCtx, pTransfer, enmSource, enmStatus, rcStatus); +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ diff --git a/src/VBox/Main/src-client/GuestShClBackendPrivate.h b/src/VBox/Main/src-client/GuestShClBackendPrivate.h new file mode 100644 index 000000000000..f485e5169f42 --- /dev/null +++ b/src/VBox/Main/src-client/GuestShClBackendPrivate.h @@ -0,0 +1,73 @@ +/* $Id: GuestShClBackendPrivate.h 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Internal native backend operation table. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef MAIN_INCLUDED_SRC_src_client_GuestShClBackendPrivate_h +#define MAIN_INCLUDED_SRC_src_client_GuestShClBackendPrivate_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include "GuestShClBackend.h" + +/** Native platform backend operations used by ShClBackend. */ +typedef struct SHCLBACKENDOPS +{ + /** Initializes the platform backend. */ + int (*pfnInit)(void); + /** Destroys the platform backend. */ + void (*pfnDestroy)(void); + /** Replaces the native callback table for test purposes. Optional. */ + void (*pfnSetCallbacks)(PSHCLCALLBACKS pCallbacks); + /** Connects a Main service connection and returns its platform context. */ + int (*pfnConnect)(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx); + /** Disconnects and destroys a platform connection context. */ + int (*pfnDisconnect)(PSHCLCONTEXT pCtx); + /** Reports guest formats to the native clipboard. */ + int (*pfnReportFormats)(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats); + /** Reads native clipboard data for the guest. */ + int (*pfnReadData)(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual); + /** Writes guest clipboard data to the native clipboard. */ + int (*pfnWriteData)(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData); + /** Synchronizes native clipboard state with the guest. */ + int (*pfnSync)(PSHCLCONTEXT pCtx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Returns callbacks for a new transfer. */ + void (*pfnTransferGetCallbacks)(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks); + /** Handles a transfer status reply. */ + int (*pfnTransferHandleStatusReply)(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus); +#endif +} SHCLBACKENDOPS; + +/** + * Returns the native Shared Clipboard backend operations for this host. + * + * @returns Immutable native backend operation table. Never NULL. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void); + +#endif /* !MAIN_INCLUDED_SRC_src_client_GuestShClBackendPrivate_h */ diff --git a/src/VBox/Main/src-client/GuestShClConn.cpp b/src/VBox/Main/src-client/GuestShClConn.cpp new file mode 100644 index 000000000000..e6522a170ec4 --- /dev/null +++ b/src/VBox/Main/src-client/GuestShClConn.cpp @@ -0,0 +1,584 @@ +/* $Id: GuestShClConn.cpp 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Service connection management implementation. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include + +#include "GuestShClConn.h" +#include "GuestShClBackend.h" +#ifdef VBOX_COM_INPROC +# include "GuestShClPrivate.h" +#endif + +#include + +#include +#include +#include + + +GuestShClConn::GuestShClConn(GuestShCl *pOwner) + : m_pOwner(pOwner) + , m_hCallsDone(NIL_RTSEMEVENTMULTI) + , m_enmState(State_Disconnected) + , m_fBackendInitialized(false) + , m_cCalls(0) +{ + RT_ZERO(m_Transport); + + int vrc = RTCritSectInit(&m_CritSect); + if (RT_FAILURE(vrc)) + throw vrc; + + vrc = RTSemEventMultiCreate(&m_hCallsDone); + if (RT_FAILURE(vrc)) + { + RTCritSectDelete(&m_CritSect); + throw vrc; + } + vrc = RTSemEventMultiSignal(m_hCallsDone); + if (RT_FAILURE(vrc)) + { + RTSemEventMultiDestroy(m_hCallsDone); + m_hCallsDone = NIL_RTSEMEVENTMULTI; + RTCritSectDelete(&m_CritSect); + throw vrc; + } +} + + +GuestShClConn::~GuestShClConn(void) +{ + Assert(m_enmState == State_Disconnected); + Assert(m_cCalls == 0); + Assert(!m_fBackendInitialized); + + if (m_hCallsDone != NIL_RTSEMEVENTMULTI) + { + RTSemEventMultiDestroy(m_hCallsDone); + m_hCallsDone = NIL_RTSEMEVENTMULTI; + } + if (RTCritSectIsInitialized(&m_CritSect)) + RTCritSectDelete(&m_CritSect); +} + + +int GuestShClConn::i_callBegin(PSHCLTRANSPORT pTransport) +{ + AssertPtrReturn(pTransport, VERR_INVALID_POINTER); + RT_ZERO(*pTransport); + + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + + if ( (m_enmState == State_Connecting || m_enmState == State_Connected) + && ShClTransportIsValid(&m_Transport)) + { + *pTransport = m_Transport; + if (m_cCalls++ == 0) + { + int const vrcReset = RTSemEventMultiReset(m_hCallsDone); + AssertFatalMsgRC(vrcReset, ("Resetting the Shared Clipboard connection call-drain event failed with %Rrc\n", + vrcReset)); + } + vrc = VINF_SUCCESS; + } + else + vrc = VERR_SHCLPB_NO_DATA; + + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock failed with %Rrc\n", vrcLeave)); + return vrc; +} + + +void GuestShClConn::i_callEnd(void) +{ + int const vrc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrc, ("Taking the Shared Clipboard connection lock while ending a call failed with %Rrc\n", vrc)); + + Assert(m_cCalls > 0); + if (m_cCalls > 0 && --m_cCalls == 0) + { + int const vrcSignal = RTSemEventMultiSignal(m_hCallsDone); + AssertFatalMsgRC(vrcSignal, ("Signalling the Shared Clipboard connection call-drain event failed with %Rrc\n", + vrcSignal)); + } + + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock while ending a call failed with %Rrc\n", + vrcLeave)); +} + + +void GuestShClConn::i_waitForCalls(void) +{ + for (;;) + { + int vrc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrc, ("Taking the Shared Clipboard connection lock while draining calls failed with %Rrc\n", vrc)); + bool const fDone = m_cCalls == 0; + vrc = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrc, ("Releasing the Shared Clipboard connection lock while draining calls failed with %Rrc\n", vrc)); + if (fDone) + return; + + vrc = RTSemEventMultiWait(m_hCallsDone, RT_INDEFINITE_WAIT); + AssertFatalMsgRC(vrc, ("Draining Shared Clipboard connection calls failed with %Rrc\n", vrc)); + } +} + + +int GuestShClConn::initBackend(void) +{ + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + bool const fInitialized = m_fBackendInitialized; + RTCritSectLeave(&m_CritSect); + if (fInitialized) + return VINF_SUCCESS; + + vrc = m_Backend.init(); + if (RT_SUCCESS(vrc)) + { + int const vrcLock = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrcLock, ("Taking the Shared Clipboard connection lock after backend initialization failed with %Rrc\n", + vrcLock)); + m_fBackendInitialized = true; + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, + ("Releasing the Shared Clipboard connection lock after backend initialization failed " + "with %Rrc\n", vrcLeave)); + } + return vrc; +} + + +int GuestShClConn::destroyBackend(void) +{ + SHCLTRANSPORT Transport; + bool fConnected; + bool fInitialized; + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + Transport = m_Transport; + fConnected = m_enmState == State_Connected; + fInitialized = m_fBackendInitialized; + RTCritSectLeave(&m_CritSect); + + int vrcDisconnect = VINF_SUCCESS; + if (fConnected) + vrcDisconnect = disconnect(&Transport); + + if (fInitialized) + { + m_Backend.destroy(); + vrc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrc, ("Taking the Shared Clipboard connection lock after backend destruction failed with %Rrc\n", vrc)); + m_fBackendInitialized = false; + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock after backend destruction failed with %Rrc\n", + vrcLeave)); + } + return RT_FAILURE(vrcDisconnect) ? vrcDisconnect : vrc; +} + + +void GuestShClConn::setBackendCallbacks(PSHCLCALLBACKS pCallbacks) +{ + m_Backend.setCallbacks(pCallbacks); +} + + +int GuestShClConn::connect(PCSHCLTRANSPORT pTransport) +{ + AssertReturn(ShClTransportIsValid(pTransport), VERR_INVALID_HANDLE); + + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + if (m_enmState != State_Disconnected) + { + RTCritSectLeave(&m_CritSect); + return VERR_RESOURCE_BUSY; + } + m_Transport = *pTransport; + m_enmState = State_Connecting; + RTCritSectLeave(&m_CritSect); + + vrc = m_Backend.connect(this); + + int const vrcLock = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrcLock, ("Taking the Shared Clipboard connection lock after backend connection failed with %Rrc\n", + vrcLock)); + if (RT_SUCCESS(vrc)) + m_enmState = State_Connected; + else + { + m_enmState = State_Disconnected; + RT_ZERO(m_Transport); + } + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock after backend connection failed with %Rrc\n", + vrcLeave)); + return vrc; +} + + +int GuestShClConn::disconnect(PCSHCLTRANSPORT pTransport) +{ + AssertReturn(ShClTransportIsValid(pTransport), VERR_INVALID_HANDLE); + + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + if ( m_enmState != State_Connected + || !ShClTransportIsEqual(&m_Transport, pTransport)) + { + RTCritSectLeave(&m_CritSect); + return VERR_INVALID_HANDLE; + } + m_enmState = State_Closing; + RTCritSectLeave(&m_CritSect); + + i_waitForCalls(); + vrc = m_Backend.disconnect(); + + int const vrcLock = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrcLock, ("Taking the Shared Clipboard connection lock after backend disconnection failed with %Rrc\n", + vrcLock)); + RT_ZERO(m_Transport); + m_enmState = State_Disconnected; + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock after backend disconnection failed with %Rrc\n", + vrcLeave)); + return vrc; +} + + +bool GuestShClConn::matches(PCSHCLTRANSPORT pTransport) const +{ + bool fMatches = false; + int const vrc = RTCritSectEnter(&m_CritSect); + if (RT_SUCCESS(vrc)) + { + fMatches = (m_enmState == State_Connecting || m_enmState == State_Connected) + && ShClTransportIsEqual(&m_Transport, pTransport); + RTCritSectLeave(&m_CritSect); + } + return fMatches; +} + + +bool GuestShClConn::isConnected(void) const +{ + bool fConnected = false; + int const vrc = RTCritSectEnter(&m_CritSect); + if (RT_SUCCESS(vrc)) + { + fConnected = m_enmState == State_Connected; + RTCritSectLeave(&m_CritSect); + } + return fConnected; +} + + +#define SHCL_CONN_SVC_CALL_BEGIN(a_Transport) \ + SHCLTRANSPORT a_Transport; \ + int vrc = i_callBegin(&(a_Transport)); \ + if (RT_FAILURE(vrc)) \ + return vrc + +#define SHCL_CONN_BACKEND_CALL_BEGIN(a_Transport) \ + SHCLTRANSPORT a_Transport; \ + int vrc = i_callBegin(&(a_Transport)); \ + if (RT_FAILURE(vrc)) \ + return vrc + + +int GuestShClConn::reportFormatsToGuest(SHCLFORMATS fFormats, SHCLFORMATS *pfReported) +{ + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnReportFormatsToGuest(Transport.hClient, fFormats, pfReported); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::reportLocalFormats(SHCLFORMATS fFormats) +{ +#ifdef VBOX_COM_INPROC + if (m_pOwner) + return m_pOwner->ReportFormatsToGuest(this, fFormats, SHCLSOURCE_LOCAL); +#endif + return reportFormatsToGuest(fFormats); +} + + +int GuestShClConn::readDataFromGuestAsync(SHCLFORMATS fFormats, PSHCLEVENT *ppEvent) +{ + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnReadDataFromGuestAsync(Transport.hClient, fFormats, ppEvent); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::readDataFromGuest(SHCLFORMAT uFormat, void **ppvData, uint32_t *pcbData) +{ + AssertPtrReturn(ppvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbData, VERR_INVALID_POINTER); + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnReadDataFromGuest(Transport.hClient, uFormat, ppvData, pcbData); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::guestDataBegin(PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken) +{ + AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(phToken, VERR_INVALID_POINTER); + *phToken = NULL; + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnGuestDataBegin(Transport.hClient, pCmdCtx, uFormat, phToken); + if ( RT_FAILURE(vrc) + || !*phToken) + i_callEnd(); + return vrc; +} + + +int GuestShClConn::guestDataComplete(SHCLGUESTDATATOKEN hToken, void const *pvData, uint32_t cbData) +{ + AssertPtrReturn(hToken, VERR_INVALID_HANDLE); + + SHCLTRANSPORT Transport; + int vrc = RTCritSectEnter(&m_CritSect); + if (RT_FAILURE(vrc)) + return vrc; + if (m_cCalls > 0 && ShClTransportIsValid(&m_Transport)) + Transport = m_Transport; + else + { + RT_ZERO(Transport); + vrc = VERR_INVALID_STATE; + } + RTCritSectLeave(&m_CritSect); + + if (RT_SUCCESS(vrc)) + { + vrc = Transport.pOps->pfnGuestDataComplete(Transport.hClient, hToken, pvData, cbData); + i_callEnd(); + } + return vrc; +} + + +void GuestShClConn::guestDataCancel(SHCLGUESTDATATOKEN hToken) +{ + AssertPtrReturnVoid(hToken); + + SHCLTRANSPORT Transport; + int const vrc = RTCritSectEnter(&m_CritSect); + if (RT_SUCCESS(vrc)) + { + if (m_cCalls > 0 && ShClTransportIsValid(&m_Transport)) + Transport = m_Transport; + else + RT_ZERO(Transport); + RTCritSectLeave(&m_CritSect); + + if (ShClTransportIsValid(&Transport)) + { + Transport.pOps->pfnGuestDataCancel(Transport.hClient, hToken); + i_callEnd(); + } + } +} + + +int GuestShClConn::syncBackend(void) +{ + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + vrc = m_Backend.sync(); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::reportFormatsToBackend(SHCLFORMATS fFormats) +{ + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + SHCLFORMATS fFiltered = VBOX_SHCL_FMT_NONE; + vrc = Transport.pOps->pfnFilterFormats(Transport.hClient, false /* fHostToGuest */, fFormats, &fFiltered); + if (RT_SUCCESS(vrc)) + fFormats = fFiltered; +#endif + if (RT_SUCCESS(vrc)) + vrc = m_Backend.reportFormats(fFormats); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::readDataFromBackend(SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) +{ + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + vrc = m_Backend.readData(uFormat, pvData, cbData, pcbActual); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::writeDataToBackend(SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + vrc = m_Backend.writeData(uFormat, pvData, cbData); + i_callEnd(); + return vrc; +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +int GuestShClConn::transferGetCallbacks(PSHCLTRANSFERCALLBACKS pCallbacks) +{ + AssertPtrReturn(pCallbacks, VERR_INVALID_POINTER); + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + m_Backend.transferGetCallbacks(pCallbacks); + i_callEnd(); + return VINF_SUCCESS; +} + + +int GuestShClConn::transferHandleStatusReply(PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) +{ + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + SHCL_CONN_BACKEND_CALL_BEGIN(Transport); + vrc = m_Backend.transferHandleStatusReply(pTransfer, enmSource, enmStatus, rcStatus); + i_callEnd(); + return vrc; +} + + +PSHCLTRANSFER GuestShClConn::transferGetByIdRetained(SHCLTRANSFERID idTransfer) +{ + SHCLTRANSPORT Transport; + int const vrc = i_callBegin(&Transport); + if (RT_FAILURE(vrc)) + return NULL; + PSHCLTRANSFER const pTransfer = Transport.pOps->pfnTransferGetByIdRetained(Transport.hClient, idTransfer); + i_callEnd(); + return pTransfer; +} + + +PSHCLTRANSFER GuestShClConn::transferGetByKeyRetained(SHCLSESSIONID idSession, SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration) +{ + SHCLTRANSPORT Transport; + int const vrc = i_callBegin(&Transport); + if (RT_FAILURE(vrc)) + return NULL; + PSHCLTRANSFER const pTransfer = Transport.pOps->pfnTransferGetByKeyRetained(Transport.hClient, + idSession, idTransfer, uGeneration); + i_callEnd(); + return pTransfer; +} + + +int GuestShClConn::transferCreate(SHCLTRANSFERDIR enmDir, SHCLSOURCE enmSource, + PSHCLTRANSFERCALLBACKS pCallbacks, SHCLTRANSFERID idTransfer, + PSHCLTRANSFER *ppTransfer) +{ + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnTransferCreate(Transport.hClient, enmDir, enmSource, pCallbacks, + idTransfer, ppTransfer); + i_callEnd(); + return vrc; +} + + +int GuestShClConn::transferInit(PSHCLTRANSFER pTransfer) +{ + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnTransferInit(Transport.hClient, pTransfer); + i_callEnd(); + return vrc; +} + + +void GuestShClConn::transferDestroyById(SHCLTRANSFERID idTransfer) +{ + SHCLTRANSPORT Transport; + int const vrc = i_callBegin(&Transport); + if (RT_SUCCESS(vrc)) + { + Transport.pOps->pfnTransferDestroyById(Transport.hClient, idTransfer); + i_callEnd(); + } +} + + +void GuestShClConn::transferDestroyAll(void) +{ + SHCLTRANSPORT Transport; + int const vrc = RTCritSectEnter(&m_CritSect); + AssertFatalMsgRC(vrc, ("Taking the Shared Clipboard connection lock while destroying transfers failed with %Rrc\n", vrc)); + Transport = m_Transport; + int const vrcLeave = RTCritSectLeave(&m_CritSect); + AssertFatalMsgRC(vrcLeave, ("Releasing the Shared Clipboard connection lock while destroying transfers failed with %Rrc\n", + vrcLeave)); + if (ShClTransportIsValid(&Transport)) + Transport.pOps->pfnTransferDestroyAll(Transport.hClient); +} + + +int GuestShClConn::transferProviderInitGuest(PSHCLTXPROVIDER pProvider) +{ + AssertPtrReturn(pProvider, VERR_INVALID_POINTER); + SHCL_CONN_SVC_CALL_BEGIN(Transport); + vrc = Transport.pOps->pfnTransferProviderInitGuest(Transport.hClient, pProvider); + i_callEnd(); + return vrc; +} + + +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +#undef SHCL_CONN_BACKEND_CALL_BEGIN +#undef SHCL_CONN_SVC_CALL_BEGIN diff --git a/src/VBox/Main/src-client/GuestShClPrivate.cpp b/src/VBox/Main/src-client/GuestShClPrivate.cpp index e868c2fe8866..682a74b4ef3f 100644 --- a/src/VBox/Main/src-client/GuestShClPrivate.cpp +++ b/src/VBox/Main/src-client/GuestShClPrivate.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClPrivate.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClPrivate.cpp 115054 2026-08-17 16:27:08Z andreas.loeffler@oracle.com $ */ /** @file * Private Shared Clipboard code. */ @@ -32,6 +32,7 @@ # include "ClipboardImpl.h" # include "ConsoleImpl.h" # include "GuestShClPrivate.h" +# include "GuestShClConn.h" # include "ProgressImpl.h" # include @@ -59,33 +60,38 @@ GuestShCl* GuestShCl::s_pInstance = NULL; - - GuestShCl::GuestShCl(Console *pConsole) : m_pConsole(pConsole) - , m_pfnExtCallback(NULL) - , m_pClient(NULL) - , m_fGuestReadsBlocked(false) - , m_cGuestReads(0) - , m_hGuestReadsDone(NIL_RTSEMEVENTMULTI) + , m_pConn(NULL) + , m_fRemoteDataReadActive(false) + , m_fRemoteFormatsPending(false) + , m_fPendingRemoteFormats(VBOX_SHCL_FMT_NONE) , m_uHostDataSeq(0) , m_uGuestDataSeq(0) { LogFlowFuncEnter(); - RT_ZERO(m_SvcExtVRDP); - int vrc = RTCritSectInit(&m_CritSect); if (RT_FAILURE(vrc)) throw vrc; - vrc = RTSemEventMultiCreate(&m_hGuestReadsDone); + vrc = RTCritSectInit(&m_RemoteFormatsCritSect); if (RT_FAILURE(vrc)) { RTCritSectDelete(&m_CritSect); throw vrc; } - RTSemEventMultiSignal(m_hGuestReadsDone); + + try + { + m_pConn = new GuestShClConn(this); + } + catch (...) + { + RTCritSectDelete(&m_RemoteFormatsCritSect); + RTCritSectDelete(&m_CritSect); + throw; + } } GuestShCl::~GuestShCl(void) @@ -100,28 +106,22 @@ void GuestShCl::uninit(void) { LogFlowFuncEnter(); - if (m_hGuestReadsDone != NIL_RTSEMEVENTMULTI) + if (m_pConn) { - int vrc = lock(); - if (RT_SUCCESS(vrc)) - { - m_fGuestReadsBlocked = true; - unlock(); - } - i_waitForGuestReads(); - RTSemEventMultiDestroy(m_hGuestReadsDone); - m_hGuestReadsDone = NIL_RTSEMEVENTMULTI; + int const vrc = m_pConn->destroyBackend(); + AssertRC(vrc); + delete m_pConn; + m_pConn = NULL; } + if (RTCritSectIsInitialized(&m_RemoteFormatsCritSect)) + RTCritSectDelete(&m_RemoteFormatsCritSect); if (RTCritSectIsInitialized(&m_CritSect)) RTCritSectDelete(&m_CritSect); - RT_ZERO(m_SvcExtVRDP); - - m_pfnExtCallback = NULL; - m_pClient = NULL; - m_fGuestReadsBlocked = false; - m_cGuestReads = 0; + m_fRemoteDataReadActive = false; + m_fRemoteFormatsPending = false; + m_fPendingRemoteFormats = VBOX_SHCL_FMT_NONE; m_uHostDataSeq = 0; m_uGuestDataSeq = 0; } @@ -298,143 +298,6 @@ bool GuestShCl::i_isGuestDataSeqCurrent(uint64_t uSeq) } -/** - * Starts a guest data read that will use the active service client outside m_CritSect. - * - * @returns VBox status code. - * @param ppClient Where to return the active client. - */ -int GuestShCl::i_beginGuestRead(PSHCLCLIENT *ppClient) -{ - AssertPtrReturn(ppClient, VERR_INVALID_POINTER); - *ppClient = NULL; - - int vrc = lock(); - if (RT_FAILURE(vrc)) - return vrc; - - if ( m_pClient - && !m_fGuestReadsBlocked) - { - *ppClient = m_pClient; - if (m_cGuestReads++ == 0) - RTSemEventMultiReset(m_hGuestReadsDone); - vrc = VINF_SUCCESS; - } - else - vrc = VERR_SHCLPB_NO_DATA; - - unlock(); - return vrc; -} - - -/** Ends a guest data read started by i_beginGuestRead(). */ -void GuestShCl::i_endGuestRead(void) -{ - int const vrc = lock(); - if (RT_SUCCESS(vrc)) - { - Assert(m_cGuestReads > 0); - if (m_cGuestReads > 0 && --m_cGuestReads == 0) - RTSemEventMultiSignal(m_hGuestReadsDone); - unlock(); - } -} - - -/** Waits until all guest data reads using the active client have completed. */ -void GuestShCl::i_waitForGuestReads(void) -{ - for (;;) - { - int vrc = lock(); - if (RT_FAILURE(vrc)) - return; - bool const fDone = m_cGuestReads == 0; - unlock(); - if (fDone) - return; - vrc = RTSemEventMultiWait(m_hGuestReadsDone, RT_INDEFINITE_WAIT); - AssertRC(vrc); - if (RT_FAILURE(vrc)) - return; - } -} - - -/** - * Registers a Shared Clipboard service extension. - * - * @returns VBox status code. - * @param pfnExtension Service extension to register. - * @param pvExtension User-supplied data pointer. Optional. - */ -int GuestShCl::RegisterServiceExtension(PFNHGCMSVCEXT pfnExtension, void *pvExtension) -{ - AssertPtrReturn(pfnExtension, VERR_INVALID_POINTER); - /* pvExtension is optional. */ - - lock(); - - LogFlowFunc(("m_pfnExtCallback=%p\n", this->m_pfnExtCallback)); - - PSHCLSVCEXT pExt = &this->m_SvcExtVRDP; /* Currently we only have one extension only. */ - - Assert(pExt->pfnExt == NULL); - - pExt->pfnExt = pfnExtension; - pExt->pvExt = pvExtension; - pExt->pfnExtCallback = this->m_pfnExtCallback; /* Assign callback function. Optional and can be NULL. */ - - if (pExt->pfnExtCallback) - { - /* Make sure to also give the extension the ability to use the callback. */ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - parms.u.SetCallback.pfnCallback = pExt->pfnExtCallback; - - /* ignore rc, callback is optional */ pExt->pfnExt(pExt->pvExt, - VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - } - - unlock(); - - return VINF_SUCCESS; -} - -/** - * Unregisters a Shared Clipboard service extension. - * - * @returns VBox status code. - * @param pfnExtension Service extension to unregister. - */ -int GuestShCl::UnregisterServiceExtension(PFNHGCMSVCEXT pfnExtension) -{ - AssertPtrReturn(pfnExtension, VERR_INVALID_POINTER); - - lock(); - - PSHCLSVCEXT pExt = &this->m_SvcExtVRDP; /* Currently we only have one extension only. */ - - AssertReturnStmt(pExt->pfnExt == pfnExtension, unlock(), VERR_INVALID_PARAMETER); - AssertPtr(pExt->pfnExt); - - /* Unregister the callback (setting to NULL). */ - SHCLEXTPARMS parms; - RT_ZERO(parms); - - /* ignore rc, callback is optional */ pExt->pfnExt(pExt->pvExt, - VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK, &parms, sizeof(parms)); - - RT_BZERO(pExt, sizeof(SHCLSVCEXT)); - - unlock(); - - return VINF_SUCCESS; -} - /** * Sends a (blocking) message to the host side of the host service. * @@ -469,17 +332,7 @@ int GuestShCl::ReadDataFromGuest(SHCLFORMAT uFormat, void **ppvData, uint32_t *p *ppvData = NULL; *pcbData = 0; - PSHCLCLIENT pClient = NULL; - int vrc = i_beginGuestRead(&pClient); - if (RT_FAILURE(vrc)) - return vrc; - - /* Do not hold m_CritSect while waiting for the guest reply: the reply callback - * validates the active client under the same lock before signalling the event. */ - vrc = ShClSvcReadDataFromGuest(pClient, uFormat, ppvData, pcbData); - - i_endGuestRead(); - return vrc; + return m_pConn->readDataFromGuest(uFormat, ppvData, pcbData); } /** @@ -498,22 +351,7 @@ int GuestShCl::ReadDataFromHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbDat AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); *pcbActual = 0; - SHCLCLIENTCMDCTX cmdCtx; - RT_ZERO(cmdCtx); - - int vrc = lock(); - if (RT_FAILURE(vrc)) - return vrc; - - PSHCLCLIENT pClient = m_pClient; - if ( pClient - && pClient->pBackend) - vrc = ShClBackendReadData(pClient->pBackend, pClient, &cmdCtx, uFormat, pvData, cbData, pcbActual); - else - vrc = VERR_SHCLPB_NO_DATA; - - unlock(); - return vrc; + return m_pConn->readDataFromBackend(uFormat, pvData, cbData, pcbActual); } /** @@ -529,21 +367,10 @@ int GuestShCl::ReportFormatsToHost(SHCLFORMATS fFormats) return vrc; ++m_uGuestDataSeq; - - PSHCLCLIENT pClient = m_pClient; - if ( pClient - && pClient->pBackend) - { -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - fFormats = shClSvcHandleFormats(false /* fHostToGuest */, pClient, fFormats); -#endif - vrc = ShClBackendReportFormats(pClient->pBackend, pClient, fFormats); - } - else - vrc = VINF_SUCCESS; - unlock(); - return vrc; + + vrc = m_pConn->reportFormatsToBackend(fFormats); + return vrc == VERR_SHCLPB_NO_DATA ? VINF_SUCCESS : vrc; } /** @@ -560,22 +387,8 @@ int GuestShCl::WriteDataToHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbData if (cbData) AssertPtrReturn(pvData, VERR_INVALID_POINTER); - SHCLCLIENTCMDCTX cmdCtx; - RT_ZERO(cmdCtx); - - int vrc = lock(); - if (RT_FAILURE(vrc)) - return vrc; - - PSHCLCLIENT pClient = m_pClient; - if ( pClient - && pClient->pBackend) - vrc = ShClBackendWriteData(pClient->pBackend, pClient, &cmdCtx, uFormat, pvData, cbData); - else - vrc = VINF_SUCCESS; - - unlock(); - return vrc; + int const vrc = m_pConn->writeDataToBackend(uFormat, pvData, cbData); + return vrc == VERR_SHCLPB_NO_DATA ? VINF_SUCCESS : vrc; } /** @@ -586,25 +399,73 @@ int GuestShCl::WriteDataToHost(SHCLFORMAT uFormat, void *pvData, uint32_t cbData */ int GuestShCl::ReportFormatsToGuest(SHCLFORMATS fFormats) { - int vrc = lock(); + int const vrc = m_pConn->reportFormatsToGuest(fFormats); + if (vrc == VERR_SHCLPB_NO_DATA || vrc == VINF_NO_CHANGE) + return VINF_SUCCESS; + if (RT_SUCCESS(vrc)) + i_incHostDataSeq(); + return vrc; +} + +/** + * Reports remote clipboard formats to the active guest clipboard client. + * + * @returns VBox status code. + * @param fFormats Formats reported by the remote clipboard peer. + */ +int GuestShCl::ReportRemoteFormatsToGuest(SHCLFORMATS fFormats) +{ + AssertReturn(ShClFormatsAreValid(fFormats), VERR_INVALID_PARAMETER); + + int vrc = RTCritSectEnter(&m_RemoteFormatsCritSect); + AssertRCReturn(vrc, vrc); + + vrc = lock(); if (RT_FAILURE(vrc)) + { + RTCritSectLeave(&m_RemoteFormatsCritSect); return vrc; + } + if (m_fRemoteDataReadActive) + { + m_fRemoteFormatsPending = true; + m_fPendingRemoteFormats = fFormats; + unlock(); + RTCritSectLeave(&m_RemoteFormatsCritSect); + return VINF_SUCCESS; + } + unlock(); - i_incHostDataSeqLocked(); + vrc = i_reportRemoteFormatsToGuestNow(fFormats); + int const vrcLeave = RTCritSectLeave(&m_RemoteFormatsCritSect); + AssertRC(vrcLeave); + return vrc; +} - PSHCLCLIENT pClient = m_pClient; - if ( pClient - && pClient->pBackend) +/** + * Reports remote clipboard formats immediately. + * + * @returns VBox status code. + * @param fFormats Remote formats, VBOX_SHCL_FMT_XXX. + * + * @note The caller must serialize remote reports and handle deferral while + * a remote-data read is active. + */ +int GuestShCl::i_reportRemoteFormatsToGuestNow(SHCLFORMATS fFormats) +{ + AssertReturn(ShClFormatsAreValid(fFormats), VERR_INVALID_PARAMETER); + + int const vrc = m_pConn->reportFormatsToGuest(fFormats, &fFormats); + if (vrc == VERR_SHCLPB_NO_DATA || vrc == VINF_NO_CHANGE) + return VINF_SUCCESS; + if (RT_SUCCESS(vrc)) { -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); -#endif - vrc = ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); + i_incHostDataSeq(); + Clipboard *pClipboard = m_pConsole->i_getClipboard(); + if (pClipboard) + pClipboard->i_reportFormats(VBOX_SHCL_MAIN_CLIENT_NONE, fFormats, ClipboardSource_Remote, + true /* fForceNotify */); } - else - vrc = VINF_SUCCESS; - - unlock(); return vrc; } @@ -613,24 +474,23 @@ int GuestShCl::ReportFormatsToGuest(SHCLFORMATS fFormats) * successful reports to the console clipboard event source. * * @returns VBox status code. - * @param pClient Clipboard client to report to. + * @param pConn Connection to report through. * @param fFormats Formats to report to the guest. * @param enmSource Source of the format report. */ -int GuestShCl::ReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, SHCLSOURCE enmSource) +int GuestShCl::ReportFormatsToGuest(GuestShClConn *pConn, SHCLFORMATS fFormats, SHCLSOURCE enmSource) { - AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertReturn(pConn == m_pConn, VERR_INVALID_HANDLE); ClipboardSource_T enmClipboardSource = ClipboardSource_Custom; switch (enmSource) { case SHCLSOURCE_LOCAL: - i_incHostDataSeq(); enmClipboardSource = ClipboardSource_Host; break; case SHCLSOURCE_REMOTE: - i_incGuestDataSeq(); enmClipboardSource = ClipboardSource_Guest; break; @@ -638,17 +498,16 @@ int GuestShCl::ReportFormatsToGuest(PSHCLCLIENT pClient, SHCLFORMATS fFormats, S AssertFailedReturn(VERR_INVALID_PARAMETER); } -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); -#endif - - int vrc; - if (pClient->pBackend) - vrc = ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); - else - vrc = VINF_SUCCESS; + int const vrc = pConn->reportFormatsToGuest(fFormats, &fFormats); + if (vrc == VINF_NO_CHANGE) + return VINF_SUCCESS; if (RT_SUCCESS(vrc)) { + if (enmSource == SHCLSOURCE_LOCAL) + i_incHostDataSeq(); + else + i_incGuestDataSeq(); + AssertPtr(m_pConsole->i_getClipboard()); if (m_pConsole->i_getClipboard()) m_pConsole->i_getClipboard()->i_reportFormats(VBOX_SHCL_MAIN_CLIENT_NONE, @@ -717,74 +576,65 @@ DECLCALLBACK(int) GuestShCl::s_HgcmDispatcher(void *pvExtension, uint32_t u32Fun GuestShCl *pThis = reinterpret_cast(pvExtension); AssertPtrReturn(pThis, VERR_INVALID_POINTER); - int vrc = pThis->i_validateSvcExtParms(u32Function, pvParms, cbParms); + int vrc = pThis->i_svcExtParmsValidate(u32Function, pvParms, cbParms); if (RT_FAILURE(vrc)) { LogFlowFuncLeaveRC(vrc); return vrc; } - PSHCLEXTPARMS pParms = (PSHCLEXTPARMS)pvParms; /* pParms might be NULL for unknown messages. */ vrc = VERR_NOT_SUPPORTED; switch (u32Function) { - case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: - vrc = pThis->i_handleSvcExtSetCallback(pParms); - break; - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - vrc = pThis->i_handleSvcExtReportFormatsToHost(pParms, pvParms, cbParms); - break; - - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - vrc = pThis->i_handleSvcExtReportFormatsToGuest(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtReportFormatsToHostCallback((PSHCLEXTPARMS)pvParms); break; case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - vrc = pThis->i_handleSvcExtDataRead(pParms, pvParms, cbParms); - break; - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE: - vrc = pThis->i_handleSvcExtDataReadVrde(pParms); + vrc = pThis->i_svcExtDataReadCallback((PSHCLEXTPARMS)pvParms); break; case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: - vrc = pThis->i_handleSvcExtDataWrite(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtDataWriteCallback((PSHCLEXTPARMS)pvParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: - vrc = pThis->i_handleSvcExtBackendInit(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendInitCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: - vrc = pThis->i_handleSvcExtBackendDestroy(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendDestroyCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: - vrc = pThis->i_handleSvcExtBackendConnect(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendConnectCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: - vrc = pThis->i_handleSvcExtBackendDisconnect(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendDisconnectCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: - vrc = pThis->i_handleSvcExtBackendSync(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtBackendSyncCallback((PSHCLEXTPARMS)pvParms, pvParms, cbParms); break; case VBOX_CLIPBOARD_EXT_FN_ERROR: - vrc = pThis->i_handleSvcExtError(pParms); + vrc = pThis->i_svcExtErrorCallback((PSHCLEXTPARMS)pvParms); break; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: + vrc = pThis->i_svcExtTransferGetCallbacksCallback((PSHCLEXTPARMS)pvParms); + break; + case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: - vrc = pThis->i_handleSvcExtFileTransfer(pParms, pvParms, cbParms); + vrc = pThis->i_svcExtFileTransferCallback((PSHCLEXTPARMS)pvParms); break; #endif default: - vrc = pThis->i_forwardToSvcExt(u32Function, pvParms, cbParms); + vrc = VERR_NOT_SUPPORTED; break; } diff --git a/src/VBox/Main/src-client/GuestShClSvcExt.cpp b/src/VBox/Main/src-client/GuestShClSvcExt.cpp index a6f4ab150a03..e69127fc2b68 100644 --- a/src/VBox/Main/src-client/GuestShClSvcExt.cpp +++ b/src/VBox/Main/src-client/GuestShClSvcExt.cpp @@ -1,4 +1,4 @@ -/* $Id: GuestShClSvcExt.cpp 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ */ +/* $Id: GuestShClSvcExt.cpp 115055 2026-08-17 16:40:05Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard service extension handling for Main. */ @@ -29,8 +29,11 @@ #include "LoggingNew.h" #include "ConsoleImpl.h" +#include "ConsoleVRDPServer.h" #include "ClipboardImpl.h" #include "GuestShClPrivate.h" +#include "GuestShClConn.h" +#include "Global.h" #include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS @@ -47,34 +50,6 @@ static size_t const s_cchShClSvcExtStringMax = _64K; -/** - * Checks whether a Shared Clipboard format mask contains only known format bits. - * - * @returns true if \a fFormats only contains VBOX_SHCL_FMT_XXX bits, false otherwise. - * @param fFormats Format mask to validate. - * @param fAllowNone Whether VBOX_SHCL_FMT_NONE is accepted. - */ -static bool shClSvcExtIsValidFormats(SHCLFORMATS fFormats, bool fAllowNone) -{ - if (fFormats == VBOX_SHCL_FMT_NONE) - return fAllowNone; - return (fFormats & ~VBOX_SHCL_FMT_VALID_MASK) == 0; -} - -/** - * Checks whether a value names exactly one Shared Clipboard format. - * - * @returns true if \a uFormat is a single valid VBOX_SHCL_FMT_XXX bit, false otherwise. - * @param uFormat Format value to validate. - */ -static bool shClSvcExtIsValidFormat(SHCLFORMAT uFormat) -{ - return uFormat != VBOX_SHCL_FMT_NONE - && (uFormat & ~VBOX_SHCL_FMT_VALID_MASK) == 0 - && (uFormat & (uFormat - 1)) == 0; -} - - /** * Validates a single Shared Clipboard format from service-extension parameters. * @@ -86,53 +61,13 @@ static bool shClSvcExtIsValidFormat(SHCLFORMAT uFormat) */ static int shClSvcExtValidateFormat(SHCLFORMAT uFormat, uint32_t u32Function) { - if (shClSvcExtIsValidFormat(uFormat)) + if (ShClFormatIsValid(uFormat)) return VINF_SUCCESS; - LogRelMax2(16, ("Shared Clipboard: Rejecting service-extension function %RU32 with invalid format %#x\n", + LogRelMax(16, ("Shared Clipboard: Rejecting service-extension function %RU32 with invalid format %#x\n", u32Function, uFormat)); return VERR_INVALID_PARAMETER; } -/** - * Checks whether a Shared Clipboard source value is valid for Main callbacks. - * - * @returns true if \a enmSource is a valid non-invalid SHCLSOURCE value, false otherwise. - * @param enmSource Source value to validate. - */ -static bool shClSvcExtIsValidSource(SHCLSOURCE enmSource) -{ - return enmSource == SHCLSOURCE_LOCAL - || enmSource == SHCLSOURCE_REMOTE; -} - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -/** - * Checks whether a Shared Clipboard transfer status value is valid for a transfer status reply. - * - * @returns true if \a uStatus is a known SHCLTRANSFERSTATUS value accepted from the guest, - * false otherwise. - * @param uStatus Transfer status value to validate. - */ -static bool shClSvcExtIsValidTransferStatus(SHCLTRANSFERSTATUS uStatus) -{ - switch (uStatus) - { - case SHCLTRANSFERSTATUS_REQUESTED: - case SHCLTRANSFERSTATUS_INITIALIZED: - case SHCLTRANSFERSTATUS_UNINITIALIZED: - case SHCLTRANSFERSTATUS_STARTED: - case SHCLTRANSFERSTATUS_COMPLETED: - case SHCLTRANSFERSTATUS_CANCELED: - case SHCLTRANSFERSTATUS_KILLED: - case SHCLTRANSFERSTATUS_ERROR: - return true; - - default: - return false; - } -} -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ - /** * Validates a string coming from the HGCM dispatcher. * @@ -178,7 +113,7 @@ static int shClSvcExtValidateUtf8Z(const char *pcszString, bool fAllowNull, bool * @param pvData Data buffer pointer. Can be NULL only when \a cbData is zero. * @param cbData Data buffer size in bytes. */ -static int shClSvcExtValidateDataBuffer(void *pvData, uint32_t cbData) +static int shClSvcExtValidateDataBuffer(void const *pvData, uint32_t cbData) { AssertReturn(cbData <= VBOX_SHCL_MAX_CHUNK_SIZE, VERR_INVALID_PARAMETER); if (cbData) @@ -187,43 +122,24 @@ static int shClSvcExtValidateDataBuffer(void *pvData, uint32_t cbData) } -/** - * Forwards a Shared Clipboard service extension request to the chained VRDP extension. - * - * @returns VBox status code returned by the chained extension, or VERR_NOT_SUPPORTED if no - * chained extension is registered. - * @param u32Function Service extension function to forward. - * @param pvParms Raw service extension parameters to forward. - * @param cbParms Size, in bytes, of \a pvParms. - */ -int GuestShCl::i_forwardToSvcExt(uint32_t u32Function, void *pvParms, uint32_t cbParms) -{ - PSHCLSVCEXT const pSvcExtVRDP = &m_SvcExtVRDP; /* Currently we have one extension only. */ - if (pSvcExtVRDP->pfnExt) - return pSvcExtVRDP->pfnExt(pSvcExtVRDP->pvExt, u32Function, pvParms, cbParms); - return VERR_NOT_SUPPORTED; -} - /** * Validates Shared Clipboard service extension parameters before dispatching a request. * * @returns VBox status code. * @retval VERR_INVALID_POINTER if a required pointer is NULL or not a valid host pointer. - * @retval VERR_INVALID_PARAMETER if a parameter ist invalid. + * @retval VERR_INVALID_PARAMETER if a parameter is invalid. + * @retval VERR_RESOURCE_BUSY if a backend connection is requested while a client is active. * @param u32Function Service extension function being dispatched. * @param pvParms Raw service extension parameters to validate. Optional for unknown * function IDs. * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtParmsValidate(uint32_t u32Function, void *pvParms, uint32_t cbParms) { switch (u32Function) { - case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: case VBOX_CLIPBOARD_EXT_FN_DATA_READ: - case VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE: case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: case VBOX_CLIPBOARD_EXT_FN_ERROR: case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: @@ -232,6 +148,7 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: #endif AssertReturn(RT_VALID_PTR(pvParms), VERR_INVALID_POINTER); @@ -242,93 +159,51 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 return VINF_SUCCESS; } - PSHCLCLIENT pActiveClient = NULL; - int vrc = lock(); - if (RT_SUCCESS(vrc)) - { - pActiveClient = m_pClient; - unlock(); - } - else - return vrc; - PSHCLEXTPARMS const pParms = (PSHCLEXTPARMS)pvParms; + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); +#define SHCL_VALIDATE_ACTIVE(a_Transport) \ + do { AssertReturn(m_pConn->matches(&(a_Transport)), VERR_INVALID_HANDLE); } while (0) + int vrc; switch (u32Function) { - case VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK: - return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: - AssertReturn(shClSvcExtIsValidFormats(pParms->u.ReportFormats.uFormats, true /* fAllowNone */), + SHCL_VALIDATE_ACTIVE(Transport); + AssertReturn(ShClFormatsAreValid(pParms->u.ReportFormats.uFormats), VERR_INVALID_PARAMETER); - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReportFormats.pClient == pActiveClient, VERR_INVALID_PARAMETER); - AssertPtrReturn(pParms->u.ReportFormats.pClient->pBackend, VERR_INVALID_POINTER); AssertReturn(pParms->u.ReportFormats.enmSource == SHCLSOURCE_INVALID, VERR_INVALID_PARAMETER); return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST: - AssertReturn(shClSvcExtIsValidFormats(pParms->u.ReportFormats.uFormats, true /* fAllowNone */), - VERR_INVALID_PARAMETER); - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReportFormats.pClient == pActiveClient, VERR_INVALID_PARAMETER); - AssertPtrReturn(pParms->u.ReportFormats.pClient->pBackend, VERR_INVALID_POINTER); - AssertReturn(shClSvcExtIsValidSource(pParms->u.ReportFormats.enmSource), VERR_INVALID_PARAMETER); - return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_DATA_READ: + SHCL_VALIDATE_ACTIVE(Transport); vrc = shClSvcExtValidateFormat(pParms->u.ReadWriteData.uFormat, u32Function); if (RT_FAILURE(vrc)) return vrc; vrc = shClSvcExtValidateDataBuffer(pParms->u.ReadWriteData.pvData, pParms->u.ReadWriteData.cbData); - if (RT_FAILURE(vrc)) - return vrc; - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReadWriteData.pClient == pActiveClient, VERR_INVALID_PARAMETER); - AssertPtrReturn(pParms->u.ReadWriteData.pClient->pBackend, VERR_INVALID_POINTER); - return VINF_SUCCESS; - - case VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE: - vrc = shClSvcExtValidateFormat(pParms->u.ReadWriteData.uFormat, u32Function); - if (RT_FAILURE(vrc)) - return vrc; - vrc = shClSvcExtValidateDataBuffer(pParms->u.ReadWriteData.pvData, pParms->u.ReadWriteData.cbData); - if (RT_FAILURE(vrc)) - return vrc; - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReadWriteData.pClient == pActiveClient, VERR_INVALID_PARAMETER); - return VINF_SUCCESS; + return vrc; case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: + SHCL_VALIDATE_ACTIVE(Transport); vrc = shClSvcExtValidateFormat(pParms->u.ReadWriteData.uFormat, u32Function); if (RT_FAILURE(vrc)) return vrc; vrc = shClSvcExtValidateDataBuffer(pParms->u.ReadWriteData.pvData, pParms->u.ReadWriteData.cbData); if (RT_FAILURE(vrc)) return vrc; - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.ReadWriteData.pClient == pActiveClient, VERR_INVALID_PARAMETER); - AssertPtrReturn(pParms->u.ReadWriteData.pClient->pBackend, VERR_INVALID_POINTER); AssertPtrReturn(pParms->u.ReadWriteData.pCmdCtx, VERR_INVALID_POINTER); return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: - AssertReturn(pActiveClient == NULL, VERR_INVALID_PARAMETER); - return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: - AssertReturn(pActiveClient == NULL, VERR_INVALID_PARAMETER); + AssertReturn(!m_pConn->isConnected(), VERR_RESOURCE_BUSY); + AssertReturn(ShClTransportIsValid(&Transport), VERR_INVALID_HANDLE); return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - return VINF_SUCCESS; - case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); + SHCL_VALIDATE_ACTIVE(Transport); return VINF_SUCCESS; case VBOX_CLIPBOARD_EXT_FN_ERROR: @@ -342,31 +217,38 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 return VINF_SUCCESS; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: + SHCL_VALIDATE_ACTIVE(Transport); + AssertPtrReturn(pParms->u.TransferCallbacks.pCallbacks, VERR_INVALID_POINTER); + return VINF_SUCCESS; + case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: { - AssertPtrReturn(pActiveClient, VERR_INVALID_POINTER); - AssertReturn(pParms->u.FileTransferData.pClient == pActiveClient, VERR_INVALID_PARAMETER); - PSHCLCLIENT const pClient = pParms->u.FileTransferData.pClient; - AssertPtrReturn(pClient->pBackend, VERR_INVALID_POINTER); + SHCL_VALIDATE_ACTIVE(Transport); PSHCLTRANSFER const pTransfer = pParms->u.FileTransferData.pTransfer; AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); AssertPtrReturn(pParms->u.FileTransferData.pReply, VERR_INVALID_POINTER); - AssertReturn(pParms->u.FileTransferData.enmShClSource == SHCLSOURCE_REMOTE, VERR_INVALID_PARAMETER); - AssertReturn(ShClTransferCtxGetTransferByKey(&pClient->Transfers.Ctx, - ShClTransferGetSessionId(pTransfer), - ShClTransferGetID(pTransfer), - ShClTransferGetGeneration(pTransfer)) == pTransfer, - VERR_INVALID_CONTEXT); + AssertReturn(ShClSourceIsValid(pParms->u.FileTransferData.enmShClSource), VERR_INVALID_PARAMETER); + PSHCLTRANSFER const pRegisteredTransfer + = m_pConn->transferGetByKeyRetained(ShClTransferGetSessionId(pTransfer), + ShClTransferGetID(pTransfer), + ShClTransferGetGeneration(pTransfer)); + if (pRegisteredTransfer != pTransfer) + { + if (pRegisteredTransfer) + ShClTransferRelease(pRegisteredTransfer); + return VERR_INVALID_CONTEXT; + } + ShClTransferRelease(pRegisteredTransfer); PSHCLREPLY const pReply = pParms->u.FileTransferData.pReply; AssertReturn(pReply->uType == VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS, VERR_INVALID_PARAMETER); AssertReturn(pReply->pvPayload == NULL, VERR_INVALID_PARAMETER); AssertReturn(pReply->cbPayload == 0, VERR_INVALID_PARAMETER); - AssertReturn(shClSvcExtIsValidTransferStatus(pReply->u.TransferStatus.uStatus), VERR_INVALID_PARAMETER); - AssertReturn( pReply->u.TransferStatus.uStatus == SHCLTRANSFERSTATUS_ERROR - || RT_SUCCESS((int)pReply->rc), VERR_INVALID_PARAMETER); - AssertReturn( pReply->u.TransferStatus.uStatus != SHCLTRANSFERSTATUS_ERROR - || RT_FAILURE((int)pReply->rc), VERR_INVALID_PARAMETER); + SHCLTRANSFERSTATUS const enmStatus = pReply->u.TransferStatus.uStatus; + AssertReturn( enmStatus != SHCLTRANSFERSTATUS_NONE + && ShClTransferStatusResultIsValid(enmStatus, (int)pReply->rc), + VERR_INVALID_PARAMETER); return VINF_SUCCESS; } #endif @@ -374,33 +256,19 @@ int GuestShCl::i_validateSvcExtParms(uint32_t u32Function, void *pvParms, uint32 default: return VINF_SUCCESS; } -} - - -/** - * Handles VBOX_CLIPBOARD_EXT_FN_SET_CALLBACK from the Shared Clipboard host service. - * - * @returns VBox status code. - * @param pParms Service extension parameters containing the callback to install. - */ -int GuestShCl::i_handleSvcExtSetCallback(PSHCLEXTPARMS pParms) -{ - m_pfnExtCallback = pParms->u.SetCallback.pfnCallback; - return VINF_SUCCESS; +#undef SHCL_VALIDATE_ACTIVE } /** * Handles VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST from the Shared Clipboard host service. * - * Reports guest clipboard formats to Main and forwards the notification to the chained VRDP extension. + * Reports guest clipboard formats to Main and connected remote-desktop clients. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtReportFormatsToHost(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtReportFormatsToHostCallback(PSHCLEXTPARMS pParms) { SHCLFORMATS fFormats = pParms->u.ReportFormats.uFormats; @@ -409,30 +277,11 @@ int GuestShCl::i_handleSvcExtReportFormatsToHost(PSHCLEXTPARMS pParms, void *pvP AssertPtr(m_pConsole->i_getClipboard()); if (m_pConsole->i_getClipboard()) m_pConsole->i_getClipboard()->i_reportFormats(VBOX_SHCL_MAIN_CLIENT_NONE, - fFormats, ClipboardSource_Guest, - true /* fForceNotify */); - - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST, pvParms, cbParms); - return vrc == VERR_NOT_SUPPORTED ? VINF_SUCCESS : vrc; -} - - -/** - * Handles VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST from the Shared Clipboard host service. - * - * Forwards host or remote clipboard format notifications to the chained VRDP extension without - * implicitly publishing them through Main or the regular backend. - * - * @returns VBox status code. - * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. - */ -int GuestShCl::i_handleSvcExtReportFormatsToGuest(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) -{ - RT_NOREF(pParms); + fFormats, ClipboardSource_Guest, + true /* fForceNotify */); - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_GUEST, pvParms, cbParms); + ConsoleVRDPServer *pVrde = m_pConsole->i_consoleVRDPServer(); + int const vrc = pVrde ? pVrde->ClipboardReportGuestFormats(fFormats) : VERR_NOT_SUPPORTED; return vrc == VERR_NOT_SUPPORTED ? VINF_SUCCESS : vrc; } @@ -440,76 +289,90 @@ int GuestShCl::i_handleSvcExtReportFormatsToGuest(PSHCLEXTPARMS pParms, void *pv /** * Handles VBOX_CLIPBOARD_EXT_FN_DATA_READ from the Shared Clipboard host service. * - * Lets the chained VRDP extension service the request first, then reads clipboard data provided - * explicitly through Main. + * Reads clipboard data from the provider selected by Main. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtDataRead(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtDataReadCallback(PSHCLEXTPARMS pParms) { - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_DATA_READ, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - { - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; + void *pvData = pParms->u.ReadWriteData.pvData; + uint32_t cbData = pParms->u.ReadWriteData.cbData; + SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; + Clipboard *pClipboard = m_pConsole->i_getClipboard(); + ClipboardSource_T enmSource = ClipboardSource_Custom; + HRESULT hrc = pClipboard ? pClipboard->i_getCurrentSource(&enmSource) : E_FAIL; - Clipboard *pClipboard = m_pConsole->i_getClipboard(); - if (pClipboard) + int vrc; + if (SUCCEEDED(hrc) && enmSource == ClipboardSource_Remote) + { + int vrcLock = RTCritSectEnter(&m_RemoteFormatsCritSect); + if (RT_FAILURE(vrcLock)) + return vrcLock; + vrcLock = lock(); + if (RT_FAILURE(vrcLock)) { - HRESULT hrc = pClipboard->i_readDataForGuest(fFormats, pvData, cbData, &pParms->u.ReadWriteData.cbActual); - vrc = SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_NO_DATA; + RTCritSectLeave(&m_RemoteFormatsCritSect); + return vrcLock; } - else - vrc = VERR_NOT_AVAILABLE; - if (RT_SUCCESS(vrc)) - LogRel2(("Shared Clipboard: Read Main clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, - pParms->u.ReadWriteData.cbActual)); - else - LogRel2(("Shared Clipboard: No explicit Main clipboard data available, vrc=%Rrc\n", vrc)); - } - return vrc; -} - + Assert(!m_fRemoteDataReadActive); + m_fRemoteDataReadActive = true; + unlock(); + int const vrcLeave = RTCritSectLeave(&m_RemoteFormatsCritSect); + AssertRC(vrcLeave); -/** - * Handles VBOX_CLIPBOARD_EXT_FN_DATA_READ_VRDE from the Shared Clipboard host service. - * - * Reads clipboard data asynchronously from the guest for the VRDE extension path and copies the - * received payload into the supplied extension buffer. - * - * @returns VBox status code. - * @param pParms Service extension parameters describing the read request. - */ -int GuestShCl::i_handleSvcExtDataReadVrde(PSHCLEXTPARMS pParms) -{ - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - PSHCLEVENT pEvent; - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; + ConsoleVRDPServer *pVrde = m_pConsole->i_consoleVRDPServer(); + vrc = pVrde ? pVrde->ClipboardReadRemoteData(fFormats, pvData, cbData, + &pParms->u.ReadWriteData.cbActual) + : VERR_NOT_AVAILABLE; - int vrc = ShClSvcReadDataFromGuestAsync(pClient, fFormats, &pEvent); - if (RT_SUCCESS(vrc)) - { - PSHCLEVENTPAYLOAD pPayload = NULL; - vrc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload); - if (RT_SUCCESS(vrc)) + vrcLock = RTCritSectEnter(&m_RemoteFormatsCritSect); + if (RT_SUCCESS(vrcLock)) { - if (pPayload) + bool fHavePendingFormats = false; + SHCLFORMATS fPendingFormats = VBOX_SHCL_FMT_NONE; + int const vrcState = lock(); + if (RT_SUCCESS(vrcState)) { - memcpy(pvData, pPayload->pvData, RT_MIN(cbData, pPayload->cbData)); - ShClPayloadDestroy(pPayload); - pPayload = NULL; + m_fRemoteDataReadActive = false; + if (m_fRemoteFormatsPending) + { + fHavePendingFormats = true; + fPendingFormats = m_fPendingRemoteFormats; + m_fRemoteFormatsPending = false; + m_fPendingRemoteFormats = VBOX_SHCL_FMT_NONE; + } + unlock(); } else - pvData = NULL; + AssertRC(vrcState); + + if (fHavePendingFormats) + { + int const vrcFormats = i_reportRemoteFormatsToGuestNow(fPendingFormats); + if (RT_FAILURE(vrcFormats)) + LogRel(("Shared Clipboard: Reporting formats deferred during a remote read failed with %Rrc\n", + vrcFormats)); + } + int const vrcLeavePending = RTCritSectLeave(&m_RemoteFormatsCritSect); + AssertRC(vrcLeavePending); } - ShClEventRelease(pEvent); + else + AssertRC(vrcLock); } + else if (pClipboard) + { + hrc = pClipboard->i_readDataForGuest(fFormats, pvData, cbData, &pParms->u.ReadWriteData.cbActual); + vrc = SUCCEEDED(hrc) ? VINF_SUCCESS : VERR_NO_DATA; + } + else + vrc = VERR_NOT_AVAILABLE; + + if (RT_SUCCESS(vrc)) + LogRel2(("Shared Clipboard: Read Main clipboard data (max %RU32 bytes), got %RU32 bytes\n", cbData, + pParms->u.ReadWriteData.cbActual)); + else + LogRelMax(16, ("Shared Clipboard: No Main clipboard data available, vrc=%Rrc\n", vrc)); return vrc; } @@ -517,40 +380,33 @@ int GuestShCl::i_handleSvcExtDataReadVrde(PSHCLEXTPARMS pParms) /** * Handles VBOX_CLIPBOARD_EXT_FN_DATA_WRITE from the Shared Clipboard host service. * - * Lets the chained VRDP extension service the request first, then signals pending guest-data waiters - * without implicitly writing guest data to the host clipboard backend. + * Mirrors the reply to VRDE and signals pending guest-data waiters. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtDataWrite(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtDataWriteCallback(PSHCLEXTPARMS pParms) { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - PSHCLCLIENTCMDCTX pCmdCtx = pParms->u.ReadWriteData.pCmdCtx; SHCLFORMATS fFormats = pParms->u.ReadWriteData.uFormat; - - PSHCLEVENT pEvent = NULL; - int vrc = ShClSvcGuestDataRetainValidatedEvent(pClient, pCmdCtx, fFormats, &pEvent); - if (RT_FAILURE(vrc) || !pEvent) + SHCLGUESTDATATOKEN hToken = NULL; + int vrc = m_pConn->guestDataBegin(pParms->u.ReadWriteData.pCmdCtx, fFormats, &hToken); + if (RT_FAILURE(vrc) || !hToken) return vrc; - int const vrcChained = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_DATA_WRITE, pvParms, cbParms); - if (vrcChained == VERR_NOT_SUPPORTED) + void const *pvData = pParms->u.ReadWriteData.pvData; + uint32_t cbData = pParms->u.ReadWriteData.cbData; + ConsoleVRDPServer *pVrde = m_pConsole->i_consoleVRDPServer(); + if (pVrde) { - void *pvData = pParms->u.ReadWriteData.pvData; - uint32_t cbData = pParms->u.ReadWriteData.cbData; - - SHCLEVENTID const idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); - vrc = ShClSvcGuestDataSignalEvent(pEvent, idEvent, pvData, cbData); - if (RT_FAILURE(vrc)) - LogRelMax(16, ("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", vrc)); - AssertRC(vrc); + int const vrcVrde = pVrde->ClipboardWriteGuestData(fFormats, pvData, cbData); + if (RT_FAILURE(vrcVrde) && vrcVrde != VERR_NOT_SUPPORTED) + LogRelMax(16, ("Shared Clipboard: Mirroring guest clipboard data to VRDE failed with %Rrc\n", vrcVrde)); } - else - vrc = vrcChained; - ShClEventRelease(pEvent); + + vrc = m_pConn->guestDataComplete(hToken, pvData, cbData); + if (RT_FAILURE(vrc)) + LogRelMax(16, ("Shared Clipboard: Signalling host about guest clipboard data failed with %Rrc\n", vrc)); + AssertRC(vrc); return vrc; } @@ -558,141 +414,93 @@ int GuestShCl::i_handleSvcExtDataWrite(PSHCLEXTPARMS pParms, void *pvParms, uint /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT from the Shared Clipboard host service. * - * Lets the chained VRDP extension initialize first, then initializes the regular Shared Clipboard - * backend if the extension did not handle the request. + * Initializes Main's native Shared Clipboard backend once. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ -int GuestShCl::i_handleSvcExtBackendInit(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendInitCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - VBOXHGCMSVCFNTABLE *pTable = pParms->u.ReadWriteData.pTable; - vrc = ShClBackendInit(pBackend, pTable); - } - return vrc; + RT_NOREF(pParms, pvParms, cbParms); + + return m_pConn->initBackend(); } /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY from the Shared Clipboard host service. * - * Lets the chained VRDP extension tear down first, then destroys the regular Shared Clipboard backend - * if the extension did not handle the request. + * Disconnects an active client, if any, and destroys Main's native backend. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ -int GuestShCl::i_handleSvcExtBackendDestroy(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendDestroyCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - ShClBackendDestroy(pBackend); - vrc = VINF_SUCCESS; - } - return vrc; + RT_NOREF(pParms, pvParms, cbParms); + + return m_pConn->destroyBackend(); } /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT from the Shared Clipboard host service. * - * Lets the chained VRDP extension connect first, then connects the regular backend and records the - * active HGCM client when the backend connection succeeds. + * Connects a client to Main's native backend and records the returned context. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ -int GuestShCl::i_handleSvcExtBackendConnect(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendConnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - bool fHeadless = pParms->u.ReadWriteData.fHeadless; - vrc = ShClBackendConnect(pBackend, pClient, fHeadless); - if (RT_SUCCESS(vrc)) - { - lock(); - m_pClient = pClient; - m_fGuestReadsBlocked = false; - unlock(); - } - } - return vrc; + RT_NOREF(pvParms, cbParms); + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + + return m_pConn->connect(&Transport); } /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT from the Shared Clipboard host service. * - * Lets the chained VRDP extension disconnect first, then disconnects the regular backend and clears - * the active HGCM client if it matches the disconnecting client. + * Disconnects a client from Main's native backend and clears its context. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ -int GuestShCl::i_handleSvcExtBackendDisconnect(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendDisconnectCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - { - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - lock(); - if (m_pClient == pClient) - m_fGuestReadsBlocked = true; - unlock(); + RT_NOREF(pvParms, cbParms); + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); - i_waitForGuestReads(); - - lock(); - vrc = ShClBackendDisconnect(pClient->pBackend, pClient); - if (m_pClient == pClient) - { - m_pClient = NULL; - m_fGuestReadsBlocked = false; - } - unlock(); - } - return vrc; + return m_pConn->disconnect(&Transport); } /** * Handles VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC from the Shared Clipboard host service. * - * Lets the chained VRDP extension synchronize first, then synchronizes the regular Shared Clipboard - * backend if the extension did not handle the request. + * Synchronizes Main's native Shared Clipboard backend. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. + * @param pvParms Unused raw protocol parameters. + * @param cbParms Size, in bytes, of \a pvParms. Unused. */ -int GuestShCl::i_handleSvcExtBackendSync(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtBackendSyncCallback(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) { - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - { - PSHCLBACKEND pBackend = pParms->u.ReadWriteData.pBackend; - PSHCLCLIENT pClient = pParms->u.ReadWriteData.pClient; - vrc = ShClBackendSync(pBackend, pClient); - } - return vrc; + RT_NOREF(pvParms, cbParms); + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + + return m_pConn->matches(&Transport) ? m_pConn->syncBackend() : VERR_INVALID_PARAMETER; } @@ -704,27 +512,39 @@ int GuestShCl::i_handleSvcExtBackendSync(PSHCLEXTPARMS pParms, void *pvParms, ui * @returns VBox status code. * @param pParms Service extension parameters containing the error details. */ -int GuestShCl::i_handleSvcExtError(PSHCLEXTPARMS pParms) +int GuestShCl::i_svcExtErrorCallback(PSHCLEXTPARMS pParms) { return ReportError(pParms->u.Error.pszId, pParms->u.Error.rc, pParms->u.Error.pszMsg); } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Handles VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS from the Shared Clipboard host service. + * + * @returns VBox status code. + * @param pParms Service extension parameters containing the callback destination. + */ +int GuestShCl::i_svcExtTransferGetCallbacksCallback(PSHCLEXTPARMS pParms) +{ + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + return m_pConn->matches(&Transport) + ? m_pConn->transferGetCallbacks(pParms->u.TransferCallbacks.pCallbacks) + : VERR_INVALID_PARAMETER; +} + + /** * Handles VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER from the Shared Clipboard host service. * - * Lets the chained VRDP extension handle transfer status first, then forwards the transfer status - * reply to the regular Shared Clipboard backend if the extension did not handle the request. + * Forwards a transfer status reply to the Shared Clipboard backend and Main. * * @returns VBox status code. * @param pParms Decoded service extension parameters. - * @param pvParms Raw service extension parameters to forward to the chained extension. - * @param cbParms Size, in bytes, of \a pvParms. */ -int GuestShCl::i_handleSvcExtFileTransfer(PSHCLEXTPARMS pParms, void *pvParms, uint32_t cbParms) +int GuestShCl::i_svcExtFileTransferCallback(PSHCLEXTPARMS pParms) { - PSHCLCLIENT pClient = pParms->u.FileTransferData.pClient; + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); PSHCLTRANSFER pTransfer = pParms->u.FileTransferData.pTransfer; SHCLSOURCE const enmShClSource = pParms->u.FileTransferData.enmShClSource; PSHCLREPLY pReply = pParms->u.FileTransferData.pReply; @@ -734,18 +554,32 @@ int GuestShCl::i_handleSvcExtFileTransfer(PSHCLEXTPARMS pParms, void *pvParms, u SHCLTRANSFERSTATUS const enmStatus = pReply->u.TransferStatus.uStatus; int const vrcTransfer = (int)pReply->rc; - int vrc = i_forwardToSvcExt(VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER, pvParms, cbParms); - if (vrc == VERR_NOT_SUPPORTED) - vrc = ShClBackendTransferHandleStatusReply(pClient->pBackend, pClient, pTransfer, enmShClSource, - pReply->u.TransferStatus.uStatus, (int)pReply->rc); + int vrc = m_pConn->matches(&Transport) + ? m_pConn->transferHandleStatusReply(pTransfer, enmShClSource, + pReply->u.TransferStatus.uStatus, (int)pReply->rc) + : VERR_INVALID_PARAMETER; - Clipboard *pClipboard = m_pConsole->i_getClipboard(); - if (pClipboard) + if (RT_SUCCESS(vrc)) { - HRESULT hrc = pClipboard->i_handleTransferStatus(idSession, idTransfer, uGeneration, - enmShClSource, enmStatus, vrcTransfer); - if (FAILED(hrc)) - LogFunc(("Main transfer status handling failed: hrc=%Rhrc\n", hrc)); + Clipboard *pClipboard = m_pConsole->i_getClipboard(); + if (pClipboard) + { + /* + * enmShClSource identifies the endpoint which issued this reply + * and is therefore the right value for the platform backend. + * Main's persistent transfer object records the data source + * instead, which is an invariant of the backing transfer even + * when the opposite endpoint reports a lifecycle transition. + */ + SHCLSOURCE const enmTransferSource = ShClTransferGetSource(pTransfer); + HRESULT const hrc = pClipboard->i_handleTransferStatus(idSession, idTransfer, uGeneration, pTransfer, + enmTransferSource, enmStatus, vrcTransfer); + if (FAILED(hrc)) + { + LogFunc(("Main transfer status handling failed: hrc=%Rhrc\n", hrc)); + vrc = Global::vboxStatusCodeFromCOM(hrc); + } + } } return vrc; diff --git a/src/VBox/Main/src-client/UsbCardReader.cpp b/src/VBox/Main/src-client/UsbCardReader.cpp index ca0d6911c3f1..7e9feb9a2e03 100644 --- a/src/VBox/Main/src-client/UsbCardReader.cpp +++ b/src/VBox/Main/src-client/UsbCardReader.cpp @@ -1,4 +1,4 @@ -/* $Id: UsbCardReader.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: UsbCardReader.cpp 114935 2026-08-10 12:46:46Z vitali.pelenjow@oracle.com $ */ /** @file * UsbCardReader - Driver Interface to USB Smart Card Reader emulation. */ @@ -708,10 +708,14 @@ int UsbCardReader::VRDENotify(uint32_t u32Id, void *pvData, uint32_t cbData) case VRDE_SCARD_NOTIFY_DETACH: { - VRDESCARDNOTIFYDETACH *p = (VRDESCARDNOTIFYDETACH *)pvData; NOREF(p); - Assert(cbData == sizeof(VRDESCARDNOTIFYDETACH)); + AssertBreakStmt(cbData == sizeof(VRDESCARDNOTIFYDETACH), vrc = VERR_INVALID_PARAMETER); + VRDESCARDNOTIFYDETACH *p = (VRDESCARDNOTIFYDETACH *)pvData; - /** @todo Just free. There should be no pending requests, because VRDP cancels them. */ + AssertBreakStmt(m_pRemote, vrc = VERR_INVALID_STATE); + AssertBreakStmt(p->u32ClientId == m_pRemote->u32ClientId, vrc = VERR_INVALID_PARAMETER); + AssertBreakStmt(p->u32DeviceId == m_pRemote->u32DeviceId, vrc = VERR_INVALID_PARAMETER); + + /* Just free. There are no pending requests, because VRDP cancels them. */ RTMemFree(m_pRemote); m_pRemote = NULL; } break; diff --git a/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp b/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp deleted file mode 100644 index b037db71d340..000000000000 --- a/src/VBox/Main/src-client/VBoxSharedClipboardSvc-utils.cpp +++ /dev/null @@ -1,415 +0,0 @@ -/* $Id: VBoxSharedClipboardSvc-utils.cpp 114526 2026-06-25 10:37:10Z andreas.loeffler@oracle.com $ */ -/** @file - * Shared Clipboard Service - Host service utility functions. - */ - -/* - * Copyright (C) 2019-2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD -#include - -#include -#include -#include - - -/** - * Validates and retains a pending guest-data event. - * - * @returns VBox status code. Stale replies for expired event IDs are ignored; - * in that case @a ppEvent is set to NULL and VINF_SUCCESS is returned. - * @param pClient Client the guest clipboard data was received from. - * @param pCmdCtx Client command context. - * @param uFormat Clipboard format of data received. - * @param ppEvent Where to return the retained event. Must be - * released with ShClEventRelease(). - * - * @thread Backend thread. - */ -int ShClSvcGuestDataRetainValidatedEvent(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, - PSHCLEVENT *ppEvent) -{ - LogFlowFuncEnter(); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); - AssertPtrReturn(ppEvent, VERR_INVALID_POINTER); - *ppEvent = NULL; - - if ( uFormat == VBOX_SHCL_FMT_NONE - || (uFormat & ~VBOX_SHCL_FMT_VALID_MASK) - || (uFormat & (uFormat - 1)) != 0) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid format %#x\n", uFormat)); - return VERR_INVALID_PARAMETER; - } - - SHCLSESSIONID const idSession = VBOX_SHCL_CONTEXTID_GET_SESSION(pCmdCtx->uContextID); - SHCLEVENTSOURCEID const idEventSource = VBOX_SHCL_CONTEXTID_GET_TRANSFER(pCmdCtx->uContextID); - const SHCLEVENTID idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); - if ( idEvent == 0 - || idEvent == NIL_SHCLEVENTID) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with invalid event %#x in context ID %#RX64\n", - idEvent, pCmdCtx->uContextID)); - return VERR_WRONG_ORDER; - } - if ( idSession != pClient->State.uSessionID - || idEventSource != pClient->EventSrc.uID) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data with mismatching context ID %#RX64" - " (session %#x/%#x, event source %#x/%#x)\n", - pCmdCtx->uContextID, idSession, pClient->State.uSessionID, - idEventSource, pClient->EventSrc.uID)); - return VERR_INVALID_CONTEXT; - } - - PSHCLEVENT pEvent = ShClEventSourceRetainFromId(&pClient->EventSrc, idEvent); - if (!RT_VALID_PTR(pEvent)) - { - LogRelMax2(16, ("Shared Clipboard: Ignoring late guest clipboard data for expired event %#x\n", idEvent)); - return VINF_SUCCESS; - } - if (pEvent->uUser != uFormat) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting guest clipboard data format %#x for event %#x, expected %#x\n", - uFormat, idEvent, pEvent->uUser)); - ShClEventRelease(pEvent); - return VERR_INVALID_CONTEXT; - } - - *ppEvent = pEvent; - LogFlowFuncLeaveRC(VINF_SUCCESS); - return VINF_SUCCESS; -} - - -/** - * Signals a retained guest-data event with clipboard data received from the guest. - * - * @returns VBox status code. - * @param pEvent Retained event to signal. - * @param idEvent Event ID to use for the optional payload wrapper. - * @param pvData Pointer to clipboard data received. This can be - * NULL if @a cbData is zero. - * @param cbData Size (in bytes) of clipboard data received. - * This can be zero. - * - * @thread Backend thread. - */ -int ShClSvcGuestDataSignalEvent(PSHCLEVENT pEvent, SHCLEVENTID idEvent, void *pvData, uint32_t cbData) -{ - LogFlowFuncEnter(); - - AssertPtrReturn(pEvent, VERR_INVALID_POINTER); - if (cbData > 0) - AssertPtrReturn(pvData, VERR_INVALID_POINTER); - - /* - * Make a copy of the data so we can attach it to the signal. - * - * Note! We still signal the waiter should we run out of memory, - * because otherwise it will be stuck waiting. - */ - int vrc = VINF_SUCCESS; - PSHCLEVENTPAYLOAD pPayload = NULL; - if (cbData > 0) - vrc = ShClPayloadCreateDupData(idEvent, pvData, cbData, &pPayload); - - /* - * Signal the event. - */ - int vrc2 = ShClEventSignalEx(pEvent, vrc, pPayload); - if (RT_FAILURE(vrc2)) - { - vrc = vrc2; - ShClPayloadDestroy(pPayload); - LogRel(("Shared Clipboard: Signalling of guest clipboard data to the host failed: %Rrc\n", vrc)); - } - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - - -/** - * Signals the host that clipboard data from the guest has been received. - * - * @returns VBox status code. Stale replies for expired event IDs are ignored. - * @param pClient Client the guest clipboard data was received from. - * @param pCmdCtx Client command context. - * @param uFormat Clipboard format of data received. - * @param pvData Pointer to clipboard data received. This can be - * NULL if @a cbData is zero. - * @param cbData Size (in bytes) of clipboard data received. - * This can be zero. - * - * @thread Backend thread. - */ -int ShClSvcGuestDataSignal(PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) -{ - LogFlowFuncEnter(); - - PSHCLEVENT pEvent = NULL; - int vrc = ShClSvcGuestDataRetainValidatedEvent(pClient, pCmdCtx, uFormat, &pEvent); - if (RT_FAILURE(vrc) || !pEvent) - return vrc; - - SHCLEVENTID const idEvent = VBOX_SHCL_CONTEXTID_GET_EVENT(pCmdCtx->uContextID); - vrc = ShClSvcGuestDataSignalEvent(pEvent, idEvent, pvData, cbData); - ShClEventRelease(pEvent); - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -/** - * Reads clipboard data from the guest, asynchronous version. - * - * @returns VBox status code. - * @param pClient Client to request to read data form. - * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). - * @param ppEvent Where to return the event for waiting for new data on success. - * Must be released by the caller with ShClEventRelease(). Optional. - * - * @thread On X11: Called from the X11 event thread. - * @thread On Windows: Called from the Windows event thread. - * - * @note This will locally initialize a transfer if VBOX_SHCL_FMT_URI_LIST is being requested from the guest. - */ -int ShClSvcReadDataFromGuestAsync(PSHCLCLIENT pClient, SHCLFORMATS fFormats, PSHCLEVENT *ppEvent) -{ - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - LogFlowFunc(("fFormats=%#x\n", fFormats)); - - if (ppEvent) - *ppEvent = NULL; - - SHCLFORMATS const fSupportedFormats = VBOX_SHCL_FMT_UNICODETEXT - | VBOX_SHCL_FMT_BITMAP - | VBOX_SHCL_FMT_HTML -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - | VBOX_SHCL_FMT_URI_LIST -#endif - ; - if ( fFormats == VBOX_SHCL_FMT_NONE - || (fFormats & ~fSupportedFormats)) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting unsupported guest clipboard data request formats %#x\n", fFormats)); - return VERR_NOT_SUPPORTED; - } - if ( ppEvent - && (fFormats & (fFormats - 1)) != 0) - { - LogRelMax2(16, ("Shared Clipboard: Rejecting multi-format guest clipboard data request %#x with single event output\n", - fFormats)); - return VERR_INVALID_PARAMETER; - } - - int vrc = VERR_NOT_SUPPORTED; - - /* Generate a separate message for every (valid) format we support. */ - while (fFormats) - { - /* Pick the next format to get from the mask: */ - /** @todo Make format reporting precedence configurable? */ - SHCLFORMAT fFormat; - if (fFormats & VBOX_SHCL_FMT_UNICODETEXT) - fFormat = VBOX_SHCL_FMT_UNICODETEXT; - else if (fFormats & VBOX_SHCL_FMT_BITMAP) - fFormat = VBOX_SHCL_FMT_BITMAP; - else if (fFormats & VBOX_SHCL_FMT_HTML) - fFormat = VBOX_SHCL_FMT_HTML; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - else if (fFormats & VBOX_SHCL_FMT_URI_LIST) - fFormat = VBOX_SHCL_FMT_URI_LIST; -#endif - else - { - vrc = VERR_NOT_SUPPORTED; - break; - } - - /* Remove it from the mask. */ - fFormats &= ~fFormat; - - if (LogRelIs2Enabled()) - { - char *pszFmt = ShClFormatsToStrA(fFormat); - LogRel2(("Shared Clipboard: Requesting guest clipboard data in format %#x/'%s'\n", - fFormat, pszFmt ? pszFmt : "")); - RTStrFree(pszFmt); - } - /* - * Allocate messages, one for each format. - */ - PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, - pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID - ? VBOX_SHCL_HOST_MSG_READ_DATA_CID : VBOX_SHCL_HOST_MSG_READ_DATA, - 2); - if (pMsg) - { - ShClSvcClientLock(pClient); - - PSHCLEVENT pEvent; - vrc = ShClEventSourceGenerateAndRegisterEvent(&pClient->EventSrc, &pEvent); - if (RT_SUCCESS(vrc)) - { - LogFlowFunc(("fFormats=%#x -> fFormat=%#x, idEvent=%#x\n", fFormats, fFormat, pEvent->idEvent)); - pEvent->uUser = fFormat; - - const uint64_t uCID = VBOX_SHCL_CONTEXTID_MAKE(pClient->State.uSessionID, pClient->EventSrc.uID, pEvent->idEvent); - - vrc = VINF_SUCCESS; - - /* Save the context ID in our legacy cruft if we have to deal with old(er) Guest Additions (< 6.1). */ - if (!(pClient->State.fGuestFeatures0 & VBOX_SHCL_GF_0_CONTEXT_ID)) - { - AssertStmt(pClient->Legacy.cCID < 4096, vrc = VERR_TOO_MUCH_DATA); - if (RT_SUCCESS(vrc)) - { - PSHCLCLIENTLEGACYCID pCID = (PSHCLCLIENTLEGACYCID)RTMemAlloc(sizeof(SHCLCLIENTLEGACYCID)); - if (pCID) - { - pCID->uCID = uCID; - pCID->enmType = 0; /* Not used yet. */ - pCID->uFormat = fFormat; - RTListAppend(&pClient->Legacy.lstCID, &pCID->Node); - pClient->Legacy.cCID++; - } - else - vrc = VERR_NO_MEMORY; - } - } - - if (RT_SUCCESS(vrc)) - { - /* - * Format the message. - */ - if (pMsg->idMsg == VBOX_SHCL_HOST_MSG_READ_DATA_CID) - HGCMSvcSetU64(&pMsg->aParms[0], uCID); - else - HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_READ_DATA); - HGCMSvcSetU32(&pMsg->aParms[1], fFormat); - - ShClSvcClientMsgAdd(pClient, pMsg, true /* fAppend */); - /* Wake up the client to let it know that there are new messages. */ - ShClSvcClientWakeup(pClient); - - /* Return event to caller. */ - if (ppEvent) - *ppEvent = pEvent; - } - - /* Remove event from list if caller did not request event handle or in case - * of failure (in this case caller should not release event). */ - if ( RT_FAILURE(vrc) - || !ppEvent) - { - ShClEventRelease(pEvent); - pEvent = NULL; - } - } - else - vrc = VERR_SHCLPB_MAX_EVENTS_REACHED; - - if (RT_FAILURE(vrc)) - ShClSvcClientMsgFree(pClient, pMsg); - - ShClSvcClientUnlock(pClient); - } - else - vrc = VERR_NO_MEMORY; - - if (RT_FAILURE(vrc)) - break; - } - - if (RT_FAILURE(vrc)) - LogRel(("Shared Clipboard: Requesting guest clipboard data failed with %Rrc\n", vrc)); - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -/** - * Reads clipboard data from the guest. - * - * @returns VBox status code. - * @retval VERR_SHCLPB_NO_DATA if no clipboard data is available. - * @param pClient Client to request to read data form. - * @param fFormats The formats being requested, OR'ed together (VBOX_SHCL_FMT_XXX). - * @param ppv Where to return the allocated data read. - * Must be free'd by the caller. - * @param pcb Where to return number of bytes read. - */ -int ShClSvcReadDataFromGuest(PSHCLCLIENT pClient, SHCLFORMAT fFormats, void **ppv, uint32_t *pcb) -{ - AssertPtrReturn(ppv, VERR_INVALID_POINTER); - AssertPtrReturn(pcb, VERR_INVALID_POINTER); - - LogFlowFuncEnter(); - - /* Request data from the guest and wait for data to arrive. */ - PSHCLEVENT pEvent; - int vrc = ShClSvcReadDataFromGuestAsync(pClient, fFormats, &pEvent); - if (RT_SUCCESS(vrc)) - { - PSHCLEVENTPAYLOAD pPayload; - vrc = ShClEventWait(pEvent, SHCL_TIMEOUT_DEFAULT_MS, &pPayload); - if (RT_SUCCESS(vrc)) - { - if ( pPayload - && pPayload->cbData) - { - *ppv = pPayload->pvData; - *pcb = pPayload->cbData; - - LogFlowFunc(("pv=%p, cb=%RU32\n", pPayload->pvData, pPayload->cbData)); - - pPayload->pvData = NULL; - pPayload->cbData = 0; - ShClPayloadDestroy(pPayload); - } - else - { - ShClPayloadDestroy(pPayload); - vrc = VERR_SHCLPB_NO_DATA; - } - } - - ShClEventRelease(pEvent); - } - - if ( RT_FAILURE(vrc) - && vrc != VERR_SHCLPB_NO_DATA) - LogRel(("Shared Clipboard: Reading data from guest failed with %Rrc\n", vrc)); - return vrc; -} - diff --git a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp index 05e35a3460cc..44b9370ec0bf 100644 --- a/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp +++ b/src/VBox/Main/src-client/darwin/ClipboardBackendDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendDarwin.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendDarwin.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host. */ @@ -31,7 +31,9 @@ *********************************************************************************************************************************/ #define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD #include -#include +#include "GuestShClBackend.h" +#include "../GuestShClBackendPrivate.h" +#include "GuestShClConn.h" #include #include @@ -42,9 +44,6 @@ #include #include "darwin-pasteboard.h" -#ifdef VBOX_COM_INPROC -# include "GuestShClPrivate.h" -#endif /********************************************************************************************************************************* @@ -59,8 +58,10 @@ typedef struct SHCLCONTEXT bool volatile fTerminate; /** The reference to the current pasteboard */ PasteboardRef hPasteboard; - /** Shared clipboard client. */ - PSHCLCLIENT pClient; + /** Main connection to the Shared Clipboard service. */ + GuestShClConn *pConn; + /** Whether @a pConn may be used by the pasteboard poller. */ + bool fClientReady; /** Random 64-bit number embedded into szGuestOwnershipFlavor. */ uint64_t idGuestOwnership; /** Ownership flavor CFStringRef returned by takePasteboardOwnership(). @@ -69,6 +70,8 @@ typedef struct SHCLCONTEXT /** The guest ownership flavor (type) string. */ char szGuestOwnershipFlavor[64]; /** Serialize access to the current pasteboard. */ + RTCRITSECT CritSectPasteboard; + /** Serialize the client pointer and its readiness state. */ RTCRITSECT CritSect; } SHCLCONTEXT; @@ -80,45 +83,111 @@ typedef struct SHCLCONTEXT static SHCLCONTEXT g_ctx; -static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @copydoc SHCLTXPROVIDERIFACE::pfnRootListRead */ +static DECLCALLBACK(int) shClSvcDarwinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pProviderCtx) { -#ifdef VBOX_COM_INPROC - return GuestShCl::GetInst()->ReportFormatsToGuest(pClient, fFormats, SHCLSOURCE_LOCAL); -#endif - return ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pProviderCtx->pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + char *pszRoots = NULL; + size_t cbRoots = 0; + int vrc = RTCritSectEnter(&pCtx->CritSectPasteboard); + if (RT_SUCCESS(vrc)) + { + vrc = readFileURLsFromPasteboard(pCtx->hPasteboard, &pszRoots, &cbRoots); + + int const vrc2 = RTCritSectLeave(&pCtx->CritSectPasteboard); + AssertRC(vrc2); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } + + if (RT_SUCCESS(vrc)) + vrc = ShClTransferRootsSetFromStringList(pProviderCtx->pTransfer, pszRoots, cbRoots); + RTStrFree(pszRoots); + return vrc; +} + + +/** @copydoc SHCLTRANSFERCALLBACKS::pfnOnCreated */ +static DECLCALLBACK(void) shClSvcDarwinTransferOnCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; + AssertPtrReturnVoid(pCtx); + + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtrReturnVoid(pTransfer); + + if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST + && ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL) + { + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); + ShClTransferProviderLocalQueryInterface(&Provider); + Provider.Interface.pfnRootListRead = shClSvcDarwinTransferIfaceHGRootListRead; + Provider.enmSource = SHCLSOURCE_LOCAL; + Provider.pvUser = pCtx; + Provider.cbUser = sizeof(*pCtx); + + int const vrc = ShClTransferSetProvider(pTransfer, &Provider); + AssertRC(vrc); + } } + +/** @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialize */ +static DECLCALLBACK(int) shClSvcDarwinTransferOnInitializeCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + + if ( ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_HOST_TO_GUEST + && ShClTransferGetSource(pTransfer) == SHCLSOURCE_LOCAL) + return ShClTransferRootListRead(pTransfer); + return VERR_NOT_SUPPORTED; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + /** * Checks if something is present on the clipboard and calls shclSvcReportMsg. * * @returns IPRT status code (ignored). * @param pCtx The context. + * @param fForce Whether to report the current pasteboard content even if + * its change was already observed. * - * @note Call must own lock. */ -static int vboxClipboardChanged(SHCLCONTEXT *pCtx) +static int vboxClipboardChanged(PSHCLCONTEXT pCtx, bool fForce) { - if (pCtx->pClient == NULL) - return VINF_SUCCESS; - - /* Retrieve the formats currently in the clipboard and supported by vbox */ + int vrc = VINF_SUCCESS; uint32_t fFormats = 0; - bool fChanged = false; - int vrc = queryNewPasteboardFormats(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->hStrOwnershipFlavor, - &fFormats, &fChanged); - if ( RT_SUCCESS(vrc) - && fChanged) + + RTCritSectEnter(&pCtx->CritSect); + + if ( pCtx->pConn + && pCtx->fClientReady) { - uint32_t uMode = pCtx->pClient->State.uMode; - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) + /* Retrieve the formats currently in the clipboard and supported by VBox. */ + bool fChanged = false; + vrc = RTCritSectEnter(&pCtx->CritSectPasteboard); + if (RT_SUCCESS(vrc)) { - vrc = shClBackendReportFormatsToGuestAndMain(pCtx->pClient, fFormats); + vrc = queryNewPasteboardFormats(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->hStrOwnershipFlavor, + fForce, &fFormats, &fChanged); + + int const vrc2 = RTCritSectLeave(&pCtx->CritSectPasteboard); + AssertRC(vrc2); + if (RT_SUCCESS(vrc)) + vrc = vrc2; } - else - vrc = VINF_SUCCESS; + if (RT_SUCCESS(vrc) && fChanged) + vrc = pCtx->pConn->reportLocalFormats(fFormats); } + RTCritSectLeave(&pCtx->CritSect); + LogFlowFuncLeaveRC(vrc); return vrc; } @@ -130,18 +199,14 @@ static int vboxClipboardChanged(SHCLCONTEXT *pCtx) */ static DECLCALLBACK(int) vboxClipboardThread(RTTHREAD ThreadSelf, void *pvUser) { - SHCLCONTEXT *pCtx = (SHCLCONTEXT *)pvUser; + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser; AssertPtr(pCtx); LogFlowFuncEnter(); int vrc; while (!ASMAtomicReadBool(&pCtx->fTerminate)) { - /* call this behind the lock because we don't know if the api is - thread safe and in any case we're calling several methods. */ - RTCritSectEnter(&g_ctx.CritSect); - vboxClipboardChanged(pCtx); - RTCritSectLeave(&g_ctx.CritSect); + vboxClipboardChanged(pCtx, false /* fForce */); /* Sleep for 200 msecs before next poll */ vrc = RTThreadUserWait(ThreadSelf, 200); @@ -154,23 +219,35 @@ static DECLCALLBACK(int) vboxClipboardThread(RTTHREAD ThreadSelf, void *pvUser) } -int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +/** + * Initializes the process-wide macOS clipboard backend. + * + * @returns VBox status code. + */ +static int shClBackendDarwinInit(void) { g_ctx.fTerminate = false; + g_ctx.fClientReady = false; int vrc; vrc = RTCritSectInit(&g_ctx.CritSect); AssertRCReturn(vrc, vrc); - vrc = initPasteboard(&g_ctx.hPasteboard); + vrc = RTCritSectInit(&g_ctx.CritSectPasteboard); if (RT_FAILURE(vrc)) { RTCritSectDelete(&g_ctx.CritSect); return vrc; } - pBackend->pHelpers = pTable->pHelpers; + vrc = initPasteboard(&g_ctx.hPasteboard); + if (RT_FAILURE(vrc)) + { + RTCritSectDelete(&g_ctx.CritSectPasteboard); + RTCritSectDelete(&g_ctx.CritSect); + return vrc; + } vrc = RTThreadCreate(&g_ctx.hThread, vboxClipboardThread, &g_ctx, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "SHCLIP"); @@ -178,16 +255,18 @@ int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) { g_ctx.hThread = NIL_RTTHREAD; destroyPasteboard(&g_ctx.hPasteboard); + RTCritSectDelete(&g_ctx.CritSectPasteboard); RTCritSectDelete(&g_ctx.CritSect); } return vrc; } -void ShClBackendDestroy(PSHCLBACKEND pBackend) +/** + * Destroys the process-wide macOS clipboard backend. + */ +static void shClBackendDarwinDestroy(void) { - RT_NOREF(pBackend); - /* * Signal the termination of the polling thread and wait for it to respond. */ @@ -195,9 +274,11 @@ void ShClBackendDestroy(PSHCLBACKEND pBackend) if (g_ctx.hThread != NIL_RTTHREAD) { int vrc = RTThreadUserSignal(g_ctx.hThread); - AssertRC(vrc); + if (RT_FAILURE(vrc)) + LogRelMax(16, ("Shared Clipboard: Waking the Darwin clipboard poller during shutdown failed with %Rrc;" + " waiting for its polling interval to expire\n", vrc)); vrc = RTThreadWait(g_ctx.hThread, RT_INDEFINITE_WAIT, NULL); - AssertRC(vrc); + AssertFatalMsgRC(vrc, ("Reaping the Darwin clipboard poller failed with %Rrc\n", vrc)); g_ctx.hThread = NIL_RTTHREAD; } @@ -205,90 +286,164 @@ void ShClBackendDestroy(PSHCLBACKEND pBackend) * Destroy the hPasteboard and uninitialize the global context record. */ destroyPasteboard(&g_ctx.hPasteboard); - g_ctx.pClient = NULL; + g_ctx.pConn = NULL; + g_ctx.fClientReady = false; + if (RTCritSectIsInitialized(&g_ctx.CritSectPasteboard)) + RTCritSectDelete(&g_ctx.CritSectPasteboard); if (RTCritSectIsInitialized(&g_ctx.CritSect)) RTCritSectDelete(&g_ctx.CritSect); } -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +/** + * Connects a Main service connection to the macOS clipboard backend. + * + * @returns VBox status code. + * @param pConn Main service connection to associate. + * @param ppCtx Where to return the backend context. + */ +static int shClBackendDarwinConnect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) { - RT_NOREF(pBackend, fHeadless); - - if (g_ctx.pClient != NULL) - { - /* One client only. */ - return VERR_NOT_SUPPORTED; - } + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); + *ppCtx = NULL; RTCritSectEnter(&g_ctx.CritSect); - pClient->State.pCtx = &g_ctx; - pClient->State.pCtx->pClient = pClient; + int vrc; + if (!g_ctx.pConn) + { + g_ctx.pConn = pConn; + g_ctx.fClientReady = false; + *ppCtx = &g_ctx; + vrc = VINF_SUCCESS; + } + else + vrc = VERR_NOT_SUPPORTED; /* One client only. */ RTCritSectLeave(&g_ctx.CritSect); - return VINF_SUCCESS; + return vrc; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Returns the macOS callbacks for a new transfer. + * + * @param pCtx Connected backend context. + * @param pCallbacks Where to return the callback table. + */ +static void shClBackendDarwinTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) +{ + AssertPtrReturnVoid(pCallbacks); + RT_ZERO(*pCallbacks); + AssertPtrReturnVoid(pCtx); + AssertPtrReturnVoid(pCtx->pConn); + + pCallbacks->pvUser = pCtx; + pCallbacks->cbUser = sizeof(*pCtx); + pCallbacks->pfnOnCreated = shClSvcDarwinTransferOnCreatedCallback; + pCallbacks->pfnOnInitialize = shClSvcDarwinTransferOnInitializeCallback; } +#endif -int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +/** + * Synchronizes macOS clipboard state with a connected guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + */ +static int shClBackendDarwinSync(PSHCLCONTEXT pCtx) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); + /* GuestShClConn publishes the connection while shClBackendDarwinConnect runs, but + * do not expose it to the poller until the initial service sync. */ RTCritSectEnter(&g_ctx.CritSect); - /* Sync the host clipboard content with the client. */ - int vrc = vboxClipboardChanged(pClient->State.pCtx); + int vrc = VINF_SUCCESS; + if (pCtx->pConn) + pCtx->fClientReady = true; + else + vrc = VERR_NOT_SUPPORTED; RTCritSectLeave(&g_ctx.CritSect); + /* Sync the host clipboard content with the client. */ + if (RT_SUCCESS(vrc)) + vrc = vboxClipboardChanged(pCtx, true /* fForce */); return vrc; } -int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +/** + * Disconnects a Main service connection from the macOS clipboard backend. + * + * @returns VBox status code. + * @param pCtx Backend context to disconnect. + */ +static int shClBackendDarwinDisconnect(PSHCLCONTEXT pCtx) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + GuestShClConn * const pConn = pCtx->pConn; +#endif RTCritSectEnter(&g_ctx.CritSect); - pClient->State.pCtx->pClient = NULL; + if (pCtx->pConn) + { + pCtx->fClientReady = false; + pCtx->pConn = NULL; + } RTCritSectLeave(&g_ctx.CritSect); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* Transfer callback tables retain pCtx as their user argument. */ + pConn->transferDestroyAll(); +#endif + return VINF_SUCCESS; } -int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +/** + * Reports guest clipboard formats to the macOS pasteboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ +static int shClBackendDarwinReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFunc(("fFormats=%02X\n", fFormats)); if (fFormats == VBOX_SHCL_FMT_NONE) { - SHCLCONTEXT *pCtx = pClient->State.pCtx; - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pCtx->CritSectPasteboard); int vrcClear = clearPasteboard(pCtx->hPasteboard, &pCtx->hStrOwnershipFlavor); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pCtx->CritSectPasteboard); return vrcClear; } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - LogRel2(("Shared Clipboard: Darwin backend does not support file-transfer clipboard offers yet\n")); + LogRelMax(16, ("Shared Clipboard: Darwin backend does not support guest-to-host file-transfer offers yet\n")); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; if (fFormats == VBOX_SHCL_FMT_NONE) { - SHCLCONTEXT *pCtx = pClient->State.pCtx; - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pCtx->CritSectPasteboard); int vrcClear = clearPasteboard(pCtx->hPasteboard, &pCtx->hStrOwnershipFlavor); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pCtx->CritSectPasteboard); return vrcClear; } } #endif - SHCLCONTEXT *pCtx = pClient->State.pCtx; - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pCtx->CritSectPasteboard); /* * Generate a unique flavor string for this format announcement. @@ -308,102 +463,120 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR takePasteboardOwnership(pCtx->hPasteboard, pCtx->idGuestOwnership, pCtx->szGuestOwnershipFlavor, szValue, &pCtx->hStrOwnershipFlavor); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pCtx->CritSectPasteboard); /* * Now, request the data from the guest. */ - return ShClSvcReadDataFromGuestAsync(pClient, fFormats, NULL /* ppEvent */); + return pCtx->pConn->readDataFromGuestAsync(fFormats, NULL /* ppEvent */); } /** - * The host reports clipboard formats to the guest clipboard. + * Reads clipboard data from the macOS pasteboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormat Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required byte count. */ -int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - RT_NOREF(pBackend); - - int vrc; - - PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); - if (pMsg) - { - HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); - HGCMSvcSetU32(&pMsg->aParms[1], fFormats); - - ShClSvcClientLock(pClient); - - vrc = shClSvcClientMsgAddAndWakeupClient(pClient, pMsg); - - ShClSvcClientUnlock(pClient); - } - else - vrc = VERR_NO_MEMORY; - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT fFormat, - void *pvData, uint32_t cbData, uint32_t *pcbActual) +static int shClBackendDarwinReadData(PSHCLCONTEXT pCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) { - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); AssertPtrReturn(pvData, VERR_INVALID_POINTER); AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); - RT_NOREF(pBackend, pCmdCtx); - - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pCtx->CritSectPasteboard); /* Default to no data available. */ *pcbActual = 0; - int vrc = readFromPasteboard(pClient->State.pCtx->hPasteboard, fFormat, pvData, cbData, pcbActual); + int vrc = readFromPasteboard(pCtx->hPasteboard, fFormat, pvData, cbData, pcbActual); if (RT_FAILURE(vrc)) LogRel(("Shared Clipboard: Error reading host clipboard data from macOS, vrc=%Rrc\n", vrc)); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pCtx->CritSectPasteboard); return vrc; } -int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData) +/** + * Writes guest clipboard data to the macOS pasteboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormat Clipboard format to write. + * @param pvData Data buffer. + * @param cbData Data size in bytes. + */ +static int shClBackendDarwinWriteData(PSHCLCONTEXT pCtx, SHCLFORMAT fFormat, void *pvData, uint32_t cbData) { - RT_NOREF(pBackend, pCmdCtx); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFuncEnter(); - RTCritSectEnter(&g_ctx.CritSect); + RTCritSectEnter(&pCtx->CritSectPasteboard); - writeToPasteboard(pClient->State.pCtx->hPasteboard, pClient->State.pCtx->idGuestOwnership, pvData, cbData, fFormat); + int vrc = writeToPasteboard(pCtx->hPasteboard, pCtx->idGuestOwnership, pvData, cbData, fFormat); - RTCritSectLeave(&g_ctx.CritSect); + RTCritSectLeave(&pCtx->CritSectPasteboard); - LogFlowFuncLeaveRC(VINF_SUCCESS); - return VINF_SUCCESS; + if (RT_FAILURE(vrc)) + LogRel(("Shared Clipboard: Writing guest data to the macOS pasteboard failed, vrc=%Rrc\n", vrc)); + + LogFlowFuncLeaveRC(vrc); + return vrc; } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# ifndef UNIT_TEST /** * Handles transfer status replies from the guest. * * @returns VBox status code. - * @param pBackend Shared Clipboard backend. - * @param pClient Shared Clipboard client context. + * @param pCtx Shared Clipboard backend context. * @param pTransfer Shared Clipboard transfer. * @param enmSource Transfer source which issued the reply. * @param enmStatus Transfer status. * @param rcStatus Transfer status code. */ -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, - SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus) +static int shClBackendDarwinTransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) { - RT_NOREF(pBackend, pClient, pTransfer, enmSource, enmStatus, rcStatus); + RT_NOREF(pCtx, pTransfer, enmSource, enmStatus, rcStatus); return VINF_SUCCESS; } -# endif /* !UNIT_TEST */ #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/** Native macOS Shared Clipboard backend operations. */ +static SHCLBACKENDOPS const s_ShClBackendDarwinOps = +{ + shClBackendDarwinInit, + shClBackendDarwinDestroy, + NULL, + shClBackendDarwinConnect, + shClBackendDarwinDisconnect, + shClBackendDarwinReportFormats, + shClBackendDarwinReadData, + shClBackendDarwinWriteData, + shClBackendDarwinSync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClBackendDarwinTransferGetCallbacks, + shClBackendDarwinTransferHandleStatusReply, +#endif +}; + + +/** + * Returns the native macOS Shared Clipboard backend operations. + * + * @returns Immutable macOS backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &s_ShClBackendDarwinOps; +} diff --git a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp index c424f64b1cd6..46030d87787b 100644 --- a/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp +++ b/src/VBox/Main/src-client/darwin/darwin-pasteboard.cpp @@ -1,4 +1,4 @@ -/* $Id: darwin-pasteboard.cpp 114770 2026-07-25 11:53:54Z knut.osmundsen@oracle.com $ */ +/* $Id: darwin-pasteboard.cpp 114987 2026-08-11 13:50:56Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Mac OS X host implementation. */ @@ -35,14 +35,22 @@ #include #include -#include #include +#include +#include #include +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include +# include +#endif #include #include #include #include +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include +#endif #include "darwin-pasteboard.h" @@ -102,6 +110,8 @@ DECLHIDDEN(void) destroyPasteboard(PasteboardRef *pPasteboardRef) * @param idOwnership Our ownership ID. * @param hStrOwnershipFlavor The ownership flavor string reference returned * by takePasteboardOwnership(). + * @param fForce Whether to inspect the current content even if + * the pasteboard change was already observed. * @param pfFormats Pointer for the bit combination of the * supported types. * @param pfChanged True if something has changed after the @@ -110,18 +120,20 @@ DECLHIDDEN(void) destroyPasteboard(PasteboardRef *pPasteboardRef) * @returns VINF_SUCCESS. */ DECLHIDDEN(int) queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t idOwnership, void *hStrOwnershipFlavor, - uint32_t *pfFormats, bool *pfChanged) + bool fForce, uint32_t *pfFormats, bool *pfChanged) { - OSStatus orc; + AssertPtrReturn(hPasteboard, VERR_INVALID_POINTER); + AssertPtrReturn(pfFormats, VERR_INVALID_POINTER); + AssertPtrReturn(pfChanged, VERR_INVALID_POINTER); *pfFormats = 0; *pfChanged = true; - PasteboardSyncFlags syncFlags; /* Make sure all is in sync */ - syncFlags = PasteboardSynchronize(hPasteboard); + PasteboardSyncFlags const syncFlags = PasteboardSynchronize(hPasteboard); /* If nothing changed return */ - if (!(syncFlags & kPasteboardModified)) + if ( !(syncFlags & kPasteboardModified) + && !fForce) { *pfChanged = false; Log2(("queryNewPasteboardFormats: no change\n")); @@ -130,90 +142,317 @@ DECLHIDDEN(int) queryNewPasteboardFormats(PasteboardRef hPasteboard, uint64_t id /* Are some items in the pasteboard? */ ItemCount cItems = 0; - orc = PasteboardGetItemCount(hPasteboard, &cItems); - if (orc == 0) + OSStatus const orcItems = PasteboardGetItemCount(hPasteboard, &cItems); + if (orcItems == noErr) { if (cItems < 1) Log(("queryNewPasteboardFormats: changed: No items on the pasteboard\n")); else { - /* The id of the first element in the pasteboard */ - PasteboardItemID idItem = 0; - orc = PasteboardGetItemIdentifier(hPasteboard, 1, &idItem); - if (orc == 0) + bool fOwnClipboard = false; + for (ItemCount idxItem = 0; idxItem < cItems; idxItem++) { - /* - * Retrieve all flavors on the pasteboard, maybe there - * is something we can use. Or maybe we're the owner. - */ - CFArrayRef hFlavors = 0; - orc = PasteboardCopyItemFlavors(hPasteboard, idItem, &hFlavors); - if (orc == 0) + PasteboardItemID idItem = 0; + OSStatus const orcItem = PasteboardGetItemIdentifier(hPasteboard, idxItem + 1, &idItem); + if (orcItem == noErr) { - CFIndex cFlavors = CFArrayGetCount(hFlavors); - for (CFIndex idxFlavor = 0; idxFlavor < cFlavors; idxFlavor++) + /* + * Retrieve all flavors on the pasteboard, maybe there + * is something we can use. Or maybe we're the owner. + */ + CFArrayRef hFlavors = NULL; + OSStatus const orcFlavors = PasteboardCopyItemFlavors(hPasteboard, idItem, &hFlavors); + if ( orcFlavors == noErr + && hFlavors) { - CFStringRef hStrFlavor = (CFStringRef)CFArrayGetValueAtIndex(hFlavors, idxFlavor); - if ( idItem == (PasteboardItemID)idOwnership - && hStrOwnershipFlavor - && CFStringCompare(hStrFlavor, (CFStringRef)hStrOwnershipFlavor, 0) == kCFCompareEqualTo) - { - /* We made the changes ourselves. */ - Log2(("queryNewPasteboardFormats: no-changed: our clipboard!\n")); - *pfChanged = false; - *pfFormats = 0; - break; - } - - if (UTTypeConformsTo(hStrFlavor, kUTTypeBMP)) + CFIndex const cFlavors = CFArrayGetCount(hFlavors); + for (CFIndex idxFlavor = 0; idxFlavor < cFlavors; idxFlavor++) { - Log(("queryNewPasteboardFormats: BMP flavor detected.\n")); - *pfFormats |= VBOX_SHCL_FMT_BITMAP; - } - else if ( UTTypeConformsTo(hStrFlavor, kUTTypeUTF8PlainText) - || UTTypeConformsTo(hStrFlavor, kUTTypeUTF16PlainText)) - { - Log(("queryNewPasteboardFormats: Unicode flavor detected.\n")); - *pfFormats |= VBOX_SHCL_FMT_UNICODETEXT; - } + CFStringRef hStrFlavor = (CFStringRef)CFArrayGetValueAtIndex(hFlavors, idxFlavor); + if ( idItem == (PasteboardItemID)idOwnership + && hStrOwnershipFlavor + && CFStringCompare(hStrFlavor, (CFStringRef)hStrOwnershipFlavor, 0) == kCFCompareEqualTo) + { + /* We made the changes ourselves. */ + Log2(("queryNewPasteboardFormats: no-changed: our clipboard!\n")); + fOwnClipboard = true; + break; + } + + if (UTTypeConformsTo(hStrFlavor, kUTTypeBMP)) + { + Log(("queryNewPasteboardFormats: BMP flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_BITMAP; + } + else if ( UTTypeConformsTo(hStrFlavor, kUTTypeUTF8PlainText) + || UTTypeConformsTo(hStrFlavor, kUTTypeUTF16PlainText)) + { + Log(("queryNewPasteboardFormats: Unicode flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_UNICODETEXT; + } #ifdef WITH_HTML_H2G - else if (UTTypeConformsTo(hStrFlavor, kUTTypeHTML)) - { - Log(("queryNewPasteboardFormats: HTML flavor detected.\n")); - *pfFormats |= VBOX_SHCL_FMT_HTML; - } + else if (UTTypeConformsTo(hStrFlavor, kUTTypeHTML)) + { + Log(("queryNewPasteboardFormats: HTML flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_HTML; + } +#endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + else if (UTTypeConformsTo(hStrFlavor, kUTTypeFileURL)) + { + Log(("queryNewPasteboardFormats: File URL flavor detected.\n")); + *pfFormats |= VBOX_SHCL_FMT_URI_LIST; + } #endif #ifdef LOG_ENABLED - else if (LogIs2Enabled()) - { - if (CFStringGetCharactersPtr(hStrFlavor)) - Log2(("queryNewPasteboardFormats: Unknown flavor: %ls.\n", CFStringGetCharactersPtr(hStrFlavor))); - else if (CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8)) - Log2(("queryNewPasteboardFormats: Unknown flavor: %s.\n", - CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8))); - else - Log2(("queryNewPasteboardFormats: Unknown flavor: ???\n")); - } + else if (LogIs2Enabled()) + { + if (CFStringGetCharactersPtr(hStrFlavor)) + Log2(("queryNewPasteboardFormats: Unknown flavor: %ls.\n", + CFStringGetCharactersPtr(hStrFlavor))); + else if (CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8)) + Log2(("queryNewPasteboardFormats: Unknown flavor: %s.\n", + CFStringGetCStringPtr(hStrFlavor, kCFStringEncodingUTF8))); + else + Log2(("queryNewPasteboardFormats: Unknown flavor: ???\n")); + } #endif + } } - - CFRelease(hFlavors); + else + Log(("queryNewPasteboardFormats: PasteboardCopyItemFlavors failed - %d (%#x)\n", + orcFlavors, orcFlavors)); + if (hFlavors) + CFRelease(hFlavors); + if (fOwnClipboard) + break; } else - Log(("queryNewPasteboardFormats: PasteboardCopyItemFlavors failed - %d (%#x)\n", orc, orc)); + Log(("queryNewPasteboardFormats: PasteboardGetItemIdentifier failed - %d (%#x)\n", orcItem, orcItem)); } - else - Log(("queryNewPasteboardFormats: PasteboardGetItemIdentifier failed - %d (%#x)\n", orc, orc)); - if (*pfChanged) + if (fOwnClipboard) + { + *pfChanged = false; + *pfFormats = 0; + } + else Log(("queryNewPasteboardFormats: changed: *pfFormats=%#x\n", *pfFormats)); } } else - Log(("queryNewPasteboardFormats: PasteboardGetItemCount failed - %d (%#x)\n", orc, orc)); + Log(("queryNewPasteboardFormats: PasteboardGetItemCount failed - %d (%#x)\n", orcItems, orcItems)); return VINF_SUCCESS; } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Converts one macOS file URL pasteboard value to a canonical file URI. + * + * @returns VBox status code. + * @param hData File URL pasteboard data. + * @param ppszURI Where to return the allocated URI. Must be + * freed with RTStrFree(). + * @param pcchURI Where to return the URI length without the + * terminator. + */ +static int darwinPasteboardFileURLToURI(CFDataRef hData, char **ppszURI, size_t *pcchURI) +{ + AssertPtrReturn(hData, VERR_INVALID_POINTER); + AssertPtrReturn(ppszURI, VERR_INVALID_POINTER); + AssertPtrReturn(pcchURI, VERR_INVALID_POINTER); + + *ppszURI = NULL; + *pcchURI = 0; + + CFIndex const cbData = CFDataGetLength(hData); + if (cbData <= 0) + return VERR_INVALID_PARAMETER; + if ((uint64_t)cbData > (uint64_t)RTPATH_MAX * 3 + 32) + return VERR_TOO_MUCH_DATA; + + const UInt8 *pbData = CFDataGetBytePtr(hData); + AssertPtrReturn(pbData, VERR_INVALID_POINTER); + + size_t cchData = 0; + int vrc = ShClHlpUtf8ValidateExact((const char *)pbData, (size_t)cbData, &cchData); + if (RT_FAILURE(vrc)) + return vrc; + if (!cchData) + return VERR_INVALID_PARAMETER; + for (size_t off = 0; off + 2 < cchData; off++) + if ( pbData[off] == '%' + && pbData[off + 1] == '0' + && pbData[off + 2] == '0') + return VERR_INVALID_PARAMETER; + + CFURLRef hURL = CFURLCreateWithBytes(kCFAllocatorDefault, pbData, (CFIndex)cchData, + kCFStringEncodingUTF8, NULL /* baseURL */); + if (!hURL) + return VERR_INVALID_PARAMETER; + + CFStringRef hScheme = CFURLCopyScheme(hURL); + CFStringRef hLocation = CFURLCopyNetLocation(hURL); + CFStringRef hQuery = CFURLCopyQueryString(hURL, NULL /* charactersToLeaveEscaped */); + CFStringRef hFragment = CFURLCopyFragment(hURL, NULL /* charactersToLeaveEscaped */); + if ( !hScheme + || CFStringCompare(hScheme, CFSTR("file"), kCFCompareCaseInsensitive) != kCFCompareEqualTo + || ( hLocation + && CFStringGetLength(hLocation) + && CFStringCompare(hLocation, CFSTR("localhost"), kCFCompareCaseInsensitive) != kCFCompareEqualTo) + || hQuery + || hFragment) + vrc = VERR_INVALID_PARAMETER; + + char szPath[RTPATH_MAX]; + if (RT_SUCCESS(vrc)) + { + if (!CFURLGetFileSystemRepresentation(hURL, true /* resolveAgainstBase */, (UInt8 *)szPath, sizeof(szPath))) + vrc = VERR_INVALID_PARAMETER; + else if (!RTPathStartsWithRoot(szPath)) + vrc = VERR_PATH_IS_RELATIVE; + else + vrc = RTStrValidateEncoding(szPath); + } + + if (RT_SUCCESS(vrc)) + { + vrc = RTUriFileCreateEx(szPath, RTPATH_STR_F_STYLE_UNIX, ppszURI, 0 /* cbUri */, NULL /* pcchUri */); + if (RT_SUCCESS(vrc)) + *pcchURI = strlen(*ppszURI); + } + + if (hFragment) + CFRelease(hFragment); + if (hQuery) + CFRelease(hQuery); + if (hLocation) + CFRelease(hLocation); + if (hScheme) + CFRelease(hScheme); + CFRelease(hURL); + return vrc; +} + + +/** + * Reads local file URLs from the macOS pasteboard as a transfer root list. + * + * @returns VBox status code. + * @param hPasteboard Reference to the pasteboard to read. + * @param ppszRoots Where to return the allocated CRLF-separated + * file URI list. Must be freed with RTStrFree(). + * @param pcbRoots Where to return the list size, including the + * terminator. + */ +DECLHIDDEN(int) readFileURLsFromPasteboard(PasteboardRef hPasteboard, char **ppszRoots, size_t *pcbRoots) +{ + AssertPtrReturn(hPasteboard, VERR_INVALID_POINTER); + AssertPtrReturn(ppszRoots, VERR_INVALID_POINTER); + AssertPtrReturn(pcbRoots, VERR_INVALID_POINTER); + + *ppszRoots = NULL; + *pcbRoots = 0; + + PasteboardSynchronize(hPasteboard); + + ItemCount cItems = 0; + OSStatus const orcItems = PasteboardGetItemCount(hPasteboard, &cItems); + if (orcItems != noErr) + return VERR_GENERAL_FAILURE; + + char *pszRoots = NULL; + size_t cbRoots = 0; + size_t cRoots = 0; + int vrc = VINF_SUCCESS; + for (ItemCount idxItem = 0; idxItem < cItems; idxItem++) + { + PasteboardItemID idItem = 0; + OSStatus const orcItem = PasteboardGetItemIdentifier(hPasteboard, idxItem + 1, &idItem); + if (orcItem != noErr) + { + vrc = VERR_GENERAL_FAILURE; + break; + } + + CFArrayRef hFlavors = NULL; + OSStatus const orcFlavors = PasteboardCopyItemFlavors(hPasteboard, idItem, &hFlavors); + if ( orcFlavors != noErr + || !hFlavors) + { + if (hFlavors) + CFRelease(hFlavors); + vrc = VERR_GENERAL_FAILURE; + break; + } + + CFStringRef hFileURLFlavor = NULL; + CFIndex const cFlavors = CFArrayGetCount(hFlavors); + for (CFIndex idxFlavor = 0; idxFlavor < cFlavors; idxFlavor++) + { + CFStringRef hFlavor = (CFStringRef)CFArrayGetValueAtIndex(hFlavors, idxFlavor); + if (UTTypeConformsTo(hFlavor, kUTTypeFileURL)) + { + hFileURLFlavor = hFlavor; + break; + } + } + + if (hFileURLFlavor) + { + CFDataRef hData = NULL; + OSStatus const orcData = PasteboardCopyItemFlavorData(hPasteboard, idItem, hFileURLFlavor, &hData); + if ( orcData == noErr + && hData) + { + char *pszURI = NULL; + size_t cchURI = 0; + vrc = darwinPasteboardFileURLToURI(hData, &pszURI, &cchURI); + if (RT_SUCCESS(vrc)) + { + size_t const cchSep = sizeof(SHCL_TRANSFER_URI_LIST_SEP_STR) - 1; + if ( cbRoots > ~(size_t)0 - cchSep - 1 + || cchURI > ~(size_t)0 - cbRoots - cchSep - 1) + vrc = VERR_TOO_MUCH_DATA; + else + { + vrc = RTStrAAppendExN(&pszRoots, 2 /* cPairs */, pszURI, cchURI, + SHCL_TRANSFER_URI_LIST_SEP_STR, cchSep); + if (RT_SUCCESS(vrc)) + { + cbRoots += cchURI + cchSep; + cRoots++; + } + } + } + RTStrFree(pszURI); + } + else + vrc = VERR_GENERAL_FAILURE; + if (hData) + CFRelease(hData); + } + + CFRelease(hFlavors); + if (RT_FAILURE(vrc)) + break; + } + + if ( RT_SUCCESS(vrc) + && !cRoots) + vrc = VERR_NOT_FOUND; + if (RT_SUCCESS(vrc)) + { + *ppszRoots = pszRoots; + *pcbRoots = cbRoots + 1; + LogRel2(("Shared Clipboard: macOS reported %zu root entries for transfer to guest\n", cRoots)); + } + else + RTStrFree(pszRoots); + return vrc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + /** * Read content from the host clipboard and write it to the internal clipboard * structure for further processing. @@ -230,6 +469,30 @@ DECLHIDDEN(int) readFromPasteboard(PasteboardRef pPasteboard, uint32_t fFormat, { Log(("readFromPasteboard: fFormat = %02X\n", fFormat)); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + if (fFormat & VBOX_SHCL_FMT_URI_LIST) + { + char *pszRoots = NULL; + size_t cbRoots = 0; + int vrc = readFileURLsFromPasteboard(pPasteboard, &pszRoots, &cbRoots); + if (RT_SUCCESS(vrc)) + { + if (cbRoots > UINT32_MAX) + vrc = VERR_TOO_MUCH_DATA; + else + { + *pcbActual = (uint32_t)cbRoots; + if (cbRoots <= cb) + memcpy(pv, pszRoots, cbRoots); + else + vrc = VINF_BUFFER_OVERFLOW; + } + } + RTStrFree(pszRoots); + return vrc; + } +#endif + /* Make sure all is in sync */ PasteboardSynchronize(pPasteboard); @@ -637,8 +900,9 @@ DECLHIDDEN(int) writeToPasteboard(PasteboardRef hPasteboard, uint64_t idOwnershi /* * Now for the UTF-8 version. */ + PCRTUTF16 const pwszUtf8 = pwszDst[0] == VBOX_SHCL_UTF16_BOM ? pwszDst + 1 : pwszDst; char *pszDst; - int vrc2 = RTUtf16ToUtf8(pwszDst, &pszDst); + int vrc2 = RTUtf16ToUtf8(pwszUtf8, &pszDst); if (RT_SUCCESS(vrc2)) { hData = CFDataCreate(kCFAllocatorDefault, (const UInt8 *)pszDst, strlen(pszDst)); diff --git a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp index bb37378abdf9..f4a37b7d06d5 100644 --- a/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp +++ b/src/VBox/Main/src-client/linux/ClipboardBackendX11.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendX11.cpp 114661 2026-07-08 10:39:13Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendX11.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - X11 backend. */ @@ -37,30 +37,25 @@ #include #include #include +#include #include #include #include -#include +#include "GuestShClBackend.h" +#include "../GuestShClBackendPrivate.h" +#include "GuestShClConn.h" #include -#ifdef VBOX_COM_INPROC -# include "GuestShClPrivate.h" -#endif - #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS # include -# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP -# include -# endif #endif - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# include "VBoxSharedClipboardSvc-transfers.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +# include #endif -/* Number of currently extablished connections. */ -static volatile uint32_t g_cShClConnections; +/** Test callback overrides applied when constructing a new X11 context. */ +static SHCLCALLBACKS g_ShClCallbackOverrides; /********************************************************************************************************************************* @@ -76,11 +71,21 @@ struct SHCLCONTEXT RTCRITSECT CritSect; /** X11 context data. */ SHCLX11CTX X11; - /** Pointer to the VBox host client data structure. */ - PSHCLCLIENT pClient; + /** Main connection to the Shared Clipboard service. */ + GuestShClConn *pConn; + /** Event source used for synchronous X11 reads. */ + SHCLEVENTSOURCE EventSrc; /** We set this when we start shutting down as a hint not to post any new * requests. */ bool fShuttingDown; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + /** Host-side asynchronous X11 HTTP file-transfer state. */ + SHCLX11TRANSFERSTATE X11TransferState; + /** Event notifying the preparation worker about a new clipboard offer. */ + RTSEMEVENT hX11TransferPreparationEvent; + /** Persistent worker which prepares guest file-transfer URI-list data. */ + RTTHREAD hX11TransferPreparationThread; +#endif }; @@ -97,44 +102,217 @@ static DECLCALLBACK(void) shClSvcX11TransferOnDestroyCallback(PSHCLTRANSFERCALLB static DECLCALLBACK(void) shClSvcX11TransferOnUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx); static DECLCALLBACK(int) shClSvcX11TransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pCtx); +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +static DECLCALLBACK(int) shClSvcX11TransferPreparationThread(RTTHREAD hThreadSelf, void *pvUser); +# endif #endif +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +/** Resets the exact transfer key bound to an in-progress preparation. */ +static void shClSvcX11TransferPreparationResetKey(PSHCLX11TRANSFERSTATE pX11TransferState) +{ + pX11TransferState->idTransfer = NIL_SHCLTRANSFERID; + pX11TransferState->uTransferGeneration = NIL_SHCLTRANSFERGEN; +} + +/** Resets the exact transfer key backing the advertised URI-list data. */ +static void shClSvcX11TransferPublishedResetKey(PSHCLX11TRANSFERSTATE pX11TransferState) +{ + pX11TransferState->idPublishedTransfer = NIL_SHCLTRANSFERID; + pX11TransferState->uPublishedTransferGeneration = NIL_SHCLTRANSFERGEN; +} + +/** Checks an exact transfer ID and generation against a transfer. */ +static bool shClSvcX11TransferKeyMatches(SHCLTRANSFERID idTransfer, SHCLTRANSFERGEN uGeneration, + PSHCLTRANSFER pTransfer) +{ + return idTransfer != NIL_SHCLTRANSFERID + && uGeneration != NIL_SHCLTRANSFERGEN + && idTransfer == ShClTransferGetID(pTransfer) + && uGeneration == ShClTransferGetGeneration(pTransfer); +} + +/** Checks whether a worker result still belongs to the current URI offer. */ +static bool shClSvcX11TransferOfferIsCurrent(PSHCLCONTEXT pCtx, uint64_t uOfferGeneration) +{ + int vrc = RTCritSectEnter(&pCtx->CritSect); + AssertRCReturn(vrc, false); + + bool const fCurrent = !pCtx->fShuttingDown + && pCtx->X11TransferState.uOfferGeneration == uOfferGeneration + && (pCtx->X11TransferState.fFormats & VBOX_SHCL_FMT_URI_LIST); + + vrc = RTCritSectLeave(&pCtx->CritSect); + AssertRC(vrc); + return fCurrent; +} + +/** + * Discards the unused transfer backing a URI-list offer hidden by a newer offer. + * + * A transfer which has already started serving an HTTP request must remain + * alive until that request finishes. Otherwise it is safe to destroy the + * transfer after the URI targets have been removed from X11. + * + * @thread X11 transfer preparation worker. + */ +static void shClSvcX11TransferPublishedCancel(PSHCLCONTEXT pCtx) +{ + SHCLTRANSFERID idTransfer; + SHCLTRANSFERGEN uGeneration; + + int vrc = RTCritSectEnter(&pCtx->CritSect); + AssertRCReturnVoid(vrc); + + idTransfer = pCtx->X11TransferState.idPublishedTransfer; + uGeneration = pCtx->X11TransferState.uPublishedTransferGeneration; + shClSvcX11TransferPublishedResetKey(&pCtx->X11TransferState); + + vrc = RTCritSectLeave(&pCtx->CritSect); + AssertRC(vrc); + + if (idTransfer == NIL_SHCLTRANSFERID) + return; + + PSHCLTRANSFER pTransfer = pCtx->pConn->transferGetByIdRetained(idTransfer); + if ( pTransfer + && shClSvcX11TransferKeyMatches(idTransfer, uGeneration, pTransfer)) + { + SHCLTRANSFERSTATUS const enmStatus = ShClTransferGetStatus(pTransfer); + if (enmStatus != SHCLTRANSFERSTATUS_STARTED) + { + ShClTransferRelease(pTransfer); + pCtx->pConn->transferDestroyById(idTransfer); + } + else + { + LogRel2(("Shared Clipboard: Keeping superseded X11 transfer %RU16/%RU64 alive while it is in use\n", + idTransfer, uGeneration)); + ShClTransferRelease(pTransfer); + } + } + else + { + if (pTransfer) + ShClTransferRelease(pTransfer); + LogRel2(("Shared Clipboard: Published X11 transfer %RU16/%RU64 was already gone or replaced\n", + idTransfer, uGeneration)); + } +} + +/** Starts the persistent host-side X11 transfer preparation worker. */ +static int shClSvcX11TransferPreparationStart(PSHCLCONTEXT pCtx) +{ + RT_ZERO(pCtx->X11TransferState); + shClSvcX11TransferPreparationResetKey(&pCtx->X11TransferState); + shClSvcX11TransferPublishedResetKey(&pCtx->X11TransferState); + pCtx->hX11TransferPreparationEvent = NIL_RTSEMEVENT; + pCtx->hX11TransferPreparationThread = NIL_RTTHREAD; + + int vrc = RTSemEventCreate(&pCtx->hX11TransferPreparationEvent); + if (RT_SUCCESS(vrc)) + { + vrc = RTThreadCreate(&pCtx->hX11TransferPreparationThread, shClSvcX11TransferPreparationThread, + pCtx, 0 /* cbStack */, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ShClX11Tx"); + if (RT_SUCCESS(vrc)) + { + vrc = RTThreadUserWait(pCtx->hX11TransferPreparationThread, RT_MS_30SEC); + if (RT_SUCCESS(vrc)) + return VINF_SUCCESS; + + pCtx->fShuttingDown = true; + int const vrcSignal = RTSemEventSignal(pCtx->hX11TransferPreparationEvent); + AssertFatalMsgRC(vrcSignal, ("Signalling the X11 transfer preparation worker after startup failure" + " failed with %Rrc\n", vrcSignal)); + int const vrcWait = RTThreadWait(pCtx->hX11TransferPreparationThread, RT_INDEFINITE_WAIT, NULL); + AssertFatalMsgRC(vrcWait, ("Reaping the X11 transfer preparation worker after startup failure" + " failed with %Rrc\n", vrcWait)); + pCtx->hX11TransferPreparationThread = NIL_RTTHREAD; + } + + RTSemEventDestroy(pCtx->hX11TransferPreparationEvent); + pCtx->hX11TransferPreparationEvent = NIL_RTSEMEVENT; + } + + return vrc; +} + +/** Stops the transfer preparation worker before its X11 and client contexts are released. */ +static int shClSvcX11TransferPreparationStop(PSHCLCONTEXT pCtx) +{ + if (pCtx->hX11TransferPreparationThread == NIL_RTTHREAD) + { + pCtx->fShuttingDown = true; + return VINF_SUCCESS; + } + + int vrc = RTCritSectEnter(&pCtx->CritSect); + AssertFatalMsgRC(vrc, ("Entering X11 backend critical section during shutdown failed with %Rrc\n", vrc)); + pCtx->fShuttingDown = true; + pCtx->X11TransferState.uOfferGeneration++; + vrc = RTCritSectLeave(&pCtx->CritSect); + AssertFatalMsgRC(vrc, ("Leaving the X11 backend critical section during shutdown failed with %Rrc\n", vrc)); + + int vrc2 = RTSemEventSignal(pCtx->hX11TransferPreparationEvent); + AssertFatalMsgRC(vrc2, ("Signalling the X11 transfer preparation worker during shutdown failed with %Rrc\n", vrc2)); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + vrc2 = RTThreadWait(pCtx->hX11TransferPreparationThread, RT_INDEFINITE_WAIT, NULL); + AssertFatalMsgRC(vrc2, ("Reaping the X11 transfer preparation worker during shutdown failed with %Rrc\n", vrc2)); + pCtx->hX11TransferPreparationThread = NIL_RTTHREAD; + + vrc2 = RTSemEventDestroy(pCtx->hX11TransferPreparationEvent); + AssertFatalMsgRC(vrc2, ("Destroying the X11 transfer preparation event failed with %Rrc\n", vrc2)); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + pCtx->hX11TransferPreparationEvent = NIL_RTSEMEVENT; + + return vrc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + + /********************************************************************************************************************************* * Backend implementation * *********************************************************************************************************************************/ -int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +/** + * Initializes the process-wide X11 clipboard backend. + * + * @returns VBox status code. + */ +static int shClBackendX11Init(void) { - RT_NOREF(pBackend); - LogFlowFuncEnter(); - /* Override the connection limit. */ - for (uintptr_t i = 0; i < RT_ELEMENTS(pTable->acMaxClients); i++) - pTable->acMaxClients[i] = RT_MIN(VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX, pTable->acMaxClients[i]); - - RT_ZERO(pBackend->Callbacks); - /* Use internal callbacks by default. */ - pBackend->Callbacks.pfnReportFormats = shClSvcX11ReportFormatsCallback; - pBackend->Callbacks.pfnOnRequestDataFromSource = shClSvcX11RequestDataFromSourceCallback; - - pBackend->pHelpers = pTable->pHelpers; + RT_ZERO(g_ShClCallbackOverrides); return VINF_SUCCESS; } -void ShClBackendDestroy(PSHCLBACKEND pBackend) +/** + * Destroys the process-wide X11 clipboard backend. + */ +static void shClBackendX11Destroy(void) { - RT_NOREF(pBackend); - LogFlowFuncEnter(); } -void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks) +/** + * Replaces the X11 callback table for testing. + * + * @param pCallbacks Callback overrides, or NULL to restore defaults. + */ +static void shClBackendX11SetCallbacks(PSHCLCALLBACKS pCallbacks) { + RT_ZERO(g_ShClCallbackOverrides); + if (!pCallbacks) + return; + #define SET_FN_IF_NOT_NULL(a_Fn) \ if (pCallbacks->pfn##a_Fn) \ - pBackend->Callbacks.pfn##a_Fn = pCallbacks->pfn##a_Fn; + g_ShClCallbackOverrides.pfn##a_Fn = pCallbacks->pfn##a_Fn; SET_FN_IF_NOT_NULL(ReportFormats); SET_FN_IF_NOT_NULL(OnClipboardRead); @@ -146,20 +324,23 @@ void ShClBackendSetCallbacks(PSHCLBACKEND pBackend, PSHCLCALLBACKS pCallbacks) } /** - * @note On the host, we assume that some other application already owns - * the clipboard and leave ownership to X11. + * Connects a Main service connection to the X11 clipboard backend. + * + * @returns VBox status code. + * @param pConn Main service connection to associate. + * @param ppCtx Where to return the allocated backend context. + * + * @note On the host, another application is assumed to own the clipboard; + * ownership remains with X11 until guest formats are announced. */ -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +static int shClBackendX11Connect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) { - int vrc; + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); - /* Check if maximum allowed connections count has reached. */ - if (ASMAtomicIncU32(&g_cShClConnections) > VBOX_SHARED_CLIPBOARD_X11_CONNECTIONS_MAX) - { - ASMAtomicDecU32(&g_cShClConnections); - LogRel(("Shared Clipboard: maximum amount for client connections reached\n")); - return VERR_OUT_OF_RESOURCES; - } + *ppCtx = NULL; + + int vrc; PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); if (pCtx) @@ -167,110 +348,180 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadles vrc = RTCritSectInit(&pCtx->CritSect); if (RT_SUCCESS(vrc)) { - vrc = ShClX11Init(&pCtx->X11, &pBackend->Callbacks, pCtx, fHeadless); + vrc = ShClEventSourceInit(&pCtx->EventSrc, 0 /* idEvtSrc */); + if (RT_FAILURE(vrc)) + { + RTCritSectDelete(&pCtx->CritSect); + RTMemFree(pCtx); + return vrc; + } + + SHCLCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pfnReportFormats = shClSvcX11ReportFormatsCallback; + Callbacks.pfnOnRequestDataFromSource = shClSvcX11RequestDataFromSourceCallback; + +#define SET_FN_IF_NOT_NULL(a_Fn) \ + if (g_ShClCallbackOverrides.pfn##a_Fn) \ + Callbacks.pfn##a_Fn = g_ShClCallbackOverrides.pfn##a_Fn; + + SET_FN_IF_NOT_NULL(ReportFormats); + SET_FN_IF_NOT_NULL(OnClipboardRead); + SET_FN_IF_NOT_NULL(OnClipboardWrite); + SET_FN_IF_NOT_NULL(OnRequestDataFromSource); + SET_FN_IF_NOT_NULL(OnSendDataToDest); + +#undef SET_FN_IF_NOT_NULL + + vrc = ShClX11Init(&pCtx->X11, &Callbacks, pCtx); if (RT_SUCCESS(vrc)) { - pClient->State.pCtx = pCtx; - pCtx->pClient = pClient; + pCtx->pConn = pConn; -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /* - * Set callbacks. - * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. - * - * Used for starting / stopping the HTTP server. - */ - RT_ZERO(pClient->Transfers.Callbacks); - - pClient->Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */ - pClient->Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); - - pClient->Transfers.Callbacks.pfnOnCreated = shClSvcX11TransferOnCreatedCallback; - pClient->Transfers.Callbacks.pfnOnInitialize = shClSvcX11TransferOnInitCallback; - pClient->Transfers.Callbacks.pfnOnDestroy = shClSvcX11TransferOnDestroyCallback; - pClient->Transfers.Callbacks.pfnOnUnregistered = shClSvcX11TransferOnUnregisteredCallback; -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + vrc = shClSvcX11TransferPreparationStart(pCtx); +#endif + if (RT_SUCCESS(vrc)) + { + vrc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */); + if (RT_SUCCESS(vrc)) + *ppCtx = pCtx; + } - vrc = ShClX11ThreadStart(&pCtx->X11, true /* grab shared clipboard */); if (RT_FAILURE(vrc)) - ShClX11Term(&pCtx->X11); + { +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + shClSvcX11TransferPreparationStop(pCtx); + AssertFatal(pCtx->hX11TransferPreparationThread == NIL_RTTHREAD); +#endif + AssertFatal(pCtx->X11.Thread == NIL_RTTHREAD); + int const vrcTerm = ShClX11Term(&pCtx->X11); + AssertFatalMsgRC(vrcTerm, ("Terminating X11 context after backend startup failure failed with %Rrc\n", + vrcTerm)); + } + } + else + { + int const vrcTerm = ShClX11Term(&pCtx->X11); + AssertFatalMsgRC(vrcTerm, ("Terminating partially initialized X11 context failed with %Rrc\n", + vrcTerm)); } if (RT_FAILURE(vrc)) - RTCritSectDelete(&pCtx->CritSect); + { + ShClEventSourceTerm(&pCtx->EventSrc); + int const vrcDelete = RTCritSectDelete(&pCtx->CritSect); + AssertFatalMsgRC(vrcDelete, ("Deleting X11 backend critical section after startup failure" + " failed with %Rrc\n", vrcDelete)); + } } if (RT_FAILURE(vrc)) - { - pClient->State.pCtx = NULL; RTMemFree(pCtx); - } } else vrc = VERR_NO_MEMORY; - if (RT_FAILURE(vrc)) - { - /* Restore active connections count. */ - ASMAtomicDecU32(&g_cShClConnections); - } - LogFlowFuncLeaveRC(vrc); return vrc; } -int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Returns the X11 callbacks for a new transfer. + * + * @param pCtx Connected backend context. + * @param pCallbacks Where to return the callback table. + */ +static void shClBackendX11TransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) { - RT_NOREF(pBackend); + AssertPtrReturnVoid(pCallbacks); + RT_ZERO(*pCallbacks); + AssertPtrReturnVoid(pCtx); + AssertPtrReturnVoid(pCtx->pConn); + + pCallbacks->pvUser = pCtx; + pCallbacks->cbUser = sizeof(*pCtx); + pCallbacks->pfnOnCreated = shClSvcX11TransferOnCreatedCallback; + pCallbacks->pfnOnInitialize = shClSvcX11TransferOnInitCallback; + pCallbacks->pfnOnDestroy = shClSvcX11TransferOnDestroyCallback; + pCallbacks->pfnOnUnregistered = shClSvcX11TransferOnUnregisteredCallback; +} +#endif - LogFlowFuncEnter(); +/** + * Synchronizes X11 clipboard state with a connected guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + */ +static int shClBackendX11Sync(PSHCLCONTEXT pCtx) +{ + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); - uint32_t uMode = pClient->State.uMode; - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) - { /* likely */ } - else - return VINF_SUCCESS; + LogFlowFuncEnter(); /* Tell the guest we have no data in case X11 is not available. If * there is data in the host clipboard it will automatically be sent to * the guest when the clipboard starts up. */ - int vrc = ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, VBOX_SHCL_FMT_NONE); + int const vrc = pCtx->pConn->reportFormatsToGuest(VBOX_SHCL_FMT_NONE); LogFlowFuncLeaveRC(vrc); return vrc; } /** - * Shuts down the shared clipboard service and "disconnect" the guest. - * Note! Host glue code + * Disconnects and destroys an X11 clipboard backend context. + * + * @returns VBox status code. + * @param pCtx Backend context to disconnect. */ -int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +static int shClBackendX11Disconnect(PSHCLCONTEXT pCtx) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFuncEnter(); - PSHCLCONTEXT pCtx = pClient->State.pCtx; - AssertPtr(pCtx); - - /* Drop the reference to the client, in case it is still there. This - * will cause any outstanding clipboard data requests from X11 to fail - * immediately. */ + /* Stop transfer preparation before releasing either the client or X11 + * context it uses. This also makes later X11 data requests fail. */ + int vrc; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + vrc = shClSvcX11TransferPreparationStop(pCtx); + AssertFatal(pCtx->hX11TransferPreparationThread == NIL_RTTHREAD); +#else pCtx->fShuttingDown = true; + vrc = VINF_SUCCESS; +#endif - int vrc = ShClX11ThreadStop(&pCtx->X11); - /** @todo handle this slightly more reasonably, or be really sure - * it won't go wrong. */ - AssertRC(vrc); + int vrc2 = ShClX11ThreadStop(&pCtx->X11); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + AssertFatal(pCtx->X11.Thread == NIL_RTTHREAD); - ShClX11Term(&pCtx->X11); - RTCritSectDelete(&pCtx->CritSect); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* Transfer callback tables retain pCtx as their user argument. Destroy + * all transfers before deleting that context; the service-side client + * teardown which follows treats an already empty context as a no-op. */ + pCtx->pConn->transferDestroyAll(); +#endif + vrc2 = ShClX11Term(&pCtx->X11); + AssertFatalMsgRC(vrc2, ("Terminating X11 clipboard context failed with %Rrc\n", vrc2)); + if (RT_SUCCESS(vrc)) + vrc = vrc2; - RTMemFree(pCtx); + vrc2 = ShClEventSourceTerm(&pCtx->EventSrc); + AssertFatalMsgRC(vrc2, ("Terminating X11 backend event source failed with %Rrc\n", vrc2)); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + vrc2 = RTCritSectDelete(&pCtx->CritSect); + AssertFatalMsgRC(vrc2, ("Deleting X11 backend critical section failed with %Rrc\n", vrc2)); + if (RT_SUCCESS(vrc)) + vrc = vrc2; - /* Decrease active connections count. */ - ASMAtomicDecU32(&g_cShClConnections); + RTMemFree(pCtx); LogFlowFuncLeaveRC(vrc); return vrc; @@ -278,69 +529,79 @@ int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) /** * Reports clipboard formats to the host clipboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. */ -int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +static int shClBackendX11ReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) { - RT_NOREF(pBackend); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); #if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - LogRelMax2(16, ("Shared Clipboard: X11 backend cannot expose guest URI-list data because HTTP transfer support is not built in; masking format %#x\n", + LogRelMax(16, ("Shared Clipboard: X11 backend cannot expose guest URI-list data because HTTP transfer support is not built in; masking format %#x\n", VBOX_SHCL_FMT_URI_LIST)); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; } #endif - int vrc = ShClX11ReportFormatsToX11Async(&pClient->State.pCtx->X11, fFormats); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + int vrc = RTCritSectEnter(&pCtx->CritSect); + if (RT_SUCCESS(vrc)) + { + if (!pCtx->fShuttingDown) + { + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + pX11TransferState->fFormats = fFormats; + pX11TransferState->uOfferGeneration++; + if (!pX11TransferState->uOfferGeneration) + pX11TransferState->uOfferGeneration = 1; + + /* Remove an old URI target immediately. The worker adds it back + * only after the new URI-list data has been prepared and cached. */ + vrc = ShClX11ReportFormatsToX11Async(&pCtx->X11, fFormats & ~VBOX_SHCL_FMT_URI_LIST); + + int vrc2 = RTSemEventSignal(pCtx->hX11TransferPreparationEvent); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } + else + vrc = VERR_WRONG_ORDER; + + int const vrc2 = RTCritSectLeave(&pCtx->CritSect); + if (RT_SUCCESS(vrc)) + vrc = vrc2; + } +#else + int vrc = ShClX11ReportFormatsToX11Async(&pCtx->X11, fFormats); +#endif LogFlowFuncLeaveRC(vrc); return vrc; } /** - * The host reports clipboard formats to the guest clipboard. + * Reports formats discovered by X11 to the connected guest. + * + * @returns VBox status code. + * @param pConn Main service connection to report through. + * @param fFormats Native formats, VBOX_SHCL_FMT_XXX. */ -int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +static int shClBackendX11ReportLocalFormats(GuestShClConn *pConn, SHCLFORMATS fFormats) { - RT_NOREF(pBackend); - - int vrc; - #if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) if (fFormats & VBOX_SHCL_FMT_URI_LIST) { - LogRelMax2(16, ("Shared Clipboard: X11 backend cannot announce host URI-list data because HTTP transfer support is not built in; masking format %#x\n", + LogRelMax(16, ("Shared Clipboard: X11 backend cannot announce host URI-list data because HTTP transfer support is not built in; masking format %#x\n", VBOX_SHCL_FMT_URI_LIST)); fFormats &= ~VBOX_SHCL_FMT_URI_LIST; } #endif - PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); - if (pMsg) - { - HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); - HGCMSvcSetU32(&pMsg->aParms[1], fFormats); - - ShClSvcClientLock(pClient); - - vrc = shClSvcClientMsgAddAndWakeupClient(pClient, pMsg); - - ShClSvcClientUnlock(pClient); - } - else - vrc = VERR_NO_MEMORY; - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ -#ifdef VBOX_COM_INPROC - return GuestShCl::GetInst()->ReportFormatsToGuest(pClient, fFormats, SHCLSOURCE_LOCAL); -#endif - return ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); + return pConn->reportLocalFormats(fFormats); } /** @@ -348,25 +609,25 @@ static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMA * * Schedules a request to the X11 event thread. * - * @note We always fail or complete asynchronously. + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param uFormat Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required byte count. */ -int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, SHCLFORMAT uFormat, - void *pvData, uint32_t cbData, uint32_t *pcbActual) +static int shClBackendX11ReadData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, uint32_t *pcbActual) { - RT_NOREF(pBackend); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); - AssertPtrReturn(pvData, VERR_INVALID_POINTER); - AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); - - RT_NOREF(pCmdCtx); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); + AssertPtrReturn(pvData, VERR_INVALID_POINTER); + AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); - LogFlowFunc(("pClient=%p, uFormat=%#x, pv=%p, cb=%RU32, pcbActual=%p\n", - pClient, uFormat, pvData, cbData, pcbActual)); + LogFlowFunc(("pConn=%p, uFormat=%#x, pv=%p, cb=%RU32, pcbActual=%p\n", + pCtx->pConn, uFormat, pvData, cbData, pcbActual)); uint32_t cbRead; - int vrc = ShClX11ReadDataFromX11(&pClient->State.pCtx->X11, &pClient->EventSrc, + int vrc = ShClX11ReadDataFromX11(&pCtx->X11, &pCtx->EventSrc, SHCL_TIMEOUT_DEFAULT_MS, uFormat, pvData, cbData, &cbRead); if (RT_SUCCESS(vrc)) { @@ -381,10 +642,18 @@ int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTC return vrc; } -int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, - SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +/** + * Writes guest clipboard data through the X11 backend. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param uFormat Clipboard format to write. + * @param pvData Data buffer. + * @param cbData Data size in bytes. + */ +static int shClBackendX11WriteData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) { - RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + RT_NOREF(pCtx, uFormat, pvData, cbData); LogFlowFuncEnter(); @@ -405,28 +674,242 @@ static DECLCALLBACK(int) shClSvcX11ReportFormatsCallback(PSHCLCONTEXT pCtx, uint LogFlowFunc(("pCtx=%p, fFormats=%#x\n", pCtx, fFormats)); - int vrc = VINF_SUCCESS; - PSHCLCLIENT pClient = pCtx->pClient; - AssertPtr(pClient); - - uint32_t uMode = pClient->State.uMode; - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) - { /* likely */ } - else - return VINF_SUCCESS; + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); - vrc = shClBackendReportFormatsToGuestAndMain(pClient, fFormats); + int const vrc = shClBackendX11ReportLocalFormats(pCtx->pConn, fFormats); LogFlowFuncLeaveRC(vrc); return vrc; } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP +/** + * Prepares and publishes one exact guest-to-host HTTP-backed URI-list offer. + * + * All guest communication and transfer waits happen on the preparation + * worker. The X11 event thread only observes the final cache-seeding format + * report and therefore never waits on the guest. + * + * @thread X11 transfer preparation worker. + */ +static int shClSvcX11TransferPrepare(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats, uint64_t uOfferGeneration) +{ + AssertReturn(fFormats & VBOX_SHCL_FMT_URI_LIST, VERR_INVALID_PARAMETER); + + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); + + /* Preserve the established protocol sequence by consuming the URI-list + * data reply before creating and initializing the file transfer. */ + void *pvData = NULL; + uint32_t cbData = 0; + int vrc = pCtx->pConn->readDataFromGuest(VBOX_SHCL_FMT_URI_LIST, &pvData, &cbData); + RTMemFree(pvData); + if ( RT_SUCCESS(vrc) + && !shClSvcX11TransferOfferIsCurrent(pCtx, uOfferGeneration)) + vrc = VERR_CANCELLED; + + PSHCLTRANSFER pTransfer = NULL; + if (RT_SUCCESS(vrc)) + { + SHCLTRANSFERCALLBACKS Callbacks; + shClBackendX11TransferGetCallbacks(pCtx, &Callbacks); + vrc = pCtx->pConn->transferCreate(SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, &Callbacks, + NIL_SHCLTRANSFERID, &pTransfer); + } + + SHCLTRANSFERID idTransfer = NIL_SHCLTRANSFERID; + SHCLTRANSFERGEN uGeneration = NIL_SHCLTRANSFERGEN; + if (RT_SUCCESS(vrc)) + { + idTransfer = ShClTransferGetID(pTransfer); + uGeneration = ShClTransferGetGeneration(pTransfer); + + int vrc2 = RTCritSectEnter(&pCtx->CritSect); + if (RT_SUCCESS(vrc2)) + { + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + if ( pX11TransferState->fPreparing + && pX11TransferState->uPreparingOfferGeneration == uOfferGeneration) + { + pX11TransferState->idTransfer = idTransfer; + pX11TransferState->uTransferGeneration = uGeneration; + } + vrc2 = RTCritSectLeave(&pCtx->CritSect); + } + if (RT_FAILURE(vrc2)) + vrc = vrc2; + } + + if (RT_SUCCESS(vrc)) + vrc = pCtx->pConn->transferInit(pTransfer); + if (RT_SUCCESS(vrc)) + { + /* Wait on this transfer object, never on global HTTP-server state. */ + vrc = ShClTransferWaitForStatus(pTransfer, SHCL_TIMEOUT_DEFAULT_MS, + SHCLTRANSFERSTATUS_INITIALIZED); + } + if ( RT_SUCCESS(vrc) + && !shClSvcX11TransferOfferIsCurrent(pCtx, uOfferGeneration)) + vrc = VERR_CANCELLED; + if (RT_SUCCESS(vrc)) + vrc = ShClTransferRootListRead(pTransfer); + if ( RT_SUCCESS(vrc) + && !ShClTransferRootsCount(pTransfer)) + vrc = VERR_SHCLPB_NO_DATA; + if (RT_SUCCESS(vrc)) + vrc = ShClTransferHttpServerRegisterTransfer(&pCtx->X11.HttpCtx.HttpServer, pTransfer); + + char *pszUriList = NULL; + size_t cbUriList = 0; + if (RT_SUCCESS(vrc)) + vrc = ShClTransferHttpConvertToStringList(&pCtx->X11.HttpCtx.HttpServer, pTransfer, + &pszUriList, &cbUriList); + if (RT_SUCCESS(vrc)) + { + if (!pszUriList || !cbUriList) + vrc = VERR_SHCLPB_NO_DATA; + else if (cbUriList > UINT32_MAX) + vrc = VERR_BUFFER_OVERFLOW; + } + + bool fPublished = false; + int vrc2 = RTCritSectEnter(&pCtx->CritSect); + if (RT_SUCCESS(vrc2)) + { + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + bool const fBoundTransfer = pTransfer + && pX11TransferState->fPreparing + && pX11TransferState->uPreparingOfferGeneration == uOfferGeneration + && shClSvcX11TransferKeyMatches(pX11TransferState->idTransfer, + pX11TransferState->uTransferGeneration, + pTransfer); + bool const fCurrentOffer = !pCtx->fShuttingDown + && pX11TransferState->uOfferGeneration == uOfferGeneration + && (pX11TransferState->fFormats & VBOX_SHCL_FMT_URI_LIST); + + if ( RT_SUCCESS(vrc) + && fBoundTransfer + && fCurrentOffer) + { + vrc = ShClX11ReportFormatsToX11AsyncEx(&pCtx->X11, pX11TransferState->fFormats, + VBOX_SHCL_FMT_URI_LIST, pszUriList, + (uint32_t)cbUriList); + if (RT_SUCCESS(vrc)) + { + pX11TransferState->idPublishedTransfer = idTransfer; + pX11TransferState->uPublishedTransferGeneration = uGeneration; + fPublished = true; + } + } + else if (RT_SUCCESS(vrc)) + vrc = VERR_CANCELLED; + + pX11TransferState->fPreparing = false; + shClSvcX11TransferPreparationResetKey(pX11TransferState); + + vrc2 = RTCritSectLeave(&pCtx->CritSect); + } + if (RT_SUCCESS(vrc)) + vrc = vrc2; + + RTStrFree(pszUriList); + + if (pTransfer) + { + /* pfnTransferCreate returns a retained transfer. Drop that + * ownership before a consuming destroy can wait for users. */ + ShClTransferRelease(pTransfer); + pTransfer = NULL; + } + + if ( !fPublished + && ShClTransferIdIsValid(idTransfer)) + pCtx->pConn->transferDestroyById(idTransfer); + + if (fPublished) + LogRel2(("Shared Clipboard: Advertised cached host X11 URI list for transfer %RU16/%RU64, offer generation %RU64\n", + idTransfer, uGeneration, uOfferGeneration)); + else if (vrc != VERR_CANCELLED) + LogRel(("Shared Clipboard: Preparing host X11 URI list for offer generation %RU64 failed with %Rrc\n", + uOfferGeneration, vrc)); + + return vrc; +} + +/** + * Persistent worker which serializes transfer preparation by clipboard offer + * generation. Multiple reports are coalesced and stale results are discarded. + */ +static DECLCALLBACK(int) shClSvcX11TransferPreparationThread(RTTHREAD hThreadSelf, void *pvUser) +{ + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pvUser; + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + + int vrc = RTThreadUserSignal(hThreadSelf); + AssertRCReturn(vrc, vrc); + + uint64_t uProcessedOfferGeneration = 0; + for (;;) + { + vrc = RTSemEventWait(pCtx->hX11TransferPreparationEvent, RT_INDEFINITE_WAIT); + if (RT_FAILURE(vrc)) + break; + + for (;;) + { + SHCLFORMATS fFormats; + uint64_t uOfferGeneration; + + vrc = RTCritSectEnter(&pCtx->CritSect); + if (RT_FAILURE(vrc)) + break; + + if (pCtx->fShuttingDown) + { + RTCritSectLeave(&pCtx->CritSect); + shClSvcX11TransferPublishedCancel(pCtx); + return VINF_SUCCESS; + } + + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + uOfferGeneration = pX11TransferState->uOfferGeneration; + if (uOfferGeneration == uProcessedOfferGeneration) + { + RTCritSectLeave(&pCtx->CritSect); + break; + } + + uProcessedOfferGeneration = uOfferGeneration; + fFormats = pX11TransferState->fFormats; + pX11TransferState->fPreparing = RT_BOOL(fFormats & VBOX_SHCL_FMT_URI_LIST); + pX11TransferState->uPreparingOfferGeneration = uOfferGeneration; + shClSvcX11TransferPreparationResetKey(pX11TransferState); + + vrc = RTCritSectLeave(&pCtx->CritSect); + if (RT_FAILURE(vrc)) + break; + + shClSvcX11TransferPublishedCancel(pCtx); + + if (fFormats & VBOX_SHCL_FMT_URI_LIST) + shClSvcX11TransferPrepare(pCtx, fFormats, uOfferGeneration); + } + + if (RT_FAILURE(vrc)) + break; + } + + shClSvcX11TransferPublishedCancel(pCtx); + LogRel(("Shared Clipboard: Host X11 transfer preparation worker failed with %Rrc\n", vrc)); + return vrc; +} +#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ + #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS /** * @copydoc SHCLTRANSFERCALLBACKS::pfnOnCreated * - * @thread Service main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static DECLCALLBACK(void) shClSvcX11TransferOnCreatedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { @@ -438,51 +921,43 @@ static DECLCALLBACK(void) shClSvcX11TransferOnCreatedCallback(PSHCLTRANSFERCALLB PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); - PSHCLCLIENT const pClient = pCtx->pClient; - AssertPtr(pClient); + AssertPtrReturnVoid(pCtx->pConn); /* * Set transfer provider. - * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. + * Those will be registered when a new transfer gets initialized. */ - /* Set the interface to the local provider by default first. */ - RT_ZERO(pClient->Transfers.Provider); - ShClTransferProviderLocalQueryInterface(&pClient->Transfers.Provider); + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); - PSHCLTXPROVIDERIFACE pIface = &pClient->Transfers.Provider.Interface; - - pClient->Transfers.Provider.enmSource = pClient->State.enmSource; - pClient->Transfers.Provider.pvUser = pClient; + int vrc = VINF_SUCCESS; switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* Guest -> Host. */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { - pIface->pfnRootListRead = ShClSvcTransferIfaceGHRootListRead; - - pIface->pfnListOpen = ShClSvcTransferIfaceGHListOpen; - pIface->pfnListClose = ShClSvcTransferIfaceGHListClose; - pIface->pfnListHdrRead = ShClSvcTransferIfaceGHListHdrRead; - pIface->pfnListEntryRead = ShClSvcTransferIfaceGHListEntryRead; - - pIface->pfnObjOpen = ShClSvcTransferIfaceGHObjOpen; - pIface->pfnObjClose = ShClSvcTransferIfaceGHObjClose; - pIface->pfnObjRead = ShClSvcTransferIfaceGHObjRead; + vrc = pCtx->pConn->transferProviderInitGuest(&Provider); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* Host -> Guest. */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { - pIface->pfnRootListRead = shClSvcX11TransferIfaceHGRootListRead; + ShClTransferProviderLocalQueryInterface(&Provider); + Provider.Interface.pfnRootListRead = shClSvcX11TransferIfaceHGRootListRead; + Provider.enmSource = SHCLSOURCE_LOCAL; + Provider.pvUser = pCtx; + Provider.cbUser = sizeof(*pCtx); break; } default: - AssertFailed(); + AssertFailedStmt(vrc = VERR_NOT_SUPPORTED); } - int vrc = ShClTransferSetProvider(pTransfer, &pClient->Transfers.Provider); RT_NOREF(vrc); + if (RT_SUCCESS(vrc)) + vrc = ShClTransferSetProvider(pTransfer, &Provider); + RT_NOREF(vrc); LogFlowFuncLeaveRC(vrc); } @@ -493,7 +968,7 @@ static DECLCALLBACK(void) shClSvcX11TransferOnCreatedCallback(PSHCLTRANSFERCALLB * For G->H: Starts the HTTP server if not done yet and registers the transfer with it. * For H->G: Called on transfer intialization to populate the transfer's root list. * - * @thread Service main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static DECLCALLBACK(int) shClSvcX11TransferOnInitCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { @@ -511,7 +986,7 @@ static DECLCALLBACK(int) shClSvcX11TransferOnInitCallback(PSHCLTRANSFERCALLBACKC switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { # ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP /* We only need to start the HTTP server when we actually receive data from the remote (host). */ @@ -520,7 +995,7 @@ static DECLCALLBACK(int) shClSvcX11TransferOnInitCallback(PSHCLTRANSFERCALLBACKC break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { vrc = ShClTransferRootListRead(pTransfer); /* Calls shClSvcX11TransferIfaceHGRootListRead(). */ break; @@ -540,7 +1015,7 @@ static DECLCALLBACK(int) shClSvcX11TransferOnInitCallback(PSHCLTRANSFERCALLBACKC * * This stops the HTTP server if not done yet. * - * @thread Service main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static DECLCALLBACK(void) shClSvcX11TransferOnDestroyCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { @@ -553,7 +1028,7 @@ static DECLCALLBACK(void) shClSvcX11TransferOnDestroyCallback(PSHCLTRANSFERCALLB PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_GUEST_TO_HOST) ShClTransferHttpServerMaybeStop(&pCtx->X11.HttpCtx); # else RT_NOREF(pCbCtx); @@ -570,11 +1045,11 @@ static DECLCALLBACK(void) shClSvcX11TransferOnDestroyCallback(PSHCLTRANSFERCALLB * @param pCtx Shared clipboard context to unregister transfer for. * @param pTransfer Transfer to unregister. * - * @thread Clipboard main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static void shClSvcX11HttpTransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer) { - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_GUEST_TO_HOST) { # ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP if (ShClTransferHttpServerIsInitialized(&pCtx->X11.HttpCtx.HttpServer)) @@ -595,12 +1070,30 @@ static void shClSvcX11HttpTransferUnregister(PSHCLCONTEXT pCtx, PSHCLTRANSFER pT * * Unregisters a (now) unregistered transfer from the HTTP server. * - * @thread Clipboard main thread. + * @thread Shared Clipboard service thread or X11 preparation worker. */ static DECLCALLBACK(void) shClSvcX11TransferOnUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, PSHCLTRANSFERCTX pTransferCtx) { RT_NOREF(pTransferCtx); - shClSvcX11HttpTransferUnregister((PSHCLCONTEXT)pCbCtx->pvUser, pCbCtx->pTransfer); + + PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; +# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP + int vrc = RTCritSectEnter(&pCtx->CritSect); + if (RT_SUCCESS(vrc)) + { + PSHCLX11TRANSFERSTATE pX11TransferState = &pCtx->X11TransferState; + if (shClSvcX11TransferKeyMatches(pX11TransferState->idTransfer, + pX11TransferState->uTransferGeneration, pCbCtx->pTransfer)) + shClSvcX11TransferPreparationResetKey(pX11TransferState); + if (shClSvcX11TransferKeyMatches(pX11TransferState->idPublishedTransfer, + pX11TransferState->uPublishedTransferGeneration, pCbCtx->pTransfer)) + shClSvcX11TransferPublishedResetKey(pX11TransferState); + + vrc = RTCritSectLeave(&pCtx->CritSect); + AssertRC(vrc); + } +# endif + shClSvcX11HttpTransferUnregister(pCtx, pCbCtx->pTransfer); } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ @@ -628,83 +1121,20 @@ static DECLCALLBACK(int) shClSvcX11RequestDataFromSourceCallback(PSHCLCONTEXT pC return VERR_WRONG_ORDER; } -#if defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) && !defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP if (uFmt == VBOX_SHCL_FMT_URI_LIST) { - *ppv = NULL; - *pcb = 0; - return VERR_NOT_SUPPORTED; + /* URI targets are advertised cache-only after the worker prepared the + * exact transfer. Never fall back to guest I/O on the X11 thread. */ + LogRelMax(16, ("Shared Clipboard: Host X11 URI-list conversion missed its prepared URI-list data cache\n")); + return VERR_SHCLPB_NO_DATA; } -#endif - - PSHCLCLIENT const pClient = pCtx->pClient; - int vrc = ShClSvcReadDataFromGuest(pClient, uFmt, ppv, pcb); - if (RT_FAILURE(vrc)) - return vrc; - - /* - * Note: We always return a generic URI list (as HTTP links) here. - * As we don't know which Atom target format was requested by the caller, the X11 clipboard codes needs - * to decide & transform the list into the actual clipboard Atom target format the caller wanted. - */ -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +#elif defined(VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS) if (uFmt == VBOX_SHCL_FMT_URI_LIST) - { - PSHCLTRANSFER pTransfer = NULL; - vrc = ShClSvcTransferCreate(pClient, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, - NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); - if (RT_SUCCESS(vrc)) - { - /* Initialize the transfer on the host side. */ - vrc = ShClSvcTransferInit(pClient, pTransfer); - } - - if (RT_SUCCESS(vrc)) - { - /* We have to wait for the guest reporting the transfer as being initialized. - * Only then we can start reading stuff. */ - vrc = ShClTransferWaitForStatus(pTransfer, SHCL_TIMEOUT_DEFAULT_MS, SHCLTRANSFERSTATUS_INITIALIZED); - if (RT_SUCCESS(vrc)) - { - vrc = ShClTransferRootListRead(pTransfer); - if (RT_SUCCESS(vrc)) - { -# ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP - /* As soon as we register the transfer with the HTTP server, the transfer needs to have its roots set. */ - PSHCLHTTPSERVER const pHttpSrv = &pCtx->X11.HttpCtx.HttpServer; - vrc = ShClTransferHttpServerRegisterTransfer(pHttpSrv, pTransfer); - if (RT_SUCCESS(vrc)) - { - char *pszData; - size_t cbData; - vrc = ShClTransferHttpConvertToStringList(pHttpSrv, pTransfer, &pszData, &cbData); - if (RT_SUCCESS(vrc)) - { - RTMemFree(*ppv); - *ppv = pszData; - *pcb = cbData; - /* ppv has ownership of pszData now. */ - } - } -# else - vrc = VERR_NOT_SUPPORTED; -# endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS_HTTP */ - } - } - } - - if ( RT_FAILURE(vrc) - && pTransfer) - ShClSvcTransferDestroy(pClient, pTransfer); + return VERR_NOT_SUPPORTED; +#endif - if (RT_FAILURE(vrc)) - { - RTMemFree(*ppv); - *ppv = NULL; - *pcb = 0; - } - } -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + int const vrc = pCtx->pConn->readDataFromGuest(uFmt, ppv, pcb); if (RT_FAILURE(vrc)) LogRel(("Shared Clipboard: Requesting X11 data in format %#x from guest failed with %Rrc\n", uFmt, vrc)); @@ -714,17 +1144,24 @@ static DECLCALLBACK(int) shClSvcX11RequestDataFromSourceCallback(PSHCLCONTEXT pC } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# ifndef UNIT_TEST /** * Handles transfer status replies from the guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param pTransfer Transfer whose status changed. + * @param enmSource Endpoint which supplied the reply. + * @param enmStatus New transfer status. + * @param rcStatus Status-specific VBox status code. */ -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus) +static int shClBackendX11TransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) { - RT_NOREF(pBackend, pClient, enmSource, rcStatus); - - PSHCLCONTEXT pCtx = pClient->State.pCtx; RT_NOREF(pCtx); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pTransfer, VERR_INVALID_POINTER); + RT_NOREF(enmSource, rcStatus); - if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_FROM_REMOTE) /* Guest -> Host */ + if (ShClTransferGetDir(pTransfer) == SHCLTRANSFERDIR_GUEST_TO_HOST) { switch (enmStatus) { @@ -750,7 +1187,6 @@ int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pCli return VINF_SUCCESS; } -# endif /* !UNIT_TEST */ /********************************************************************************************************************************* @@ -762,16 +1198,15 @@ static DECLCALLBACK(int) shClSvcX11TransferIfaceHGRootListRead(PSHCLTXPROVIDERCT { LogFlowFuncEnter(); - PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; - AssertPtr(pClient); - - AssertPtr(pClient->State.pCtx); - PSHCLX11CTX pX11 = &pClient->State.pCtx->X11; + PSHCLCONTEXT pBackendCtx = (PSHCLCONTEXT)pCtx->pvUser; + AssertPtrReturn(pBackendCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pBackendCtx->pConn, VERR_INVALID_POINTER); + PSHCLX11CTX pX11 = &pBackendCtx->X11; /* X supplies the data asynchronously, so we need to wait for data to arrive first. */ void *pvData; uint32_t cbData; - int vrc = ShClX11ReadDataFromX11Ex(pX11, &pClient->EventSrc, SHCL_TIMEOUT_DEFAULT_MS, VBOX_SHCL_FMT_URI_LIST, + int vrc = ShClX11ReadDataFromX11Ex(pX11, &pBackendCtx->EventSrc, SHCL_TIMEOUT_DEFAULT_MS, VBOX_SHCL_FMT_URI_LIST, &pvData, &cbData); if (RT_SUCCESS(vrc)) { @@ -780,16 +1215,45 @@ static DECLCALLBACK(int) shClSvcX11TransferIfaceHGRootListRead(PSHCLTXPROVIDERCT LogRelMax2(16, ("Shared Clipboard: Host reported %RU64 X11 root entries for transfer to guest\n", ShClTransferRootsCount(pCtx->pTransfer))); else - LogRelMax2(16, ("Shared Clipboard: Converting X11 URI-list clipboard data (%RU32 bytes) to transfer roots failed with %Rrc\n", + LogRelMax(16, ("Shared Clipboard: Converting X11 URI-list clipboard data (%RU32 bytes) to transfer roots failed with %Rrc\n", cbData, vrc)); RTMemFree(pvData); } else - LogRelMax2(16, ("Shared Clipboard: Reading X11 URI-list clipboard data for transfer failed with %Rrc\n", vrc)); + LogRelMax(16, ("Shared Clipboard: Reading X11 URI-list clipboard data for transfer failed with %Rrc\n", vrc)); LogFlowFuncLeaveRC(vrc); return vrc; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + +/** Native X11 Shared Clipboard backend operations. */ +static SHCLBACKENDOPS const s_ShClBackendX11Ops = +{ + shClBackendX11Init, + shClBackendX11Destroy, + shClBackendX11SetCallbacks, + shClBackendX11Connect, + shClBackendX11Disconnect, + shClBackendX11ReportFormats, + shClBackendX11ReadData, + shClBackendX11WriteData, + shClBackendX11Sync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClBackendX11TransferGetCallbacks, + shClBackendX11TransferHandleStatusReply, +#endif +}; + + +/** + * Returns the native X11 Shared Clipboard backend operations. + * + * @returns Immutable X11 backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &s_ShClBackendX11Ops; +} diff --git a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp index 42789ce24347..b886d4bdd976 100644 --- a/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp +++ b/src/VBox/Main/src-client/win/ClipboardBackendWin.cpp @@ -1,4 +1,4 @@ -/* $Id: ClipboardBackendWin.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ +/* $Id: ClipboardBackendWin.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ /** @file * Shared Clipboard Service - Win32 host. */ @@ -34,7 +34,9 @@ #include #include -#include +#include "GuestShClBackend.h" +#include "../GuestShClBackendPrivate.h" +#include "GuestShClConn.h" #include #include #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS @@ -55,26 +57,21 @@ #include #include /* Needed for shell objects. */ -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# include "VBoxSharedClipboardSvc-transfers.h" -#endif -#ifdef VBOX_COM_INPROC -# include "GuestShClPrivate.h" -#endif - /********************************************************************************************************************************* * Structures and Typedefs * *********************************************************************************************************************************/ /** - * Global context information used by the host glue for the X11 clipboard backend. + * Global context information used by the host glue for the Windows clipboard backend. */ struct SHCLCONTEXT { /** Handle for window message handling thread. */ RTTHREAD hThread; - /** Structure for keeping and communicating with service client. */ - PSHCLCLIENT pClient; + /** Result of initializing the window message handling thread. */ + int vrcThreadStartup; + /** Main connection to the Shared Clipboard service. */ + GuestShClConn *pConn; /** Windows-specific context data. */ SHCLWINCTX Win; }; @@ -84,9 +81,11 @@ struct SHCLCONTEXT * Prototypes * *********************************************************************************************************************************/ static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx); +static int shClBackendWinThreadStop(PSHCLCONTEXT pCtx, bool fWaitDiagnostic); #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS static DECLCALLBACK(int) shClSvcWinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCTX pCtx); +static void shClBackendWinTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks); #endif @@ -178,7 +177,7 @@ static int vboxClipboardSvcWinDataGet(SHCLFORMAT u32Format, const void *pvSrc, u */ static int vboxClipboardSvcWinReadDataFromGuestWorker(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void **ppvData, uint32_t *pcbData) { - return ShClSvcReadDataFromGuest(pCtx->pClient, uFmt, ppvData, pcbData); + return pCtx->pConn->readDataFromGuest(uFmt, ppvData, pcbData); } static int vboxClipboardSvcWinReadDataFromGuest(PSHCLCONTEXT pCtx, UINT uWinFormat, void **ppvData, uint32_t *pcbData) @@ -234,45 +233,33 @@ static DECLCALLBACK(void) shClSvcWinTransferOnCreatedCallback(PSHCLTRANSFERCALLB PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; AssertPtr(pTransfer); - PSHCLCLIENT const pClient = pCtx->pClient; - AssertPtr(pClient); + AssertPtrReturnVoid(pCtx->pConn); /* * Set transfer provider. - * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. + * Those will be registered when a new transfer gets initialized. */ - /* Set the interface to the local provider by default first. */ - RT_ZERO(pClient->Transfers.Provider); - ShClTransferProviderLocalQueryInterface(&pClient->Transfers.Provider); - - PSHCLTXPROVIDERIFACE pIface = &pClient->Transfers.Provider.Interface; - - pClient->Transfers.Provider.enmSource = pClient->State.enmSource; - pClient->Transfers.Provider.pvUser = pClient; + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); int vrc = VINF_SUCCESS; switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { - pIface->pfnRootListRead = ShClSvcTransferIfaceGHRootListRead; - - pIface->pfnListOpen = ShClSvcTransferIfaceGHListOpen; - pIface->pfnListClose = ShClSvcTransferIfaceGHListClose; - pIface->pfnListHdrRead = ShClSvcTransferIfaceGHListHdrRead; - pIface->pfnListEntryRead = ShClSvcTransferIfaceGHListEntryRead; - - pIface->pfnObjOpen = ShClSvcTransferIfaceGHObjOpen; - pIface->pfnObjClose = ShClSvcTransferIfaceGHObjClose; - pIface->pfnObjRead = ShClSvcTransferIfaceGHObjRead; + vrc = pCtx->pConn->transferProviderInitGuest(&Provider); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { - pIface->pfnRootListRead = shClSvcWinTransferIfaceHGRootListRead; + ShClTransferProviderLocalQueryInterface(&Provider); + Provider.Interface.pfnRootListRead = shClSvcWinTransferIfaceHGRootListRead; + Provider.enmSource = SHCLSOURCE_LOCAL; + Provider.pvUser = pCtx; + Provider.cbUser = sizeof(*pCtx); break; } @@ -283,7 +270,7 @@ static DECLCALLBACK(void) shClSvcWinTransferOnCreatedCallback(PSHCLTRANSFERCALLB if (RT_SUCCESS(vrc)) { - vrc = ShClTransferSetProvider(pTransfer, &pClient->Transfers.Provider); + vrc = ShClTransferSetProvider(pTransfer, &Provider); if (RT_SUCCESS(vrc)) vrc = ShClWinTransferCreate(&pCtx->Win, pTransfer); } @@ -313,13 +300,13 @@ static DECLCALLBACK(int) shClSvcWinTransferOnInitializeCallback(PSHCLTRANSFERCAL switch (ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { vrc = ShClWinTransferInitialize(&pCtx->Win, pTransfer); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: { vrc = ShClTransferRootListRead(pTransfer); /* Calls shClSvcWinTransferIfaceHGRootListRead(). */ break; @@ -338,12 +325,12 @@ static DECLCALLBACK(int) shClSvcWinTransferOnInitializeCallback(PSHCLTRANSFERCAL * @copydoc SHCLTRANSFERCALLBACKS::pfnOnInitialized * * Called by ShClTransferInit via VbglR3. - * For H->G: Called on transfer intialization to start the data transfer for the "in-flight" IDataObject. - * For G->H: Nothing to do here. + * For G->H: Starts the data transfer for the "in-flight" IDataObject. + * For H->G: Nothing to do here. * * @thread Clipboard main thread. */ -static DECLCALLBACK(void) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +static DECLCALLBACK(int) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) { LogFlowFuncEnter(); @@ -357,13 +344,13 @@ static DECLCALLBACK(void) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERC switch(ShClTransferGetDir(pTransfer)) { - case SHCLTRANSFERDIR_FROM_REMOTE: /* H->G */ + case SHCLTRANSFERDIR_GUEST_TO_HOST: { vrc = ShClWinTransferStart(&pCtx->Win, pTransfer); break; } - case SHCLTRANSFERDIR_TO_REMOTE: /* G->H */ + case SHCLTRANSFERDIR_HOST_TO_GUEST: break; default: @@ -371,6 +358,26 @@ static DECLCALLBACK(void) shClSvcWinTransferOnInitializedCallback(PSHCLTRANSFERC } LogFlowFuncLeaveRC(vrc); + return vrc; +} + +/** + * @copydoc SHCLTRANSFERCALLBACKS::pfnOnUnregistered + * + * Disables callbacks on the IDataObject and drops its long-lived transfer + * reference before consuming teardown waits for temporary transfer users. + * + * @thread Service main thread. + */ +static DECLCALLBACK(void) shClSvcWinTransferOnUnregisteredCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx, + PSHCLTRANSFERCTX pTransferCtx) +{ + RT_NOREF(pTransferCtx); + + PSHCLTRANSFER pTransfer = pCbCtx->pTransfer; + AssertPtr(pTransfer); + + ShClWinTransferUnregister(pTransfer); } /** @@ -406,15 +413,23 @@ static DECLCALLBACK(int) shClSvcWinDataObjectTransferBeginCallback(ShClWinDataOb PSHCLCONTEXT pCtx = (PSHCLCONTEXT)pCbCtx->pvUser; AssertPtr(pCtx); + SHCLTRANSFERCALLBACKS Callbacks; + shClBackendWinTransferGetCallbacks(pCtx, &Callbacks); + PSHCLTRANSFER pTransfer; - int vrc = ShClSvcTransferCreate(pCtx->pClient, SHCLTRANSFERDIR_FROM_REMOTE, SHCLSOURCE_REMOTE, - NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); + int vrc = pCtx->pConn->transferCreate(SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, &Callbacks, + NIL_SHCLTRANSFERID /* Creates a new transfer ID */, &pTransfer); if (RT_SUCCESS(vrc)) { + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + /* Initialize the transfer on the host side. */ - vrc = ShClSvcTransferInit(pCtx->pClient, pTransfer); + vrc = pCtx->pConn->transferInit(pTransfer); + ShClTransferRelease(pTransfer); + pTransfer = NULL; + if (RT_FAILURE(vrc)) - ShClSvcTransferDestroy(pCtx->pClient, pTransfer); + pCtx->pConn->transferDestroyById(idTransfer); } LogFlowFuncLeaveRC(vrc); @@ -527,7 +542,7 @@ static LRESULT CALLBACK vboxClipboardSvcWinWndProcMain(PSHCLCONTEXT pCtx, RTStrFree(pszFmts); } if ( uFmtVBox == VBOX_SHCL_FMT_NONE - || pCtx->pClient == NULL) + || !pCtx->pConn) { /* Unsupported clipboard format is requested. */ LogFunc(("WM_RENDERFORMAT unsupported format requested or client is not active\n")); @@ -537,7 +552,7 @@ static LRESULT CALLBACK vboxClipboardSvcWinWndProcMain(PSHCLCONTEXT pCtx, { void *pvData = NULL; uint32_t cbData = 0; - int vrc = ShClSvcReadDataFromGuest(pCtx->pClient, uFmtVBox, &pvData, &cbData); + int vrc = pCtx->pConn->readDataFromGuest(uFmtVBox, &pvData, &cbData); if (RT_SUCCESS(vrc)) { /* Wrap HTML clipboard content info CF_HTML format if needed. */ @@ -748,8 +763,9 @@ DECLCALLBACK(int) vboxClipboardSvcWinThread(RTTHREAD hThreadSelf, void *pvUser) LogRel(("Shared Clipboard: Initialized window thread OLE\n")); } #endif + pCtx->vrcThreadStartup = vrc; int vrc2 = RTThreadUserSignal(hThreadSelf); - AssertRC(vrc2); + AssertFatalMsgRC(vrc2, ("Signalling Windows clipboard thread startup failed with %Rrc\n", vrc2)); fThreadSignalled = true; @@ -785,21 +801,84 @@ DECLCALLBACK(int) vboxClipboardSvcWinThread(RTTHREAD hThreadSelf, void *pvUser) if (!fThreadSignalled) { + pCtx->vrcThreadStartup = vrc; int vrc2 = RTThreadUserSignal(hThreadSelf); - AssertRC(vrc2); + AssertFatalMsgRC(vrc2, ("Signalling terminal Windows clipboard thread startup failed with %Rrc\n", vrc2)); } LogFlowFuncLeaveRC(vrc); return vrc; } -static int shClBackendReportFormatsToGuestAndMain(PSHCLCLIENT pClient, SHCLFORMATS fFormats) +/** + * Stops and joins the Windows clipboard worker. + * + * @returns Worker result. + * @param pCtx Backend context whose worker to stop. + * @param fWaitDiagnostic Whether to log a finite diagnostic wait before + * waiting indefinitely. + */ +static int shClBackendWinThreadStop(PSHCLCONTEXT pCtx, bool fWaitDiagnostic) { -#ifdef VBOX_COM_INPROC - return GuestShCl::GetInst()->ReportFormatsToGuest(pClient, fFormats, SHCLSOURCE_LOCAL); -#else - return ShClBackendReportFormatsToGuest(pClient->pBackend, pClient, fFormats); -#endif + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertReturn(pCtx->hThread != NIL_RTTHREAD, VERR_INVALID_STATE); + + RTNATIVETHREAD const idNativeThread = RTThreadGetNative(pCtx->hThread); + AssertFatal(idNativeThread != NIL_RTNATIVETHREAD); + + /* A context without a window is on a terminal startup/exit path and does not enter GetMessage(). */ + bool fStopPosted = pCtx->Win.hWnd == NULL; + DWORD dwWindowPostError = ERROR_SUCCESS; + if (pCtx->Win.hWnd) + { + fStopPosted = RT_BOOL(PostMessage(pCtx->Win.hWnd, WM_DESTROY, 0 /* wParam */, 0 /* lParam */)); + if (!fStopPosted) + dwWindowPostError = GetLastError(); + } + + DWORD dwThreadPostError = ERROR_SUCCESS; + if (!fStopPosted) + { + fStopPosted = RT_BOOL(PostThreadMessage((DWORD)idNativeThread, WM_QUIT, 0 /* wParam */, 0 /* lParam */)); + if (!fStopPosted) + dwThreadPostError = GetLastError(); + } + + int vrcThread = VERR_IPE_UNINITIALIZED_STATUS; + if (!fStopPosted) + { + /* Posting can legitimately lose a race with an already exiting worker. */ + int const vrcWait = RTThreadWait(pCtx->hThread, RT_MS_30SEC /* cMillies */, &vrcThread); + if (RT_SUCCESS(vrcWait)) + { + pCtx->hThread = NIL_RTTHREAD; + return vrcThread; + } + + AssertFatalMsgFailed(("Could not stop Windows clipboard thread %RTnthrd: PostMessage error %u," + " PostThreadMessage error %u, wait status %Rrc\n", + idNativeThread, dwWindowPostError, dwThreadPostError, vrcWait)); + } + + int vrcWait; + if (fWaitDiagnostic) + { + vrcWait = RTThreadWait(pCtx->hThread, RT_MS_30SEC /* cMillies */, &vrcThread); + if (RT_FAILURE(vrcWait)) + { + LogRel(("Shared Clipboard: Windows clipboard thread did not terminate promptly (%Rrc); waiting indefinitely\n", + vrcWait)); + vrcWait = RTThreadWait(pCtx->hThread, RT_INDEFINITE_WAIT, &vrcThread); + } + } + else + vrcWait = RTThreadWait(pCtx->hThread, RT_INDEFINITE_WAIT, &vrcThread); + + /* Main releases the client after disconnect, so allowing this worker to survive is never safe. */ + AssertFatalMsgRC(vrcWait, ("Reaping Windows clipboard thread %RTnthrd failed with %Rrc\n", + idNativeThread, vrcWait)); + pCtx->hThread = NIL_RTTHREAD; + return vrcThread; } /** @@ -817,12 +896,12 @@ static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx) int vrc; - if (pCtx->pClient) + if (pCtx->pConn) { SHCLFORMATS fFormats = 0; vrc = ShClWinGetFormats(&pCtx->Win, &fFormats); if (RT_SUCCESS(vrc)) - vrc = shClBackendReportFormatsToGuestAndMain(pCtx->pClient, fFormats); + vrc = pCtx->pConn->reportLocalFormats(fFormats); } else /* If we don't have any client data (yet), bail out. */ vrc = VINF_NO_CHANGE; @@ -835,9 +914,13 @@ static int vboxClipboardSvcWinSyncInternal(PSHCLCONTEXT pCtx) /********************************************************************************************************************************* * Backend implementation * *********************************************************************************************************************************/ -int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) +/** + * Initializes the process-wide Windows clipboard backend. + * + * @returns VBox status code. + */ +static int shClBackendWinInit(void) { - pBackend->pHelpers = pTable->pHelpers; #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS HRESULT hr = OleInitialize(NULL); if (FAILED(hr)) @@ -852,19 +935,29 @@ int ShClBackendInit(PSHCLBACKEND pBackend, VBOXHGCMSVCFNTABLE *pTable) return VINF_SUCCESS; } -void ShClBackendDestroy(PSHCLBACKEND pBackend) +/** + * Destroys the process-wide Windows clipboard backend. + */ +static void shClBackendWinDestroy(void) { - RT_NOREF(pBackend); - #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS OleSetClipboard(NULL); /* Make sure to flush the clipboard on destruction. */ OleUninitialize(); #endif } -int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadless) +/** + * Connects a Main service connection to the Windows clipboard backend. + * + * @returns VBox status code. + * @param pConn Main service connection to associate. + * @param ppCtx Where to return the allocated backend context. + */ +static int shClBackendWinConnect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) { - RT_NOREF(pBackend, fHeadless); + AssertPtrReturn(pConn, VERR_INVALID_POINTER); + AssertPtrReturn(ppCtx, VERR_INVALID_POINTER); + *ppCtx = NULL; LogFlowFuncEnter(); @@ -873,6 +966,9 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadles PSHCLCONTEXT pCtx = (PSHCLCONTEXT)RTMemAllocZ(sizeof(SHCLCONTEXT)); if (pCtx) { + pCtx->pConn = pConn; + pCtx->vrcThreadStartup = VERR_IPE_UNINITIALIZED_STATUS; + vrc = ShClWinCtxInit(&pCtx->Win); if (RT_SUCCESS(vrc)) { @@ -880,29 +976,47 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadles RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "ShClWin"); if (RT_SUCCESS(vrc)) { - int vrc2 = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */); - AssertRC(vrc2); + vrc = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */); + if (RT_FAILURE(vrc)) + { + int const vrcStartup = vrc; + LogRel(("Shared Clipboard: Waiting for the Windows clipboard thread to initialize failed with %Rrc;" + " waiting for it before tearing down the context\n", vrc)); + + /* The worker always signals after either creating its window or reaching a terminal startup failure. */ + int vrc2; + do + { + vrc2 = RTThreadUserWait(pCtx->hThread, RT_MS_30SEC); + if (vrc2 == VERR_TIMEOUT) + LogRel(("Shared Clipboard: Windows clipboard thread still has not completed startup;" + " waiting again\n")); + } while (vrc2 == VERR_TIMEOUT); + + AssertFatalMsgRC(vrc2, ("Waiting for Windows clipboard thread %RTnthrd to finish startup failed" + " with %Rrc\n", RTThreadGetNative(pCtx->hThread), vrc2)); + shClBackendWinThreadStop(pCtx, false /* fWaitDiagnostic */); + + /* Preserve the startup-wait error requested by the caller. */ + vrc = vrcStartup; + } + else if (RT_FAILURE(pCtx->vrcThreadStartup)) + { + /* The worker can keep its window loop alive after a terminal clipboard-chain initialization failure. */ + vrc = pCtx->vrcThreadStartup; + shClBackendWinThreadStop(pCtx, false /* fWaitDiagnostic */); + } } } - pClient->State.pCtx = pCtx; - pClient->State.pCtx->pClient = pClient; - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - /* - * Set callbacks. - * Those will be registered within ShClSvcTransferInit() when a new transfer gets initialized. - */ - RT_ZERO(pClient->Transfers.Callbacks); - - pClient->Transfers.Callbacks.pvUser = pCtx; /* Assign context as user-provided callback data. */ - pClient->Transfers.Callbacks.cbUser = sizeof(SHCLCONTEXT); - - pClient->Transfers.Callbacks.pfnOnCreated = shClSvcWinTransferOnCreatedCallback; - pClient->Transfers.Callbacks.pfnOnInitialize = shClSvcWinTransferOnInitializeCallback; - pClient->Transfers.Callbacks.pfnOnInitialized = shClSvcWinTransferOnInitializedCallback; - pClient->Transfers.Callbacks.pfnOnDestroy = shClSvcWinTransferOnDestroyCallback; -#endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + if (RT_SUCCESS(vrc)) + *ppCtx = pCtx; + else + { + AssertFatal(pCtx->hThread == NIL_RTTHREAD); + ShClWinCtxDestroy(&pCtx->Win); + RTMemFree(pCtx); + } } else vrc = VERR_NO_MEMORY; @@ -911,65 +1025,89 @@ int ShClBackendConnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, bool fHeadles return vrc; } -int ShClBackendSync(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Returns the Windows callbacks for a new transfer. + * + * @param pCtx Connected backend context. + * @param pCallbacks Where to return the callback table. + */ +static void shClBackendWinTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) { - RT_NOREF(pBackend); + AssertPtrReturnVoid(pCallbacks); + RT_ZERO(*pCallbacks); + AssertPtrReturnVoid(pCtx); + AssertPtrReturnVoid(pCtx->pConn); + + pCallbacks->pvUser = pCtx; + pCallbacks->cbUser = sizeof(*pCtx); + pCallbacks->pfnOnCreated = shClSvcWinTransferOnCreatedCallback; + pCallbacks->pfnOnInitialize = shClSvcWinTransferOnInitializeCallback; + pCallbacks->pfnOnInitialized = shClSvcWinTransferOnInitializedCallback; + pCallbacks->pfnOnUnregistered = shClSvcWinTransferOnUnregisteredCallback; + pCallbacks->pfnOnDestroy = shClSvcWinTransferOnDestroyCallback; +} +#endif +/** + * Synchronizes Windows clipboard state with a connected guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + */ +static int shClBackendWinSync(PSHCLCONTEXT pCtx) +{ /* Sync the host clipboard content with the client. */ - return vboxClipboardSvcWinSyncInternal(pClient->State.pCtx); + return vboxClipboardSvcWinSyncInternal(pCtx); } -int ShClBackendDisconnect(PSHCLBACKEND pBackend, PSHCLCLIENT pClient) +/** + * Disconnects and destroys a Windows clipboard backend context. + * + * @returns VBox status code. + * @param pCtx Backend context to disconnect. + */ +static int shClBackendWinDisconnect(PSHCLCONTEXT pCtx) { - RT_NOREF(pBackend); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFuncEnter(); int vrc = VINF_SUCCESS; - PSHCLCONTEXT pCtx = pClient->State.pCtx; - if (pCtx) + if (pCtx->hThread != NIL_RTTHREAD) { - if (pCtx->Win.hWnd) - PostMessage(pCtx->Win.hWnd, WM_DESTROY, 0 /* wParam */, 0 /* lParam */); - - if (pCtx->hThread != NIL_RTTHREAD) - { - LogFunc(("Waiting for thread to terminate ...\n")); - - /* Wait for the window thread to terminate. */ - vrc = RTThreadWait(pCtx->hThread, RT_MS_30SEC /* Timeout in ms */, NULL); - if (RT_FAILURE(vrc)) - LogRel(("Shared Clipboard: Waiting for window thread termination failed with vrc=%Rrc\n", vrc)); - - pCtx->hThread = NIL_RTTHREAD; - } - - ShClWinCtxDestroy(&pCtx->Win); + LogFunc(("Waiting for thread to terminate ...\n")); + vrc = shClBackendWinThreadStop(pCtx, true /* fWaitDiagnostic */); + } - if (RT_SUCCESS(vrc)) - { - RTMemFree(pCtx); - pCtx = NULL; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /* Disable an in-flight IDataObject callback before the consuming detach + * pass below. */ + ShClWinCtxDisableDataObjectCallbacks(&pCtx->Win); - pClient->State.pCtx = NULL; - } - } + /* Transfer callbacks retain pCtx as their user argument. */ + pCtx->pConn->transferDestroyAll(); +#endif + ShClWinCtxDestroy(&pCtx->Win); + RTMemFree(pCtx); LogFlowFuncLeaveRC(vrc); return vrc; } -int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) +/** + * Reports guest clipboard formats to Windows. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param fFormats Guest formats, VBOX_SHCL_FMT_XXX. + */ +static int shClBackendWinReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) { - RT_NOREF(pBackend); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - PSHCLCONTEXT pCtx = pClient->State.pCtx; AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); LogFlowFunc(("fFormats=0x%x, hWnd=%p\n", fFormats, pCtx->Win.hWnd)); @@ -982,59 +1120,28 @@ int ShClBackendReportFormats(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFOR return VINF_SUCCESS; } -int ShClBackendReportFormatsToGuest(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, SHCLFORMATS fFormats) -{ - RT_NOREF(pBackend); - - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - - int vrc; - - uint32_t uMode = pClient->State.uMode; - if ( uMode == VBOX_SHCL_MODE_BIDIRECTIONAL - || uMode == VBOX_SHCL_MODE_HOST_TO_GUEST) - { /* likely */ } - else - return VINF_SUCCESS; - - fFormats = shClSvcHandleFormats(true /* fHostToGuest */, pClient, fFormats); - - PSHCLCLIENTMSG pMsg = ShClSvcClientMsgAlloc(pClient, VBOX_SHCL_HOST_MSG_FORMATS_REPORT, 2); - if (pMsg) - { - HGCMSvcSetU32(&pMsg->aParms[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); - HGCMSvcSetU32(&pMsg->aParms[1], fFormats); - - ShClSvcClientLock(pClient); - - vrc = shClSvcClientMsgAddAndWakeupClient(pClient, pMsg); - - ShClSvcClientUnlock(pClient); - } - else - vrc = VERR_NO_MEMORY; - - LogFlowFuncLeaveRC(vrc); - return vrc; -} - -int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, - SHCLFORMAT uFmt, void *pvData, uint32_t cbData, uint32_t *pcbActual) +/** + * Reads data from the Windows clipboard. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param uFmt Clipboard format to read. + * @param pvData Destination buffer. + * @param cbData Destination buffer size in bytes. + * @param pcbActual Where to return the actual or required byte count. + */ +static int shClBackendWinReadData(PSHCLCONTEXT pCtx, SHCLFORMAT uFmt, void *pvData, uint32_t cbData, uint32_t *pcbActual) { - AssertPtrReturn(pClient, VERR_INVALID_POINTER); - AssertPtrReturn(pCmdCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx, VERR_INVALID_POINTER); + AssertPtrReturn(pCtx->pConn, VERR_INVALID_POINTER); AssertPtrReturn(pvData, VERR_INVALID_POINTER); AssertPtrReturn(pcbActual, VERR_INVALID_POINTER); - RT_NOREF(pBackend, pCmdCtx); - - AssertPtrReturn(pClient->State.pCtx, VERR_INVALID_POINTER); - LogFlowFunc(("uFmt=%#x\n", uFmt)); HANDLE hClip = NULL; - const PSHCLWINCTX pWinCtx = &pClient->State.pCtx->Win; + const PSHCLWINCTX pWinCtx = &pCtx->Win; /* * The guest wants to read data in the given format. @@ -1170,10 +1277,18 @@ int ShClBackendReadData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTC return vrc; } -int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENTCMDCTX pCmdCtx, - SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +/** + * Writes guest clipboard data through the Windows backend. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param uFormat Clipboard format to write. + * @param pvData Data buffer. + * @param cbData Data size in bytes. + */ +static int shClBackendWinWriteData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) { - RT_NOREF(pBackend, pClient, pCmdCtx, uFormat, pvData, cbData); + RT_NOREF(pCtx, uFormat, pvData, cbData); LogFlowFuncEnter(); @@ -1184,17 +1299,23 @@ int ShClBackendWriteData(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLCLIENT } #ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# ifndef UNIT_TEST /** * Handles transfer status replies from the guest. + * + * @returns VBox status code. + * @param pCtx Connected backend context. + * @param pTransfer Transfer whose status changed. + * @param enmSource Endpoint which supplied the reply. + * @param enmStatus New transfer status. + * @param rcStatus Status-specific VBox status code. */ -int ShClBackendTransferHandleStatusReply(PSHCLBACKEND pBackend, PSHCLCLIENT pClient, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus) +static int shClBackendWinTransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, SHCLSOURCE enmSource, + SHCLTRANSFERSTATUS enmStatus, int rcStatus) { - RT_NOREF(pBackend, pClient, pTransfer, enmSource, enmStatus, rcStatus); + RT_NOREF(pCtx, pTransfer, enmSource, enmStatus, rcStatus); return VINF_SUCCESS; } -# endif /* !UNIT_TEST */ /********************************************************************************************************************************* @@ -1206,11 +1327,9 @@ static DECLCALLBACK(int) shClSvcWinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCT { LogFlowFuncEnter(); - PSHCLCLIENT pClient = (PSHCLCLIENT)pCtx->pvUser; - AssertPtr(pClient); - - AssertPtr(pClient->State.pCtx); - PSHCLWINCTX pWin = &pClient->State.pCtx->Win; + PSHCLCONTEXT pBackendCtx = (PSHCLCONTEXT)pCtx->pvUser; + AssertPtrReturn(pBackendCtx, VERR_INVALID_POINTER); + PSHCLWINCTX pWin = &pBackendCtx->Win; int vrc = ShClWinTransferGetRootsFromClipboard(pWin, pCtx->pTransfer); @@ -1218,3 +1337,33 @@ static DECLCALLBACK(int) shClSvcWinTransferIfaceHGRootListRead(PSHCLTXPROVIDERCT return vrc; } #endif /* VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS */ + + +/** Native Windows Shared Clipboard backend operations. */ +static SHCLBACKENDOPS const s_ShClBackendWinOps = +{ + shClBackendWinInit, + shClBackendWinDestroy, + NULL, + shClBackendWinConnect, + shClBackendWinDisconnect, + shClBackendWinReportFormats, + shClBackendWinReadData, + shClBackendWinWriteData, + shClBackendWinSync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + shClBackendWinTransferGetCallbacks, + shClBackendWinTransferHandleStatusReply, +#endif +}; + + +/** + * Returns the native Windows Shared Clipboard backend operations. + * + * @returns Immutable Windows backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &s_ShClBackendWinOps; +} diff --git a/src/VBox/Main/src-server/ApplianceImplImport.cpp b/src/VBox/Main/src-server/ApplianceImplImport.cpp index 1e4f063ab377..2a23f00f66aa 100644 --- a/src/VBox/Main/src-server/ApplianceImplImport.cpp +++ b/src/VBox/Main/src-server/ApplianceImplImport.cpp @@ -1,4 +1,4 @@ -/* $Id: ApplianceImplImport.cpp 114705 2026-07-14 13:35:15Z alexander.eichner@oracle.com $ */ +/* $Id: ApplianceImplImport.cpp 115044 2026-08-17 11:41:07Z serkan.bayraktar@oracle.com $ */ /** @file * IAppliance and IVirtualSystem COM class implementations. */ @@ -381,9 +381,9 @@ HRESULT Appliance::interpret() /* The NVRAM file does not have a entry so we only need to check the OVF details. */ if (vsysThis.strNvramPath.isNotEmpty()) pNewDesc->i_addEntry(VirtualSystemDescriptionType_NVRAM, "", vsysThis.strNvramPath, vsysThis.strNvramPath); - /* Check if any of the serial ports is configured with mode raw file. */ if (vsysThis.pelmVBoxMachine) { + /* Check if any of the serial ports is configured with mode raw file. */ settings::SerialPortsList const &llSerialPorts = pNewDesc->m->pConfig->hardwareMachine.llSerialPorts; for (settings::SerialPortsList::const_iterator port_it = llSerialPorts.begin(); @@ -397,6 +397,18 @@ HRESULT Appliance::interpret() break; } } + /* Check if shared folders are configured. */ + if (!pNewDesc->m->pConfig->hardwareMachine.llSharedFolders.empty()) + { + i_addWarning(tr("Virtual appliance \"%s\" was configured with machine shared folder(s) " + "This setting will not be imported."), vsysThis.strName.c_str()); + } + /* Check if a custom NVRAM file path is configured. */ + if (pNewDesc->m->pConfig->hardwareMachine.nvramSettings.strNvramPath.isNotEmpty()) + { + i_addWarning(tr("Virtual appliance \"%s\" was configured with a custom NVRAM file path. " + "This setting will not be imported."), vsysThis.strName.c_str()); + } } /* Audio */ Utf8Str strSoundCard; @@ -5598,7 +5610,6 @@ void Appliance::i_importVBoxMachine(ComObjPtr &vsdescT strSrcFilePath.append(RTPATH_SLASH_STR); strSrcFilePath.append(stack.strNvramPath); } - /* The basename of the destination filename needs to be the VM's name in * order to match the VM's INvramStore::nonVolatileStorageFile attribute so * that EFI can find it when booting the VM. */ @@ -6134,6 +6145,8 @@ void Appliance::i_importVBoxMachine(ComObjPtr &vsdescT hrc = pNewMachine.createObject(); if (FAILED(hrc)) throw hrc; config.sanitizeImportedSerialPorts(); + config.sanitizeSharedFolderSettings(); + config.sanitizeImportedNvramSettings(); // this magic constructor fills the new machine object with the MachineConfig // instance that we created from the vbox:Machine @@ -6282,7 +6295,9 @@ void Appliance::i_importMachines(ImportStack &stack) std::list vsdeNvram = vsdescThis->i_findByType(VirtualSystemDescriptionType_NVRAM); if (!vsdeNvram.empty()) stack.strNvramPath = vsdeNvram.front()->strVBoxCurrent; - + /* Do not allow paths in nvram file name. */ + if (RTPathHasPath(stack.strNvramPath.c_str())) + stack.strNvramPath.setNull(); #ifdef VBOX_WITH_USB // USB controller std::list vsdeUSBController = diff --git a/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp b/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp index 2b32987cec6e..897be0d034af 100644 --- a/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp +++ b/src/VBox/Main/src-server/darwin/HostDnsServiceDarwin.cpp @@ -1,4 +1,4 @@ -/* $Id: HostDnsServiceDarwin.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: HostDnsServiceDarwin.cpp 115039 2026-08-14 14:46:06Z andreas.loeffler@oracle.com $ */ /** @file * Darwin specific DNS information fetching. */ @@ -241,6 +241,7 @@ int HostDnsServiceDarwin::updateInfo(void) { CFStringRef const serverAddressRef = (CFStringRef)CFArrayGetValueAtIndex(serverArrayRef, i); if (serverAddressRef) + { if (!queryCFStringAsUtf8Str(serverAddressRef, strTmp, _16K)) { LogRel(("HostDnsServiceDarwin: idx: %u: Failed to convert address.\n", i)); @@ -273,6 +274,7 @@ int HostDnsServiceDarwin::updateInfo(void) } else LogRel(("HostDnsServiceDarwin: line %u: bad nameserver address %s\n", i, strTmp.c_str())); + } } } @@ -313,4 +315,3 @@ void HostDnsServiceDarwin::Data::performShutdownCallback(void *pInfo) AssertPtrReturnVoid(pThis->m); ASMAtomicXchgBool(&pThis->m->m_fStop, true); } - diff --git a/src/VBox/Main/testcase/Makefile.kmk b/src/VBox/Main/testcase/Makefile.kmk index 880bdcd8542a..4c0cbeba5389 100644 --- a/src/VBox/Main/testcase/Makefile.kmk +++ b/src/VBox/Main/testcase/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114609 2026-07-03 15:22:37Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 115083 2026-08-19 12:04:11Z andreas.loeffler@oracle.com $ ## @file # Sub-Makefile for the VBox API testcases. # @@ -48,7 +48,9 @@ ifndef VBOX_ONLY_SDK $(if $(VBOX_WITH_GUEST_CONTROL),tstGuestCtrlPaths,) \ tstMediumLock \ tstSafeArray \ - tstClipboard \ + tstClipboardAPI \ + tstClipboardMain \ + tstClipboardMain2HostSvc \ tstRecording \ tstSettings \ tstBstr \ @@ -327,17 +329,133 @@ tstSafeArray_SOURCES = tstSafeArray.cpp # -# tstClipboard +# tstClipboardAPI # -tstClipboard_TEMPLATE = VBoxMainClientTstExe -tstClipboard_INCS = \ +tstClipboardAPI_TEMPLATE = VBoxMainTstExe +tstClipboardAPI_INCS = \ ../include \ - $(PATH_OUT)/obj/VBoxAPIWrap \ - $(PATH_OUT)/obj/Main -tstClipboard_SOURCES = \ - tstClipboard.cpp + $(PATH_OUT)/obj/Main \ + $(VBOX_MAIN_APIWRAPPER_INCS) +tstClipboardAPI_DEFS = UNIT_TEST VBOX_COM_INPROC VBOX_WITH_SHARED_CLIPBOARD +tstClipboardAPI_SOURCES = \ + ../src-all/AutoCaller.cpp \ + ../src-all/EventImpl.cpp \ + ../src-all/GlobalStatusConversion.cpp \ + ../src-all/ObjectsTracker.cpp \ + ../src-all/VirtualBoxBase.cpp \ + ../src-all/VirtualBoxErrorInfoImpl.cpp \ + ../src-client/ClipboardFormatImpl.cpp \ + ../src-client/ClipboardItemImpl.cpp \ + ../src-client/ClipboardSessionImpl.cpp \ + ../src-client/HostClipboardImpl.cpp \ + $(PATH_OUT)/obj/Main/VBoxEvents.cpp \ + tstClipboardAPI.cpp +tstClipboardAPI_LIBS = \ + $(PATH_STAGE_LIB)/VBoxAPIWrap$(VBOX_SUFF_LIB) +tstClipboardAPI_INTERMEDIATES = $(VBOX_MAIN_APIWRAPPER_GEN_HDRS) +ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardAPI_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardAPI_SOURCES += \ + ../src-all/ProgressImpl.cpp \ + ../src-client/ClipboardTransferDataImpl.cpp \ + ../src-client/ClipboardTransferDirectoryImpl.cpp \ + ../src-client/ClipboardTransferFileImpl.cpp \ + ../src-client/ClipboardTransferFsObjInfoImpl.cpp \ + ../src-client/ClipboardTransferImpl.cpp \ + ../src-client/ClipboardTransferManagerImpl.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers-provider-local.cpp +endif + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_SDK) + tstClipboardAPI_CLEAN = $(tstClipboardAPI_0_OUTDIR)/tstClipboardAPI.run + + $$(tstClipboardAPI_0_OUTDIR)/tstClipboardAPI.run: $$(tstClipboardAPI_1_STAGE_TARGET) | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstClipboardAPI_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardAPI_0_OUTDIR)/tstClipboardAPI.run + endif +endif + + +# +# tstClipboardMain +# +tstClipboardMain_TEMPLATE = VBoxMainTstExe +tstClipboardMain_INCS = \ + ../include \ + ../src-client +tstClipboardMain_DEFS = VBOX_WITH_SHARED_CLIPBOARD +tstClipboardMain_SOURCES = \ + ../src-client/GuestShClBackend.cpp \ + ../src-client/GuestShClConn.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ + tstClipboardMain.cpp ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - tstClipboard_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardMain_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +endif + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_SDK) + tstClipboardMain_CLEAN = $(tstClipboardMain_0_OUTDIR)/tstClipboardMain.run + + $$(tstClipboardMain_0_OUTDIR)/tstClipboardMain.run: $$(tstClipboardMain_1_STAGE_TARGET) | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstClipboardMain_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardMain_0_OUTDIR)/tstClipboardMain.run + endif +endif + + +# +# tstClipboardMain2HostSvc +# +tstClipboardMain2HostSvc_TEMPLATE = VBoxMainTstExe +tstClipboardMain2HostSvc_INCS = \ + ../include \ + ../src-client \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard +tstClipboardMain2HostSvc_DEFS = \ + UNIT_TEST \ + VBOX_WITH_HGCM \ + VBOX_WITH_SHARED_CLIPBOARD \ + VBOX_WITH_SHARED_CLIPBOARD_HOST +tstClipboardMain2HostSvc_SOURCES = \ + ../src-client/GuestShClBackend.cpp \ + ../src-client/GuestShClConn.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-ext.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-client.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-host.cpp \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transport.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-common.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-helper.cpp \ + tstClipboardMain2HostSvc.cpp +ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardMain2HostSvc_DEFS += VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardMain2HostSvc_SOURCES += \ + $(PATH_ROOT)/src/VBox/HostServices/SharedClipboard/VBoxSharedClipboardSvc-transfers.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-path.cpp \ + $(PATH_ROOT)/src/VBox/GuestHost/SharedClipboard/clipboard-transfers.cpp +endif + +if defined(VBOX_WITH_TESTCASES) && !defined(VBOX_ONLY_SDK) + tstClipboardMain2HostSvc_CLEAN = $(tstClipboardMain2HostSvc_0_OUTDIR)/tstClipboardMain2HostSvc.run + + $$(tstClipboardMain2HostSvc_0_OUTDIR)/tstClipboardMain2HostSvc.run: $$(tstClipboardMain2HostSvc_1_STAGE_TARGET) | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstClipboardMain2HostSvc_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstClipboardMain2HostSvc_0_OUTDIR)/tstClipboardMain2HostSvc.run + endif endif diff --git a/src/VBox/Main/testcase/tstClipboard.cpp b/src/VBox/Main/testcase/tstClipboard.cpp deleted file mode 100644 index de196c477a92..000000000000 --- a/src/VBox/Main/testcase/tstClipboard.cpp +++ /dev/null @@ -1,2998 +0,0 @@ -/* $Id: tstClipboard.cpp 114632 2026-07-07 15:27:30Z andreas.loeffler@oracle.com $ */ -/** @file - * Main API Testcase - Clipboard. - */ - -/* - * Copyright (C) 2026 Oracle and/or its affiliates. - * - * This file is part of VirtualBox base platform packages, as - * available from https://www.virtualbox.org. - * - * This program is free software; you can redistribute it and/or - * modify it under the terms of the GNU General Public License - * as published by the Free Software Foundation, in version 3 of the - * License. - * - * This program is distributed in the hope that it will be useful, but - * WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU - * General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, see . - * - * SPDX-License-Identifier: GPL-3.0-only - */ - - -/********************************************************************************************************************************* -* Header Files * -*********************************************************************************************************************************/ -#include -#include -#include -#include -#include -#include -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS -# include -#endif -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include - -using namespace com; - - -/********************************************************************************************************************************* -* Global Variables * -*********************************************************************************************************************************/ -/** The testcase logger. */ -static PRTLOGGER g_pLogger; - - -/********************************************************************************************************************************* -* Internal Functions * -*********************************************************************************************************************************/ -/** - * Creates a clipboard format object through the public Main API. - * - * @returns COM status code. - * @param pClipboard Clipboard API object to use. - * @param pszMimeType MIME type for the format object. - * @param ptrFormat Where to return the created format object. - */ -static HRESULT tstCreateFormat(IClipboard *pClipboard, const char *pszMimeType, ComPtr &ptrFormat) -{ - AssertPtrReturn(pClipboard, E_POINTER); - return pClipboard->CreateFormat(Bstr(pszMimeType).raw(), ptrFormat.asOutParam()); -} - - -/** - * Creates a clipboard item object through the public Main API. - * - * @returns COM status code. - * @param pClipboard Clipboard API object to use. - * @param enmSource Clipboard source. - * @param ptrFormat Clipboard format object. - * @param abData Payload bytes. - * @param ptrItem Where to return the created item object. - */ -static HRESULT tstCreateItem(IClipboard *pClipboard, ClipboardSource_T enmSource, const ComPtr &ptrFormat, - const std::vector &abData, ComPtr &ptrItem) -{ - AssertPtrReturn(pClipboard, E_POINTER); - - SafeArray aBuffer; - if (!abData.empty()) - { - HRESULT hrc = aBuffer.initFrom(&abData[0], abData.size()); - if (FAILED(hrc)) - return hrc; - } - - return pClipboard->CreateItem(enmSource, ptrFormat, ComSafeArrayAsInParam(aBuffer), ptrItem.asOutParam()); -} - - - -/** - * Checks whether a byte SafeArray contains the expected bytes. - * - * @returns true if the buffers match, false otherwise. - * @param aBuffer Buffer to check. - * @param abExpected Expected payload bytes. - */ -static bool tstByteArrayEquals(const SafeArray &aBuffer, const std::vector &abExpected) -{ - if (aBuffer.size() != abExpected.size()) - return false; - if (abExpected.empty()) - return true; - return !memcmp(aBuffer.raw(), &abExpected[0], abExpected.size()); -} - - -/** - * Creates deterministic byte test data from a NUL-terminated string, excluding the terminator. - * - * @returns Byte vector containing the string bytes. - * @param pszData Test data string. - */ -static std::vector tstBytesFromString(const char *pszData) -{ - AssertPtrReturn(pszData, std::vector()); - - size_t const cbData = strlen(pszData); - std::vector abData(cbData); - if (cbData) - memcpy(&abData[0], pszData, cbData); - return abData; -} - - -/** - * Initializes a byte SafeArray from a vector. - * - * @returns COM status code. - * @param abData Source bytes. - * @param aData SafeArray to initialize. - */ -static HRESULT tstSafeArrayFromBytes(const std::vector &abData, SafeArray &aData) -{ - if (abData.empty()) - return S_OK; - return aData.initFrom(&abData[0], abData.size()); -} - - -/** - * Reads raw clipboard data and checks the source, MIME type, and payload. - * - * @returns true if the read matched the expected data, false otherwise. - * @param pClipboard Clipboard API object to use. - * @param pszWhat Description used in failure messages. - * @param enmExpectedSource Expected clipboard source. - * @param pszExpectedMimeType Expected MIME type. - * @param abExpected Expected payload bytes. - */ -static bool tstReadDataRawEquals(IClipboard *pClipboard, const char *pszWhat, ClipboardSource_T enmExpectedSource, - const char *pszExpectedMimeType, const std::vector &abExpected) -{ - AssertPtrReturn(pClipboard, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(pszExpectedMimeType, false); - - ClipboardSource_T enmReadSource = ClipboardSource_Custom; - Bstr bstrRequestedMimeType(""); - Bstr bstrReadMimeType; - SafeArray aReadBuffer; - HRESULT hrc = pClipboard->ReadDataRaw(ClipboardAction_Copy, bstrRequestedMimeType.raw(), &enmReadSource, - bstrReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aReadBuffer)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: ReadDataRaw failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - bool fRc = true; - if (enmReadSource != enmExpectedSource) - { - RTTestIFailed("%s: ReadDataRaw source %d, expected %d\n", pszWhat, enmReadSource, enmExpectedSource); - fRc = false; - } - - Utf8Str strReadMimeType(bstrReadMimeType); - if (RTStrCmp(strReadMimeType.c_str(), pszExpectedMimeType)) - { - RTTestIFailed("%s: ReadDataRaw MIME '%s', expected '%s'\n", pszWhat, strReadMimeType.c_str(), pszExpectedMimeType); - fRc = false; - } - - if (!tstByteArrayEquals(aReadBuffer, abExpected)) - { - RTTestIFailed("%s: ReadDataRaw returned %zu bytes, expected %zu\n", pszWhat, aReadBuffer.size(), abExpected.size()); - fRc = false; - } - - return fRc; -} - - -/** - * Creates a clipboard session through the public Main API. - */ -static HRESULT tstCreateSession(IClipboard *pClipboard, const IClipboardSessionFlag_T *paFlags, size_t cFlags, - ComPtr &ptrSession) -{ - AssertPtrReturn(pClipboard, E_POINTER); - if (cFlags && !paFlags) - return E_POINTER; - - SafeArray aFlags; - for (size_t i = 0; i < cFlags; i++) - if (!aFlags.push_back(paFlags[i])) - return E_OUTOFMEMORY; - - return pClipboard->CreateSession(ComSafeArrayAsInParam(aFlags), ptrSession.asOutParam()); -} - - - -/** - * Registers a passive listener for the specified event types. - */ -static HRESULT tstRegisterClipboardListener(IEventSource *pEventSource, const VBoxEventType_T *paEventTypes, size_t cEventTypes, - ComPtr &ptrListener) -{ - AssertPtrReturn(pEventSource, E_POINTER); - AssertPtrReturn(paEventTypes, E_POINTER); - AssertReturn(cEventTypes > 0, E_INVALIDARG); - - ptrListener.setNull(); - HRESULT hrc = pEventSource->CreateListener(ptrListener.asOutParam()); - if (FAILED(hrc)) - return hrc; - - SafeArray aEventTypes; - for (size_t i = 0; i < cEventTypes; i++) - if (!aEventTypes.push_back(paEventTypes[i])) - return E_OUTOFMEMORY; - - hrc = pEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aEventTypes), FALSE /* aActive */); - if (FAILED(hrc)) - ptrListener.setNull(); - return hrc; -} - - -/** - * Marks a waitable event as processed. Passive clipboard listeners should normally see non-waitable events. - */ -static void tstClipboardMaybeProcessEvent(IEventSource *pEventSource, IEventListener *pListener, IEvent *pEvent) -{ - AssertPtrReturnVoid(pEventSource); - AssertPtrReturnVoid(pListener); - AssertPtrReturnVoid(pEvent); - - BOOL fWaitable = FALSE; - HRESULT hrc = pEvent->COMGETTER(Waitable)(&fWaitable); - if (SUCCEEDED(hrc) && fWaitable) - pEventSource->EventProcessed(pListener, pEvent); -} - - -static bool tstClipboardIsEventTypeExpected(VBoxEventType_T enmType, const VBoxEventType_T *paExpectedTypes, size_t cExpectedTypes) -{ - for (size_t i = 0; i < cExpectedTypes; i++) - if (paExpectedTypes[i] == enmType) - return true; - return false; -} - - -/** - * Waits for the next event and verifies that it has one of the expected types. - */ -static bool tstClipboardWaitForAnyEvent(IEventSource *pEventSource, IEventListener *pListener, - const VBoxEventType_T *paExpectedTypes, size_t cExpectedTypes, - uint32_t cMsTimeout, const char *pszWhat, - ComPtr &ptrEvent, VBoxEventType_T *penmType) -{ - AssertPtrReturn(pEventSource, false); - AssertPtrReturn(pListener, false); - AssertPtrReturn(paExpectedTypes, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(penmType, false); - - ptrEvent.setNull(); - *penmType = VBoxEventType_Invalid; - - HRESULT hrc = pEventSource->GetEvent(pListener, cMsTimeout, ptrEvent.asOutParam()); - if (FAILED(hrc)) - { - RTTestIFailed("%s: GetEvent failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - if (ptrEvent.isNull()) - { - RTTestIFailed("%s: GetEvent returned no event\n", pszWhat); - return false; - } - - hrc = ptrEvent->COMGETTER(Type)(penmType); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Type) failed, hrc=%Rhrc\n", pszWhat, hrc); - tstClipboardMaybeProcessEvent(pEventSource, pListener, ptrEvent); - return false; - } - - if (!tstClipboardIsEventTypeExpected(*penmType, paExpectedTypes, cExpectedTypes)) - { - RTTestIFailed("%s: GetEvent returned type %d\n", pszWhat, *penmType); - tstClipboardMaybeProcessEvent(pEventSource, pListener, ptrEvent); - return false; - } - - return true; -} - - -/** - * Verifies that a listener has no queued event. - */ -static bool tstClipboardExpectNoEvent(IEventSource *pEventSource, IEventListener *pListener, uint32_t cMsTimeout, - const char *pszWhat) -{ - AssertPtrReturn(pEventSource, false); - AssertPtrReturn(pListener, false); - AssertPtrReturn(pszWhat, false); - - ComPtr ptrEvent; - HRESULT hrc = pEventSource->GetEvent(pListener, cMsTimeout, ptrEvent.asOutParam()); - if ( hrc == VBOX_E_OBJECT_NOT_FOUND - || (SUCCEEDED(hrc) && ptrEvent.isNull())) - return true; - - VBoxEventType_T enmType = VBoxEventType_Invalid; - if (SUCCEEDED(hrc)) - ptrEvent->COMGETTER(Type)(&enmType); - RTTestIFailed("%s: unexpected event, hrc=%Rhrc, type=%d\n", pszWhat, hrc, enmType); - if (ptrEvent.isNotNull()) - tstClipboardMaybeProcessEvent(pEventSource, pListener, ptrEvent); - return false; -} - - -/** - * Verifies the metadata common to clipboard event interfaces. - */ -template -static bool tstClipboardCheckEventMetadata(EventT *pEvent, const char *pszWhat, ULONG idExpectedClient, - LONG64 *pi64Revision = NULL) -{ - AssertPtrReturn(pEvent, false); - AssertPtrReturn(pszWhat, false); - - bool fRc = true; - LONG64 i64Revision = 0; - HRESULT hrc = pEvent->COMGETTER(Revision)(&i64Revision); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Revision) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (i64Revision <= 0) - { - RTTestIFailed("%s: revision is %RI64\n", pszWhat, i64Revision); - fRc = false; - } - - ULONG idClient = UINT32_MAX; - hrc = pEvent->COMGETTER(ClientId)(&idClient); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(ClientId) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (idClient != idExpectedClient) - { - RTTestIFailed("%s: client ID %RU32, expected %RU32\n", pszWhat, (uint32_t)idClient, (uint32_t)idExpectedClient); - fRc = false; - } - - if (pi64Revision) - *pi64Revision = i64Revision; - return fRc; -} - - -template -static bool tstClipboardCheckEventMetadata(const ComPtr &ptrEvent, const char *pszWhat, ULONG idExpectedClient, - LONG64 *pi64Revision = NULL) -{ - return tstClipboardCheckEventMetadata((EventT *)ptrEvent, pszWhat, idExpectedClient, pi64Revision); -} - - -/** - * Checks whether a format array contains exactly the expected MIME type. - */ -static bool tstClipboardCheckSingleFormat(SafeIfaceArray &aFormats, const char *pszExpectedMimeType, - const char *pszWhat) -{ - AssertPtrReturn(pszExpectedMimeType, false); - AssertPtrReturn(pszWhat, false); - - if (aFormats.size() != 1) - { - RTTestIFailed("%s: got %zu formats, expected 1\n", pszWhat, aFormats.size()); - return false; - } - - Bstr bstrMimeType; - HRESULT hrc = aFormats[0]->COMGETTER(MimeType)(bstrMimeType.asOutParam()); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(MimeType) failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - Utf8Str strMimeType(bstrMimeType); - if (!RTStrCmp(strMimeType.c_str(), pszExpectedMimeType)) - return true; - - RTTestIFailed("%s: format MIME '%s', expected '%s'\n", pszWhat, strMimeType.c_str(), pszExpectedMimeType); - return false; -} - - -/** - * Verifies a clipboard item payload. - */ -static bool tstClipboardCheckItemPayload(IClipboardItem *pItem, const char *pszWhat, ClipboardSource_T enmExpectedSource, - const char *pszExpectedMimeType, const std::vector &abExpected) -{ - AssertPtrReturn(pItem, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(pszExpectedMimeType, false); - - bool fRc = true; - ClipboardSource_T enmSource = ClipboardSource_Custom; - HRESULT hrc = pItem->COMGETTER(Source)(&enmSource); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardItem::COMGETTER(Source) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmSource != enmExpectedSource) - { - RTTestIFailed("%s: item source %d, expected %d\n", pszWhat, enmSource, enmExpectedSource); - fRc = false; - } - - ComPtr ptrFormat; - hrc = pItem->COMGETTER(Format)(ptrFormat.asOutParam()); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardItem::COMGETTER(Format) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (ptrFormat.isNull()) - { - RTTestIFailed("%s: item has no format\n", pszWhat); - fRc = false; - } - else - { - Bstr bstrMimeType; - hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardFormat::COMGETTER(MimeType) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else - { - Utf8Str strMimeType(bstrMimeType); - if (RTStrCmp(strMimeType.c_str(), pszExpectedMimeType)) - { - RTTestIFailed("%s: item MIME '%s', expected '%s'\n", pszWhat, strMimeType.c_str(), pszExpectedMimeType); - fRc = false; - } - } - } - - SafeArray aBuffer; - hrc = pItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aBuffer)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardItem::COMGETTER(Buffer) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (!tstByteArrayEquals(aBuffer, abExpected)) - { - RTTestIFailed("%s: item payload has %zu bytes, expected %zu\n", pszWhat, aBuffer.size(), abExpected.size()); - fRc = false; - } - - return fRc; -} - - -/** - * Reads the possibly-null IClipboardEvent::item payload from a concrete clipboard event. - */ -static bool tstClipboardGetEventItem(IEvent *pEvent, const char *pszWhat, ComPtr &ptrItem) -{ - AssertPtrReturn(pEvent, false); - AssertPtrReturn(pszWhat, false); - - ptrItem.setNull(); - ComPtr ptrClipboardEvent(pEvent); - if (ptrClipboardEvent.isNull()) - { - RTTestIFailed("%s: event does not implement IClipboardEvent\n", pszWhat); - return false; - } - - HRESULT hrc = ptrClipboardEvent->COMGETTER(Item)(ptrItem.asOutParam()); - if (SUCCEEDED(hrc)) - return true; - - RTTestIFailed("%s: IClipboardEvent::COMGETTER(Item) failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; -} - - -/** - * Verifies an OnClipboardDataChanged event and optionally returns its inherited item. - */ -static bool tstClipboardCheckDataChangedEvent(IEvent *pEvent, const char *pszWhat, ULONG idExpectedClient, - ClipboardAction_T enmExpectedAction, ComPtr *pptrItem, - LONG64 *pi64Revision = NULL) -{ - AssertPtrReturn(pEvent, false); - AssertPtrReturn(pszWhat, false); - - ComPtr ptrDataEvent(pEvent); - if (ptrDataEvent.isNull()) - { - RTTestIFailed("%s: event does not implement IClipboardDataChangedEvent\n", pszWhat); - return false; - } - - bool fRc = tstClipboardCheckEventMetadata(ptrDataEvent, pszWhat, idExpectedClient, pi64Revision); - - ClipboardAction_T enmAction = ClipboardAction_Custom; - HRESULT hrc = ptrDataEvent->COMGETTER(Action)(&enmAction); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Action) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmAction != enmExpectedAction) - { - RTTestIFailed("%s: action %d, expected %d\n", pszWhat, enmAction, enmExpectedAction); - fRc = false; - } - - if (pptrItem && !tstClipboardGetEventItem(pEvent, pszWhat, *pptrItem)) - fRc = false; - return fRc; -} - - -/** - * Verifies an OnClipboardFormatChanged event. - */ -static bool tstClipboardCheckFormatChangedEvent(IEvent *pEvent, const char *pszWhat, ULONG idExpectedClient, - ClipboardSource_T enmExpectedSource, const char *pszExpectedMimeType, - LONG64 *pi64Revision = NULL) -{ - AssertPtrReturn(pEvent, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(pszExpectedMimeType, false); - - ComPtr ptrFormatEvent(pEvent); - if (ptrFormatEvent.isNull()) - { - RTTestIFailed("%s: event does not implement IClipboardFormatChangedEvent\n", pszWhat); - return false; - } - - bool fRc = tstClipboardCheckEventMetadata(ptrFormatEvent, pszWhat, idExpectedClient, pi64Revision); - - ClipboardSource_T enmSource = ClipboardSource_Custom; - HRESULT hrc = ptrFormatEvent->COMGETTER(ClipboardSource)(&enmSource); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(ClipboardSource) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (enmSource != enmExpectedSource) - { - RTTestIFailed("%s: source %d, expected %d\n", pszWhat, enmSource, enmExpectedSource); - fRc = false; - } - - SafeIfaceArray aFormats; - hrc = ptrFormatEvent->COMGETTER(Formats)(ComSafeArrayAsOutParam(aFormats)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: COMGETTER(Formats) failed, hrc=%Rhrc\n", pszWhat, hrc); - fRc = false; - } - else if (!tstClipboardCheckSingleFormat(aFormats, pszExpectedMimeType, pszWhat)) - fRc = false; - - return fRc; -} - - - -/** - * Verifies a clipboard item payload. - * Reads raw clipboard data through a clipboard session and checks the source, MIME type, and payload. - */ -static bool tstReadSessionDataRawEquals(IClipboardSession *pSession, const char *pszWhat, - ClipboardSource_T enmExpectedSource, const char *pszExpectedMimeType, - const std::vector &abExpected) -{ - AssertPtrReturn(pSession, false); - AssertPtrReturn(pszWhat, false); - AssertPtrReturn(pszExpectedMimeType, false); - - ClipboardSource_T enmReadSource = ClipboardSource_Custom; - Bstr bstrRequestedMimeType(""); - Bstr bstrReadMimeType; - SafeArray aReadBuffer; - HRESULT hrc = pSession->ReadDataRaw(ClipboardAction_Copy, bstrRequestedMimeType.raw(), &enmReadSource, - bstrReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aReadBuffer)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IClipboardSession::ReadDataRaw failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - bool fRc = true; - if (enmReadSource != enmExpectedSource) - { - RTTestIFailed("%s: session ReadDataRaw source %d, expected %d\n", pszWhat, enmReadSource, enmExpectedSource); - fRc = false; - } - - Utf8Str strReadMimeType(bstrReadMimeType); - if (RTStrCmp(strReadMimeType.c_str(), pszExpectedMimeType)) - { - RTTestIFailed("%s: session ReadDataRaw MIME '%s', expected '%s'\n", pszWhat, strReadMimeType.c_str(), - pszExpectedMimeType); - fRc = false; - } - - if (!tstByteArrayEquals(aReadBuffer, abExpected)) - { - RTTestIFailed("%s: session ReadDataRaw returned %zu bytes, expected %zu\n", pszWhat, aReadBuffer.size(), - abExpected.size()); - fRc = false; - } - - return fRc; -} - - -/** - * Closes and releases a clipboard session. - */ -static void tstClipboardCloseSession(ComPtr &ptrSession, const char *pszWhat) -{ - AssertPtrReturnVoid(pszWhat); - - if (ptrSession.isNull()) - return; - - HRESULT hrc = ptrSession->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("%s: IClipboardSession::Close failed, hrc=%Rhrc\n", pszWhat, hrc)); - ptrSession.setNull(); -} - - - -/** - * Writes data through IHostClipboard::SetData and verifies regular Main state is unchanged. - * - * @returns true if all readbacks matched, false otherwise. - * @param pClipboard Clipboard API object to use for readback. - * @param pHostClipboard Host clipboard endpoint to test. - * @param pszMimeType MIME type to publish to the native host clipboard. - * @param abData Deterministic payload bytes to publish. - * @param enmExpectedSource Expected regular Main clipboard source after publication. - * @param pszExpectedMimeType Expected regular Main clipboard MIME type after publication. - * @param abExpected Expected regular Main clipboard payload after publication. - * @param cReads Number of repeated readbacks to perform. - * @param pszWhat Description used in failure messages. - */ -static bool tstHostClipboardSetDataAndKeepReadBack(IClipboard *pClipboard, IHostClipboard *pHostClipboard, - const char *pszMimeType, const std::vector &abData, - ClipboardSource_T enmExpectedSource, const char *pszExpectedMimeType, - const std::vector &abExpected, unsigned cReads, - const char *pszWhat) -{ - AssertPtrReturn(pClipboard, false); - AssertPtrReturn(pHostClipboard, false); - AssertPtrReturn(pszMimeType, false); - AssertPtrReturn(pszExpectedMimeType, false); - AssertPtrReturn(pszWhat, false); - - SafeArray aData; - HRESULT hrc = tstSafeArrayFromBytes(abData, aData); - if (FAILED(hrc)) - { - RTTestIFailed("%s: SafeArray initFrom failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - hrc = pHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, Bstr(pszMimeType).raw(), - ComSafeArrayAsInParam(aData)); - if (FAILED(hrc)) - { - RTTestIFailed("%s: IHostClipboard::SetData failed, hrc=%Rhrc\n", pszWhat, hrc); - return false; - } - - bool fRc = true; - for (unsigned i = 0; i < cReads; i++) - { - char szWhat[128]; - RTStrPrintf(szWhat, sizeof(szWhat), "%s preserved readback %u", pszWhat, i); - if (!tstReadDataRawEquals(pClipboard, szWhat, enmExpectedSource, pszExpectedMimeType, abExpected)) - fRc = false; - } - return fRc; -} - - -/** - * Tries to observe a native host-source readback after IHostClipboard publication. - * - * This is intentionally guarded: headless or no-backend environments may never - * reflect the native clipboard back into Main as ClipboardSource_Host. - * - * @param hTest Test handle. - * @param pClipboard Clipboard API object to use. - * @param pszMimeType Expected MIME type. - * @param abExpected Expected payload bytes. - */ -static void tstHostClipboardTryNativeReadBack(RTTEST hTest, IClipboard *pClipboard, const char *pszMimeType, - const std::vector &abExpected) -{ - RTTestSub(hTest, "IHostClipboard native host clipboard observation"); - - for (unsigned i = 0; i < 20; i++) - { - ClipboardSource_T enmReadSource = ClipboardSource_Custom; - Bstr bstrRequestedMimeType(""); - Bstr bstrReadMimeType; - SafeArray aReadBuffer; - HRESULT hrc = pClipboard->ReadDataRaw(ClipboardAction_Copy, bstrRequestedMimeType.raw(), &enmReadSource, - bstrReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aReadBuffer)); - if (SUCCEEDED(hrc) && enmReadSource == ClipboardSource_Host) - { - Utf8Str strReadMimeType(bstrReadMimeType); - if ( !RTStrCmp(strReadMimeType.c_str(), pszMimeType) - && tstByteArrayEquals(aReadBuffer, abExpected)) - return; - } - RTThreadSleep(100); - } - - RTTestSkipped(hTest, "Native host clipboard did not become observable as host-owned data; this is expected on " - "headless/no-backend or lazy/no-op backend runs"); -} - - -/** - * Tests the sole host clipboard endpoint implementation exposed by IClipboard. - * - * @param hTest Test handle. - * @param pClipboard Clipboard API object to use. - * @param pClipboardSettings Clipboard settings for mode validation. - * @param pEventSource Clipboard event source for data request events. - * @param ptrTextFormat Supported text format object. - * @param bstrGuestReadMimeType MIME type from the regular readback path. - * @param aGuestReadBuffer Payload from the regular readback path. - * @param abRoundTripBuffer Expected regular readback payload bytes. - */ -static void tstHostClipboard(RTTEST hTest, IClipboard *pClipboard, IClipboardSettings *pClipboardSettings, - IEventSource *pEventSource, const ComPtr &ptrTextFormat, - const Bstr &bstrGuestReadMimeType, const SafeArray &aGuestReadBuffer, - const std::vector &abRoundTripBuffer) -{ - RTTestSub(hTest, "IHostClipboard public endpoint"); - - HRESULT hrc = S_OK; - bool fListenerRegistered = false; - ComPtr ptrListener; - ComPtr ptrHostClipboard; - - do - { - /* Verify the endpoint object is reachable from the public clipboard API. */ - hrc = pClipboard->COMGETTER(HostClipboard)(ptrHostClipboard.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(HostClipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrHostClipboard.isNull(), ("COMGETTER(HostClipboard) returned NULL\n")); - - /* Verify the native host endpoint can be explicitly reset. */ - hrc = ptrHostClipboard->Clear(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard Clear failed, hrc=%Rhrc\n", hrc)); - - std::vector > vecHostFormats; - vecHostFormats.push_back(ptrTextFormat); - SafeIfaceArray aHostTextFormats(vecHostFormats); - - /* Verify guest-origin offers are blocked when mode disallows guest-to-host publication. */ - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_HostToGuest); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMSETTER(Mode) HostToGuest before IHostClipboard failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aHostTextFormats)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ACCESS_DENIED, - ("HostClipboard ReportFormats in HostToGuest mode returned hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Host, - ComSafeArrayAsInParam(aHostTextFormats)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ACCESS_DENIED, - ("HostClipboard ReportFormats with host source returned hrc=%Rhrc\n", hrc)); - - ComPtr ptrOctetStreamFormat; - hrc = tstCreateFormat(pClipboard, "application/octet-stream", ptrOctetStreamFormat); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateFormat(octet-stream) failed, hrc=%Rhrc\n", hrc)); - vecHostFormats.clear(); - vecHostFormats.push_back(ptrOctetStreamFormat); - SafeIfaceArray aHostOctetFormats(vecHostFormats); - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aHostOctetFormats)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("HostClipboard ReportFormats with application/octet-stream returned hrc=%Rhrc\n", hrc)); - - /* Restore bidirectional mode before exercising successful IHostClipboard guest publication. */ - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMSETTER(Mode) Bidirectional before IHostClipboard failed, hrc=%Rhrc\n", hrc)); - - /* Verify guest format offers and early ProvideData rejection paths. */ - vecHostFormats.clear(); - vecHostFormats.push_back(ptrTextFormat); - SafeIfaceArray aHostGuestTextFormats(vecHostFormats); - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aHostGuestTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard ReportFormats(guest) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ProvideData(0 /* requestId */, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ERROR, - ("HostClipboard ProvideData with zero request ID returned hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ProvideData(1 /* requestId */, ClipboardAction_Copy, ClipboardSource_Guest, - Bstr("application/octet-stream").raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("HostClipboard ProvideData with application/octet-stream returned hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ProvideData(1 /* requestId */, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ERROR, - ("HostClipboard ProvideData without pending request returned hrc=%Rhrc\n", hrc)); - - /* Validate ProvideData against a real pending request ID generated by Main. */ - hrc = pEventSource->CreateListener(ptrListener.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateListener(data requested) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrListener.isNull()); - SafeArray aRequestEventTypes; - aRequestEventTypes.push_back(VBoxEventType_OnClipboardDataRequested); - hrc = pEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aRequestEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(data requested) failed, hrc=%Rhrc\n", hrc)); - fListenerRegistered = SUCCEEDED(hrc); - - ComPtr ptrInternalClipboardControl(pClipboard); - RTTESTI_CHECK_MSG_BREAK(!ptrInternalClipboardControl.isNull(), ("Query IInternalClipboardControl returned NULL\n")); - - ULONG idRequest = 0; - hrc = ptrInternalClipboardControl->RequestData(bstrGuestReadMimeType.raw(), &idRequest); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IInternalClipboardControl::RequestData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idRequest != 0, ("IInternalClipboardControl::RequestData returned zero request ID\n")); - - ComPtr ptrRequestEvent; - hrc = pEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrRequestEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent(data requested) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrRequestEvent.isNull(), ("GetEvent(data requested) returned no event\n")); - - VBoxEventType_T enmRequestEventType = VBoxEventType_Invalid; - hrc = ptrRequestEvent->COMGETTER(Type)(&enmRequestEventType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)(data requested) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmRequestEventType == VBoxEventType_OnClipboardDataRequested, - ("GetEvent(data requested) returned type %d, expected %d\n", - enmRequestEventType, VBoxEventType_OnClipboardDataRequested)); - - BOOL fRequestEventWaitable = TRUE; - hrc = ptrRequestEvent->COMGETTER(Waitable)(&fRequestEventWaitable); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Waitable)(data requested) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(!fRequestEventWaitable, ("OnClipboardDataRequested unexpectedly became waitable\n")); - - ComPtr ptrDataRequestedEvent = ptrRequestEvent; - RTTESTI_CHECK(!ptrDataRequestedEvent.isNull()); - if (ptrDataRequestedEvent.isNotNull()) - { - RTTESTI_CHECK(tstClipboardCheckEventMetadata(ptrDataRequestedEvent, "data requested event", - VBOX_SHCL_MAIN_CLIENT_NONE)); - - ULONG idEventRequest = 0; - hrc = ptrDataRequestedEvent->COMGETTER(RequestId)(&idEventRequest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(RequestId) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idEventRequest == idRequest, - ("Request event ID %RU32, expected %RU32\n", (uint32_t)idEventRequest, (uint32_t)idRequest)); - - ClipboardAction_T enmRequestAction = ClipboardAction_Custom; - hrc = ptrDataRequestedEvent->COMGETTER(Action)(&enmRequestAction); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Action) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmRequestAction == ClipboardAction_Copy); - - ClipboardSource_T enmRequestSource = ClipboardSource_Custom; - hrc = ptrDataRequestedEvent->COMGETTER(ClipboardSource)(&enmRequestSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardSource) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmRequestSource == ClipboardSource_Guest); - - ComPtr ptrRequestFormat; - hrc = ptrDataRequestedEvent->COMGETTER(Format)(ptrRequestFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrRequestFormat.isNull()); - if (ptrRequestFormat.isNotNull()) - { - Bstr bstrRequestMimeType; - hrc = ptrRequestFormat->COMGETTER(MimeType)(bstrRequestMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(MimeType)(request format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrRequestMimeType).c_str(), "text/plain;charset=utf-8")); - } - } - - hrc = ptrHostClipboard->ProvideData(idRequest, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard ProvideData with pending request failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->ProvideData(idRequest, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ERROR, - ("HostClipboard ProvideData with consumed request ID returned hrc=%Rhrc\n", hrc)); - - if (ptrListener.isNotNull()) - pEventSource->UnregisterListener(ptrListener); - fListenerRegistered = false; - ptrListener.setNull(); - - /* Ensure data supplied for the pending request became the guest-owned clipboard content. */ - ClipboardSource_T enmProvidedReadSource = ClipboardSource_Custom; - Bstr bstrProvidedRequestedMimeType(""); - Bstr bstrProvidedReadMimeType; - SafeArray aProvidedReadBuffer; - hrc = pClipboard->ReadDataRaw(ClipboardAction_Copy, bstrProvidedRequestedMimeType.raw(), &enmProvidedReadSource, - bstrProvidedReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aProvidedReadBuffer)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadDataRaw after HostClipboard ProvideData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmProvidedReadSource == ClipboardSource_Guest); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrProvidedReadMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aProvidedReadBuffer, abRoundTripBuffer), - ("ReadDataRaw after HostClipboard ProvideData returned %zu bytes, expected %zu\n", - aProvidedReadBuffer.size(), abRoundTripBuffer.size())); - - /* Seed host-owned Main state before lazy guest publication. */ - static const char s_szHostStateText[] = "tstClipboard host state preserved across IHostClipboard"; - std::vector abHostStateData = tstBytesFromString(s_szHostStateText); - SafeArray aHostStateData; - hrc = tstSafeArrayFromBytes(abHostStateData, aHostStateData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(host state data) failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmHostStateWrittenSource = ClipboardSource_Custom; - Bstr bstrHostStateWrittenMimeType; - SafeArray aHostStateWrittenBuffer; - hrc = pClipboard->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, bstrGuestReadMimeType.raw(), - ComSafeArrayAsInParam(aHostStateData), &enmHostStateWrittenSource, - bstrHostStateWrittenMimeType.asOutParam(), - ComSafeArrayAsOutParam(aHostStateWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteDataRaw(host state before IHostClipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmHostStateWrittenSource == ClipboardSource_Host); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aHostStateWrittenBuffer, abHostStateData), - ("WriteDataRaw(host state before IHostClipboard) returned %zu bytes, expected %zu\n", - aHostStateWrittenBuffer.size(), abHostStateData.size())); - Utf8Str strHostStateMimeType(bstrHostStateWrittenMimeType); - tstReadDataRawEquals(pClipboard, "host state before IHostClipboard publication", ClipboardSource_Host, - strHostStateMimeType.c_str(), abHostStateData); - - hrc = ptrHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aHostGuestTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard ReportFormats(guest) over host state failed, hrc=%Rhrc\n", hrc)); - - hrc = pEventSource->CreateListener(ptrListener.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateListener(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrListener.isNull()); - hrc = pEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aRequestEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(lazy host request) failed, hrc=%Rhrc\n", hrc)); - fListenerRegistered = SUCCEEDED(hrc); - - ULONG idLazyRequest = 0; - hrc = ptrInternalClipboardControl->RequestData(bstrGuestReadMimeType.raw(), &idLazyRequest); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IInternalClipboardControl::RequestData after HostClipboard ReportFormats failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idLazyRequest != 0, ("IInternalClipboardControl::RequestData after HostClipboard ReportFormats returned zero request ID\n")); - - ComPtr ptrLazyRequestEvent; - hrc = pEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrLazyRequestEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrLazyRequestEvent.isNull(), ("GetEvent(lazy host request) returned no event\n")); - - VBoxEventType_T enmLazyRequestEventType = VBoxEventType_Invalid; - hrc = ptrLazyRequestEvent->COMGETTER(Type)(&enmLazyRequestEventType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmLazyRequestEventType == VBoxEventType_OnClipboardDataRequested, - ("GetEvent(lazy host request) returned type %d, expected %d\n", - enmLazyRequestEventType, VBoxEventType_OnClipboardDataRequested)); - - ComPtr ptrLazyDataRequestedEvent = ptrLazyRequestEvent; - RTTESTI_CHECK(!ptrLazyDataRequestedEvent.isNull()); - if (ptrLazyDataRequestedEvent.isNotNull()) - { - RTTESTI_CHECK(tstClipboardCheckEventMetadata(ptrLazyDataRequestedEvent, "lazy data requested event", - VBOX_SHCL_MAIN_CLIENT_NONE)); - - ULONG idLazyEventRequest = 0; - hrc = ptrLazyDataRequestedEvent->COMGETTER(RequestId)(&idLazyEventRequest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(RequestId)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idLazyEventRequest == idLazyRequest, - ("Lazy request event ID %RU32, expected %RU32\n", - (uint32_t)idLazyEventRequest, (uint32_t)idLazyRequest)); - - ClipboardAction_T enmLazyRequestAction = ClipboardAction_Custom; - hrc = ptrLazyDataRequestedEvent->COMGETTER(Action)(&enmLazyRequestAction); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Action)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmLazyRequestAction == ClipboardAction_Copy); - - ClipboardSource_T enmLazyRequestSource = ClipboardSource_Custom; - hrc = ptrLazyDataRequestedEvent->COMGETTER(ClipboardSource)(&enmLazyRequestSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardSource)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmLazyRequestSource == ClipboardSource_Guest); - - ComPtr ptrLazyRequestFormat; - hrc = ptrLazyDataRequestedEvent->COMGETTER(Format)(ptrLazyRequestFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Format)(lazy host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrLazyRequestFormat.isNull()); - if (ptrLazyRequestFormat.isNotNull()) - { - Bstr bstrLazyRequestMimeType; - hrc = ptrLazyRequestFormat->COMGETTER(MimeType)(bstrLazyRequestMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(MimeType)(lazy host request format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrLazyRequestMimeType).c_str(), "text/plain;charset=utf-8")); - } - } - - hrc = ptrHostClipboard->ProvideData(idLazyRequest, ClipboardAction_Copy, ClipboardSource_Guest, - bstrGuestReadMimeType.raw(), ComSafeArrayAsInParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("HostClipboard ProvideData for lazy host request failed, hrc=%Rhrc\n", hrc)); - - if (ptrListener.isNotNull()) - pEventSource->UnregisterListener(ptrListener); - fListenerRegistered = false; - ptrListener.setNull(); - - tstReadDataRawEquals(pClipboard, "IHostClipboard ReportFormats lazy guest data over host state", ClipboardSource_Guest, - "text/plain;charset=utf-8", abRoundTripBuffer); - - /* Reseed host-owned Main state before verifying SetData does not replace it. */ - aHostStateWrittenBuffer.setNull(); - hrc = pClipboard->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, bstrGuestReadMimeType.raw(), - ComSafeArrayAsInParam(aHostStateData), &enmHostStateWrittenSource, - bstrHostStateWrittenMimeType.asOutParam(), - ComSafeArrayAsOutParam(aHostStateWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteDataRaw(host state after lazy IHostClipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmHostStateWrittenSource == ClipboardSource_Host); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aHostStateWrittenBuffer, abHostStateData), - ("WriteDataRaw(host state after lazy IHostClipboard) returned %zu bytes, expected %zu\n", - aHostStateWrittenBuffer.size(), abHostStateData.size())); - tstReadDataRawEquals(pClipboard, "host state after lazy IHostClipboard publication", ClipboardSource_Host, - strHostStateMimeType.c_str(), abHostStateData); - - RTTestSub(hTest, "IHostClipboard SetData preserves Main state"); - /* Verify deterministic SetData payloads do not overwrite host-owned Main state. */ - struct TSTHOSTCLIPBOARDSETDATA - { - const char *pszMimeType; - const char *pszData; - const char *pszWhat; - }; - static const TSTHOSTCLIPBOARDSETDATA s_aHostClipboardSetData[] = - { - { "text/plain;charset=utf-8", "tstClipboard IHostClipboard text publish #1\n", "text/plain #1" }, - { "text/html", "

tstClipboard IHostClipboard html publish

", "text/html" }, - { "text/plain;charset=utf-8", "tstClipboard IHostClipboard text publish #2\n", "text/plain #2" } - }; - - bool fHaveLastHostClipboardSetData = false; - const char *pszLastHostClipboardMimeType = NULL; - std::vector abLastHostClipboardData; - for (unsigned i = 0; i < RT_ELEMENTS(s_aHostClipboardSetData); i++) - { - std::vector abSetData = tstBytesFromString(s_aHostClipboardSetData[i].pszData); - char szWhat[128]; - RTStrPrintf(szWhat, sizeof(szWhat), "IHostClipboard SetData %s", s_aHostClipboardSetData[i].pszWhat); - if (tstHostClipboardSetDataAndKeepReadBack(pClipboard, ptrHostClipboard, s_aHostClipboardSetData[i].pszMimeType, - abSetData, ClipboardSource_Host, strHostStateMimeType.c_str(), - abHostStateData, 3 /* cReads */, szWhat)) - { - fHaveLastHostClipboardSetData = true; - pszLastHostClipboardMimeType = s_aHostClipboardSetData[i].pszMimeType; - abLastHostClipboardData = abSetData; - } - } - - if (fHaveLastHostClipboardSetData) - { - SafeArray aLastHostClipboardData; - hrc = tstSafeArrayFromBytes(abLastHostClipboardData, aLastHostClipboardData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(last host clipboard data) failed, hrc=%Rhrc\n", hrc)); - - /* Verify SetData rejects wrong source, empty data, unsupported format, and disallowed mode. */ - hrc = ptrHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Host, Bstr(pszLastHostClipboardMimeType).raw(), - ComSafeArrayAsInParam(aLastHostClipboardData)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ACCESS_DENIED, - ("HostClipboard SetData with host source returned hrc=%Rhrc\n", hrc)); - - SafeArray aEmptyHostClipboardData; - hrc = ptrHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, Bstr(pszLastHostClipboardMimeType).raw(), - ComSafeArrayAsInParam(aEmptyHostClipboardData)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_NO_DATA, - ("HostClipboard SetData with empty data returned hrc=%Rhrc\n", hrc)); - - hrc = ptrHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, Bstr("application/octet-stream").raw(), - ComSafeArrayAsInParam(aLastHostClipboardData)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("HostClipboard SetData with application/octet-stream returned hrc=%Rhrc\n", hrc)); - - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_HostToGuest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), - ("COMSETTER(Mode) HostToGuest before HostClipboard SetData denial failed, hrc=%Rhrc\n", hrc)); - if (SUCCEEDED(hrc)) - { - hrc = ptrHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, - Bstr(pszLastHostClipboardMimeType).raw(), - ComSafeArrayAsInParam(aLastHostClipboardData)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_ACCESS_DENIED, - ("HostClipboard SetData in HostToGuest mode returned hrc=%Rhrc\n", hrc)); - - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), - ("COMSETTER(Mode) Bidirectional after HostClipboard SetData denial failed, " - "hrc=%Rhrc\n", hrc)); - } - - /* Ensure rejection paths did not disturb the preserved host-owned Main payload. */ - tstReadDataRawEquals(pClipboard, "IHostClipboard SetData after failure cases", ClipboardSource_Host, - strHostStateMimeType.c_str(), abHostStateData); - tstHostClipboardTryNativeReadBack(hTest, pClipboard, pszLastHostClipboardMimeType, abLastHostClipboardData); - } - } while (0); - - if (fListenerRegistered && pEventSource && ptrListener.isNotNull()) - pEventSource->UnregisterListener(ptrListener); - ptrListener.setNull(); - - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMSETTER(Mode) Bidirectional after IHostClipboard failed, hrc=%Rhrc\n", hrc)); -} - - -/** - * Configures Shared Clipboard logging to go to stdout. - */ -static void tstInitLogging(void) -{ - RTUINT fFlags = RTLOGFLAGS_PREFIX_THREAD | RTLOGFLAGS_PREFIX_TIME_PROG; -#if defined(RT_OS_WINDOWS) || defined(RT_OS_OS2) - fFlags |= RTLOGFLAGS_USECRLF; -#endif - static const char * const s_apszLogGroups[] = VBOX_LOGGROUP_NAMES; - int vrc = RTLogCreate(&g_pLogger, fFlags, "all.e.l.f", "TST_CLIPBOARD_LOG", - RT_ELEMENTS(s_apszLogGroups), s_apszLogGroups, RTLOGDEST_STDOUT, NULL); - if (RT_SUCCESS(vrc)) - { - vrc = RTLogGroupSettings(g_pLogger, "main.e.l+shared_clipboard.e.l.l2.f"); - if (RT_SUCCESS(vrc)) - RTLogRelSetDefaultInstance(g_pLogger); - } - else - RTMsgWarning("Failed to create shared clipboard logger: %Rrc", vrc); -} - - -/** - * Logs COM error information for a testcase failure. - * - * @param hTest Test handle. - * @param pszWhat Operation that failed. - * @param errorInfo Error information to log. - */ -static void tstLogErrorInfo(RTTEST hTest, const char *pszWhat, const ErrorInfo &errorInfo) -{ - if (!errorInfo.isBasicAvailable()) - { - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: no error info available\n", pszWhat); - return; - } - - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: hrc=%Rhrc, text='%ls'\n", - pszWhat, errorInfo.getResultCode(), errorInfo.getText().raw()); - if (errorInfo.getComponent().raw()) - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: component='%ls'\n", pszWhat, errorInfo.getComponent().raw()); - if (errorInfo.getInterfaceName().raw()) - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "%s: interface='%ls'\n", pszWhat, errorInfo.getInterfaceName().raw()); -} - - -/** - * Waits for a temporary testcase machine to become unlocked before unregistering it. - * - * @returns COM status code from the last SessionState query. - * @param hTest Test handle. - * @param pMachine Machine to query. - */ -static HRESULT tstWaitMachineUnlocked(RTTEST hTest, IMachine *pMachine) -{ - AssertPtrReturn(pMachine, E_POINTER); - - HRESULT hrc = S_OK; - SessionState_T enmSessionState = SessionState_Unlocked; - for (uint32_t i = 0; i < 100; ++i) - { - hrc = pMachine->COMGETTER(SessionState)(&enmSessionState); - if (FAILED(hrc)) - return hrc; - if (enmSessionState == SessionState_Unlocked) - return S_OK; - RTThreadSleep(100); - } - - RTTestPrintf(hTest, RTTESTLVL_ALWAYS, "Machine still not unlocked; SessionState=%d\n", enmSessionState); - return VBOX_E_INVALID_OBJECT_STATE; -} - - -/** - * Tests clipboard object factory methods and the returned public objects. - * - * @param hTest Test handle. - * @param pClipboard Clipboard API object to use. - */ -static void tstClipboardPublicObjects(RTTEST hTest, IClipboard *pClipboard) -{ - RTTestSub(hTest, "Clipboard public objects"); - - ComPtr ptrFormat; - HRESULT hrc = tstCreateFormat(pClipboard, "text/plain;charset=utf-8", ptrFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat failed, hrc=%Rhrc\n", hrc)); - - Bstr bstrMimeType; - hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardFormat::COMGETTER(MimeType) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrMimeType).c_str(), "text/plain;charset=utf-8")); - - hrc = ptrFormat->COMSETTER(MimeType)(Bstr("text/html").raw()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardFormat::COMSETTER(MimeType) failed, hrc=%Rhrc\n", hrc)); - bstrMimeType.setNull(); - hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardFormat::COMGETTER(MimeType) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrMimeType).c_str(), "text/html")); - - ComPtr ptrTextFormat; - hrc = tstCreateFormat(pClipboard, "text/plain;charset=utf-8", ptrTextFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat(text) failed, hrc=%Rhrc\n", hrc)); - - static const char s_szText[] = "Hello from the Main clipboard testcase"; - std::vector abText(sizeof(s_szText)); - memcpy(&abText[0], s_szText, sizeof(s_szText)); - - ComPtr ptrItem; - hrc = tstCreateItem(pClipboard, ClipboardSource_Host, ptrTextFormat, abText, ptrItem); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateItem failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmSource = ClipboardSource_Guest; - hrc = ptrItem->COMGETTER(Source)(&enmSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Source) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmSource == ClipboardSource_Host); - - ComPtr ptrItemFormat; - hrc = ptrItem->COMGETTER(Format)(ptrItemFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrItemFormat.isNull()); - if (ptrItemFormat.isNotNull()) - { - Bstr bstrItemMimeType; - hrc = ptrItemFormat->COMGETTER(MimeType)(bstrItemMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::Format::COMGETTER(MimeType) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrItemMimeType).c_str(), "text/plain;charset=utf-8")); - } - - SafeArray aReadText; - hrc = ptrItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aReadText)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Buffer) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aReadText, abText), - ("IClipboardItem::COMGETTER(Buffer) returned %zu bytes, expected %zu\n", aReadText.size(), abText.size())); - - ULONG cbItem = 0; - hrc = ptrItem->COMGETTER(Size)(&cbItem); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Size) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cbItem == sizeof(s_szText)); - - ComPtr ptrHtmlFormat; - hrc = tstCreateFormat(pClipboard, "text/html", ptrHtmlFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat(html) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrItem->COMSETTER(Format)(ptrHtmlFormat); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMSETTER(Format) failed, hrc=%Rhrc\n", hrc)); - ptrItemFormat.setNull(); - hrc = ptrItem->COMGETTER(Format)(ptrItemFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Format) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrItemFormat.isNull()); - if (ptrItemFormat.isNotNull()) - { - Bstr bstrItemMimeType; - hrc = ptrItemFormat->COMGETTER(MimeType)(bstrItemMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::Format::COMGETTER(MimeType) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrItemMimeType).c_str(), "text/html")); - } - - static const char s_szHtml[] = "

Hello from the Main clipboard testcase

"; - SafeArray aHtml; - hrc = aHtml.initFrom(reinterpret_cast(s_szHtml), sizeof(s_szHtml)); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("SafeArray initFrom failed, hrc=%Rhrc\n", hrc)); - hrc = ptrItem->COMSETTER(Buffer)(ComSafeArrayAsInParam(aHtml)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMSETTER(Buffer) failed, hrc=%Rhrc\n", hrc)); - - SafeArray aReadHtml; - hrc = ptrItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aReadHtml)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Buffer) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadHtml.size() == sizeof(s_szHtml)); - if (aReadHtml.size() == sizeof(s_szHtml)) - RTTESTI_CHECK(!memcmp(aReadHtml.raw(), s_szHtml, sizeof(s_szHtml))); - - hrc = ptrItem->COMGETTER(Size)(&cbItem); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardItem::COMGETTER(Size) after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cbItem == sizeof(s_szHtml)); -} - - - -/** - * Tests public clipboard session creation, event routing and close semantics. - * Tests the public clipboard session API. - * - * @param hTest Test handle. - * @param pClipboard Live console clipboard object. - * @param pClipboardSettings Live clipboard settings object. - * @param pEventSource Live console clipboard event source. - */ -static void tstClipboardPublicSessionApi(RTTEST hTest, IClipboard *pClipboard, IClipboardSettings *pClipboardSettings, - IEventSource *pEventSource) -{ - RTTestSub(hTest, "Clipboard public session API"); - - RTTESTI_CHECK_RETV(pClipboard != NULL); - RTTESTI_CHECK_RETV(pClipboardSettings != NULL); - RTTESTI_CHECK_RETV(pEventSource != NULL); - - RTTESTI_CHECK_MSG(VBOX_SHCL_MAIN_CLIENT_NONE == 0, - ("VBOX_SHCL_MAIN_CLIENT_NONE is %RU32, expected 0\n", (uint32_t)VBOX_SHCL_MAIN_CLIENT_NONE)); - - HRESULT hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("COMSETTER(Mode)(Bidirectional) before sessions failed, hrc=%Rhrc\n", hrc)); - - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("Reset before sessions failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrTextFormat; - hrc = tstCreateFormat(pClipboard, "text/plain;charset=utf-8", ptrTextFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat(session text) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrHtmlFormat; - hrc = tstCreateFormat(pClipboard, "text/html", ptrHtmlFormat); - RTTESTI_CHECK_MSG_RETV(SUCCEEDED(hrc), ("CreateFormat(session html) failed, hrc=%Rhrc\n", hrc)); - - std::vector > vecTextFormats; - vecTextFormats.push_back(ptrTextFormat); - SafeIfaceArray aTextFormats(vecTextFormats); - - std::vector > vecHtmlFormats; - vecHtmlFormats.push_back(ptrHtmlFormat); - SafeIfaceArray aHtmlFormats(vecHtmlFormats); - - - /* Basic session construction, identity, accessors, raw data, format offer, and close. */ - { - ComPtr ptrSessionA; - ComPtr ptrSessionB; - ComPtr ptrObserverEventSource; - ComPtr ptrObserverListener; - bool fObserverListenerRegistered = false; - do - { - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrSessionA); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(empty A) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrSessionA.isNull(), ("CreateSession(empty A) returned NULL\n")); - - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrSessionB); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(empty B) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrSessionB.isNull(), ("CreateSession(empty B) returned NULL\n")); - - ULONG idSessionA = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSessionA->COMGETTER(Id)(&idSessionA); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(Id)(A) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSessionA != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Session A returned anonymous/zero client ID\n")); - - ULONG idSessionB = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSessionB->COMGETTER(Id)(&idSessionB); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(Id)(B) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSessionB != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Session B returned anonymous/zero client ID\n")); - RTTESTI_CHECK_MSG(idSessionA != idSessionB, - ("Session IDs are not distinct: %RU32\n", (uint32_t)idSessionA)); - - ComPtr ptrSessionEventSource; - hrc = ptrSessionA->COMGETTER(EventSource)(ptrSessionEventSource.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(EventSource) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrSessionEventSource.isNull()); - - ComPtr ptrSessionHostClipboard; - hrc = ptrSessionA->COMGETTER(HostClipboard)(ptrSessionHostClipboard.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(HostClipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrSessionHostClipboard.isNull()); - - hrc = ptrSessionB->COMGETTER(EventSource)(ptrObserverEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IClipboardSession::COMGETTER(EventSource)(observer) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrObserverEventSource.isNull(), ("IClipboardSession::COMGETTER(EventSource)(observer) returned NULL\n")); - - static VBoxEventType_T const s_aBasicEventTypes[] = - { - VBoxEventType_OnClipboardFormatChanged, - VBoxEventType_OnClipboardDataChanged - }; - hrc = tstRegisterClipboardListener(ptrObserverEventSource, s_aBasicEventTypes, RT_ELEMENTS(s_aBasicEventTypes), - ptrObserverListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(session observer) failed, hrc=%Rhrc\n", hrc)); - fObserverListenerRegistered = true; - - SafeIfaceArray aReadFormats; - hrc = ptrSessionA->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::ReadFormats(empty) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadFormats.size() == 0); - - hrc = ptrSessionA->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::WriteFormats failed, hrc=%Rhrc\n", hrc)); - - LONG64 i64FormatRevision = 0; - ComPtr ptrFormatEvent; - VBoxEventType_T enmFormatEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aBasicEventTypes, - RT_ELEMENTS(s_aBasicEventTypes), 1000 /* cMsTimeout */, - "session observer format", ptrFormatEvent, &enmFormatEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - RTTESTI_CHECK(enmFormatEventType == VBoxEventType_OnClipboardFormatChanged); - if (enmFormatEventType == VBoxEventType_OnClipboardFormatChanged) - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrFormatEvent, "session observer format event", - idSessionA, ClipboardSource_Host, - "text/plain;charset=utf-8", &i64FormatRevision)); - } - - aReadFormats.setNull(); - hrc = ptrSessionA->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::ReadFormats(after WriteFormats) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardCheckSingleFormat(aReadFormats, "text/plain;charset=utf-8", - "session ReadFormats after WriteFormats")); - - static const char s_szSessionRawText[] = "tstClipboard session raw data"; - std::vector abSessionRawData = tstBytesFromString(s_szSessionRawText); - SafeArray aSessionRawData; - hrc = tstSafeArrayFromBytes(abSessionRawData, aSessionRawData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(session raw data) failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmWrittenSource = ClipboardSource_Custom; - Bstr bstrWrittenMimeType; - SafeArray aWrittenBuffer; - hrc = ptrSessionA->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aSessionRawData), &enmWrittenSource, - bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IClipboardSession::WriteDataRaw failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmWrittenSource == ClipboardSource_Host); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrWrittenMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK(tstByteArrayEquals(aWrittenBuffer, abSessionRawData)); - - LONG64 i64DataRevision = 0; - ComPtr ptrDataEvent; - VBoxEventType_T enmDataEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aBasicEventTypes, - RT_ELEMENTS(s_aBasicEventTypes), 1000 /* cMsTimeout */, - "session observer data", ptrDataEvent, &enmDataEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - RTTESTI_CHECK(enmDataEventType == VBoxEventType_OnClipboardDataChanged); - if (enmDataEventType == VBoxEventType_OnClipboardDataChanged) - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrDataEvent, "session observer data event", idSessionA, - ClipboardAction_Copy, NULL /* pptrItem */, &i64DataRevision)); - } - if (i64FormatRevision > 0 && i64DataRevision > 0) - RTTESTI_CHECK_MSG(i64DataRevision > i64FormatRevision, - ("session data revision %RI64, expected greater than format revision %RI64\n", - i64DataRevision, i64FormatRevision)); - RTTESTI_CHECK(tstReadSessionDataRawEquals(ptrSessionA, "session raw round-trip", ClipboardSource_Host, - "text/plain;charset=utf-8", abSessionRawData)); - - hrc = ptrSessionA->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::Close(basic session A) failed, hrc=%Rhrc\n", hrc)); - ULONG idSessionAAfterClose = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSessionA->COMGETTER(Id)(&idSessionAAfterClose); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Id)(basic session A after Close) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSessionAAfterClose == idSessionA, - ("Closed session ID changed from %RU32 to %RU32\n", - (uint32_t)idSessionA, (uint32_t)idSessionAAfterClose)); - - ComPtr ptrClosedEventSource; - hrc = ptrSessionA->COMGETTER(EventSource)(ptrClosedEventSource.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("COMGETTER(EventSource)(closed session A) unexpectedly succeeded\n")); - - ComPtr ptrClosedHostClipboard; - hrc = ptrSessionA->COMGETTER(HostClipboard)(ptrClosedHostClipboard.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("COMGETTER(HostClipboard)(closed session A) unexpectedly succeeded\n")); - - SafeIfaceArray aClosedFormats; - hrc = ptrSessionA->ReadFormats(ComSafeArrayAsOutParam(aClosedFormats)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("ReadFormats(closed session A) unexpectedly succeeded\n")); - - ClipboardSource_T enmClosedReadSource = ClipboardSource_Custom; - Bstr bstrClosedRequestedMimeType(""); - Bstr bstrClosedReadMimeType; - SafeArray aClosedReadBuffer; - hrc = ptrSessionA->ReadDataRaw(ClipboardAction_Copy, bstrClosedRequestedMimeType.raw(), &enmClosedReadSource, - bstrClosedReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aClosedReadBuffer)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("ReadDataRaw(closed session A) unexpectedly succeeded\n")); - - ClipboardSource_T enmClosedWrittenSource = ClipboardSource_Custom; - Bstr bstrClosedWrittenMimeType; - SafeArray aClosedWrittenBuffer; - hrc = ptrSessionA->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, - Bstr("text/plain;charset=utf-8").raw(), ComSafeArrayAsInParam(aSessionRawData), - &enmClosedWrittenSource, bstrClosedWrittenMimeType.asOutParam(), - ComSafeArrayAsOutParam(aClosedWrittenBuffer)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("WriteDataRaw(closed session A) unexpectedly succeeded\n")); - - hrc = ptrSessionA->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("WriteFormats(closed session A) unexpectedly succeeded\n")); - - hrc = ptrSessionA->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::Close(closed session A) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrSessionHostClipboard->Clear(); - RTTESTI_CHECK_MSG(FAILED(hrc), ("HostClipboard::Clear(closed session A endpoint) unexpectedly succeeded\n")); - hrc = ptrSessionHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("HostClipboard::ReportFormats(closed session A endpoint) unexpectedly succeeded\n")); - hrc = ptrSessionHostClipboard->SetData(ClipboardAction_Copy, ClipboardSource_Guest, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aSessionRawData)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("HostClipboard::SetData(closed session A endpoint) unexpectedly succeeded\n")); - hrc = ptrSessionHostClipboard->ProvideData(1 /* aRequestId */, ClipboardAction_Copy, ClipboardSource_Guest, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aSessionRawData)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("HostClipboard::ProvideData(closed session A endpoint) unexpectedly succeeded\n")); - - SafeIfaceArray aOwnerClearedFormats; - hrc = pClipboard->ReadFormats(ComSafeArrayAsOutParam(aOwnerClearedFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadFormats after closing owning session failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(aOwnerClearedFormats.size() == 0, - ("Closing owning session left %zu clipboard formats advertised\n", aOwnerClearedFormats.size())); - } while (0); - - if (fObserverListenerRegistered && ptrObserverEventSource.isNotNull() && ptrObserverListener.isNotNull()) - ptrObserverEventSource->UnregisterListener(ptrObserverListener); - ptrObserverListener.setNull(); - - tstClipboardCloseSession(ptrSessionA, "basic session A"); - tstClipboardCloseSession(ptrSessionB, "basic session B"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after basic session test failed, hrc=%Rhrc\n", hrc)); - } - - - /* Session host clipboard endpoints tag lazy native-host requests with the session client ID. */ - { - ComPtr ptrHostSession; - ComPtr ptrSessionHostClipboard; - ComPtr ptrRequestListener; - bool fRequestListenerRegistered = false; - do - { - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrHostSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(host clipboard) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrHostSession.isNull(), ("CreateSession(host clipboard) returned NULL\n")); - - ULONG idHostSession = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrHostSession->COMGETTER(Id)(&idHostSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(host clipboard session) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idHostSession != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Host clipboard session returned zero client ID\n")); - - hrc = ptrHostSession->COMGETTER(HostClipboard)(ptrSessionHostClipboard.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(HostClipboard)(session) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrSessionHostClipboard.isNull(), ("COMGETTER(HostClipboard)(session) returned NULL\n")); - - hrc = ptrSessionHostClipboard->ReportFormats(ClipboardAction_Copy, ClipboardSource_Guest, - ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Session HostClipboard::ReportFormats failed, hrc=%Rhrc\n", hrc)); - - hrc = pEventSource->CreateListener(ptrRequestListener.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateListener(session host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrRequestListener.isNull(), ("CreateListener(session host request) returned NULL\n")); - SafeArray aRequestEventTypes; - aRequestEventTypes.push_back(VBoxEventType_OnClipboardDataRequested); - hrc = pEventSource->RegisterListener(ptrRequestListener, ComSafeArrayAsInParam(aRequestEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(session host request) failed, hrc=%Rhrc\n", hrc)); - fRequestListenerRegistered = true; - - ComPtr ptrInternalClipboardControl(pClipboard); - RTTESTI_CHECK_MSG_BREAK(!ptrInternalClipboardControl.isNull(), ("Query IInternalClipboardControl(session host) returned NULL\n")); - - ULONG idRequest = 0; - Bstr bstrMimeType("text/plain;charset=utf-8"); - hrc = ptrInternalClipboardControl->RequestData(bstrMimeType.raw(), &idRequest); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("IInternalClipboardControl::RequestData(session host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idRequest != 0, ("IInternalClipboardControl::RequestData(session host) returned zero request ID\n")); - - ComPtr ptrRequestEvent; - hrc = pEventSource->GetEvent(ptrRequestListener, 1000 /* aTimeout */, ptrRequestEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent(session host request) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrRequestEvent.isNull(), ("GetEvent(session host request) returned no event\n")); - - ComPtr ptrDataRequestedEvent = ptrRequestEvent; - RTTESTI_CHECK(!ptrDataRequestedEvent.isNull()); - if (ptrDataRequestedEvent.isNotNull()) - { - RTTESTI_CHECK(tstClipboardCheckEventMetadata(ptrDataRequestedEvent, "session host data requested event", - idHostSession)); - - ULONG idEventRequest = 0; - hrc = ptrDataRequestedEvent->COMGETTER(RequestId)(&idEventRequest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(RequestId)(session host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idEventRequest == idRequest, - ("Session host request event ID %RU32, expected %RU32\n", - (uint32_t)idEventRequest, (uint32_t)idRequest)); - - ClipboardSource_T enmRequestSource = ClipboardSource_Custom; - hrc = ptrDataRequestedEvent->COMGETTER(ClipboardSource)(&enmRequestSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardSource)(session host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmRequestSource == ClipboardSource_Guest); - } - - static const char s_szSessionHostText[] = "tstClipboard session host clipboard data"; - std::vector abSessionHostData = tstBytesFromString(s_szSessionHostText); - SafeArray aSessionHostData; - hrc = tstSafeArrayFromBytes(abSessionHostData, aSessionHostData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(session host data) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrSessionHostClipboard->ProvideData(idRequest, ClipboardAction_Copy, ClipboardSource_Guest, - bstrMimeType.raw(), ComSafeArrayAsInParam(aSessionHostData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Session HostClipboard::ProvideData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstReadSessionDataRawEquals(ptrHostSession, "session HostClipboard ProvideData readback", - ClipboardSource_Guest, "text/plain;charset=utf-8", abSessionHostData)); - } while (0); - - if (fRequestListenerRegistered && ptrRequestListener.isNotNull()) - pEventSource->UnregisterListener(ptrRequestListener); - ptrRequestListener.setNull(); - - tstClipboardCloseSession(ptrHostSession, "session HostClipboard session"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after session HostClipboard test failed, hrc=%Rhrc\n", hrc)); - } - - - /* IncludeInitialState replays the state current when a session listener registers. */ - { - ComPtr ptrInitialSession; - ComPtr ptrInitialEventSource; - ComPtr ptrInitialListener; - bool fInitialListenerRegistered = false; - do - { - hrc = pClipboard->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteFormats(seed initial state) failed, hrc=%Rhrc\n", hrc)); - - static IClipboardSessionFlag_T const s_aInitialFlags[] = - { - IClipboardSessionFlag_IncludeInitialState - }; - hrc = tstCreateSession(pClipboard, s_aInitialFlags, RT_ELEMENTS(s_aInitialFlags), ptrInitialSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(IncludeInitialState) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrInitialSession.isNull(), ("CreateSession(IncludeInitialState) returned NULL\n")); - - hrc = ptrInitialSession->COMGETTER(EventSource)(ptrInitialEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(initial) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrInitialEventSource.isNull(), ("COMGETTER(EventSource)(initial) returned NULL\n")); - - hrc = pClipboard->WriteFormats(ComSafeArrayAsInParam(aHtmlFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteFormats(update initial state before listener) failed, hrc=%Rhrc\n", hrc)); - - static VBoxEventType_T const s_aInitialEventTypes[] = - { - VBoxEventType_OnClipboardFormatChanged - }; - hrc = tstRegisterClipboardListener(ptrInitialEventSource, s_aInitialEventTypes, RT_ELEMENTS(s_aInitialEventTypes), - ptrInitialListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(initial state) failed, hrc=%Rhrc\n", hrc)); - fInitialListenerRegistered = true; - - ComPtr ptrInitialEvent; - VBoxEventType_T enmInitialEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrInitialEventSource, ptrInitialListener, s_aInitialEventTypes, - RT_ELEMENTS(s_aInitialEventTypes), 1000 /* cMsTimeout */, - "IncludeInitialState", ptrInitialEvent, &enmInitialEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - RTTESTI_CHECK(enmInitialEventType == VBoxEventType_OnClipboardFormatChanged); - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrInitialEvent, "IncludeInitialState format event", - VBOX_SHCL_MAIN_CLIENT_NONE, ClipboardSource_Host, - "text/html")); - } - - SafeIfaceArray aInitialReadFormats; - hrc = ptrInitialSession->ReadFormats(ComSafeArrayAsOutParam(aInitialReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardSession::ReadFormats(IncludeInitialState) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(tstClipboardCheckSingleFormat(aInitialReadFormats, "text/html", - "IncludeInitialState current formats")); - } while (0); - - if (fInitialListenerRegistered && ptrInitialEventSource.isNotNull() && ptrInitialListener.isNotNull()) - ptrInitialEventSource->UnregisterListener(ptrInitialListener); - ptrInitialListener.setNull(); - - tstClipboardCloseSession(ptrInitialSession, "IncludeInitialState session"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after IncludeInitialState test failed, hrc=%Rhrc\n", hrc)); - } - - - /* ExcludeOwnChanges suppresses events for the writing session while other sessions see its client ID. */ - { - ComPtr ptrSessionA; - ComPtr ptrSessionB; - ComPtr ptrEventSourceA; - ComPtr ptrEventSourceB; - ComPtr ptrListenerA; - ComPtr ptrListenerB; - bool fListenerARegistered = false; - bool fListenerBRegistered = false; - do - { - static IClipboardSessionFlag_T const s_aExcludeOwnFlags[] = - { - IClipboardSessionFlag_ExcludeOwnChanges - }; - hrc = tstCreateSession(pClipboard, s_aExcludeOwnFlags, RT_ELEMENTS(s_aExcludeOwnFlags), ptrSessionA); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(ExcludeOwnChanges) failed, hrc=%Rhrc\n", hrc)); - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrSessionB); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(observer) failed, hrc=%Rhrc\n", hrc)); - - ULONG idSessionA = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSessionA->COMGETTER(Id)(&idSessionA); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(ExcludeOwnChanges) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSessionA != VBOX_SHCL_MAIN_CLIENT_NONE, - ("ExcludeOwnChanges session returned zero client ID\n")); - - hrc = ptrSessionA->COMGETTER(EventSource)(ptrEventSourceA.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(A) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrSessionB->COMGETTER(EventSource)(ptrEventSourceB.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(B) failed, hrc=%Rhrc\n", hrc)); - - static VBoxEventType_T const s_aOwnEventTypes[] = - { - VBoxEventType_OnClipboardFormatChanged, - VBoxEventType_OnClipboardDataChanged - }; - hrc = tstRegisterClipboardListener(ptrEventSourceA, s_aOwnEventTypes, RT_ELEMENTS(s_aOwnEventTypes), ptrListenerA); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(A own changes) failed, hrc=%Rhrc\n", hrc)); - fListenerARegistered = true; - - static VBoxEventType_T const s_aDataEventTypes[] = - { - VBoxEventType_OnClipboardDataChanged - }; - hrc = tstRegisterClipboardListener(ptrEventSourceB, s_aDataEventTypes, RT_ELEMENTS(s_aDataEventTypes), ptrListenerB); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(B observer) failed, hrc=%Rhrc\n", hrc)); - fListenerBRegistered = true; - - static const char s_szSessionAText[] = "tstClipboard session A writes"; - std::vector abSessionAData = tstBytesFromString(s_szSessionAText); - SafeArray aSessionAData; - hrc = tstSafeArrayFromBytes(abSessionAData, aSessionAData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(session A data) failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmWrittenSource = ClipboardSource_Custom; - Bstr bstrWrittenMimeType; - SafeArray aWrittenBuffer; - hrc = ptrSessionA->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aSessionAData), &enmWrittenSource, - bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Session A WriteDataRaw failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrObservedEvent; - VBoxEventType_T enmObservedEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrEventSourceB, ptrListenerB, s_aDataEventTypes, - RT_ELEMENTS(s_aDataEventTypes), 1000 /* cMsTimeout */, - "ExcludeOwnChanges observer", ptrObservedEvent, &enmObservedEventType); - RTTESTI_CHECK(fRc); - if (fRc) - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrObservedEvent, "ExcludeOwnChanges observer data event", - idSessionA, ClipboardAction_Copy, NULL /* pptrItem */)); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrEventSourceA, ptrListenerA, 250 /* cMsTimeout */, - "ExcludeOwnChanges writer")); - } while (0); - - if (fListenerARegistered && ptrEventSourceA.isNotNull() && ptrListenerA.isNotNull()) - ptrEventSourceA->UnregisterListener(ptrListenerA); - if (fListenerBRegistered && ptrEventSourceB.isNotNull() && ptrListenerB.isNotNull()) - ptrEventSourceB->UnregisterListener(ptrListenerB); - ptrListenerA.setNull(); - ptrListenerB.setNull(); - - tstClipboardCloseSession(ptrSessionA, "ExcludeOwnChanges session A"); - tstClipboardCloseSession(ptrSessionB, "ExcludeOwnChanges session B"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after ExcludeOwnChanges test failed, hrc=%Rhrc\n", hrc)); - } - - - /* IncludePayload attaches the item payload to data events for sessions that request it. */ - { - ComPtr ptrWriterSession; - ComPtr ptrPayloadSession; - ComPtr ptrNoPayloadSession; - ComPtr ptrPayloadEventSource; - ComPtr ptrNoPayloadEventSource; - ComPtr ptrPayloadListener; - ComPtr ptrNoPayloadListener; - bool fPayloadListenerRegistered = false; - bool fNoPayloadListenerRegistered = false; - do - { - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrWriterSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(payload writer) failed, hrc=%Rhrc\n", hrc)); - ULONG idWriterSession = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrWriterSession->COMGETTER(Id)(&idWriterSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(payload writer) failed, hrc=%Rhrc\n", hrc)); - - static IClipboardSessionFlag_T const s_aPayloadFlags[] = - { - IClipboardSessionFlag_IncludePayload - }; - hrc = tstCreateSession(pClipboard, s_aPayloadFlags, RT_ELEMENTS(s_aPayloadFlags), ptrPayloadSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(IncludePayload) failed, hrc=%Rhrc\n", hrc)); - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrNoPayloadSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(no payload observer) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrPayloadSession->COMGETTER(EventSource)(ptrPayloadEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(IncludePayload) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrNoPayloadSession->COMGETTER(EventSource)(ptrNoPayloadEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(no payload) failed, hrc=%Rhrc\n", hrc)); - - static VBoxEventType_T const s_aDataEventTypes[] = - { - VBoxEventType_OnClipboardDataChanged - }; - hrc = tstRegisterClipboardListener(ptrPayloadEventSource, s_aDataEventTypes, RT_ELEMENTS(s_aDataEventTypes), - ptrPayloadListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(IncludePayload) failed, hrc=%Rhrc\n", hrc)); - fPayloadListenerRegistered = true; - hrc = tstRegisterClipboardListener(ptrNoPayloadEventSource, s_aDataEventTypes, RT_ELEMENTS(s_aDataEventTypes), - ptrNoPayloadListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(no payload) failed, hrc=%Rhrc\n", hrc)); - fNoPayloadListenerRegistered = true; - - static const char s_szPayloadText[] = "tstClipboard session payload data"; - std::vector abPayloadData = tstBytesFromString(s_szPayloadText); - SafeArray aPayloadData; - hrc = tstSafeArrayFromBytes(abPayloadData, aPayloadData); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("SafeArray initFrom(payload data) failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmWrittenSource = ClipboardSource_Custom; - Bstr bstrWrittenMimeType; - SafeArray aWrittenBuffer; - hrc = ptrWriterSession->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Host, - Bstr("text/plain;charset=utf-8").raw(), - ComSafeArrayAsInParam(aPayloadData), &enmWrittenSource, - bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Payload writer WriteDataRaw failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrPayloadEvent; - VBoxEventType_T enmPayloadEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrPayloadEventSource, ptrPayloadListener, s_aDataEventTypes, - RT_ELEMENTS(s_aDataEventTypes), 1000 /* cMsTimeout */, - "IncludePayload listener", ptrPayloadEvent, &enmPayloadEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - ComPtr ptrPayloadItem; - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrPayloadEvent, "IncludePayload data event", idWriterSession, - ClipboardAction_Copy, &ptrPayloadItem)); - RTTESTI_CHECK_MSG(!ptrPayloadItem.isNull(), ("IncludePayload data event did not include an item\n")); - if (ptrPayloadItem.isNotNull()) - RTTESTI_CHECK(tstClipboardCheckItemPayload(ptrPayloadItem, "IncludePayload event item", - ClipboardSource_Host, "text/plain;charset=utf-8", abPayloadData)); - } - - ComPtr ptrNoPayloadEvent; - VBoxEventType_T enmNoPayloadEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrNoPayloadEventSource, ptrNoPayloadListener, s_aDataEventTypes, - RT_ELEMENTS(s_aDataEventTypes), 1000 /* cMsTimeout */, - "No IncludePayload listener", ptrNoPayloadEvent, &enmNoPayloadEventType); - RTTESTI_CHECK(fRc); - if (fRc) - { - ComPtr ptrNoPayloadItem; - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrNoPayloadEvent, "No IncludePayload data event", - idWriterSession, ClipboardAction_Copy, &ptrNoPayloadItem)); - RTTESTI_CHECK_MSG(ptrNoPayloadItem.isNull(), - ("No IncludePayload data event unexpectedly included an item\n")); - } - } while (0); - - if (fPayloadListenerRegistered && ptrPayloadEventSource.isNotNull() && ptrPayloadListener.isNotNull()) - ptrPayloadEventSource->UnregisterListener(ptrPayloadListener); - if (fNoPayloadListenerRegistered && ptrNoPayloadEventSource.isNotNull() && ptrNoPayloadListener.isNotNull()) - ptrNoPayloadEventSource->UnregisterListener(ptrNoPayloadListener); - ptrPayloadListener.setNull(); - ptrNoPayloadListener.setNull(); - - tstClipboardCloseSession(ptrWriterSession, "IncludePayload writer session"); - tstClipboardCloseSession(ptrPayloadSession, "IncludePayload observer session"); - tstClipboardCloseSession(ptrNoPayloadSession, "No IncludePayload observer session"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after IncludePayload test failed, hrc=%Rhrc\n", hrc)); - } - - - /* ExcludeReflections suppresses matching anonymous format reflections, not direct session events. */ - { - ComPtr ptrReflectSession; - ComPtr ptrObserverSession; - ComPtr ptrReflectEventSource; - ComPtr ptrObserverEventSource; - ComPtr ptrReflectListener; - ComPtr ptrObserverListener; - bool fReflectListenerRegistered = false; - bool fObserverListenerRegistered = false; - do - { - static IClipboardSessionFlag_T const s_aExcludeReflectionFlags[] = - { - IClipboardSessionFlag_ExcludeReflections - }; - hrc = tstCreateSession(pClipboard, s_aExcludeReflectionFlags, RT_ELEMENTS(s_aExcludeReflectionFlags), - ptrReflectSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(ExcludeReflections) failed, hrc=%Rhrc\n", hrc)); - hrc = tstCreateSession(pClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrObserverSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(reflection observer) failed, hrc=%Rhrc\n", hrc)); - - ULONG idReflectSession = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrReflectSession->COMGETTER(Id)(&idReflectSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(ExcludeReflections) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idReflectSession != VBOX_SHCL_MAIN_CLIENT_NONE, - ("ExcludeReflections session returned zero client ID\n")); - - hrc = ptrReflectSession->COMGETTER(EventSource)(ptrReflectEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(ExcludeReflections) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrObserverSession->COMGETTER(EventSource)(ptrObserverEventSource.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(EventSource)(reflection observer) failed, hrc=%Rhrc\n", hrc)); - - static VBoxEventType_T const s_aFormatEventTypes[] = - { - VBoxEventType_OnClipboardFormatChanged - }; - hrc = tstRegisterClipboardListener(ptrReflectEventSource, s_aFormatEventTypes, RT_ELEMENTS(s_aFormatEventTypes), - ptrReflectListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(reflection writer) failed, hrc=%Rhrc\n", hrc)); - fReflectListenerRegistered = true; - hrc = tstRegisterClipboardListener(ptrObserverEventSource, s_aFormatEventTypes, RT_ELEMENTS(s_aFormatEventTypes), - ptrObserverListener); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterListener(reflection observer) failed, hrc=%Rhrc\n", hrc)); - fObserverListenerRegistered = true; - - hrc = ptrReflectSession->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Session WriteFormats(ExcludeReflections) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrReflectEvent; - VBoxEventType_T enmReflectEventType = VBoxEventType_Invalid; - bool fRc = tstClipboardWaitForAnyEvent(ptrReflectEventSource, ptrReflectListener, s_aFormatEventTypes, - RT_ELEMENTS(s_aFormatEventTypes), 1000 /* cMsTimeout */, - "ExcludeReflections direct writer", ptrReflectEvent, &enmReflectEventType); - RTTESTI_CHECK(fRc); - if (fRc) - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrReflectEvent, "ExcludeReflections direct writer format event", - idReflectSession, ClipboardSource_Host, - "text/plain;charset=utf-8")); - - ComPtr ptrObserverEvent; - VBoxEventType_T enmObserverEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aFormatEventTypes, - RT_ELEMENTS(s_aFormatEventTypes), 1000 /* cMsTimeout */, - "ExcludeReflections observer", ptrObserverEvent, &enmObserverEventType); - RTTESTI_CHECK(fRc); - if (fRc) - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrObserverEvent, "ExcludeReflections observer format event", - idReflectSession, ClipboardSource_Host, - "text/plain;charset=utf-8")); - - hrc = pClipboard->WriteFormats(ComSafeArrayAsInParam(aTextFormats)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("Anonymous WriteFormats(ExcludeReflections echo) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrObserverEchoEvent; - VBoxEventType_T enmObserverEchoEventType = VBoxEventType_Invalid; - fRc = tstClipboardWaitForAnyEvent(ptrObserverEventSource, ptrObserverListener, s_aFormatEventTypes, - RT_ELEMENTS(s_aFormatEventTypes), 1000 /* cMsTimeout */, - "ExcludeReflections observer echo", ptrObserverEchoEvent, &enmObserverEchoEventType); - RTTESTI_CHECK(fRc); - if (fRc) - RTTESTI_CHECK(tstClipboardCheckFormatChangedEvent(ptrObserverEchoEvent, - "ExcludeReflections observer anonymous echo event", - VBOX_SHCL_MAIN_CLIENT_NONE, ClipboardSource_Host, - "text/plain;charset=utf-8")); - RTTESTI_CHECK(tstClipboardExpectNoEvent(ptrReflectEventSource, ptrReflectListener, 250 /* cMsTimeout */, - "ExcludeReflections anonymous echo")); - } while (0); - - if (fReflectListenerRegistered && ptrReflectEventSource.isNotNull() && ptrReflectListener.isNotNull()) - ptrReflectEventSource->UnregisterListener(ptrReflectListener); - if (fObserverListenerRegistered && ptrObserverEventSource.isNotNull() && ptrObserverListener.isNotNull()) - ptrObserverEventSource->UnregisterListener(ptrObserverListener); - ptrReflectListener.setNull(); - ptrObserverListener.setNull(); - - tstClipboardCloseSession(ptrReflectSession, "ExcludeReflections session"); - tstClipboardCloseSession(ptrObserverSession, "ExcludeReflections observer session"); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after ExcludeReflections test failed, hrc=%Rhrc\n", hrc)); - } - - hrc = pClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMSETTER(Mode)(Bidirectional) after sessions failed, hrc=%Rhrc\n", hrc)); - hrc = pClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset after session API subtest failed, hrc=%Rhrc\n", hrc)); -} - - - -/** - * Tests the public console clipboard API. - * - * @param hTest Test handle. - */ -static void tstClipboardPublicApi(RTTEST hTest) -{ - RTTestSub(hTest, "Clipboard public API"); - - HRESULT hrc = S_OK; - bool fMachineRegistered = false; - bool fMachineLocked = false; - bool fListenerRegistered = false; - ComPtr ptrVirtualBoxClient; - ComPtr ptrVirtualBox; - ComPtr ptrSession; - ComPtr ptrMachine; - ComPtr ptrConsole; - ComPtr ptrClipboard; - ComPtr ptrEventSource; - ComPtr ptrListener; - ComPtr ptrSurvivingSession; - ULONG idSurvivingSession = VBOX_SHCL_MAIN_CLIENT_NONE; - - do - { - /* Create the frontend objects and a throwaway VM used for live console clipboard testing. */ - hrc = ptrVirtualBoxClient.createInprocObject(CLSID_VirtualBoxClient); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("createInprocObject(CLSID_VirtualBoxClient) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrVirtualBoxClient->COMGETTER(VirtualBox)(ptrVirtualBox.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(VirtualBox) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrHost; - hrc = ptrVirtualBox->COMGETTER(Host)(ptrHost.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Host) failed, hrc=%Rhrc\n", hrc)); - PlatformArchitecture_T enmPlatformArch = PlatformArchitecture_x86; - hrc = ptrHost->COMGETTER(Architecture)(&enmPlatformArch); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Architecture) failed, hrc=%Rhrc\n", hrc)); - - RTUUID uuid; - int vrc = RTUuidCreate(&uuid); - RTTESTI_CHECK_RC_BREAK(vrc, VINF_SUCCESS); - char szMachineName[64]; - RTStrPrintf(szMachineName, sizeof(szMachineName), "tstClipboard-%RTuuid", &uuid); - - SafeArray aGroups; - hrc = ptrVirtualBox->CreateMachine(NULL, /* Settings file */ - Bstr(szMachineName).raw(), /* Name */ - enmPlatformArch, - ComSafeArrayAsInParam(aGroups), /* Groups */ - NULL, /* OS type */ - NULL, /* Flags */ - NULL, /* Cipher */ - NULL, /* Password ID */ - NULL, /* Password */ - ptrMachine.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateMachine failed, hrc=%Rhrc\n", hrc)); - - /* Verify default clipboard settings before enabling the directions this testcase exercises. */ - ComPtr ptrClipboardSettings; - hrc = ptrMachine->COMGETTER(Clipboard)(ptrClipboardSettings.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(IMachine::Clipboard) failed, hrc=%Rhrc\n", hrc)); - - ClipboardMode_T enmInitialMode = ClipboardMode_Bidirectional; - hrc = ptrClipboardSettings->COMGETTER(Mode)(&enmInitialMode); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(IClipboardSettings::Mode) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmInitialMode == ClipboardMode_Disabled); - - BOOL fFileTransfersEnabled = TRUE; - hrc = ptrClipboardSettings->COMGETTER(FileTransfersEnabled)(&fFileTransfersEnabled); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(IClipboardSettings::FileTransfersEnabled) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!fFileTransfersEnabled); - - hrc = ptrClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMSETTER(Mode) failed, hrc=%Rhrc\n", hrc)); -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - hrc = ptrClipboardSettings->COMSETTER(FileTransfersEnabled)(TRUE); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMSETTER(FileTransfersEnabled) failed, hrc=%Rhrc\n", hrc)); -#endif - - /* Register and launch the VM so the console-scoped clipboard service is available. */ - hrc = ptrVirtualBox->RegisterMachine(ptrMachine); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("RegisterMachine failed, hrc=%Rhrc\n", hrc)); - fMachineRegistered = true; - - hrc = ptrSession.createInprocObject(CLSID_Session); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("createInprocObject(CLSID_Session) failed, hrc=%Rhrc\n", hrc)); - - ComPtr ptrLaunchProgress; - hrc = ptrMachine->LaunchVMProcess(ptrSession, Bstr("headless").raw(), - ComSafeArrayNullInParam(), ptrLaunchProgress.asOutParam()); - if (FAILED(hrc)) - { - tstLogErrorInfo(hTest, "LaunchVMProcess", ErrorInfo(ptrMachine, COM_IIDOF(IMachine))); - RTTestSkipped(hTest, "LaunchVMProcess failed, hrc=%Rhrc", hrc); - break; - } - RTTESTI_CHECK_MSG_BREAK(!ptrLaunchProgress.isNull(), ("LaunchVMProcess returned no progress object\n")); - hrc = ptrLaunchProgress->WaitForCompletion(-1); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WaitForCompletion(LaunchVMProcess) failed, hrc=%Rhrc\n", hrc)); - LONG lrcLaunchResult = S_OK; - hrc = ptrLaunchProgress->COMGETTER(ResultCode)(&lrcLaunchResult); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(ResultCode) failed, hrc=%Rhrc\n", hrc)); - if (FAILED((HRESULT)lrcLaunchResult)) - { - tstLogErrorInfo(hTest, "LaunchVMProcess progress", ProgressErrorInfo(ptrLaunchProgress)); - RTTestSkipped(hTest, "LaunchVMProcess result code is %Rhrc", lrcLaunchResult); - break; - } - fMachineLocked = true; - - /* Resolve the live console and clipboard objects under test. */ - hrc = ptrSession->COMGETTER(Console)(ptrConsole.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Console) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrConsole.isNull(), ("COMGETTER(Console) returned NULL\n")); - - ComPtr ptrSessionMachine; - hrc = ptrSession->COMGETTER(Machine)(ptrSessionMachine.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Machine) failed, hrc=%Rhrc\n", hrc)); - hrc = ptrSessionMachine->COMGETTER(Clipboard)(ptrClipboardSettings.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(IInternalSession::Machine::Clipboard) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrConsole->COMGETTER(Clipboard)(ptrClipboard.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Clipboard) failed, hrc=%Rhrc\n", hrc)); - - /* Verify object construction helpers before exercising live clipboard state. */ - tstClipboardPublicObjects(hTest, ptrClipboard); - RTTestSub(hTest, "Clipboard public API setup"); - - /* Verify transfer source-path storage and the auxiliary transfer/event-source getters. */ -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - ComPtr ptrTransfers; - hrc = ptrClipboard->COMGETTER(Transfers)(ptrTransfers.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Transfers) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrTransfers.isNull()); - - SafeIfaceArray aTransfers; - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any initial) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aTransfers.size() == 0); - aTransfers.setNull(); - hrc = ptrTransfers->GetTransfers((ClipboardTransferDirection_T)999, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("GetTransfers(invalid direction) returned hrc=%Rhrc, expected E_INVALIDARG\n", hrc)); - - char szTmpDir[RTPATH_MAX]; - char szFile1[RTPATH_MAX]; - vrc = RTPathTemp(szTmpDir, sizeof(szTmpDir)); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTPathTemp failed, vrc=%Rrc\n", vrc)); - RTStrPrintf(szFile1, sizeof(szFile1), "%s/tstClipboard-%RU64-1.txt", szTmpDir, RTTimeNanoTS()); - - static const char s_szFile1Data[] = "clipboard transfer data one"; - RTFILE hFile = NIL_RTFILE; - vrc = RTFileOpen(&hFile, szFile1, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTFileOpen(%s) failed, vrc=%Rrc\n", szFile1, vrc)); - vrc = RTFileWrite(hFile, s_szFile1Data, sizeof(s_szFile1Data) - 1, NULL /* pcbWritten */); - RTTESTI_CHECK_MSG(RT_SUCCESS(vrc), ("RTFileWrite(%s) failed, vrc=%Rrc\n", szFile1, vrc)); - RTFileClose(hFile); - - - ComPtr ptrTransfer; - hrc = ptrTransfers->CreateTransfer(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, - ClipboardAction_Copy, ptrTransfer.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateTransfer(ToGuest, Host, Copy) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrTransfer.isNull(), ("CreateTransfer(ToGuest, Host, Copy) returned NULL transfer\n")); - SafeArray aSourcePaths; - hrc = ptrTransfer->GetSourcePaths(ComSafeArrayAsOutParam(aSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSourcePaths initial failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSourcePaths.size() == 0); - - SafeArray aNewSourcePaths; - RTTESTI_CHECK(aNewSourcePaths.push_back(Bstr(szFile1).raw())); - hrc = ptrTransfer->SetSourcePaths(ComSafeArrayAsInParam(aNewSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetSourcePaths failed, hrc=%Rhrc\n", hrc)); - aSourcePaths.setNull(); - hrc = ptrTransfer->GetSourcePaths(ComSafeArrayAsOutParam(aSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSourcePaths after set failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSourcePaths.size() == 1); - if (aSourcePaths.size() == 1) - RTTESTI_CHECK(!RTUtf16Cmp(aSourcePaths[0], Bstr(szFile1).raw())); - - hrc = ptrTransfers->Add(ptrTransfer); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferManager::Add(source-path transfer) failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrTransfers->Pause(ptrTransfer); - RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Pause returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); - hrc = ptrTransfers->Resume(ptrTransfer); - RTTESTI_CHECK_MSG(hrc == E_NOTIMPL, ("IClipboardTransferManager::Resume returned hrc=%Rhrc, expected E_NOTIMPL\n", hrc)); - - aTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after SourcePaths) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aTransfers.size() == 1); - SafeIfaceArray aGuestTransfers; - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToGuest, 0, ComSafeArrayAsOutParam(aGuestTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToGuest after SourcePaths) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aGuestTransfers.size() == 1); - SafeIfaceArray aHostTransfers; - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_ToHost, 0, ComSafeArrayAsOutParam(aHostTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(ToHost after SourcePaths) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aHostTransfers.size() == 0); - if (aTransfers.size() == 1) - { - SafeIfaceArray aRootNodes; - hrc = aTransfers[0]->Roots(ComSafeArrayAsOutParam(aRootNodes)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::Roots failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aRootNodes.size() == 1); - if (aRootNodes.size() == 1) - { - Bstr bstrPath; - hrc = aRootNodes[0]->COMGETTER(Path)(bstrPath.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFsObjInfo::COMGETTER(Path) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrPath).c_str(), RTPathFilename(szFile1))); - } - - ComPtr ptrInvalidNode; - hrc = aTransfers[0]->Query(Bstr("../host-file").raw(), ptrInvalidNode.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::Query('../host-file') unexpectedly succeeded\n")); - SafeIfaceArray aInvalidNodes; - hrc = aTransfers[0]->List(Bstr("/absolute").raw(), ClipboardTransferListFlag_None, ComSafeArrayAsOutParam(aInvalidNodes)); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::List('/absolute') unexpectedly succeeded\n")); - - ComPtr ptrUnsupportedFile; - hrc = aTransfers[0]->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadWrite, - FileOpenAction_OpenExisting, FileSharingMode_Read, 0 /* creationMode */, - ptrUnsupportedFile.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::OpenFile(ReadWrite) unexpectedly succeeded\n")); - hrc = aTransfers[0]->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadOnly, - FileOpenAction_CreateOrReplace, FileSharingMode_Read, 0 /* creationMode */, - ptrUnsupportedFile.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::OpenFile(CreateOrReplace) unexpectedly succeeded\n")); - - ComPtr ptrFile; - hrc = aTransfers[0]->OpenFile(Bstr(RTPathFilename(szFile1)).raw(), FileAccessMode_ReadOnly, - FileOpenAction_OpenExisting, FileSharingMode_Read, 0 /* creationMode */, - ptrFile.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::OpenFile failed, hrc=%Rhrc\n", hrc)); - if (SUCCEEDED(hrc) && ptrFile.isNotNull()) - { - LONG64 cbInitial = -1; - hrc = ptrFile->COMGETTER(InitialSize)(&cbInitial); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::COMGETTER(InitialSize) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cbInitial == (LONG64)sizeof(s_szFile1Data) - 1); - LONG64 cbSize = -1; - hrc = ptrFile->QuerySize(&cbSize); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::QuerySize failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cbSize == (LONG64)sizeof(s_szFile1Data) - 1); - ComPtr ptrFileInfo; - hrc = ptrFile->QueryInfo(ptrFileInfo.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::QueryInfo failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrFileInfo.isNull()); - SafeArray aWriteData; - RTTESTI_CHECK(aWriteData.push_back('x')); - ULONG cbWritten = 0; - hrc = ptrFile->Write(ComSafeArrayAsInParam(aWriteData), 0 /* timeoutMS */, &cbWritten); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferFile::Write unexpectedly succeeded\n")); - - SafeArray aFileData; - hrc = ptrFile->Read(sizeof(s_szFile1Data) - 1, 0 /* timeoutMS */, ComSafeArrayAsOutParam(aFileData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::Read failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aFileData.size() == sizeof(s_szFile1Data) - 1); - if (aFileData.size() == sizeof(s_szFile1Data) - 1) - RTTESTI_CHECK(!memcmp(aFileData.raw(), s_szFile1Data, sizeof(s_szFile1Data) - 1)); - hrc = ptrFile->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferFile::Close failed, hrc=%Rhrc\n", hrc)); - } - - ComPtr ptrTransferData; - hrc = aTransfers[0]->COMGETTER(Data)(ptrTransferData.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::COMGETTER(Data) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrTransferData.isNull()); - - LONG64 cRoots = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_RootList, Bstr("").raw(), Bstr("").raw(), - 0 /* aFlags */, &cRoots); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Open(RootList) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cRoots == 1); - - Bstr bstrRootName; - ULONG fInfo = 0; - SafeArray aInfo; - SafeArray aData; - hrc = ptrTransferData->Read(ClipboardTransferDataType_RootList, 0 /* aHandle */, 0 /* aSize */, - 0 /* aFlags */, bstrRootName.asOutParam(), &fInfo, - ComSafeArrayAsOutParam(aInfo), ComSafeArrayAsOutParam(aData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Read(RootList) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(fInfo & VBOX_SHCL_INFO_F_FSOBJINFO); - RTTESTI_CHECK(aInfo.size() == sizeof(SHCLFSOBJINFO)); - - LONG64 hInvalid = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_List, Bstr("../parent").raw(), Bstr("").raw(), - 0 /* aFlags */, &hInvalid); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("IClipboardTransferData::Open(List '../parent') returned hrc=%Rhrc\n", hrc)); - hrc = ptrTransferData->Open(ClipboardTransferDataType_List, Bstr("/absolute").raw(), Bstr("").raw(), - 0 /* aFlags */, &hInvalid); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("IClipboardTransferData::Open(List '/absolute') returned hrc=%Rhrc\n", hrc)); - hrc = ptrTransferData->Open(ClipboardTransferDataType_Object, Bstr("dir\\file").raw(), Bstr("").raw(), - SHCL_OBJ_CF_ACCESS_READ | SHCL_OBJ_CF_ACCESS_DENYWRITE, &hInvalid); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("IClipboardTransferData::Open(Object 'dir\\file') returned hrc=%Rhrc\n", hrc)); - hrc = ptrTransferData->Open(ClipboardTransferDataType_Object, Bstr("C:file").raw(), Bstr("").raw(), - SHCL_OBJ_CF_ACCESS_READ | SHCL_OBJ_CF_ACCESS_DENYWRITE, &hInvalid); - RTTESTI_CHECK_MSG(hrc == E_INVALIDARG, ("IClipboardTransferData::Open(Object 'C:file') returned hrc=%Rhrc\n", hrc)); - - LONG64 hObj = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_Object, bstrRootName.raw(), Bstr("").raw(), - SHCL_OBJ_CF_ACCESS_READ | SHCL_OBJ_CF_ACCESS_DENYWRITE, &hObj); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Open(Object) failed, hrc=%Rhrc\n", hrc)); - SafeArray aObjData; - Bstr bstrObjName; - ULONG fObjInfo = 0; - SafeArray aObjInfo; - hrc = ptrTransferData->Read(ClipboardTransferDataType_Object, hObj, sizeof(s_szFile1Data) - 1, - 0 /* aFlags */, bstrObjName.asOutParam(), &fObjInfo, - ComSafeArrayAsOutParam(aObjInfo), ComSafeArrayAsOutParam(aObjData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Read(Object) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aObjData.size() == sizeof(s_szFile1Data) - 1); - if (aObjData.size() == sizeof(s_szFile1Data) - 1) - RTTESTI_CHECK(!memcmp(aObjData.raw(), s_szFile1Data, sizeof(s_szFile1Data) - 1)); - ULONG cbObjWritten = 0; - hrc = ptrTransferData->Write(ClipboardTransferDataType_Object, hObj, Bstr("").raw(), 0 /* aInfoFlags */, - ComSafeArrayAsInParam(aObjInfo), ComSafeArrayAsInParam(aObjData), - 1 /* aFlags */, &cbObjWritten); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferData::Write(Object with flags) unexpectedly succeeded\n")); - RTTESTI_CHECK(cbObjWritten == 0); - hrc = ptrTransferData->Close(ClipboardTransferDataType_Object, hObj); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Close(Object) failed, hrc=%Rhrc\n", hrc)); - } - - char szDir1[RTPATH_MAX]; - char szDirFile1[RTPATH_MAX]; - RTStrPrintf(szDir1, sizeof(szDir1), "%s/tstClipboard-%RU64-dir", szTmpDir, RTTimeNanoTS()); - vrc = RTDirCreate(szDir1, 0700 /* fMode */, 0 /* fCreate */); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTDirCreate(%s) failed, vrc=%Rrc\n", szDir1, vrc)); - RTStrPrintf(szDirFile1, sizeof(szDirFile1), "%s/tstClipboard-list-entry.txt", szDir1); - hFile = NIL_RTFILE; - vrc = RTFileOpen(&hFile, szDirFile1, RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); - RTTESTI_CHECK_MSG_BREAK(RT_SUCCESS(vrc), ("RTFileOpen(%s) failed, vrc=%Rrc\n", szDirFile1, vrc)); - static const char s_szDirFile1Data[] = "clipboard transfer list data"; - vrc = RTFileWrite(hFile, s_szDirFile1Data, sizeof(s_szDirFile1Data) - 1, NULL /* pcbWritten */); - RTTESTI_CHECK_MSG(RT_SUCCESS(vrc), ("RTFileWrite(%s) failed, vrc=%Rrc\n", szDirFile1, vrc)); - RTFileClose(hFile); - - SafeArray aDirSourcePaths; - RTTESTI_CHECK(aDirSourcePaths.push_back(Bstr(szDir1).raw())); - hrc = ptrTransfer->SetSourcePaths(ComSafeArrayAsInParam(aDirSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("SetSourcePaths directory failed, hrc=%Rhrc\n", hrc)); - aSourcePaths.setNull(); - hrc = ptrTransfer->GetSourcePaths(ComSafeArrayAsOutParam(aSourcePaths)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSourcePaths directory failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSourcePaths.size() == 1); - if (aSourcePaths.size() == 1) - RTTESTI_CHECK(!RTUtf16Cmp(aSourcePaths[0], Bstr(szDir1).raw())); - aTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after directory SourcePaths) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aTransfers.size() == 1); - if (aTransfers.size() == 1) - { - SafeIfaceArray aDirNodes; - hrc = aTransfers[0]->List(Bstr("").raw(), ClipboardTransferListFlag_None, ComSafeArrayAsOutParam(aDirNodes)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::List(recursive) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aDirNodes.size() == 2); - SafeIfaceArray aDirRootOnly; - hrc = aTransfers[0]->List(Bstr("").raw(), ClipboardTransferListFlag_NoRecursion, ComSafeArrayAsOutParam(aDirRootOnly)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::List(NoRecursion) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aDirRootOnly.size() == 1); - - ComPtr ptrInvalidDirectory; - hrc = aTransfers[0]->OpenDirectory(Bstr("").raw(), ClipboardTransferListFlag_NoRecursion, - ptrInvalidDirectory.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransfer::OpenDirectory(empty path) unexpectedly succeeded\n")); - - ComPtr ptrDirectory; - hrc = aTransfers[0]->OpenDirectory(Bstr(RTPathFilename(szDir1)).raw(), ClipboardTransferListFlag_NoRecursion, - ptrDirectory.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::OpenDirectory failed, hrc=%Rhrc\n", hrc)); - if (SUCCEEDED(hrc) && ptrDirectory.isNotNull()) - { - SafeIfaceArray aChildren; - hrc = ptrDirectory->ListEx(16, ClipboardTransferListFlag_NoRecursion, ComSafeArrayAsOutParam(aChildren)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::ListEx failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aChildren.size() == 1); - hrc = ptrDirectory->Rewind(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::Rewind failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrChild; - hrc = ptrDirectory->Read(ptrChild.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::Read after rewind failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrChild.isNull()); - hrc = ptrDirectory->Close(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferDirectory::Close failed, hrc=%Rhrc\n", hrc)); - } - - ComPtr ptrTransferData; - hrc = aTransfers[0]->COMGETTER(Data)(ptrTransferData.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransfer::COMGETTER(Data directory) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrTransferData.isNull()); - - LONG64 cRoots = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_RootList, Bstr("").raw(), Bstr("").raw(), - 0 /* aFlags */, &cRoots); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Open(directory RootList) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(cRoots == 1); - - Bstr bstrDirRootName; - ULONG fDirRootInfo = 0; - SafeArray aDirRootInfo; - SafeArray aDirRootData; - hrc = ptrTransferData->Read(ClipboardTransferDataType_RootList, 0 /* aHandle */, 0 /* aSize */, - 0 /* aFlags */, bstrDirRootName.asOutParam(), &fDirRootInfo, - ComSafeArrayAsOutParam(aDirRootInfo), ComSafeArrayAsOutParam(aDirRootData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Read(directory RootList) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(fDirRootInfo & VBOX_SHCL_INFO_F_FSOBJINFO); - RTTESTI_CHECK(aDirRootInfo.size() == sizeof(SHCLFSOBJINFO)); - - LONG64 hList = 0; - hrc = ptrTransferData->Open(ClipboardTransferDataType_List, bstrDirRootName.raw(), Bstr("").raw(), - 0 /* aFlags */, &hList); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Open(List) failed, hrc=%Rhrc\n", hrc)); - - Bstr bstrListName; - ULONG fListInfo = 0; - SafeArray aListInfo; - SafeArray aListData; - hrc = ptrTransferData->Read(ClipboardTransferDataType_List, hList, 0 /* aSize */, 0 /* aFlags */, - bstrListName.asOutParam(), &fListInfo, - ComSafeArrayAsOutParam(aListInfo), ComSafeArrayAsOutParam(aListData)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Read(List) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrListName).c_str(), RTPathFilename(szDirFile1))); - RTTESTI_CHECK(fListInfo & VBOX_SHCL_INFO_F_FSOBJINFO); - RTTESTI_CHECK(aListInfo.size() == sizeof(SHCLFSOBJINFO)); - - ULONG cbListWritten = 0; - hrc = ptrTransferData->Write(ClipboardTransferDataType_List, hList, Bstr("new-entry").raw(), - VBOX_SHCL_INFO_F_FSOBJINFO, ComSafeArrayAsInParam(aListInfo), - ComSafeArrayAsInParam(aListData), 0 /* aFlags */, &cbListWritten); - RTTESTI_CHECK_MSG(FAILED(hrc), ("IClipboardTransferData::Write(List) unexpectedly succeeded\n")); - RTTESTI_CHECK(cbListWritten == 0); - - hrc = ptrTransferData->Close(ClipboardTransferDataType_List, hList); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferData::Close(List) failed, hrc=%Rhrc\n", hrc)); - } - - hrc = ptrTransfers->Remove(ptrTransfer); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IClipboardTransferManager::Remove(source-path transfer) failed, hrc=%Rhrc\n", hrc)); - aTransfers.setNull(); - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after source-path transfer remove) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aTransfers.size() == 0); - RTFileDelete(szDirFile1); - RTDirRemove(szDir1); - RTFileDelete(szFile1); -#else - ComPtr ptrTransfers; - hrc = ptrClipboard->COMGETTER(Transfers)(ptrTransfers.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("COMGETTER(Transfers) unexpectedly succeeded without transfer support\n")); - RTTESTI_CHECK(ptrTransfers.isNull()); -#endif - - hrc = ptrClipboard->COMGETTER(EventSource)(ptrEventSource.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(EventSource) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrEventSource.isNull()); - - tstClipboardPublicSessionApi(hTest, ptrClipboard, ptrClipboardSettings, ptrEventSource); - RTTestSub(hTest, "Clipboard public API operations"); - - /* Register a broad passive listener for mode changes and explicit host-origin writes. */ - hrc = ptrEventSource->CreateListener(ptrListener.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("CreateListener failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrListener.isNull()); - SafeArray aEventTypes; - aEventTypes.push_back(VBoxEventType_OnClipboardModeChanged); - aEventTypes.push_back(VBoxEventType_OnClipboardSourceChanged); - aEventTypes.push_back(VBoxEventType_OnClipboardFormatChanged); - aEventTypes.push_back(VBoxEventType_OnClipboardDataChanged); - aEventTypes.push_back(VBoxEventType_OnClipboardDataRequested); - hrc = ptrEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("RegisterListener failed, hrc=%Rhrc\n", hrc)); - fListenerRegistered = SUCCEEDED(hrc); - - /* Check mode-change events before any data writes alter clipboard ownership. */ - hrc = ptrClipboardSettings->COMSETTER(Mode)(ClipboardMode_HostToGuest); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMSETTER(Mode) live change failed, hrc=%Rhrc\n", hrc)); - if (SUCCEEDED(hrc)) - { - ComPtr ptrEvent; - hrc = ptrEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrEvent.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetEvent(mode) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(!ptrEvent.isNull(), ("GetEvent(mode) returned no event\n")); - if (ptrEvent.isNotNull()) - { - VBoxEventType_T enmType = VBoxEventType_Invalid; - hrc = ptrEvent->COMGETTER(Type)(&enmType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)(mode) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmType == VBoxEventType_OnClipboardModeChanged, - ("GetEvent(mode) returned type %d, expected %d\n", - enmType, VBoxEventType_OnClipboardModeChanged)); - - ComPtr ptrModeEvent = ptrEvent; - RTTESTI_CHECK(!ptrModeEvent.isNull()); - if (ptrModeEvent.isNotNull()) - { - ClipboardMode_T enmMode = ClipboardMode_Disabled; - hrc = ptrModeEvent->COMGETTER(ClipboardMode)(&enmMode); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardMode) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmMode == ClipboardMode_HostToGuest, - ("GetEvent(mode) returned mode %d, expected %d\n", - enmMode, ClipboardMode_HostToGuest)); - } - } - } - - /* Verify the initial live clipboard has no formats, data, or queued data events. */ - SafeIfaceArray aReadFormats; - hrc = ptrClipboard->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadFormats failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadFormats.size() == 0); - - ComPtr ptrTextFormat; - hrc = tstCreateFormat(ptrClipboard, "text/plain;charset=utf-8", ptrTextFormat); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateFormat(text) failed, hrc=%Rhrc\n", hrc)); - - BOOL fAvailable = TRUE; - hrc = ptrClipboard->IsFormatAvailable(ClipboardSource_Host, ptrTextFormat, &fAvailable); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IsFormatAvailable failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!fAvailable); - - SafeIfaceArray aSupportedFormats; - hrc = ptrClipboard->GetSupportedFormats(ClipboardSource_Host, ComSafeArrayAsOutParam(aSupportedFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSupportedFormats failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSupportedFormats.size() == 0); - - ComPtr ptrReadItem; - hrc = ptrClipboard->ReadData(ClipboardAction_Copy, ptrReadItem.asOutParam()); - RTTESTI_CHECK_MSG(FAILED(hrc), ("ReadData without available formats unexpectedly succeeded\n")); - - ComPtr ptrUnexpectedEvent; - hrc = ptrEventSource->GetEvent(ptrListener, 0 /* aTimeout */, ptrUnexpectedEvent.asOutParam()); - RTTESTI_CHECK_MSG( hrc == VBOX_E_OBJECT_NOT_FOUND - || ptrUnexpectedEvent.isNull(), - ("Unexpected clipboard event before explicit API write, hrc=%Rhrc\n", hrc)); - - /* Exercise validation failures for empty payloads and unsupported MIME types. */ - std::vector abBuffer; - ComPtr ptrEmptyItem; - hrc = tstCreateItem(ptrClipboard, ClipboardSource_Host, ptrTextFormat, abBuffer, ptrEmptyItem); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateItem(empty) failed, hrc=%Rhrc\n", hrc)); - ComPtr ptrWrittenItem; - hrc = ptrClipboard->WriteData(ClipboardAction_Copy, ptrEmptyItem, ptrWrittenItem.asOutParam()); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_NO_DATA, ("WriteData with empty buffer returned hrc=%Rhrc\n", hrc)); - - ComPtr ptrUnsupportedFormat; - hrc = tstCreateFormat(ptrClipboard, "application/x-unsupported", ptrUnsupportedFormat); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateFormat(unsupported) failed, hrc=%Rhrc\n", hrc)); - abBuffer.push_back('x'); - ComPtr ptrUnsupportedItem; - hrc = tstCreateItem(ptrClipboard, ClipboardSource_Host, ptrUnsupportedFormat, abBuffer, ptrUnsupportedItem); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateItem(unsupported) failed, hrc=%Rhrc\n", hrc)); - ptrWrittenItem.setNull(); - hrc = ptrClipboard->WriteData(ClipboardAction_Copy, ptrUnsupportedItem, ptrWrittenItem.asOutParam()); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("WriteData with unsupported MIME type returned hrc=%Rhrc\n", hrc)); - - /* Write host-origin data and verify the expected source/format/data events. */ - static const char s_szRoundTripText[] = "tstClipboard host to guest round-trip data"; - std::vector abRoundTripBuffer(sizeof(s_szRoundTripText)); - memcpy(&abRoundTripBuffer[0], s_szRoundTripText, sizeof(s_szRoundTripText)); - - ComPtr ptrTextItem; - hrc = tstCreateItem(ptrClipboard, ClipboardSource_Host, ptrTextFormat, abRoundTripBuffer, ptrTextItem); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateItem(text) failed, hrc=%Rhrc\n", hrc)); - ptrWrittenItem.setNull(); - hrc = ptrClipboard->WriteData(ClipboardAction_Copy, ptrTextItem, ptrWrittenItem.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrWrittenItem.isNull()); - - static VBoxEventType_T const s_aExpectedEvents[] = - { - VBoxEventType_OnClipboardSourceChanged, - VBoxEventType_OnClipboardFormatChanged, - VBoxEventType_OnClipboardDataChanged - }; - for (size_t i = 0; i < RT_ELEMENTS(s_aExpectedEvents); i++) - { - ComPtr ptrEvent; - hrc = ptrEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent[%zu] failed, hrc=%Rhrc\n", i, hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrEvent.isNull(), ("GetEvent[%zu] returned no event\n", i)); - - VBoxEventType_T enmType = VBoxEventType_Invalid; - hrc = ptrEvent->COMGETTER(Type)(&enmType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)[%zu] failed, hrc=%Rhrc\n", i, hrc)); - RTTESTI_CHECK_MSG(enmType == s_aExpectedEvents[i], - ("GetEvent[%zu] returned type %d, expected %d\n", i, enmType, s_aExpectedEvents[i])); - RTTESTI_CHECK_MSG(enmType != VBoxEventType_OnClipboardDataRequested, - ("Explicit API write unexpectedly emitted OnClipboardDataRequested\n")); - if (enmType == VBoxEventType_OnClipboardFormatChanged) - { - ComPtr ptrFormatEvent = ptrEvent; - RTTESTI_CHECK(!ptrFormatEvent.isNull()); - if (ptrFormatEvent.isNotNull()) - { - RTTESTI_CHECK(tstClipboardCheckEventMetadata(ptrFormatEvent, "public API format event", - VBOX_SHCL_MAIN_CLIENT_NONE)); - - ClipboardSource_T enmSource = ClipboardSource_Custom; - hrc = ptrFormatEvent->COMGETTER(ClipboardSource)(&enmSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(ClipboardSource)(format event) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(enmSource == ClipboardSource_Host, - ("Format event source %d, expected %d\n", enmSource, ClipboardSource_Host)); - - SafeIfaceArray aEventFormats; - hrc = ptrFormatEvent->COMGETTER(Formats)(ComSafeArrayAsOutParam(aEventFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Formats)(format event) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(aEventFormats.size() == 1, - ("Format event returned %zu formats, expected 1\n", aEventFormats.size())); - if (aEventFormats.size() == 1) - { - Bstr bstrEventMimeType; - hrc = aEventFormats[0]->COMGETTER(MimeType)(bstrEventMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(MimeType)(format event) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrEventMimeType).c_str(), "text/plain;charset=utf-8")); - } - } - } - else if (enmType == VBoxEventType_OnClipboardDataChanged) - RTTESTI_CHECK(tstClipboardCheckDataChangedEvent(ptrEvent, "public API data event", - VBOX_SHCL_MAIN_CLIENT_NONE, ClipboardAction_Copy, - NULL /* pptrItem */)); - } - - ComPtr ptrUnexpectedAfterWrite; - hrc = ptrEventSource->GetEvent(ptrListener, 0 /* aTimeout */, ptrUnexpectedAfterWrite.asOutParam()); - RTTESTI_CHECK_MSG( hrc == VBOX_E_OBJECT_NOT_FOUND - || ptrUnexpectedAfterWrite.isNull(), - ("Unexpected clipboard event after explicit API write, hrc=%Rhrc\n", hrc)); - if (ptrUnexpectedAfterWrite.isNotNull()) - { - BOOL fWaitable = FALSE; - hrc = ptrUnexpectedAfterWrite->COMGETTER(Waitable)(&fWaitable); - if (SUCCEEDED(hrc) && fWaitable) - ptrEventSource->EventProcessed(ptrListener, ptrUnexpectedAfterWrite); - } - - if (ptrListener.isNotNull()) - ptrEventSource->UnregisterListener(ptrListener); - fListenerRegistered = false; - ptrListener.setNull(); - - /* Confirm the host-origin write is visible through query and raw read APIs. */ - aReadFormats.setNull(); - hrc = ptrClipboard->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadFormats after WriteData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadFormats.size() == 1); - - fAvailable = FALSE; - hrc = ptrClipboard->IsFormatAvailable(ClipboardSource_Host, ptrTextFormat, &fAvailable); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("IsFormatAvailable after WriteData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(fAvailable); - - aSupportedFormats.setNull(); - hrc = ptrClipboard->GetSupportedFormats(ClipboardSource_Host, ComSafeArrayAsOutParam(aSupportedFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetSupportedFormats after WriteData failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aSupportedFormats.size() == 1); - - ClipboardSource_T enmGuestReadSource = ClipboardSource_Custom; - Bstr bstrGuestRequestedMimeType(""); - Bstr bstrGuestReadMimeType; - SafeArray aGuestReadBuffer; - hrc = ptrClipboard->ReadDataRaw(ClipboardAction_Copy, bstrGuestRequestedMimeType.raw(), &enmGuestReadSource, - bstrGuestReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aGuestReadBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("ReadDataRaw(host -> guest) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmGuestReadSource == ClipboardSource_Host); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrGuestReadMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aGuestReadBuffer, abRoundTripBuffer), - ("ReadDataRaw(host -> guest) returned %zu bytes, expected %zu\n", - aGuestReadBuffer.size(), abRoundTripBuffer.size())); - - ClipboardSource_T enmTextPlainReadSource = ClipboardSource_Custom; - Bstr bstrTextPlainRequestedMimeType("text/plain"); - Bstr bstrTextPlainReadMimeType; - SafeArray aTextPlainReadBuffer; - hrc = ptrClipboard->ReadDataRaw(ClipboardAction_Copy, bstrTextPlainRequestedMimeType.raw(), &enmTextPlainReadSource, - bstrTextPlainReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aTextPlainReadBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("ReadDataRaw(text/plain, host -> guest) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmTextPlainReadSource == ClipboardSource_Host); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrTextPlainReadMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aTextPlainReadBuffer, abRoundTripBuffer), - ("ReadDataRaw(text/plain, host -> guest) returned %zu bytes, expected %zu\n", - aTextPlainReadBuffer.size(), abRoundTripBuffer.size())); - - /* Switch to bidirectional mode and verify raw guest-origin writes. */ - hrc = ptrClipboardSettings->COMSETTER(Mode)(ClipboardMode_Bidirectional); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), - ("COMSETTER(Mode) bidirectional before guest write failed, hrc=%Rhrc\n", hrc)); - - ClipboardSource_T enmWrittenSource = ClipboardSource_Custom; - Bstr bstrWrittenMimeType; - SafeArray aWrittenBuffer; - hrc = ptrClipboard->WriteDataRaw(ClipboardAction_Copy, ClipboardSource_Guest, bstrGuestReadMimeType.raw(), - ComSafeArrayAsInParam(aGuestReadBuffer), &enmWrittenSource, - bstrWrittenMimeType.asOutParam(), ComSafeArrayAsOutParam(aWrittenBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("WriteDataRaw(guest -> host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmWrittenSource == ClipboardSource_Guest); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrWrittenMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aWrittenBuffer, abRoundTripBuffer), - ("WriteDataRaw(guest -> host) returned %zu bytes, expected %zu\n", - aWrittenBuffer.size(), abRoundTripBuffer.size())); - - enmTextPlainReadSource = ClipboardSource_Custom; - bstrTextPlainReadMimeType.setNull(); - aTextPlainReadBuffer.setNull(); - hrc = ptrClipboard->ReadDataRaw(ClipboardAction_Copy, bstrTextPlainRequestedMimeType.raw(), &enmTextPlainReadSource, - bstrTextPlainReadMimeType.asOutParam(), ComSafeArrayAsOutParam(aTextPlainReadBuffer)); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("ReadDataRaw(text/plain, guest -> host) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmTextPlainReadSource == ClipboardSource_Guest); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrTextPlainReadMimeType).c_str(), "text/plain;charset=utf-8")); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aTextPlainReadBuffer, abRoundTripBuffer), - ("ReadDataRaw(text/plain, guest -> host) returned %zu bytes, expected %zu\n", - aTextPlainReadBuffer.size(), abRoundTripBuffer.size())); - - /* Verify the object read API sees the same guest-origin payload. */ - ptrReadItem.setNull(); - hrc = ptrClipboard->ReadData(ClipboardAction_Copy, ptrReadItem.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("ReadData after guest write failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrReadItem.isNull()); - if (ptrReadItem.isNotNull()) - { - ClipboardSource_T enmRoundTripSource = ClipboardSource_Custom; - hrc = ptrReadItem->COMGETTER(Source)(&enmRoundTripSource); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Source)(round-trip) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(enmRoundTripSource == ClipboardSource_Guest); - - ComPtr ptrRoundTripFormat; - hrc = ptrReadItem->COMGETTER(Format)(ptrRoundTripFormat.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Format)(round-trip) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrRoundTripFormat.isNull()); - if (ptrRoundTripFormat.isNotNull()) - { - Bstr bstrRoundTripMimeType; - hrc = ptrRoundTripFormat->COMGETTER(MimeType)(bstrRoundTripMimeType.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(MimeType)(round-trip) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!RTStrCmp(Utf8Str(bstrRoundTripMimeType).c_str(), "text/plain;charset=utf-8")); - } - - SafeArray aRoundTripBuffer; - hrc = ptrReadItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aRoundTripBuffer)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Buffer)(round-trip) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(tstByteArrayEquals(aRoundTripBuffer, abRoundTripBuffer), - ("ReadData(round-trip) returned %zu bytes, expected %zu\n", - aRoundTripBuffer.size(), abRoundTripBuffer.size())); - } - - /* Exercise the explicit IHostClipboard endpoint after normal IClipboard paths are known-good. */ - tstHostClipboard(hTest, ptrClipboard, ptrClipboardSettings, ptrEventSource, ptrTextFormat, - bstrGuestReadMimeType, aGuestReadBuffer, abRoundTripBuffer); - - /* Verify unsupported format offers fail before checking repeat format notifications. */ - std::vector > vecFormats; - vecFormats.push_back(ptrTextFormat); - vecFormats.push_back(ptrUnsupportedFormat); - SafeIfaceArray aWriteFormats(vecFormats); - hrc = ptrClipboard->WriteFormats(ComSafeArrayAsInParam(aWriteFormats)); - RTTESTI_CHECK_MSG(hrc == VBOX_E_SHCL_FORMAT_NOT_SUPPORTED, - ("WriteFormats with unsupported MIME type returned hrc=%Rhrc\n", hrc)); - - /* Listen only for format changes to prove repeated offers still notify observers. */ - hrc = ptrEventSource->CreateListener(ptrListener.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("CreateListener(format) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(!ptrListener.isNull()); - SafeArray aFormatEventTypes; - aFormatEventTypes.push_back(VBoxEventType_OnClipboardFormatChanged); - hrc = ptrEventSource->RegisterListener(ptrListener, ComSafeArrayAsInParam(aFormatEventTypes), FALSE /* aActive */); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("RegisterListener(format) failed, hrc=%Rhrc\n", hrc)); - fListenerRegistered = SUCCEEDED(hrc); - - vecFormats.clear(); - vecFormats.push_back(ptrTextFormat); - SafeIfaceArray aWriteTextFormats(vecFormats); - hrc = ptrClipboard->WriteFormats(ComSafeArrayAsInParam(aWriteTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("WriteFormats failed, hrc=%Rhrc\n", hrc)); - - hrc = ptrClipboard->WriteFormats(ComSafeArrayAsInParam(aWriteTextFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Repeated WriteFormats failed, hrc=%Rhrc\n", hrc)); - - /* Repeated explicit host offers must remain visible to observers such as VBoxManage clipboard listen. */ - for (unsigned i = 0; i < 2; i++) - { - ComPtr ptrEvent; - hrc = ptrEventSource->GetEvent(ptrListener, 1000 /* aTimeout */, ptrEvent.asOutParam()); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("GetEvent(format write %u) failed, hrc=%Rhrc\n", i, hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrEvent.isNull(), ("GetEvent(format write %u) returned no event\n", i)); - VBoxEventType_T enmType = VBoxEventType_Invalid; - hrc = ptrEvent->COMGETTER(Type)(&enmType); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("COMGETTER(Type)(format write %u) failed, hrc=%Rhrc\n", i, hrc)); - RTTESTI_CHECK_MSG(enmType == VBoxEventType_OnClipboardFormatChanged, - ("GetEvent(format write %u) returned type %d, expected %d\n", - i, enmType, VBoxEventType_OnClipboardFormatChanged)); - } - - if (ptrListener.isNotNull()) - ptrEventSource->UnregisterListener(ptrListener); - fListenerRegistered = false; - ptrListener.setNull(); - - /* Reset clears transient clipboard transfer and format state. */ - hrc = ptrClipboard->Reset(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("Reset failed, hrc=%Rhrc\n", hrc)); - -#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS - SafeIfaceArray aResetTransfers; - hrc = ptrTransfers->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aResetTransfers)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("GetTransfers(Any after Reset) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aResetTransfers.size() == 0); -#endif - - aReadFormats.setNull(); - hrc = ptrClipboard->ReadFormats(ComSafeArrayAsOutParam(aReadFormats)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrc), ("ReadFormats after Reset failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK(aReadFormats.size() == 0); - - RTTestSub(hTest, "Clipboard session survives console teardown setup"); - hrc = tstCreateSession(ptrClipboard, NULL /* paFlags */, 0 /* cFlags */, ptrSurvivingSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("CreateSession(surviving) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG_BREAK(!ptrSurvivingSession.isNull(), ("CreateSession(surviving) returned NULL\n")); - hrc = ptrSurvivingSession->COMGETTER(Id)(&idSurvivingSession); - RTTESTI_CHECK_MSG_BREAK(SUCCEEDED(hrc), ("COMGETTER(Id)(surviving) failed, hrc=%Rhrc\n", hrc)); - RTTESTI_CHECK_MSG(idSurvivingSession != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Surviving session returned anonymous/zero client ID before teardown\n")); - } while (0); - - /* Clean up listeners and VM state regardless of which subtest exited early. */ - if (fListenerRegistered && ptrEventSource.isNotNull() && ptrListener.isNotNull()) - ptrEventSource->UnregisterListener(ptrListener); - ptrListener.setNull(); - ptrEventSource.setNull(); - ptrClipboard.setNull(); - - if (fMachineLocked && !ptrConsole.isNull()) - { - ComPtr ptrPowerDownProgress; - HRESULT hrcPowerDown = ptrConsole->PowerDown(ptrPowerDownProgress.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcPowerDown), ("PowerDown failed, hrc=%Rhrc\n", hrcPowerDown)); - if (SUCCEEDED(hrcPowerDown) && !ptrPowerDownProgress.isNull()) - { - hrcPowerDown = ptrPowerDownProgress->WaitForCompletion(-1); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcPowerDown), ("WaitForCompletion(PowerDown) failed, hrc=%Rhrc\n", hrcPowerDown)); - } - } - - ptrConsole.setNull(); - - if (fMachineLocked) - { - HRESULT hrcUnlock = ptrSession->UnlockMachine(); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcUnlock), ("UnlockMachine failed, hrc=%Rhrc\n", hrcUnlock)); - } - ptrSession.setNull(); - - if (fMachineRegistered) - { - HRESULT hrcWait = tstWaitMachineUnlocked(hTest, ptrMachine); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcWait), ("Waiting for machine unlock failed, hrc=%Rhrc\n", hrcWait)); - - SafeIfaceArray aMedia; - HRESULT hrcCleanup = ptrMachine->Unregister(CleanupMode_DetachAllReturnHardDisksOnly, ComSafeArrayAsOutParam(aMedia)); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcCleanup), ("Unregister failed, hrc=%Rhrc\n", hrcCleanup)); - if (SUCCEEDED(hrcCleanup)) - { - ComPtr ptrProgress; - hrcCleanup = ptrMachine->DeleteConfig(ComSafeArrayAsInParam(aMedia), ptrProgress.asOutParam()); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcCleanup), ("DeleteConfig failed, hrc=%Rhrc\n", hrcCleanup)); - if (SUCCEEDED(hrcCleanup) && !ptrProgress.isNull()) - { - hrcCleanup = ptrProgress->WaitForCompletion(-1); - RTTESTI_CHECK_MSG(SUCCEEDED(hrcCleanup), ("WaitForCompletion(DeleteConfig) failed, hrc=%Rhrc\n", hrcCleanup)); - } - } - } - - if (ptrSurvivingSession.isNotNull()) - { - RTTestSub(hTest, "Clipboard session after console teardown"); - - ULONG idAfterTeardown = VBOX_SHCL_MAIN_CLIENT_NONE; - hrc = ptrSurvivingSession->COMGETTER(Id)(&idAfterTeardown); - RTTESTI_CHECK_MSG( FAILED(hrc) - || idAfterTeardown == idSurvivingSession, - ("Surviving session ID changed from %RU32 to %RU32, hrc=%Rhrc\n", - (uint32_t)idSurvivingSession, (uint32_t)idAfterTeardown, hrc)); - RTTESTI_CHECK_MSG( FAILED(hrc) - || idAfterTeardown != VBOX_SHCL_MAIN_CLIENT_NONE, - ("Surviving session returned anonymous/zero client ID after teardown\n")); - - SafeIfaceArray aFormats; - hrc = ptrSurvivingSession->ReadFormats(ComSafeArrayAsOutParam(aFormats)); - RTTESTI_CHECK_MSG(FAILED(hrc), - ("ReadFormats on surviving session after console teardown unexpectedly succeeded\n")); - - ptrSurvivingSession.setNull(); - } -} - - -int main() -{ - RTTEST hTest; - RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboard", &hTest); - if (rcExit != RTEXITCODE_SUCCESS) - return rcExit; - - tstInitLogging(); - - RTTestBanner(hTest); - - HRESULT hrc = Initialize(); - if (FAILED(hrc)) - { - RTTestFailed(hTest, "Failed to initialize COM, hrc=%Rhrc", hrc); - return RTTestSummaryAndDestroy(hTest); - } - - tstClipboardPublicApi(hTest); - - Shutdown(); - return RTTestSummaryAndDestroy(hTest); -} diff --git a/src/VBox/Main/testcase/tstClipboardAPI.cpp b/src/VBox/Main/testcase/tstClipboardAPI.cpp new file mode 100644 index 000000000000..a8e4d89e21b8 --- /dev/null +++ b/src/VBox/Main/testcase/tstClipboardAPI.cpp @@ -0,0 +1,441 @@ +/* $Id: tstClipboardAPI.cpp 115056 2026-08-17 16:44:52Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Public API object testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include "ClipboardFormatImpl.h" +#include "ClipboardItemImpl.h" +#include "ClipboardImpl.h" +#include "ClipboardSessionImpl.h" +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +# include "ClipboardTransferImpl.h" +# include "ClipboardTransferManagerImpl.h" +#endif + +#include +#include + + +/** @name Parent Clipboard stubs for unexercised session delegation paths. + * @{ */ +/** Test stub for Clipboard::i_registerSession(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_registerSession(VBOXSHCLMAINCLIENTID, ClipboardSession *, uint32_t, const ComPtr &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_unregisterSession(). */ +void Clipboard::i_unregisterSession(VBOXSHCLMAINCLIENTID) +{ +} + +/** Test stub for Clipboard::i_fireSessionInitialState(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_fireSessionInitialState(VBOXSHCLMAINCLIENTID) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_readFormatObjects(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_readFormatObjects(std::vector > &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_readDataRaw(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_readDataRaw(ClipboardAction_T, const com::Utf8Str &, ClipboardSource_T *, com::Utf8Str &, + std::vector &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_writeDataRaw(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_writeDataRaw(VBOXSHCLMAINCLIENTID, ClipboardAction_T, ClipboardSource_T, const com::Utf8Str &, + const std::vector &, ClipboardSource_T *, com::Utf8Str &, std::vector &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_writeFormatObjects(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_writeFormatObjects(VBOXSHCLMAINCLIENTID, const std::vector > &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_hostClipboardReportFormats(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_hostClipboardReportFormats(VBOXSHCLMAINCLIENTID, ClipboardAction_T, ClipboardSource_T, + const std::vector > &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_hostClipboardProvideData(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_hostClipboardProvideData(VBOXSHCLMAINCLIENTID, ULONG, ClipboardAction_T, ClipboardSource_T, + const com::Utf8Str &, const std::vector &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_hostClipboardSetData(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_hostClipboardSetData(VBOXSHCLMAINCLIENTID, ClipboardAction_T, ClipboardSource_T, + const com::Utf8Str &, const std::vector &) +{ + return E_NOTIMPL; +} + +/** Test stub for Clipboard::i_hostClipboardClear(); always returns E_NOTIMPL. */ +HRESULT Clipboard::i_hostClipboardClear(VBOXSHCLMAINCLIENTID) +{ + return E_NOTIMPL; +} +/** @} */ + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Validates an action for the test-local transfer objects. + * + * @returns true if @a enmAction is a public ClipboardAction_T value. + * @param enmAction Action to validate. + */ +bool ShClMainIsValidAction(ClipboardAction_T enmAction) +{ + return enmAction == ClipboardAction_Copy + || enmAction == ClipboardAction_Cut + || enmAction == ClipboardAction_Paste + || enmAction == ClipboardAction_Custom; +} + +/** + * Validates a source for the test-local transfer objects. + * + * @returns true if @a enmSource is a public ClipboardSource_T value. + * @param enmSource Source to validate. + */ +bool ShClMainIsValidSource(ClipboardSource_T enmSource) +{ + return enmSource == ClipboardSource_Host + || enmSource == ClipboardSource_Guest + || enmSource == ClipboardSource_Remote + || enmSource == ClipboardSource_Custom; +} + +/** Test stub for the unexercised parent transfer-cancel callback. */ +HRESULT Clipboard::i_transferCancel(SHCLSESSIONID, SHCLTRANSFERID, SHCLTRANSFERGEN) +{ + return E_NOTIMPL; +} + +/** Test stub for the unexercised parent transfer-event callback. */ +void Clipboard::i_fireClipboardTransferEvent(VBOXSHCLMAINCLIENTID, IClipboardTransfer *, ClipboardTransferState_T, + ClipboardTransferInteraction_T, const com::Utf8Str &, + const com::Utf8Str &, ClipboardError_T) +{ +} +#endif + + +/** + * @page pg_tstClipboardAPI Main Shared Clipboard public API testcase + * + * Tests the state and ownership semantics of the small public Main clipboard + * objects without constructing a VM, HGCM service or native clipboard backend. + */ + + +/** Tests IClipboardFormat and IClipboardItem value-object semantics. */ +static void tstClipboardValues(void) +{ + RTTestISub("Formats and items"); + + ComObjPtr ptrFormatObj; + HRESULT hrc = ptrFormatObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + hrc = ptrFormatObj->init(com::Utf8Str("text/plain;charset=utf-8")); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrFormat; + hrc = ptrFormatObj.queryInterfaceTo(ptrFormat.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + com::Bstr bstrMimeType; + hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(!RTStrCmp(com::Utf8Str(bstrMimeType).c_str(), "text/plain;charset=utf-8")); + + hrc = ptrFormat->COMSETTER(MimeType)(com::Bstr("text/html").raw()); + RTTESTI_CHECK_RC(hrc, S_OK); + bstrMimeType.setNull(); + hrc = ptrFormat->COMGETTER(MimeType)(bstrMimeType.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(!RTStrCmp(com::Utf8Str(bstrMimeType).c_str(), "text/html")); + + static uint8_t const s_abPayload[] = { 0, 1, 2, 0xff }; + std::vector abPayload(s_abPayload, s_abPayload + RT_ELEMENTS(s_abPayload)); + ComObjPtr ptrItemObj; + hrc = ptrItemObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + hrc = ptrItemObj->init(7, ClipboardSource_Host, ptrFormat, abPayload); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrItem; + hrc = ptrItemObj.queryInterfaceTo(ptrItem.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ULONG idItem = 0; + ClipboardSource_T enmSource = ClipboardSource_Custom; + ULONG cbItem = 0; + hrc = ptrItem->COMGETTER(Id)(&idItem); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrItem->COMGETTER(Source)(&enmSource); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrItem->COMGETTER(Size)(&cbItem); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(idItem == 7); + RTTESTI_CHECK(enmSource == ClipboardSource_Host); + RTTESTI_CHECK(cbItem == sizeof(s_abPayload)); + + com::SafeArray aRead; + hrc = ptrItem->COMGETTER(Buffer)(ComSafeArrayAsOutParam(aRead)); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(aRead.size() == sizeof(s_abPayload)); + if (aRead.size() == sizeof(s_abPayload)) + RTTESTI_CHECK(!memcmp(aRead.raw(), s_abPayload, sizeof(s_abPayload))); + + static uint8_t const s_abReplacement[] = { 9, 8, 7 }; + com::SafeArray aReplacement; + hrc = aReplacement.initFrom(s_abReplacement, RT_ELEMENTS(s_abReplacement)); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrItem->COMSETTER(Buffer)(ComSafeArrayAsInParam(aReplacement)); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrItem->COMGETTER(Size)(&cbItem); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(cbItem == sizeof(s_abReplacement)); +} + + +/** Tests IClipboardSession identity, endpoint state and idempotent close. */ +static void tstClipboardSession(void) +{ + RTTestISub("Session state"); + + ComObjPtr ptrSessionObj; + HRESULT hrc = ptrSessionObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + hrc = ptrSessionObj->initForTesting(17, 0); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrSession; + hrc = ptrSessionObj.queryInterfaceTo(ptrSession.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ULONG idSession = 0; + hrc = ptrSession->COMGETTER(Id)(&idSession); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(idSession == 17); + + ComPtr ptrEventSource; + hrc = ptrSession->COMGETTER(EventSource)(ptrEventSource.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(ptrEventSource.isNotNull()); + + hrc = ptrSession->Close(); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrSession->Close(); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrSession->COMGETTER(Id)(&idSession); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(idSession == 17); + + ptrEventSource.setNull(); + RTTESTI_CHECK_RC(RTTestIDisableAssertions(), VINF_SUCCESS); + hrc = ptrSession->COMGETTER(EventSource)(ptrEventSource.asOutParam()); + RTTESTI_CHECK_RC(RTTestIRestoreAssertions(), VINF_SUCCESS); + RTTESTI_CHECK(FAILED(hrc)); + RTTESTI_CHECK(ptrEventSource.isNull()); +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Tests IClipboardTransfer metadata without attaching a data-plane backend. */ +static void tstClipboardTransfer(void) +{ + RTTestISub("Transfer metadata"); + + ComObjPtr ptrTransferObj; + HRESULT hrc = ptrTransferObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + ComPtr ptrItem; + ComPtr ptrProgress; + hrc = ptrTransferObj->init(23, ClipboardTransferDirection_ToGuest, ClipboardSource_Host, + ClipboardAction_Copy, ptrItem, ptrProgress); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrTransfer; + hrc = ptrTransferObj.queryInterfaceTo(ptrTransfer.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ULONG idTransfer = 0; + ClipboardTransferDirection_T enmDirection = ClipboardTransferDirection_Any; + ClipboardSource_T enmSource = ClipboardSource_Custom; + ClipboardAction_T enmAction = ClipboardAction_Invalid; + ClipboardTransferState_T enmState = ClipboardTransferState_Removed; + hrc = ptrTransfer->COMGETTER(Id)(&idTransfer); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Direction)(&enmDirection); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Source)(&enmSource); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Action)(&enmAction); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(State)(&enmState); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(idTransfer == 23); + RTTESTI_CHECK(enmDirection == ClipboardTransferDirection_ToGuest); + RTTESTI_CHECK(enmSource == ClipboardSource_Host); + RTTESTI_CHECK(enmAction == ClipboardAction_Copy); + RTTESTI_CHECK(enmState == ClipboardTransferState_Added); + + ptrTransferObj->i_setState(ClipboardTransferState_Failed, com::Utf8Str("failed"), ClipboardError_OperationFailed); + com::Bstr bstrMessage; + ClipboardError_T enmError = ClipboardError_None; + hrc = ptrTransfer->COMGETTER(State)(&enmState); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Message)(bstrMessage.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + hrc = ptrTransfer->COMGETTER(Error)(&enmError); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(enmState == ClipboardTransferState_Failed); + RTTESTI_CHECK(!RTStrCmp(com::Utf8Str(bstrMessage).c_str(), "failed")); + RTTESTI_CHECK(enmError == ClipboardError_OperationFailed); + + ComPtr ptrData; + RTTESTI_CHECK_RC(RTTestIDisableAssertions(), VINF_SUCCESS); + hrc = ptrTransfer->COMGETTER(Data)(ptrData.asOutParam()); + RTTESTI_CHECK_RC(RTTestIRestoreAssertions(), VINF_SUCCESS); + RTTESTI_CHECK(FAILED(hrc)); + RTTESTI_CHECK(ptrData.isNull()); +} + + +/** Tests IClipboardTransferManager ownership, filtering and removal. */ +static void tstClipboardTransferManager(void) +{ + RTTestISub("Transfer manager"); + + ComObjPtr ptrManagerObj; + HRESULT hrc = ptrManagerObj.createObject(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + hrc = ptrManagerObj->init(); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + ComPtr ptrManager; + hrc = ptrManagerObj.queryInterfaceTo(ptrManager.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + if (FAILED(hrc)) + return; + + com::SafeIfaceArray aTransfers; + hrc = ptrManager->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(aTransfers.size() == 0); + + ComPtr ptrTransfer; + hrc = ptrManager->Create(ClipboardTransferDirection_ToGuest, ClipboardSource_Host, ClipboardAction_Copy, + ptrTransfer.asOutParam()); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(ptrTransfer.isNotNull()); + + aTransfers.setNull(); + hrc = ptrManager->GetTransfers(ClipboardTransferDirection_ToGuest, 0, ComSafeArrayAsOutParam(aTransfers)); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(aTransfers.size() == 1); + if (aTransfers.size() == 1) + RTTESTI_CHECK(aTransfers[0] == ptrTransfer); + + hrc = ptrManager->Remove(ptrTransfer); + RTTESTI_CHECK_RC(hrc, S_OK); + aTransfers.setNull(); + hrc = ptrManager->GetTransfers(ClipboardTransferDirection_Any, 0, ComSafeArrayAsOutParam(aTransfers)); + RTTESTI_CHECK_RC(hrc, S_OK); + RTTESTI_CHECK(aTransfers.size() == 0); + + RTTESTI_CHECK_RC(RTTestIDisableAssertions(), VINF_SUCCESS); + hrc = ptrManager->Remove(ptrTransfer); + RTTESTI_CHECK_RC(RTTestIRestoreAssertions(), VINF_SUCCESS); + RTTESTI_CHECK(FAILED(hrc)); +} +#endif + + +int main(int argc, char **argv) +{ + RT_NOREF(argc, argv); + RTTEST hTest; + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardAPI", &hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(hTest); + + tstClipboardValues(); + tstClipboardSession(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstClipboardTransfer(); + tstClipboardTransferManager(); +#endif + return RTTestSummaryAndDestroy(hTest); +} diff --git a/src/VBox/Main/testcase/tstClipboardMain.cpp b/src/VBox/Main/testcase/tstClipboardMain.cpp new file mode 100644 index 000000000000..4777dd55f88b --- /dev/null +++ b/src/VBox/Main/testcase/tstClipboardMain.cpp @@ -0,0 +1,913 @@ +/* $Id: tstClipboardMain.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Connection and service-extension testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include "GuestShClBackendPrivate.h" +#include "GuestShClConn.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include + + +/** + * @page pg_tstClipboardMain Main Shared Clipboard connection testcase + * + * This is the Main-side unit test for the Shared Clipboard connection boundary. + * It compiles the production GuestShClConn and ShClBackend dispatcher against + * two small fakes: one implements the service operation table, the other + * implements the native backend operation table. + * + * The test checks backend lifetime, opaque transport identity, operation + * forwarding and connection pinning while a guest-data token is outstanding. + * With transfers enabled it also checks retained lookups and complete + * session/ID/generation keys; transfer contents are not involved. + * + * No VM, HGCM service, native clipboard, filesystem provider or HTTP transport + * is constructed here. Those belong to their respective component tests. + */ + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Test service session ID. */ +#define TST_SHCL_SESSION_ID UINT16_C(42) +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Test transfer ID. */ +# define TST_SHCL_TRANSFER_ID UINT16_C(7) +/** Test transfer generation. */ +# define TST_SHCL_TRANSFER_GEN UINT64_C(0x1122334455667788) +#endif + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Fake opaque service client. */ +struct SHCLCLIENTOPAQUE +{ + /** Identity marker. */ + uint32_t uMagic; +}; + +/** Fake retained guest-data token. */ +struct SHCLGUESTDATATOKENOPAQUE +{ + /** Identity marker. */ + uint32_t uMagic; +}; + +/** Complete definition of the otherwise opaque service command context. */ +struct _SHCLCLIENTCMDCTX +{ + /** Guest context ID identifying the pending reply. */ + uint64_t uContextID; +}; + +/** Fake native backend connection context. */ +struct SHCLCONTEXT +{ + /** Main connection owning this context. */ + GuestShClConn *pConn; +}; + +/** State shared by the service and native-backend fakes. */ +typedef struct TSTSHCLSTATE +{ + /** Fake service client. */ + SHCLCLIENTOPAQUE Client; + /** Fake backend context. */ + SHCLCONTEXT BackendCtx; + /** Fake guest-data token. */ + SHCLGUESTDATATOKENOPAQUE Token; + /** Configured backend initialization result. */ + int vrcBackendInit; + /** Configured backend connection result. */ + int vrcBackendConnect; + /** Configured backend synchronization result. */ + int vrcBackendSync; + /** Configured service guest-data-begin result. */ + int vrcGuestDataBegin; + /** Whether guest-data begin returns success without a token. */ + bool fGuestDataNullToken; + + /** Backend initialization calls. */ + uint32_t cBackendInit; + /** Backend destruction calls. */ + uint32_t cBackendDestroy; + /** Backend connection calls. */ + uint32_t cBackendConnect; + /** Backend disconnection calls. */ + uint32_t cBackendDisconnect; + /** Backend format-report calls. */ + uint32_t cBackendReportFormats; + /** Backend read calls. */ + uint32_t cBackendRead; + /** Backend write calls. */ + uint32_t cBackendWrite; + /** Backend synchronization calls. */ + uint32_t cBackendSync; + /** Last backend format value. */ + SHCLFORMATS fBackendFormats; + /** Last backend format/data type. */ + SHCLFORMAT uBackendFormat; + /** Last backend buffer. */ + void *pvBackendData; + /** Last backend buffer size. */ + uint32_t cbBackendData; + /** Most recently installed backend callback table. */ + PSHCLCALLBACKS pBackendCallbacks; + + /** Service filter calls. */ + uint32_t cSvcFilter; + /** Service guest-format report calls. */ + uint32_t cSvcReportFormats; + /** Asynchronous service read calls. */ + uint32_t cSvcReadAsync; + /** Synchronous service read calls. */ + uint32_t cSvcRead; + /** Guest-data begin calls. */ + uint32_t cGuestDataBegin; + /** Guest-data completion calls. */ + uint32_t cGuestDataComplete; + /** Guest-data cancellation calls. */ + uint32_t cGuestDataCancel; + /** Last service format value. */ + SHCLFORMATS fSvcFormats; + /** Last guest-data format. */ + SHCLFORMAT uGuestDataFormat; + /** Last completed guest-data buffer. */ + void const *pvGuestData; + /** Last completed guest-data size. */ + uint32_t cbGuestData; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Fake transfer known to the service. */ + SHCLTRANSFER Transfer; + /** References held on the fake transfer. */ + uint32_t cTransferRefs; + /** Transfer callback-table requests made to the backend. */ + uint32_t cBackendTransferCallbacks; + /** Transfer status reports made to the backend. */ + uint32_t cBackendTransferStatus; + /** Transfer lookup-by-ID calls. */ + uint32_t cSvcTransferGetById; + /** Transfer lookup-by-key calls. */ + uint32_t cSvcTransferGetByKey; + /** Transfer create calls. */ + uint32_t cSvcTransferCreate; + /** Transfer initialization calls. */ + uint32_t cSvcTransferInit; + /** Transfer destroy-by-ID calls. */ + uint32_t cSvcTransferDestroyById; + /** Transfer destroy-all calls. */ + uint32_t cSvcTransferDestroyAll; + /** Provider initialization calls. */ + uint32_t cSvcProviderInit; + /** Last transfer direction. */ + SHCLTRANSFERDIR enmTransferDir; + /** Last transfer source. */ + SHCLSOURCE enmTransferSource; + /** Last transfer status. */ + SHCLTRANSFERSTATUS enmTransferStatus; + /** Last transfer status result. */ + int vrcTransferStatus; +#endif +} TSTSHCLSTATE; + +/** Disconnect worker arguments. */ +typedef struct TSTDISCONNECTARGS +{ + /** Connection to disconnect. */ + GuestShClConn *pConn; + /** Service transport identifying the connection. */ + SHCLTRANSPORT Transport; + /** Event signalled before entering disconnect. */ + RTSEMEVENT hStarted; + /** Result returned by disconnect. */ + int vrc; +} TSTDISCONNECTARGS; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Test framework handle. */ +static RTTEST g_hTest; +/** Shared fake state. */ +static TSTSHCLSTATE g_State; + + +/********************************************************************************************************************************* +* Fake native backend * +*********************************************************************************************************************************/ +/** @copydoc SHCLBACKENDOPS::pfnInit */ +static int tstBackendInit(void) +{ + g_State.cBackendInit++; + return g_State.vrcBackendInit; +} + +/** @copydoc SHCLBACKENDOPS::pfnDestroy */ +static void tstBackendDestroy(void) +{ + g_State.cBackendDestroy++; +} + +/** @copydoc SHCLBACKENDOPS::pfnSetCallbacks */ +static void tstBackendSetCallbacks(PSHCLCALLBACKS pCallbacks) +{ + g_State.pBackendCallbacks = pCallbacks; +} + +/** @copydoc SHCLBACKENDOPS::pfnConnect */ +static int tstBackendConnect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) +{ + g_State.cBackendConnect++; + if (RT_FAILURE(g_State.vrcBackendConnect)) + return g_State.vrcBackendConnect; + g_State.BackendCtx.pConn = pConn; + *ppCtx = &g_State.BackendCtx; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnDisconnect */ +static int tstBackendDisconnect(PSHCLCONTEXT pCtx) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + pCtx->pConn->transferDestroyAll(); +#endif + g_State.cBackendDisconnect++; + g_State.BackendCtx.pConn = NULL; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnReportFormats */ +static int tstBackendReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendReportFormats++; + g_State.fBackendFormats = fFormats; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnReadData */ +static int tstBackendReadData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, + uint32_t *pcbActual) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendRead++; + g_State.uBackendFormat = uFormat; + g_State.pvBackendData = pvData; + g_State.cbBackendData = cbData; + if (pcbActual) + *pcbActual = cbData; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnWriteData */ +static int tstBackendWriteData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendWrite++; + g_State.uBackendFormat = uFormat; + g_State.pvBackendData = pvData; + g_State.cbBackendData = cbData; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnSync */ +static int tstBackendSync(PSHCLCONTEXT pCtx) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendSync++; + return g_State.vrcBackendSync; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** @copydoc SHCLBACKENDOPS::pfnTransferGetCallbacks */ +static void tstBackendTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendTransferCallbacks++; + RT_ZERO(*pCallbacks); + pCallbacks->pvUser = &g_State; + pCallbacks->cbUser = sizeof(g_State); +} + +/** @copydoc SHCLBACKENDOPS::pfnTransferHandleStatusReply */ +static int tstBackendTransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, + SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int vrcStatus) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + g_State.cBackendTransferStatus++; + g_State.enmTransferSource = enmSource; + g_State.enmTransferStatus = enmStatus; + g_State.vrcTransferStatus = vrcStatus; + return VINF_SUCCESS; +} +#endif + +/** Fake backend operation table selected by ShClBackend. */ +static SHCLBACKENDOPS const g_BackendOps = +{ + tstBackendInit, + tstBackendDestroy, + tstBackendSetCallbacks, + tstBackendConnect, + tstBackendDisconnect, + tstBackendReportFormats, + tstBackendReadData, + tstBackendWriteData, + tstBackendSync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstBackendTransferGetCallbacks, + tstBackendTransferHandleStatusReply, +#endif +}; + +/** + * Supplies the fake backend to ShClBackend. + * + * @returns Immutable fake backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &g_BackendOps; +} + + +/********************************************************************************************************************************* +* Fake service endpoint * +*********************************************************************************************************************************/ +/** Checks that an operation was made against the fake service client. */ +static void tstSvcCheckClient(SHCLCLIENTHANDLE hClient) +{ + RTTESTI_CHECK(hClient == &g_State.Client); +} + +/** @copydoc SHCLSVCOPS::pfnFilterFormats */ +static DECLCALLBACK(int) tstSvcFilterFormats(SHCLCLIENTHANDLE hClient, bool fHostToGuest, + SHCLFORMATS fFormats, SHCLFORMATS *pfFiltered) +{ + tstSvcCheckClient(hClient); + g_State.cSvcFilter++; + g_State.fSvcFormats = fFormats; + *pfFiltered = fHostToGuest ? fFormats : fFormats & ~VBOX_SHCL_FMT_HTML; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnReportFormatsToGuest */ +static DECLCALLBACK(int) tstSvcReportFormatsToGuest(SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + SHCLFORMATS *pfReported) +{ + tstSvcCheckClient(hClient); + g_State.cSvcReportFormats++; + g_State.fSvcFormats = fFormats; + if (pfReported) + *pfReported = fFormats; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnReadDataFromGuestAsync */ +static DECLCALLBACK(int) tstSvcReadDataFromGuestAsync(SHCLCLIENTHANDLE hClient, SHCLFORMATS fFormats, + PSHCLEVENT *ppEvent) +{ + tstSvcCheckClient(hClient); + g_State.cSvcReadAsync++; + g_State.fSvcFormats = fFormats; + RTTESTI_CHECK(ppEvent == NULL); + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnReadDataFromGuest */ +static DECLCALLBACK(int) tstSvcReadDataFromGuest(SHCLCLIENTHANDLE hClient, SHCLFORMAT uFormat, + void **ppvData, uint32_t *pcbData) +{ + static char const s_achData[] = "guest"; + tstSvcCheckClient(hClient); + g_State.cSvcRead++; + g_State.uGuestDataFormat = uFormat; + *ppvData = RTMemDup(s_achData, sizeof(s_achData)); + if (!*ppvData) + return VERR_NO_MEMORY; + *pcbData = sizeof(s_achData); + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnGuestDataBegin */ +static DECLCALLBACK(int) tstSvcGuestDataBegin(SHCLCLIENTHANDLE hClient, PSHCLCLIENTCMDCTX pCmdCtx, + SHCLFORMAT uFormat, PSHCLGUESTDATATOKEN phToken) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(pCmdCtx != NULL); + g_State.cGuestDataBegin++; + g_State.uGuestDataFormat = uFormat; + if (RT_SUCCESS(g_State.vrcGuestDataBegin) && !g_State.fGuestDataNullToken) + *phToken = &g_State.Token; + return g_State.vrcGuestDataBegin; +} + +/** @copydoc SHCLSVCOPS::pfnGuestDataComplete */ +static DECLCALLBACK(int) tstSvcGuestDataComplete(SHCLCLIENTHANDLE hClient, SHCLGUESTDATATOKEN hToken, + void const *pvData, uint32_t cbData) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(hToken == &g_State.Token); + g_State.cGuestDataComplete++; + g_State.pvGuestData = pvData; + g_State.cbGuestData = cbData; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnGuestDataCancel */ +static DECLCALLBACK(void) tstSvcGuestDataCancel(SHCLCLIENTHANDLE hClient, SHCLGUESTDATATOKEN hToken) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(hToken == &g_State.Token); + g_State.cGuestDataCancel++; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Retains the fake transfer without depending on the GuestHost transfer data plane. */ +static void tstTransferRetain(void) +{ + ASMAtomicIncU32(&g_State.cTransferRefs); +} + +/** @copydoc SHCLSVCOPS::pfnTransferGetByIdRetained */ +static DECLCALLBACK(PSHCLTRANSFER) tstSvcTransferGetByIdRetained(SHCLCLIENTHANDLE hClient, + SHCLTRANSFERID idTransfer) +{ + tstSvcCheckClient(hClient); + g_State.cSvcTransferGetById++; + if (idTransfer != TST_SHCL_TRANSFER_ID) + return NULL; + tstTransferRetain(); + return &g_State.Transfer; +} + +/** @copydoc SHCLSVCOPS::pfnTransferGetByKeyRetained */ +static DECLCALLBACK(PSHCLTRANSFER) tstSvcTransferGetByKeyRetained(SHCLCLIENTHANDLE hClient, + SHCLSESSIONID idSession, + SHCLTRANSFERID idTransfer, + SHCLTRANSFERGEN uGeneration) +{ + tstSvcCheckClient(hClient); + g_State.cSvcTransferGetByKey++; + if ( idSession != TST_SHCL_SESSION_ID + || idTransfer != TST_SHCL_TRANSFER_ID + || uGeneration != TST_SHCL_TRANSFER_GEN) + return NULL; + tstTransferRetain(); + return &g_State.Transfer; +} + +/** @copydoc SHCLSVCOPS::pfnTransferCreate */ +static DECLCALLBACK(int) tstSvcTransferCreate(SHCLCLIENTHANDLE hClient, SHCLTRANSFERDIR enmDir, + SHCLSOURCE enmSource, PSHCLTRANSFERCALLBACKS pCallbacks, + SHCLTRANSFERID idTransfer, PSHCLTRANSFER *ppTransfer) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(pCallbacks != NULL); + RTTESTI_CHECK(idTransfer == TST_SHCL_TRANSFER_ID); + g_State.cSvcTransferCreate++; + g_State.enmTransferDir = enmDir; + g_State.enmTransferSource = enmSource; + tstTransferRetain(); + *ppTransfer = &g_State.Transfer; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnTransferInit */ +static DECLCALLBACK(int) tstSvcTransferInit(SHCLCLIENTHANDLE hClient, PSHCLTRANSFER pTransfer) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + g_State.cSvcTransferInit++; + return VINF_SUCCESS; +} + +/** @copydoc SHCLSVCOPS::pfnTransferDestroyById */ +static DECLCALLBACK(void) tstSvcTransferDestroyById(SHCLCLIENTHANDLE hClient, SHCLTRANSFERID idTransfer) +{ + tstSvcCheckClient(hClient); + RTTESTI_CHECK(idTransfer == TST_SHCL_TRANSFER_ID); + g_State.cSvcTransferDestroyById++; +} + +/** @copydoc SHCLSVCOPS::pfnTransferDestroyAll */ +static DECLCALLBACK(void) tstSvcTransferDestroyAll(SHCLCLIENTHANDLE hClient) +{ + tstSvcCheckClient(hClient); + g_State.cSvcTransferDestroyAll++; +} + +/** @copydoc SHCLSVCOPS::pfnTransferProviderInitGuest */ +static DECLCALLBACK(int) tstSvcTransferProviderInitGuest(SHCLCLIENTHANDLE hClient, PSHCLTXPROVIDER pProvider) +{ + tstSvcCheckClient(hClient); + g_State.cSvcProviderInit++; + RT_ZERO(*pProvider); + pProvider->enmSource = SHCLSOURCE_REMOTE; + pProvider->pvUser = &g_State; + pProvider->cbUser = sizeof(g_State); + return VINF_SUCCESS; +} +#endif + +/** Fake service operation table. */ +static SHCLSVCOPS const g_SvcOps = +{ + sizeof(SHCLSVCOPS), + tstSvcFilterFormats, + tstSvcReportFormatsToGuest, + tstSvcReadDataFromGuestAsync, + tstSvcReadDataFromGuest, + tstSvcGuestDataBegin, + tstSvcGuestDataComplete, + tstSvcGuestDataCancel, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstSvcTransferGetByIdRetained, + tstSvcTransferGetByKeyRetained, + tstSvcTransferCreate, + tstSvcTransferInit, + tstSvcTransferDestroyById, + tstSvcTransferDestroyAll, + tstSvcTransferProviderInitGuest, +#endif +}; + +/** Returns the fake service transport. */ +static SHCLTRANSPORT tstTransport(void) +{ + SHCLTRANSPORT Transport; + Transport.hClient = &g_State.Client; + Transport.pOps = &g_SvcOps; + return Transport; +} + + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + + +/********************************************************************************************************************************* +* Minimal transfer metadata implementation * +*********************************************************************************************************************************/ +/* These functions model the transfer metadata contract used by Main. The + * testcase deliberately does not link the transfer data-plane implementation. */ +bool ShClTransferStatusResultIsValid(SHCLTRANSFERSTATUS enmStatus, int vrcTransfer) +{ + switch (enmStatus) + { + case SHCLTRANSFERSTATUS_REQUESTED: + case SHCLTRANSFERSTATUS_INITIALIZED: + case SHCLTRANSFERSTATUS_UNINITIALIZED: + case SHCLTRANSFERSTATUS_STARTED: + case SHCLTRANSFERSTATUS_COMPLETED: + return RT_SUCCESS(vrcTransfer); + + case SHCLTRANSFERSTATUS_CANCELED: + return vrcTransfer == VERR_CANCELLED; + + case SHCLTRANSFERSTATUS_KILLED: + case SHCLTRANSFERSTATUS_ERROR: + return RT_FAILURE(vrcTransfer); + + default: + return false; + } +} + +uint32_t ShClTransferRelease(PSHCLTRANSFER pTransfer) +{ + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + uint32_t const cRefs = ASMAtomicDecU32(&g_State.cTransferRefs); + RTTESTI_CHECK(cRefs < UINT32_MAX); + return cRefs; +} + +SHCLTRANSFERID ShClTransferGetID(PSHCLTRANSFER pTransfer) +{ + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + return TST_SHCL_TRANSFER_ID; +} + +SHCLSESSIONID ShClTransferGetSessionId(PSHCLTRANSFER pTransfer) +{ + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + return TST_SHCL_SESSION_ID; +} + +SHCLTRANSFERGEN ShClTransferGetGeneration(PSHCLTRANSFER pTransfer) +{ + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + return TST_SHCL_TRANSFER_GEN; +} +#endif + + +/********************************************************************************************************************************* +* Helpers * +*********************************************************************************************************************************/ +/** Resets the fake state to successful defaults. */ +static void tstStateReset(void) +{ + RT_ZERO(g_State); + g_State.Client.uMagic = UINT32_C(0x434c4950); + g_State.Token.uMagic = UINT32_C(0x544f4b4e); + g_State.vrcBackendInit = VINF_SUCCESS; + g_State.vrcBackendConnect = VINF_SUCCESS; + g_State.vrcBackendSync = VINF_SUCCESS; + g_State.vrcGuestDataBegin = VINF_SUCCESS; +} + +/** Initializes and connects a test connection. */ +static void tstConnect(GuestShClConn &Conn, SHCLTRANSPORT *pTransport) +{ + *pTransport = tstTransport(); + RTTESTI_CHECK_RC(Conn.initBackend(), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.connect(pTransport), VINF_SUCCESS); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Disconnects and destroys a test connection. */ +static void tstDisconnect(GuestShClConn &Conn, PCSHCLTRANSPORT pTransport) +{ + if (Conn.isConnected()) + RTTESTI_CHECK_RC(Conn.disconnect(pTransport), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); +} +#endif + +/** Executes disconnect on a worker thread. */ +static DECLCALLBACK(int) tstDisconnectThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + TSTDISCONNECTARGS *pArgs = (TSTDISCONNECTARGS *)pvUser; + RTSemEventSignal(pArgs->hStarted); + pArgs->vrc = pArgs->pConn->disconnect(&pArgs->Transport); + return pArgs->vrc; +} + + +/********************************************************************************************************************************* +* Testcases * +*********************************************************************************************************************************/ +/** Tests backend initialization and destruction ownership. */ +static void tstBackendLifecycle(void) +{ + RTTestISub("Backend lifecycle"); + tstStateReset(); + + GuestShClConn Conn(NULL); + g_State.vrcBackendInit = VERR_NOT_SUPPORTED; + RTTESTI_CHECK_RC(Conn.initBackend(), VERR_NOT_SUPPORTED); + RTTESTI_CHECK(g_State.cBackendInit == 1); + + g_State.vrcBackendInit = VINF_SUCCESS; + RTTESTI_CHECK_RC(Conn.initBackend(), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.initBackend(), VINF_SUCCESS); + RTTESTI_CHECK(g_State.cBackendInit == 2); + + SHCLCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Conn.setBackendCallbacks(&Callbacks); + RTTESTI_CHECK(g_State.pBackendCallbacks == &Callbacks); + + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); + RTTESTI_CHECK(g_State.cBackendDestroy == 1); +} + +/** Tests transport identity and service/backend forwarding. */ +static void tstConnectionAndForwarding(void) +{ + RTTestISub("Connection and forwarding"); + tstStateReset(); + + GuestShClConn Conn(NULL); + SHCLTRANSPORT Transport = tstTransport(); + SHCLTRANSPORT Invalid = Transport; + Invalid.pOps = NULL; + RTTESTI_CHECK_RC(RTTestIDisableAssertions(), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.connect(&Invalid), VERR_INVALID_HANDLE); + RTTESTI_CHECK_RC(RTTestIRestoreAssertions(), VINF_SUCCESS); + + RTTESTI_CHECK_RC(Conn.initBackend(), VINF_SUCCESS); + g_State.vrcBackendConnect = VERR_NOT_AVAILABLE; + RTTESTI_CHECK_RC(Conn.connect(&Transport), VERR_NOT_AVAILABLE); + RTTESTI_CHECK(!Conn.isConnected()); + g_State.vrcBackendConnect = VINF_SUCCESS; + RTTESTI_CHECK_RC(Conn.connect(&Transport), VINF_SUCCESS); + RTTESTI_CHECK(Conn.matches(&Transport)); + RTTESTI_CHECK_RC(Conn.connect(&Transport), VERR_RESOURCE_BUSY); + + SHCLTRANSPORT Wrong = Transport; + SHCLCLIENTOPAQUE OtherClient = { UINT32_C(0xdeadbeef) }; + Wrong.hClient = &OtherClient; + RTTESTI_CHECK_RC(Conn.disconnect(&Wrong), VERR_INVALID_HANDLE); + + SHCLFORMATS fReported = 0; + RTTESTI_CHECK_RC(Conn.reportFormatsToGuest(VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML, &fReported), + VINF_SUCCESS); + RTTESTI_CHECK(fReported == (VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML)); + RTTESTI_CHECK(g_State.cSvcReportFormats == 1); + + RTTESTI_CHECK_RC(Conn.readDataFromGuestAsync(VBOX_SHCL_FMT_UNICODETEXT, NULL), VINF_SUCCESS); + + void *pvData = NULL; + uint32_t cbData = 0; + RTTESTI_CHECK_RC(Conn.readDataFromGuest(VBOX_SHCL_FMT_UNICODETEXT, &pvData, &cbData), VINF_SUCCESS); + RTTESTI_CHECK(cbData == sizeof("guest")); + RTTESTI_CHECK(pvData && !memcmp(pvData, "guest", sizeof("guest"))); + RTMemFree(pvData); + + RTTESTI_CHECK_RC(Conn.reportFormatsToBackend(VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML), VINF_SUCCESS); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RTTESTI_CHECK(g_State.cSvcFilter == 1); + RTTESTI_CHECK(g_State.fBackendFormats == VBOX_SHCL_FMT_UNICODETEXT); +#else + RTTESTI_CHECK(g_State.fBackendFormats == (VBOX_SHCL_FMT_UNICODETEXT | VBOX_SHCL_FMT_HTML)); +#endif + + uint8_t abData[8]; + uint32_t cbActual = 0; + RTTESTI_CHECK_RC(Conn.readDataFromBackend(VBOX_SHCL_FMT_UNICODETEXT, abData, sizeof(abData), &cbActual), + VINF_SUCCESS); + RTTESTI_CHECK(cbActual == sizeof(abData)); + RTTESTI_CHECK_RC(Conn.writeDataToBackend(VBOX_SHCL_FMT_UNICODETEXT, abData, sizeof(abData)), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.syncBackend(), VINF_SUCCESS); + + RTTESTI_CHECK_RC(Conn.disconnect(&Transport), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.reportFormatsToBackend(VBOX_SHCL_FMT_UNICODETEXT), VERR_SHCLPB_NO_DATA); + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); +} + +/** Tests retained guest-data tokens and disconnect draining. */ +static void tstGuestDataTokens(void) +{ + RTTestISub("Guest-data token ownership"); + tstStateReset(); + + GuestShClConn Conn(NULL); + SHCLTRANSPORT Transport; + tstConnect(Conn, &Transport); + SHCLCLIENTCMDCTX CmdCtx = { UINT64_C(0x1234) }; + + SHCLGUESTDATATOKEN hToken = NULL; + g_State.fGuestDataNullToken = true; + RTTESTI_CHECK_RC(Conn.guestDataBegin(&CmdCtx, VBOX_SHCL_FMT_UNICODETEXT, &hToken), VINF_SUCCESS); + RTTESTI_CHECK(hToken == NULL); + + g_State.fGuestDataNullToken = false; + g_State.vrcGuestDataBegin = VERR_INVALID_CONTEXT; + RTTESTI_CHECK_RC(Conn.guestDataBegin(&CmdCtx, VBOX_SHCL_FMT_UNICODETEXT, &hToken), VERR_INVALID_CONTEXT); + + g_State.vrcGuestDataBegin = VINF_SUCCESS; + RTTESTI_CHECK_RC(Conn.guestDataBegin(&CmdCtx, VBOX_SHCL_FMT_UNICODETEXT, &hToken), VINF_SUCCESS); + RTTESTI_CHECK(hToken == &g_State.Token); + Conn.guestDataCancel(hToken); + RTTESTI_CHECK(g_State.cGuestDataCancel == 1); + + hToken = NULL; + RTTESTI_CHECK_RC(Conn.guestDataBegin(&CmdCtx, VBOX_SHCL_FMT_HTML, &hToken), VINF_SUCCESS); + + TSTDISCONNECTARGS Args; + Args.pConn = &Conn; + Args.Transport = Transport; + Args.hStarted = NIL_RTSEMEVENT; + Args.vrc = VERR_IPE_UNINITIALIZED_STATUS; + RTTESTI_CHECK_RC(RTSemEventCreate(&Args.hStarted), VINF_SUCCESS); + RTTHREAD hThread = NIL_RTTHREAD; + RTTESTI_CHECK_RC(RTThreadCreate(&hThread, tstDisconnectThread, &Args, 0, RTTHREADTYPE_DEFAULT, + RTTHREADFLAGS_WAITABLE, "shcl-discon"), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTSemEventWait(Args.hStarted, RT_MS_5SEC), VINF_SUCCESS); + + for (uint32_t i = 0; i < 5000 && Conn.isConnected(); i++) + RTThreadSleep(1); + RTTESTI_CHECK(!Conn.isConnected()); + RTTESTI_CHECK(g_State.cBackendDisconnect == 0); + + static char const s_achReply[] = "reply"; + RTTESTI_CHECK_RC(Conn.guestDataComplete(hToken, s_achReply, sizeof(s_achReply)), VINF_SUCCESS); + int vrcThread = VERR_IPE_UNINITIALIZED_STATUS; + RTTESTI_CHECK_RC(RTThreadWait(hThread, RT_MS_5SEC, &vrcThread), VINF_SUCCESS); + RTTESTI_CHECK_RC(vrcThread, VINF_SUCCESS); + RTTESTI_CHECK(g_State.cBackendDisconnect == 1); + RTTESTI_CHECK(g_State.cGuestDataComplete == 1); + RTTESTI_CHECK(g_State.pvGuestData == s_achReply); + RTTESTI_CHECK_RC(RTSemEventDestroy(Args.hStarted), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.destroyBackend(), VINF_SUCCESS); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Tests Main's transfer metadata and full-key forwarding. */ +static void tstTransfers(void) +{ + RTTestISub("Transfer metadata forwarding"); + tstStateReset(); + + GuestShClConn Conn(NULL); + SHCLTRANSPORT Transport; + tstConnect(Conn, &Transport); + + PSHCLTRANSFER pTransfer = Conn.transferGetByIdRetained(TST_SHCL_TRANSFER_ID); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + if (pTransfer) + ShClTransferRelease(pTransfer); + RTTESTI_CHECK(Conn.transferGetByIdRetained(TST_SHCL_TRANSFER_ID + 1) == NULL); + + pTransfer = Conn.transferGetByKeyRetained(TST_SHCL_SESSION_ID, TST_SHCL_TRANSFER_ID, + TST_SHCL_TRANSFER_GEN + 1); + RTTESTI_CHECK(pTransfer == NULL); + pTransfer = Conn.transferGetByKeyRetained(TST_SHCL_SESSION_ID, TST_SHCL_TRANSFER_ID, + TST_SHCL_TRANSFER_GEN); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + if (pTransfer) + ShClTransferRelease(pTransfer); + + SHCLTRANSFERCALLBACKS Callbacks; + RT_ZERO(Callbacks); + RTTESTI_CHECK_RC(Conn.transferGetCallbacks(&Callbacks), VINF_SUCCESS); + RTTESTI_CHECK(Callbacks.pvUser == &g_State); + + pTransfer = NULL; + RTTESTI_CHECK_RC(Conn.transferCreate(SHCLTRANSFERDIR_HOST_TO_GUEST, SHCLSOURCE_LOCAL, &Callbacks, + TST_SHCL_TRANSFER_ID, &pTransfer), VINF_SUCCESS); + RTTESTI_CHECK(pTransfer == &g_State.Transfer); + if (pTransfer) + ShClTransferRelease(pTransfer); + RTTESTI_CHECK_RC(Conn.transferInit(&g_State.Transfer), VINF_SUCCESS); + RTTESTI_CHECK_RC(Conn.transferHandleStatusReply(&g_State.Transfer, SHCLSOURCE_REMOTE, + SHCLTRANSFERSTATUS_REQUESTED, VINF_SUCCESS), VINF_SUCCESS); + RTTESTI_CHECK(g_State.enmTransferStatus == SHCLTRANSFERSTATUS_REQUESTED); + + SHCLTXPROVIDER Provider; + RT_ZERO(Provider); + RTTESTI_CHECK_RC(Conn.transferProviderInitGuest(&Provider), VINF_SUCCESS); + RTTESTI_CHECK(Provider.enmSource == SHCLSOURCE_REMOTE); + Conn.transferDestroyById(TST_SHCL_TRANSFER_ID); + + RTTESTI_CHECK(g_State.cTransferRefs == 0); + tstDisconnect(Conn, &Transport); + RTTESTI_CHECK(g_State.cSvcTransferDestroyAll == 1); +} +#endif + + +/** Testcase entry point. */ +int main(void) +{ + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardMain", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + tstBackendLifecycle(); + tstConnectionAndForwarding(); + tstGuestDataTokens(); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstTransfers(); +#endif + + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp b/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp new file mode 100644 index 000000000000..9fef9c091f47 --- /dev/null +++ b/src/VBox/Main/testcase/tstClipboardMain2HostSvc.cpp @@ -0,0 +1,763 @@ +/* $Id: tstClipboardMain2HostSvc.cpp 115060 2026-08-17 17:28:06Z andreas.loeffler@oracle.com $ */ +/** @file + * Main Shared Clipboard - Host Service integration testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#define LOG_GROUP LOG_GROUP_SHARED_CLIPBOARD +#include "GuestShClBackendPrivate.h" +#include "GuestShClConn.h" + +#include +#include + +#include +#include +#include + + +/** + * @page pg_tstClipboardMain2HostSvc Main to Shared Clipboard Host Service integration testcase + * + * Links the production HGCM service to Main's production connection, backend + * dispatcher. A small adapter replaces only Console-facing notifications, + * while a fake backend replaces the native operating-system clipboard. + * + * The test covers registration, connect/sync/disconnect, the real service + * transport, messages in both directions, guest-data reply ownership and one + * transfer handshake, including post-initialization error propagation. VM + * construction, public Main API objects, native clipboard contents and transfer + * data providers are outside its scope. + */ + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** State attached to a synthetic HGCM call. */ +struct VBOXHGCMCALLHANDLE_TYPEDEF +{ + /** Whether the Host Service completed the call. */ + bool fCompleted; + /** Completion status supplied by the Host Service. */ + int32_t rc; +}; + +/** Fake native backend context. */ +struct SHCLCONTEXT +{ + /** Main connection owning the context. */ + GuestShClConn *pConn; +}; + +/** Integration-test state. */ +typedef struct TSTSHCLSTATE +{ + /** Whether the Host Service was loaded successfully. */ + bool fServiceLoaded; + /** Whether Main's test extension was registered successfully. */ + bool fExtensionRegistered; + /** Fake native backend context. */ + SHCLCONTEXT BackendCtx; + /** Number of native backend initializations. */ + uint32_t cBackendInit; + /** Number of native backend destructions. */ + uint32_t cBackendDestroy; + /** Number of native backend connections. */ + uint32_t cBackendConnect; + /** Number of native backend disconnections. */ + uint32_t cBackendDisconnect; + /** Number of native backend synchronizations. */ + uint32_t cBackendSync; + /** Number of guest format notifications crossing into Main. */ + uint32_t cGuestFormats; + /** Last guest format mask received by Main. */ + SHCLFORMATS fGuestFormats; +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + /** Number of transfer callback-table requests reaching the backend. */ + uint32_t cTransferCallbacks; + /** Number of transfer status notifications reaching the backend. */ + uint32_t cTransferStatuses; + /** Session of the last transfer notification. */ + SHCLSESSIONID idTransferSession; + /** ID of the last transfer notification. */ + SHCLTRANSFERID idTransfer; + /** Generation of the last transfer notification. */ + SHCLTRANSFERGEN uTransferGeneration; + /** Last transfer status. */ + SHCLTRANSFERSTATUS enmTransferStatus; +#endif +} TSTSHCLSTATE; + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** State recorded while checking the post-initialization callback contract. */ +typedef struct TSTTRANSFERINITIALIZED +{ + /** Result returned by the post-initialization callback. */ + int vrcCallback; + /** Number of post-initialization callback invocations. */ + uint32_t cInitialized; + /** Number of destruction callback invocations. */ + uint32_t cDestroyed; + /** Transfer status observed by the post-initialization callback. */ + SHCLTRANSFERSTATUS enmStatus; + /** Whether the transfer lock was owned by the callback thread. */ + bool fLockOwned; +} TSTTRANSFERINITIALIZED; +#endif + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Test framework handle. */ +static RTTEST g_hTest; +/** Loaded HGCM service table. */ +static VBOXHGCMSVCFNTABLE g_Table; +/** Helpers supplied to the HGCM service. */ +static VBOXHGCMSVCHELPERS g_Helpers; +/** Shared integration state. */ +static TSTSHCLSTATE g_State; + + +/********************************************************************************************************************************* +* External Symbols * +*********************************************************************************************************************************/ +extern "C" DECLCALLBACK(DECLEXPORT(int)) VBoxHGCMSvcLoad(VBOXHGCMSVCFNTABLE *pTable); + + +/********************************************************************************************************************************* +* Fake Native Backend * +*********************************************************************************************************************************/ +/** @copydoc SHCLBACKENDOPS::pfnInit */ +static int tstBackendInit(void) +{ + g_State.cBackendInit++; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnDestroy */ +static void tstBackendDestroy(void) +{ + g_State.cBackendDestroy++; +} + +/** @copydoc SHCLBACKENDOPS::pfnSetCallbacks */ +static void tstBackendSetCallbacks(PSHCLCALLBACKS pCallbacks) +{ + RT_NOREF(pCallbacks); +} + +/** @copydoc SHCLBACKENDOPS::pfnConnect */ +static int tstBackendConnect(GuestShClConn *pConn, PSHCLCONTEXT *ppCtx) +{ + g_State.cBackendConnect++; + g_State.BackendCtx.pConn = pConn; + *ppCtx = &g_State.BackendCtx; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnDisconnect */ +static int tstBackendDisconnect(PSHCLCONTEXT pCtx) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + pCtx->pConn->transferDestroyAll(); +#endif + g_State.cBackendDisconnect++; + g_State.BackendCtx.pConn = NULL; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnReportFormats */ +static int tstBackendReportFormats(PSHCLCONTEXT pCtx, SHCLFORMATS fFormats) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cGuestFormats++; + g_State.fGuestFormats = fFormats; + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnReadData */ +static int tstBackendReadData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData, + uint32_t *pcbActual) +{ + RT_NOREF(pCtx, uFormat, pvData, cbData); + *pcbActual = 0; + return VERR_NO_DATA; +} + +/** @copydoc SHCLBACKENDOPS::pfnWriteData */ +static int tstBackendWriteData(PSHCLCONTEXT pCtx, SHCLFORMAT uFormat, void *pvData, uint32_t cbData) +{ + RT_NOREF(pCtx, uFormat, pvData, cbData); + return VINF_SUCCESS; +} + +/** @copydoc SHCLBACKENDOPS::pfnSync */ +static int tstBackendSync(PSHCLCONTEXT pCtx) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cBackendSync++; + return VINF_SUCCESS; +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** Records and returns the configured post-initialization result. */ +static DECLCALLBACK(int) tstTransferInitializedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + TSTTRANSFERINITIALIZED *pState = (TSTTRANSFERINITIALIZED *)pCbCtx->pvUser; + RTTESTI_CHECK_RET(pState != NULL, VERR_INVALID_POINTER); + RTTESTI_CHECK_RET(pCbCtx->cbUser == sizeof(*pState), VERR_INVALID_PARAMETER); + + pState->cInitialized++; + pState->fLockOwned = RTCritSectIsOwner(&pCbCtx->pTransfer->CritSect); + pState->enmStatus = ShClTransferGetStatus(pCbCtx->pTransfer); + return pState->vrcCallback; +} + +/** Records destruction of the transfer used by the callback contract test. */ +static DECLCALLBACK(void) tstTransferDestroyedCallback(PSHCLTRANSFERCALLBACKCTX pCbCtx) +{ + TSTTRANSFERINITIALIZED *pState = (TSTTRANSFERINITIALIZED *)pCbCtx->pvUser; + RTTESTI_CHECK(pState != NULL); + if (pState) + pState->cDestroyed++; +} + +/** @copydoc SHCLBACKENDOPS::pfnTransferGetCallbacks */ +static void tstBackendTransferGetCallbacks(PSHCLCONTEXT pCtx, PSHCLTRANSFERCALLBACKS pCallbacks) +{ + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cTransferCallbacks++; + RT_ZERO(*pCallbacks); +} + +/** @copydoc SHCLBACKENDOPS::pfnTransferHandleStatusReply */ +static int tstBackendTransferHandleStatusReply(PSHCLCONTEXT pCtx, PSHCLTRANSFER pTransfer, + SHCLSOURCE enmSource, SHCLTRANSFERSTATUS enmStatus, int rcStatus) +{ + RT_NOREF(enmSource, rcStatus); + RTTESTI_CHECK(pCtx == &g_State.BackendCtx); + g_State.cTransferStatuses++; + g_State.idTransferSession = ShClTransferGetSessionId(pTransfer); + g_State.idTransfer = ShClTransferGetID(pTransfer); + g_State.uTransferGeneration = ShClTransferGetGeneration(pTransfer); + g_State.enmTransferStatus = enmStatus; + return VINF_SUCCESS; +} +#endif + +/** Backend operation table selected by Main's backend dispatcher. */ +static SHCLBACKENDOPS const g_BackendOps = +{ + tstBackendInit, + tstBackendDestroy, + tstBackendSetCallbacks, + tstBackendConnect, + tstBackendDisconnect, + tstBackendReportFormats, + tstBackendReadData, + tstBackendWriteData, + tstBackendSync, +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstBackendTransferGetCallbacks, + tstBackendTransferHandleStatusReply, +#endif +}; + +/** + * Returns the fake native backend to production Main code. + * + * @returns Immutable fake backend operation table. + */ +PCSHCLBACKENDOPS ShClBackendGetOps(void) +{ + return &g_BackendOps; +} + + +/********************************************************************************************************************************* +* Main Service Extension Adapter * +*********************************************************************************************************************************/ +/** + * Dispatches the connection-owned portion of Main's service extension. + * + * Console-facing notifications are reduced to deterministic sinks. Connection + * ownership and backend dispatch use production Main code; all parameters here + * are produced by the real Host Service. + * + * @returns VBox status code. + * @param pvExtension GuestShClConn instance receiving the request. + * @param uFunction VBOX_CLIPBOARD_EXT_FN_XXX function number. + * @param pvParms Service-extension parameters. + * @param cbParms Size of @a pvParms in bytes. + */ +static DECLCALLBACK(int) tstMainExtension(void *pvExtension, uint32_t uFunction, void *pvParms, uint32_t cbParms) +{ + GuestShClConn * const pConn = (GuestShClConn *)pvExtension; + RTTESTI_CHECK_RET(cbParms == sizeof(SHCLEXTPARMS), VERR_INVALID_PARAMETER); + PSHCLEXTPARMS const pParms = (PSHCLEXTPARMS)pvParms; + int vrc; + switch (uFunction) + { + case VBOX_CLIPBOARD_EXT_FN_BACKEND_INIT: + return pConn->initBackend(); + case VBOX_CLIPBOARD_EXT_FN_BACKEND_DESTROY: + return pConn->destroyBackend(); + case VBOX_CLIPBOARD_EXT_FN_BACKEND_CONNECT: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + return pConn->connect(&Transport); + } + case VBOX_CLIPBOARD_EXT_FN_BACKEND_DISCONNECT: + { + SHCLTRANSPORT const Transport = ShClSvcExtGetTransport(pParms); + return pConn->disconnect(&Transport); + } + case VBOX_CLIPBOARD_EXT_FN_BACKEND_SYNC: + return pConn->syncBackend(); + case VBOX_CLIPBOARD_EXT_FN_FORMAT_REPORT_TO_HOST: + return pConn->reportFormatsToBackend(pParms->u.ReportFormats.uFormats); + case VBOX_CLIPBOARD_EXT_FN_DATA_WRITE: + { + SHCLGUESTDATATOKEN hToken = NULL; + vrc = pConn->guestDataBegin(pParms->u.ReadWriteData.pCmdCtx, + pParms->u.ReadWriteData.uFormat, &hToken); + if (RT_SUCCESS(vrc)) + vrc = pConn->guestDataComplete(hToken, pParms->u.ReadWriteData.pvData, + pParms->u.ReadWriteData.cbData); + return vrc; + } +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + case VBOX_CLIPBOARD_EXT_FN_TRANSFER_CALLBACKS: + return pConn->transferGetCallbacks(pParms->u.TransferCallbacks.pCallbacks); + case VBOX_CLIPBOARD_EXT_FN_FILE_TRANSFER: + return pConn->transferHandleStatusReply(pParms->u.FileTransferData.pTransfer, + pParms->u.FileTransferData.enmShClSource, + pParms->u.FileTransferData.pReply->u.TransferStatus.uStatus, + (int)pParms->u.FileTransferData.pReply->rc); +#endif + default: + return VERR_NOT_SUPPORTED; + } +} + + +/********************************************************************************************************************************* +* Test Helpers * +*********************************************************************************************************************************/ +/** + * Completes one synthetic guest call. + * + * @returns VINF_SUCCESS. + * @param hCall Synthetic call handle to complete. + * @param rc Guest-call result. + */ +static DECLCALLBACK(int) tstCallComplete(VBOXHGCMCALLHANDLE hCall, int32_t rc) +{ + hCall->fCompleted = true; + hCall->rc = rc; + return VINF_SUCCESS; +} + +/** + * Executes a guest call expected to complete synchronously. + * + * @returns Guest-call result. + * @param pvClient HGCM client state. + * @param uFunction Guest function number. + * @param cParms Number of HGCM parameters. + * @param paParms HGCM parameters. Optional if @a cParms is zero. + */ +static int tstGuestCall(void *pvClient, uint32_t uFunction, uint32_t cParms, VBOXHGCMSVCPARM *paParms) +{ + VBOXHGCMCALLHANDLE_TYPEDEF Call; + Call.fCompleted = false; + Call.rc = VERR_IPE_UNINITIALIZED_STATUS; + g_Table.pfnCall(g_Table.pvService, &Call, 1 /* idClient */, pvClient, + uFunction, cParms, paParms, 0 /* tsArrival */); + RTTESTI_CHECK_MSG_RET(Call.fCompleted, ("Guest function %RU32 did not complete\n", uFunction), + VERR_INTERNAL_ERROR); + return Call.rc; +} + +/** + * Loads and registers the real Host Service with Main's test adapter. + * + * @returns VBox status code. + * @param pConn Main connection registered as the extension target. + */ +static int tstLoad(GuestShClConn *pConn) +{ + RT_ZERO(g_Table); + RT_ZERO(g_Helpers); + g_Helpers.pfnCallComplete = tstCallComplete; + g_Table.cbSize = sizeof(g_Table); + g_Table.u32Version = VBOX_HGCM_SVC_VERSION; + g_Table.pHelpers = &g_Helpers; + + int vrc = VBoxHGCMSvcLoad(&g_Table); + if (RT_SUCCESS(vrc)) + { + g_State.fServiceLoaded = true; + vrc = g_Table.pfnRegisterExtension(g_Table.pvService, tstMainExtension, pConn); + if (RT_SUCCESS(vrc)) + g_State.fExtensionRegistered = true; + } + return vrc; +} + +/** + * Enables bidirectional clipboard traffic and reports modern guest features. + * + * @param pvClient Connected HGCM client state. + */ +static void tstNegotiate(void *pvClient) +{ + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, VBOX_SHCL_MODE_BIDIRECTIONAL); + int vrc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_MODE, 1, &Parm); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + HGCMSvcSetU32(&Parm, VBOX_SHCL_TRANSFER_MODE_F_ENABLED); + vrc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_SET_TRANSFER_MODE, 1, &Parm); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); +#endif + + VBOXHGCMSVCPARM aFeatures[2]; + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + | VBOX_SHCL_GF_0_TRANSFERS +#endif + ); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + RTTESTI_CHECK(g_State.cBackendSync == 1); + + HGCMSvcSetU64(&aFeatures[0], VBOX_SHCL_GF_0_CONTEXT_ID | VBOX_SHCL_GF_0_TRANSFERS); + HGCMSvcSetU64(&aFeatures[1], VBOX_SHCL_GF_1_MUST_BE_ONE); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FEATURES, RT_ELEMENTS(aFeatures), aFeatures); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(g_State.cBackendSync == 1); +#endif +} + + +/********************************************************************************************************************************* +* Test Cases * +*********************************************************************************************************************************/ +/** + * Checks service registration and the complete connection lifecycle. + * + * @param pConn Main connection used by the extension adapter. + * @param ppvClient Where to return the connected HGCM client state. + */ +static void tstLifecycle(GuestShClConn *pConn, void **ppvClient) +{ + RTTestISub("Service and Main lifecycle"); + int vrc = tstLoad(pConn); + RTTESTI_CHECK_RC_RETV(vrc, VINF_SUCCESS); + + void *pvClient = RTMemAllocZ(g_Table.cbClient); + RTTESTI_CHECK_RETV(pvClient != NULL); + vrc = g_Table.pfnConnect(g_Table.pvService, 1, pvClient, 0 /* fRequestor */, false /* fRestoring */); + if (RT_FAILURE(vrc)) + { + RTTestIFailed("Connecting the HGCM client failed: %Rrc", vrc); + RTMemFree(pvClient); + return; + } + + RTTESTI_CHECK(pConn->isConnected()); + RTTESTI_CHECK(g_State.cBackendInit == 1); + RTTESTI_CHECK(g_State.cBackendConnect == 1); + RTTESTI_CHECK(g_State.cBackendSync == 1); + *ppvClient = pvClient; +} + +/** + * Checks messages flowing from Main through the real Host Service to the guest. + * + * @param pvClient Connected HGCM client state. + * @param pConn Connected Main service connection. + */ +static void tstFormats(void *pvClient, GuestShClConn *pConn) +{ + RTTestISub("Format messages in both directions"); + SHCLFORMATS fReported = VBOX_SHCL_FMT_NONE; + int vrc = pConn->reportFormatsToGuest(VBOX_SHCL_FMT_UNICODETEXT, &fReported); + RTTESTI_CHECK_RC_OK(vrc); + RTTESTI_CHECK(fReported == VBOX_SHCL_FMT_UNICODETEXT); + + VBOXHGCMSVCPARM aGet[2]; + HGCMSvcSetU32(&aGet[0], VBOX_SHCL_HOST_MSG_FORMATS_REPORT); + HGCMSvcSetU32(&aGet[1], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aGet), aGet); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(aGet[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + + VBOXHGCMSVCPARM Parm; + HGCMSvcSetU32(&Parm, VBOX_SHCL_FMT_HTML); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPORT_FORMATS, 1, &Parm); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(g_State.cGuestFormats == 1); + RTTESTI_CHECK(g_State.fGuestFormats == VBOX_SHCL_FMT_HTML); +} + +/** + * Checks a service-owned reply context across the real extension boundary. + * + * @param pvClient Connected HGCM client state. + * @param pConn Connected Main service connection. + */ +static void tstGuestData(void *pvClient, GuestShClConn *pConn) +{ + RTTestISub("Guest data reply ownership"); + PSHCLEVENT pEvent = NULL; + int vrc = pConn->readDataFromGuestAsync(VBOX_SHCL_FMT_UNICODETEXT, &pEvent); + RTTESTI_CHECK_RC_RETV(vrc, VINF_SUCCESS); + RTTESTI_CHECK_RETV(pEvent != NULL); + + VBOXHGCMSVCPARM aGet[2]; + HGCMSvcSetU64(&aGet[0], VBOX_SHCL_HOST_MSG_READ_DATA_CID); + HGCMSvcSetU32(&aGet[1], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aGet), aGet); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + uint64_t const uContext = aGet[0].u.uint64; + RTTESTI_CHECK(aGet[1].u.uint32 == VBOX_SHCL_FMT_UNICODETEXT); + + static uint8_t const s_abData[] = { 'm', 'a', 'i', 'n', '\0' }; + VBOXHGCMSVCPARM aWrite[VBOX_SHCL_CPARMS_DATA_WRITE]; + HGCMSvcSetU64(&aWrite[0], uContext); + HGCMSvcSetU32(&aWrite[1], VBOX_SHCL_FMT_UNICODETEXT); + HGCMSvcSetPv(&aWrite[2], (void *)s_abData, sizeof(s_abData)); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_DATA_WRITE, RT_ELEMENTS(aWrite), aWrite); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + + PSHCLEVENTPAYLOAD pPayload = NULL; + vrc = ShClEventWait(pEvent, RT_MS_1SEC, &pPayload); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + if (pPayload) + { + RTTESTI_CHECK(pPayload->cbData == sizeof(s_abData)); + RTTESTI_CHECK(memcmp(pPayload->pvData, s_abData, sizeof(s_abData)) == 0); + ShClPayloadDestroy(pPayload); + } + RTTESTI_CHECK(ShClEventRelease(pEvent) == 0); +} + +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS +/** + * Checks post-initialization callback failure propagation across Main and the Host Service. + * + * @param pvClient Connected HGCM client state. + * @param pConn Connected Main service connection. + */ +static void tstTransferInitializedResult(void *pvClient, GuestShClConn *pConn) +{ + RTTestISub("Transfer initialized callback result"); + + TSTTRANSFERINITIALIZED State; + RT_ZERO(State); + State.vrcCallback = VERR_SHCLPB_NO_DATA; + + SHCLTRANSFERCALLBACKS Callbacks; + RT_ZERO(Callbacks); + Callbacks.pfnOnInitialized = tstTransferInitializedCallback; + Callbacks.pfnOnDestroy = tstTransferDestroyedCallback; + Callbacks.pvUser = &State; + Callbacks.cbUser = sizeof(State); + + PSHCLTRANSFER pTransfer = NULL; + int vrc = pConn->transferCreate(SHCLTRANSFERDIR_GUEST_TO_HOST, SHCLSOURCE_REMOTE, &Callbacks, + NIL_SHCLTRANSFERID, &pTransfer); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + if (RT_FAILURE(vrc)) + return; + + SHCLSESSIONID const idSession = ShClTransferGetSessionId(pTransfer); + SHCLTRANSFERID const idTransfer = ShClTransferGetID(pTransfer); + + vrc = pConn->transferInit(pTransfer); + RTTESTI_CHECK_RC(vrc, State.vrcCallback); + RTTESTI_CHECK(State.cInitialized == 1); + RTTESTI_CHECK(State.enmStatus == SHCLTRANSFERSTATUS_INITIALIZED); + RTTESTI_CHECK(!State.fLockOwned); + + VBOXHGCMSVCPARM aStatus[VBOX_SHCL_CPARMS_TRANSFER_STATUS]; + HGCMSvcSetU64(&aStatus[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aStatus[1], 0); + HGCMSvcSetU32(&aStatus[2], 0); + HGCMSvcSetU32(&aStatus[3], 0); + HGCMSvcSetU32(&aStatus[4], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatus), aStatus); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(aStatus[0].u.uint64) == idSession); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(aStatus[0].u.uint64) == idTransfer); + RTTESTI_CHECK(aStatus[1].u.uint32 == SHCLTRANSFERDIR_GUEST_TO_HOST); + RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_ERROR); + RTTESTI_CHECK((int32_t)aStatus[3].u.uint32 == State.vrcCallback); + + ShClTransferRelease(pTransfer); + pConn->transferDestroyById(idTransfer); + RTTESTI_CHECK(State.cDestroyed == 1); + + HGCMSvcSetU64(&aStatus[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aStatus[1], 0); + HGCMSvcSetU32(&aStatus[2], 0); + HGCMSvcSetU32(&aStatus[3], 0); + HGCMSvcSetU32(&aStatus[4], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatus), aStatus); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(aStatus[0].u.uint64) == idSession); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(aStatus[0].u.uint64) == idTransfer); + RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_UNINITIALIZED); +} + + +/** + * Checks one guest-requested transfer reaching Main and the native backend. + * + * @param pvClient Connected HGCM client state. + */ +static void tstTransfer(void *pvClient) +{ + RTTestISub("Transfer control-plane handshake"); + VBOXHGCMSVCPARM aReply[VBOX_SHCL_CPARMS_REPLY_MIN + 1]; + HGCMSvcSetU64(&aReply[0], 0); + HGCMSvcSetU32(&aReply[1], VBOX_SHCL_TX_REPLYMSGTYPE_TRANSFER_STATUS); + HGCMSvcSetU32(&aReply[2], VINF_SUCCESS); + HGCMSvcSetPv(&aReply[3], NULL, 0); + HGCMSvcSetU32(&aReply[4], SHCLTRANSFERSTATUS_REQUESTED); + int vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_REPLY, RT_ELEMENTS(aReply), aReply); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(g_State.cTransferCallbacks == 1); + RTTESTI_CHECK(g_State.cTransferStatuses == 1); + RTTESTI_CHECK(g_State.idTransferSession != NIL_SHCLSESSIONID); + RTTESTI_CHECK(g_State.idTransfer != NIL_SHCLTRANSFERID); + RTTESTI_CHECK(g_State.uTransferGeneration != NIL_SHCLTRANSFERGEN); + RTTESTI_CHECK(g_State.enmTransferStatus == SHCLTRANSFERSTATUS_REQUESTED); + + VBOXHGCMSVCPARM aStatus[VBOX_SHCL_CPARMS_TRANSFER_STATUS]; + HGCMSvcSetU64(&aStatus[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aStatus[1], 0); + HGCMSvcSetU32(&aStatus[2], 0); + HGCMSvcSetU32(&aStatus[3], 0); + HGCMSvcSetU32(&aStatus[4], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatus), aStatus); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_SESSION(aStatus[0].u.uint64) == g_State.idTransferSession); + RTTESTI_CHECK(VBOX_SHCL_CONTEXTID_GET_TRANSFER(aStatus[0].u.uint64) == g_State.idTransfer); + RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_REQUESTED); + + VBOXHGCMSVCPARM aCancel[2]; + HGCMSvcSetU64(&aCancel[0], VBOX_SHCL_CONTEXTID_MAKE(g_State.idTransferSession, g_State.idTransfer, 0)); + HGCMSvcSetU64(&aCancel[1], g_State.uTransferGeneration); + vrc = g_Table.pfnHostCall(g_Table.pvService, VBOX_SHCL_HOST_FN_CANCEL, RT_ELEMENTS(aCancel), aCancel); + RTTESTI_CHECK_RC_OK(vrc); + + HGCMSvcSetU64(&aStatus[0], VBOX_SHCL_HOST_MSG_TRANSFER_STATUS); + HGCMSvcSetU32(&aStatus[1], 0); + HGCMSvcSetU32(&aStatus[2], 0); + HGCMSvcSetU32(&aStatus[3], 0); + HGCMSvcSetU32(&aStatus[4], 0); + vrc = tstGuestCall(pvClient, VBOX_SHCL_GUEST_FN_MSG_GET, RT_ELEMENTS(aStatus), aStatus); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTTESTI_CHECK(aStatus[2].u.uint32 == SHCLTRANSFERSTATUS_CANCELED); +} +#endif + +/** + * Disconnects Main, unregisters its extension and unloads the Host Service. + * + * @param pvClient Connected HGCM client state. May be NULL. + * @param pConn Main service connection. + */ +static void tstShutdown(void *pvClient, GuestShClConn *pConn) +{ + RTTestISub("Orderly shutdown"); + if (pvClient) + { + int const vrc = g_Table.pfnDisconnect(g_Table.pvService, 1, pvClient); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + RTMemFree(pvClient); + } + RTTESTI_CHECK(!pConn->isConnected()); + RTTESTI_CHECK(g_State.cBackendDisconnect == 1); + + if (g_State.fExtensionRegistered) + { + int const vrc = g_Table.pfnRegisterExtension(g_Table.pvService, NULL, NULL); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + g_State.fExtensionRegistered = false; + RTTESTI_CHECK(g_State.cBackendDestroy == 1); + } + if (g_State.fServiceLoaded) + { + int const vrc = g_Table.pfnUnload(g_Table.pvService); + RTTESTI_CHECK_RC(vrc, VINF_SUCCESS); + g_State.fServiceLoaded = false; + } +} + +/** Testcase entry point. */ +int main(void) +{ + RTEXITCODE rcExit = RTTestInitAndCreate("tstClipboardMain2HostSvc", &g_hTest); + if (rcExit != RTEXITCODE_SUCCESS) + return rcExit; + RTTestBanner(g_hTest); + + RT_ZERO(g_State); + try + { + GuestShClConn Conn(NULL); + void *pvClient = NULL; + tstLifecycle(&Conn, &pvClient); + if (pvClient) + { + tstNegotiate(pvClient); + tstFormats(pvClient, &Conn); + tstGuestData(pvClient, &Conn); +#ifdef VBOX_WITH_SHARED_CLIPBOARD_TRANSFERS + tstTransferInitializedResult(pvClient, &Conn); + tstTransfer(pvClient); +#endif + } + tstShutdown(pvClient, &Conn); + } + catch (int vrc) + { + RTTestIFailed("Constructing Main's clipboard connection failed: %Rrc", vrc); + } + + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/Main/xml/Settings.cpp b/src/VBox/Main/xml/Settings.cpp index 137785135a35..f17af645cdab 100644 --- a/src/VBox/Main/xml/Settings.cpp +++ b/src/VBox/Main/xml/Settings.cpp @@ -1,4 +1,4 @@ -/* $Id: Settings.cpp 114800 2026-07-27 16:53:06Z andreas.loeffler@oracle.com $ */ +/* $Id: Settings.cpp 114949 2026-08-10 13:32:24Z serkan.bayraktar@oracle.com $ */ /** @file * Settings File Manipulation API. * @@ -9710,6 +9710,16 @@ void MachineConfigFile::sanitizeImportedSerialPorts() } } +void MachineConfigFile::sanitizeSharedFolderSettings() +{ + hardwareMachine.llSharedFolders.clear(); +} + +void MachineConfigFile::sanitizeImportedNvramSettings() +{ + hardwareMachine.nvramSettings.strNvramPath.setNull(); +} + /** * Called from write() before calling ConfigFileBase::createStubDocument(). * This adjusts the settings version in m->sv if incompatible settings require diff --git a/src/VBox/NetworkServices/Dhcpd/Db.cpp b/src/VBox/NetworkServices/Dhcpd/Db.cpp index bd1243d89d70..934f3bde5933 100644 --- a/src/VBox/NetworkServices/Dhcpd/Db.cpp +++ b/src/VBox/NetworkServices/Dhcpd/Db.cpp @@ -1,4 +1,4 @@ -/* $Id: Db.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: Db.cpp 115034 2026-08-13 11:21:59Z andreas.loeffler@oracle.com $ */ /** @file * DHCP server - address database */ @@ -362,7 +362,8 @@ Db::Db() Db::~Db() { - /** @todo free bindings */ + for (bindings_t::iterator it = m_bindings.begin(); it != m_bindings.end(); ++it) + delete *it; } diff --git a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk index a2213551f792..ee13f5afaef3 100644 --- a/src/VBox/NetworkServices/Dhcpd/Makefile.kmk +++ b/src/VBox/NetworkServices/Dhcpd/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112878 2026-02-09 09:27:34Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for the DHCP server. # @@ -28,6 +28,11 @@ SUB_DEPTH := ../../../.. include $(KBUILD_PATH)/subheader.kmk +# Include testcases. +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES) + include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk +endif + # # Hardended stub executable. # @@ -35,6 +40,7 @@ ifdef VBOX_WITH_HARDENING PROGRAMS += VBoxNetDHCPHardened VBoxNetDHCPHardened_TEMPLATE = VBoxR3HardenedExe VBoxNetDHCPHardened_NAME = VBoxNetDHCP + VBoxNetDHCPHardened_DEFS += $(if $(and $(VBOX_WITH_DRIVERLESS_NEM_FALLBACK),$(VBOX_WITH_INTNET_SERVICE_IN_R3)),VBOX_WITH_DRIVERLESS_NEM_FALLBACK,) VBoxNetDHCPHardened_SOURCES = VBoxNetDhcpdHardened.cpp VBoxNetDHCPHardened_LDFLAGS.win = /SUBSYSTEM:windows $(call VBOX_SET_VER_INFO_DLL,VBoxNetDHCPHardened,VirtualBox DHCP Server,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description. @@ -58,7 +64,8 @@ else VBoxNetDHCP_DEFS = KBUILD_TYPE=\"$(KBUILD_TYPE)\" endif VBoxNetDHCP_DEFS += \ - $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) #VBoxNetDHCP_DEFS += IPv6 #VBoxNetDHCP_DEFS.linux = WITH_VALGRIND diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp index b8e648fa5aa1..88474dedc1db 100644 --- a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp +++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpd.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetDhcpd.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxNetDhcpd.cpp 114886 2026-08-07 08:28:10Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetDhcpd - DHCP server for host-only and NAT networks. */ @@ -48,6 +48,10 @@ #endif #include "DhcpdInternal.h" +#ifdef VBOXNETDHCPD_INPROC_TESTING +# include +# include +#endif #include #include @@ -107,6 +111,14 @@ class VBoxNetDhcpd int main(int argc, char **argv); +#ifdef VBOXNETDHCPD_INPROC_TESTING + void testSetStartupEvent(RTSEMEVENT hEvtStartup); + int testStop(); + int testQueryStartupStatus(); + bool testIsRunning(); + Config *testTakeConfig(); +#endif + private: /** The logger instance. */ PRTLOGGER m_pStderrReleaseLogger; @@ -121,6 +133,23 @@ class VBoxNetDhcpd /** DHCP server instance. */ DHCPD m_server; +#ifdef VBOXNETDHCPD_INPROC_TESTING + /** Event signalled after in-process startup succeeds or fails. */ + RTSEMEVENT m_hTestStartup; + /** In-process startup status. */ + int32_t volatile m_rcTestStartup; + /** Whether the in-process daemon is pumping IntNet packets. */ + bool volatile m_fTestRunning; + /** Whether the lwIP core was initialized by the in-process daemon. */ + bool m_fTestLwipInitialized; + /** Whether the in-process daemon added its lwIP network interface. */ + bool m_fTestNetIfAdded; + + void testSignalStartup(int rc); + static DECLCALLBACK(void) testLwipFiniCB(void *pvUser); + void testLwipFini(); +#endif + int logInitStderr(); /* @@ -157,6 +186,13 @@ VBoxNetDhcpd::VBoxNetDhcpd() m_LwipNetif(), m_Config(NULL), m_Dhcp4Pcb(NULL) +#ifdef VBOXNETDHCPD_INPROC_TESTING + , m_hTestStartup(NIL_RTSEMEVENT), + m_rcTestStartup(VERR_WRONG_ORDER), + m_fTestRunning(false), + m_fTestLwipInitialized(false), + m_fTestNetIfAdded(false) +#endif { logInitStderr(); } @@ -173,6 +209,115 @@ VBoxNetDhcpd::~VBoxNetDhcpd() } +#ifdef VBOXNETDHCPD_INPROC_TESTING +/** + * Configures the event used to report in-process daemon startup. + * + * @param hEvtStartup Event to signal after startup succeeds or fails. + */ +void VBoxNetDhcpd::testSetStartupEvent(RTSEMEVENT hEvtStartup) +{ + m_hTestStartup = hEvtStartup; +} + + +/** + * Interrupts the in-process daemon's IntNet receive wait. + * + * @returns VBox status code. + */ +int VBoxNetDhcpd::testStop() +{ + if (m_hIf != NULL) + return IntNetR3IfWaitAbort(m_hIf); + return VINF_SUCCESS; +} + + +/** + * Queries the in-process daemon startup result. + * + * @returns VBox status code reported by startup. + */ +int VBoxNetDhcpd::testQueryStartupStatus() +{ + return ASMAtomicReadS32(&m_rcTestStartup); +} + + +/** + * Checks whether the in-process daemon is pumping IntNet packets. + * + * @returns true if the daemon is running, false otherwise. + */ +bool VBoxNetDhcpd::testIsRunning() +{ + return ASMAtomicReadBool(&m_fTestRunning); +} + + +/** + * Detaches the configuration so the testcase can delete it after this object. + * + * @returns Detached configuration, or NULL if no configuration was created. + */ +Config *VBoxNetDhcpd::testTakeConfig() +{ + Config *pConfig = m_Config; + m_Config = NULL; + return pConfig; +} + + +/** + * Reports completion of in-process daemon startup. + * + * @param rc Startup status. + */ +void VBoxNetDhcpd::testSignalStartup(int rc) +{ + ASMAtomicWriteS32(&m_rcTestStartup, rc); + ASMAtomicWriteBool(&m_fTestRunning, RT_SUCCESS(rc)); + if (m_hTestStartup != NIL_RTSEMEVENT) + RTSemEventSignal(m_hTestStartup); +} + + +/** + * Removes the in-process daemon's lwIP objects on the lwIP thread. + * + * @param pvUser VBoxNetDhcpd instance. + */ +/* static */ DECLCALLBACK(void) VBoxNetDhcpd::testLwipFiniCB(void *pvUser) +{ + VBoxNetDhcpd *pThis = static_cast(pvUser); + AssertPtrReturnVoid(pThis); + pThis->testLwipFini(); +} + + +/** + * Removes the in-process daemon's lwIP protocol and interface state. + */ +void VBoxNetDhcpd::testLwipFini() +{ + if (m_Dhcp4Pcb != NULL) + { + udp_remove(m_Dhcp4Pcb); + m_Dhcp4Pcb = NULL; + } + + if (m_fTestNetIfAdded) + { + netif_set_link_down(&m_LwipNetif); + netif_set_down(&m_LwipNetif); + netif_remove(&m_LwipNetif); + m_fTestNetIfAdded = false; + } +} +#endif /* VBOXNETDHCPD_INPROC_TESTING */ + + /* * We don't know the name of the release log file until we parse our * configuration because we use network name as basename. To get @@ -342,6 +487,10 @@ err_t VBoxNetDhcpd::netifLinkOutput(pbuf *pPBuf) int VBoxNetDhcpd::main(int argc, char **argv) { +#ifdef VBOXNETDHCPD_INPROC_TESTING + bool fTestStartupSignalled = false; +#endif + /* * Register string format types. */ @@ -353,7 +502,12 @@ int VBoxNetDhcpd::main(int argc, char **argv) */ m_Config = Config::create(argc, argv); if (m_Config == NULL) + { +#ifdef VBOXNETDHCPD_INPROC_TESTING + testSignalStartup(VERR_GENERAL_FAILURE); +#endif return VERR_GENERAL_FAILURE; + } /* * Initialize the server. @@ -369,11 +523,24 @@ int VBoxNetDhcpd::main(int argc, char **argv) rc = vboxLwipCoreInitialize(lwipInitCB, this); if (RT_SUCCESS(rc)) { +#ifdef VBOXNETDHCPD_INPROC_TESTING + m_fTestLwipInitialized = true; + if ( !m_fTestNetIfAdded + || m_Dhcp4Pcb == NULL) + rc = VERR_NET_INIT_FAILED; + else + { + testSignalStartup(VINF_SUCCESS); + fTestStartupSignalled = true; + } +#endif + /* * Pump packets more or less for ever. */ - rc = IntNetR3IfPumpPkts(m_hIf, ifInput, this, - NULL /*pfnInputGso*/, NULL /*pvUserGso*/); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfPumpPkts(m_hIf, ifInput, this, + NULL /*pfnInputGso*/, NULL /*pvUserGso*/); } else DHCP_LOG_MSG_ERROR(("Terminating - vboxLwipCoreInitialize failed: %Rrc\n", rc)); @@ -383,6 +550,17 @@ int VBoxNetDhcpd::main(int argc, char **argv) } else DHCP_LOG_MSG_ERROR(("Terminating - Dhcpd::init failed: %Rrc\n", rc)); + +#ifdef VBOXNETDHCPD_INPROC_TESTING + ASMAtomicWriteBool(&m_fTestRunning, false); + if (!fTestStartupSignalled) + testSignalStartup(rc); + if (m_fTestLwipInitialized) + { + vboxLwipCoreFinalize(testLwipFiniCB, this); + m_fTestLwipInitialized = false; + } +#endif return rc; } @@ -406,6 +584,9 @@ void VBoxNetDhcpd::lwipInit() if (pNetif == NULL) return; +#ifdef VBOXNETDHCPD_INPROC_TESTING + m_fTestNetIfAdded = true; +#endif netif_set_up(pNetif); netif_set_link_up(pNetif); @@ -503,6 +684,7 @@ void VBoxNetDhcpd::dhcp4Recv(struct udp_pcb *pcb, struct pbuf *p, /* * Entry point. */ +#ifndef VBOXNETDHCPD_INPROC_TESTING extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv) { VBoxNetDhcpd Dhcpd; @@ -511,18 +693,22 @@ extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv) } -#ifndef VBOX_WITH_HARDENING +# ifndef VBOX_WITH_HARDENING int main(int argc, char **argv) { +# ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + int rc = RTR3InitExe(argc, &argv, 0 /* fFlags */); +# else int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); +# endif if (RT_SUCCESS(rc)) return TrustedMain(argc, argv); return RTMsgInitFailure(rc); } -# ifdef RT_OS_WINDOWS +# ifdef RT_OS_WINDOWS /** (We don't want a console usually.) */ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { @@ -530,6 +716,7 @@ int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine return main(__argc, __argv); } -# endif /* RT_OS_WINDOWS */ +# endif /* RT_OS_WINDOWS */ -#endif /* !VBOX_WITH_HARDENING */ +# endif /* !VBOX_WITH_HARDENING */ +#endif /* !VBOXNETDHCPD_INPROC_TESTING */ diff --git a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp index f0b2108d9c0e..cbc16abd9ee9 100644 --- a/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp +++ b/src/VBox/NetworkServices/Dhcpd/VBoxNetDhcpdHardened.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetDhcpdHardened.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxNetDhcpdHardened.cpp 114885 2026-08-07 08:19:17Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetDhcpd - Hardened main(). */ @@ -30,6 +30,10 @@ int main(int argc, char **argv, char **envp) { +#ifdef VBOX_WITH_DRIVERLESS_NEM_FALLBACK + return SUPR3HardenedMain("VBoxNetDHCP", SUPSECMAIN_FLAGS_DRIVERLESS_NEM_FALLBACK, argc, argv, envp); +#else return SUPR3HardenedMain("VBoxNetDHCP", 0 /* fFlags */, argc, argv, envp); +#endif } diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/Makefile.kmk b/src/VBox/NetworkServices/Dhcpd/testcase/Makefile.kmk new file mode 100644 index 000000000000..41cb6f311fa8 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/testcase/Makefile.kmk @@ -0,0 +1,95 @@ +# $Id: Makefile.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ +## @file +# Sub-Makefile for VBoxNetDHCP testcases. + +# +# Copyright (C) 2026 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH := ../../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES) + + PROGRAMS += tstVBoxNetDhcpd + + tstVBoxNetDhcpd_TEMPLATE = VBoxR3TstExe + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstVBoxNetDhcpd_0_OUTDIR)/tstVBoxNetDhcpd.run + endif + tstVBoxNetDhcpd_CLEAN = $(tstVBoxNetDhcpd_0_OUTDIR)/tstVBoxNetDhcpd.run + + ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING + tstVBoxNetDhcpd_DEFS = KBUILD_TYPE="$(KBUILD_TYPE)" + else + tstVBoxNetDhcpd_DEFS = KBUILD_TYPE=\"$(KBUILD_TYPE)\" + endif + + tstVBoxNetDhcpd_DEFS += \ + TESTCASE VBOXNETDHCPD_INPROC_TESTING VBOX_WITH_INTNET_SERVICE_IN_R3 VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC \ + VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH VBOX_INTNET_TESTCASE_LOCALIPC + + ifneq ($(KBUILD_TARGET),win) + tstVBoxNetDhcpd_DEFS += VBOX_WITH_XPCOM + tstVBoxNetDhcpd_INCS += $(VBOX_XPCOM_INCS) + endif + + tstVBoxNetDhcpd_SDKS += VBoxLwipDhcp + + tstVBoxNetDhcpd_INCS += \ + .. \ + ../../NetLib \ + ../../IntNetSwitch + + # Do not list ../VBoxNetDhcpd.cpp directly here. The wrapper below includes it + # after defining VBOXNETDHCPD_INPROC_TESTING, guaranteeing that the in-process + # testcase exports are compiled even on Windows/kBuild targets. + tstVBoxNetDhcpd_SOURCES = \ + tstVBoxNetDhcpd.cpp \ + VBoxNetDhcpdInProc.cpp \ + VBoxIntNetSwitchInProc.cpp \ + ../ClientId.cpp \ + ../Config.cpp \ + ../DHCPD.cpp \ + ../Db.cpp \ + ../DhcpMessage.cpp \ + ../DhcpOptions.cpp \ + ../IPv4Pool.cpp \ + ../Timestamp.cpp \ + ../../IntNetSwitch/SrvIntNetWrapper.cpp \ + ../../NetLib/IntNetIf.cpp \ + ../../../Main/glue/VBoxLogRelCreate.cpp \ + ../../../Main/glue/GetVBoxUserHomeDirectory.cpp + + tstVBoxNetDhcpd_LIBS = \ + $(LIB_RUNTIME) + + tstVBoxNetDhcpd_LIBS.solaris += socket nsl + + $$(tstVBoxNetDhcpd_0_OUTDIR)/tstVBoxNetDhcpd.run: \ + $$(tstVBoxNetDhcpd_1_STAGE_TARGET) \ + | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstVBoxNetDhcpd_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + +endif # VBOX_WITH_TESTCASES && VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/VBoxIntNetSwitchInProc.cpp b/src/VBox/NetworkServices/Dhcpd/testcase/VBoxIntNetSwitchInProc.cpp new file mode 100644 index 000000000000..2086e69b30a3 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/testcase/VBoxIntNetSwitchInProc.cpp @@ -0,0 +1,317 @@ +/* $Id: VBoxIntNetSwitchInProc.cpp 115072 2026-08-19 07:32:45Z andreas.loeffler@oracle.com $ */ +/** @file + * In-process local IPC IntNet switch for tstVBoxNetDhcpd. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH +# define VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH +#endif +#ifndef VBOX_INTNET_TESTCASE_LOCALIPC +# define VBOX_INTNET_TESTCASE_LOCALIPC +#endif + +#include "../../IntNetSwitch/VBoxIntNetSwitch.cpp" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Valid embedded switch handle magic. */ +#define VBOXINTNETSWITCHTEST_MAGIC UINT32_C(0x49505357) +/** Invalidated embedded switch handle magic. */ +#define VBOXINTNETSWITCHTEST_MAGIC_DEAD UINT32_C(0x69707377) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** Embedded production IntNet local IPC service state. */ +typedef struct VBOXINTNETSWITCHTEST +{ + /** Magic value (VBOXINTNETSWITCHTEST_MAGIC). */ + uint32_t u32Magic; + /** Local IPC server. */ + RTLOCALIPCSERVER hServer; + /** Waitable acceptor thread. */ + RTTHREAD hAcceptor; + /** Set when the acceptor should stop. */ + bool volatile fStop; + /** Whether the R3-built IntNet switching core was initialized. */ + bool fIntNetInited; + /** Unique service name selected through VBOX_INTNET_R3_SVC_NAME. */ + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; +} VBOXINTNETSWITCHTEST; +typedef VBOXINTNETSWITCHTEST *PVBOXINTNETSWITCHTEST; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** + * Accepts local IPC clients and starts the session worker. + * + * @returns VBox status code. + * @param hThreadSelf Thread handle, unused. + * @param pvUser Embedded switch state. + */ +static DECLCALLBACK(int) vboxIntNetSwitchTestAcceptThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + PVBOXINTNETSWITCHTEST pTest = (PVBOXINTNETSWITCHTEST)pvUser; + AssertPtrReturn(pTest, VERR_INVALID_POINTER); + AssertReturn(pTest->u32Magic == VBOXINTNETSWITCHTEST_MAGIC, VERR_INVALID_HANDLE); + + for (;;) + { + RTLOCALIPCSESSION hClient = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcServerListen(pTest->hServer, &hClient); +#ifdef RT_OS_WINDOWS + if (rc == VERR_TRY_AGAIN) + { + RTThreadSleep(10); + continue; + } +#endif + if (RT_FAILURE(rc)) + break; + if (ASMAtomicReadBool(&pTest->fStop)) + { + RTLocalIpcSessionClose(hClient); + break; + } + if (!intnetR3TryReserveSlot(&g_DevExt.cRefs, g_DevExt.cMaxConnections)) + { + RTLocalIpcSessionClose(hClient); + RTThreadSleep(10); + continue; + } + + PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession)); + if (pSession == NULL) + { + RTLocalIpcSessionClose(hClient); + intnetR3ReleaseConnectionSlot(&g_DevExt); + continue; + } + + pSession->pDevExt = &g_DevExt; + pSession->hIpcSession = hClient; + pSession->hThread = NIL_RTTHREAD; + pSession->hIpcPokeThread = NIL_RTTHREAD; + pSession->hIpcPokeEvt = NIL_RTSEMEVENT; + pSession->fIpcPokeStopping = false; + pSession->hIpcIoMtx = NIL_RTSEMMUTEX; + int rcThread = RTSemMutexCreate(&pSession->hIpcIoMtx); + if (RT_SUCCESS(rcThread)) + rcThread = RTSemEventCreate(&pSession->hIpcPokeEvt); + if (RT_FAILURE(rcThread)) + { + intnetR3SessionDestroy(pSession); + continue; + } + + if (!intnetR3TryReserveSlots(&g_DevExt.cThreads, g_DevExt.cMaxThreads, + INTNETR3_THREADS_PER_LOCALIPC_SESSION)) + { + intnetR3SessionDestroy(pSession); + RTThreadSleep(10); + continue; + } + + rcThread = RTThreadCreate(&pSession->hIpcPokeThread, intnetR3LocalIpcPokeThread, pSession, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "DhcpPoke"); + if (RT_FAILURE(rcThread)) + { + intnetR3SessionDestroy(pSession); + ASMAtomicSubU32(&g_DevExt.cThreads, INTNETR3_THREADS_PER_LOCALIPC_SESSION); + continue; + } + rcThread = RTThreadCreate(&pSession->hThread, intnetR3LocalIpcSessionThread, pSession, 0 /*cbStack*/, + RTTHREADTYPE_IO, 0 /*fFlags*/, "DhcpIntNet"); + if (RT_FAILURE(rcThread)) + { + intnetR3SessionDestroy(pSession); + ASMAtomicDecU32(&g_DevExt.cThreads); /* Reserved request worker was never started. */ + } + } + return VINF_SUCCESS; +} + + +/** + * Releases an embedded switch after all service threads have stopped. + * + * @param pTest Embedded switch state. + */ +static void vboxIntNetSwitchTestFree(PVBOXINTNETSWITCHTEST pTest) +{ + AssertPtrReturnVoid(pTest); + Assert(pTest->hServer == NIL_RTLOCALIPCSERVER); + Assert(pTest->hAcceptor == NIL_RTTHREAD); + Assert(ASMAtomicReadU32(&g_DevExt.cRefs) == 0); + + if (pTest->fIntNetInited) + { + IntNetR0Term(); + pTest->fIntNetInited = false; + } + RTCritSectDelete(&g_DevExt.CritSect); + RTEnvUnset("VBOX_INTNET_R3_SVC_NAME"); + + pTest->u32Magic = VBOXINTNETSWITCHTEST_MAGIC_DEAD; + RTMemFree(pTest); +} + + +/** + * Starts the IntNet switch using a unique local IPC service name. + * + * @returns VBox status code. + * @param ppvHandle Where to return the opaque embedded switch handle. + */ +extern "C" int VBoxIntNetSwitchTestStart(void **ppvHandle) +{ + AssertPtrReturn(ppvHandle, VERR_INVALID_POINTER); + *ppvHandle = NULL; + + PVBOXINTNETSWITCHTEST pTest = (PVBOXINTNETSWITCHTEST)RTMemAllocZ(sizeof(*pTest)); + if (pTest == NULL) + return VERR_NO_MEMORY; + pTest->u32Magic = VBOXINTNETSWITCHTEST_MAGIC; + pTest->hServer = NIL_RTLOCALIPCSERVER; + pTest->hAcceptor = NIL_RTTHREAD; + + int rc = RTCritSectInit(&g_DevExt.CritSect); + if (RT_FAILURE(rc)) + { + RTMemFree(pTest); + return rc; + } + g_DevExt.pObjs = NULL; + intnetR3InitLimits(&g_DevExt); + + rc = IntNetR0Init(); + if (RT_SUCCESS(rc)) + pTest->fIntNetInited = true; + if (RT_SUCCESS(rc)) + { + RTUUID Uuid; + char szUuid[RTUUID_STR_LENGTH]; + rc = RTUuidCreate(&Uuid); + if (RT_SUCCESS(rc)) + rc = RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pTest->szService, sizeof(pTest->szService), "tst-vboxnetdhcp-%s", szUuid); + if (cch <= 0 || (size_t)cch >= sizeof(pTest->szService)) + rc = VERR_BUFFER_OVERFLOW; + } + } + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SVC_NAME", pTest->szService); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcServerCreate(&pTest->hServer, pTest->szService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + rc = RTThreadCreate(&pTest->hAcceptor, vboxIntNetSwitchTestAcceptThread, pTest, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "DhcpIntNet0"); + if (RT_SUCCESS(rc)) + { + *ppvHandle = pTest; + return VINF_SUCCESS; + } + + if (pTest->hServer != NIL_RTLOCALIPCSERVER) + { + RTLocalIpcServerDestroy(pTest->hServer); + pTest->hServer = NIL_RTLOCALIPCSERVER; + } + vboxIntNetSwitchTestFree(pTest); + return rc; +} + + +/** + * Stops and destroys the embedded production IntNet switch. + * + * @returns VBox status code. + * @param pvHandle Opaque handle returned by VBoxIntNetSwitchTestStart. + */ +extern "C" int VBoxIntNetSwitchTestStop(void *pvHandle) +{ + PVBOXINTNETSWITCHTEST pTest = (PVBOXINTNETSWITCHTEST)pvHandle; + AssertPtrReturn(pTest, VERR_INVALID_HANDLE); + AssertReturn(pTest->u32Magic == VBOXINTNETSWITCHTEST_MAGIC, VERR_INVALID_HANDLE); + + int rc = VINF_SUCCESS; + if (pTest->hServer != NIL_RTLOCALIPCSERVER) + { + ASMAtomicWriteBool(&pTest->fStop, true); + + /* Wake the blocking server listen with a local dummy connection. */ + RTLOCALIPCSESSION hTmp = NIL_RTLOCALIPCSESSION; + int rc2 = RTLocalIpcSessionConnect(&hTmp, pTest->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc2)) + RTLocalIpcSessionClose(hTmp); + + rc2 = RTLocalIpcServerDestroy(pTest->hServer); + if (RT_FAILURE(rc2)) + rc = rc2; + pTest->hServer = NIL_RTLOCALIPCSERVER; + } + + if (pTest->hAcceptor != NIL_RTTHREAD) + { + int rcThread = VINF_SUCCESS; + int rc2 = RTThreadWait(pTest->hAcceptor, RT_MS_5SEC, &rcThread); + if (RT_FAILURE(rc2)) + return rc2; + pTest->hAcceptor = NIL_RTTHREAD; + if (RT_FAILURE(rcThread) && RT_SUCCESS(rc)) + rc = rcThread; + } + + uint64_t const msStart = RTTimeMilliTS(); + while ( ( ASMAtomicReadU32(&g_DevExt.cRefs) != 0 + || ASMAtomicReadU32(&g_DevExt.cThreads) != 0) + && RTTimeMilliTS() - msStart < RT_MS_5SEC) + RTThreadSleep(1); + if ( ASMAtomicReadU32(&g_DevExt.cRefs) != 0 + || ASMAtomicReadU32(&g_DevExt.cThreads) != 0) + return VERR_TIMEOUT; + + vboxIntNetSwitchTestFree(pTest); + return rc; +} diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/VBoxNetDhcpdInProc.cpp b/src/VBox/NetworkServices/Dhcpd/testcase/VBoxNetDhcpdInProc.cpp new file mode 100644 index 000000000000..2afaeea28bbe --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/testcase/VBoxNetDhcpdInProc.cpp @@ -0,0 +1,268 @@ +/* $Id: VBoxNetDhcpdInProc.cpp 115072 2026-08-19 07:32:45Z andreas.loeffler@oracle.com $ */ +/** @file + * In-process VBoxNetDHCP source shim for tstVBoxNetDhcpd. + * + * This guarantees that VBoxNetDhcpd.cpp is compiled with the testcase hooks, + * independent of target-global preprocessor definitions on a specific kBuild + * host/toolchain combination. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOXNETDHCPD_INPROC_TESTING +# define VBOXNETDHCPD_INPROC_TESTING 1 +#endif + +#include "../VBoxNetDhcpd.cpp" + +#include +#include +#include +#include +#include +#include +#include + +#include + + +/********************************************************************************************************************************* +* Defined Constants And Macros * +*********************************************************************************************************************************/ +/** Valid in-process DHCP daemon handle magic. */ +#define VBOXNETDHCPDTEST_MAGIC UINT32_C(0x44484350) +/** Invalidated in-process DHCP daemon handle magic. */ +#define VBOXNETDHCPDTEST_MAGIC_DEAD UINT32_C(0x64686370) + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** In-process VBoxNetDHCP testcase context. */ +typedef struct VBOXNETDHCPDTEST +{ + /** Magic value (VBOXNETDHCPDTEST_MAGIC). */ + uint32_t u32Magic; + /** Daemon instance. */ + VBoxNetDhcpd *pDhcpd; + /** Waitable daemon thread. */ + RTTHREAD hThread; + /** Daemon startup completion event. */ + RTSEMEVENT hEvtStartup; + /** Duplicated argument count. */ + int cArgs; + /** Duplicated argument vector. */ + char **papszArgs; +} VBOXNETDHCPDTEST; +typedef VBOXNETDHCPDTEST *PVBOXNETDHCPDTEST; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** + * Releases an in-process daemon context after its thread has stopped. + * + * @param pTest In-process daemon context. + */ +static void vboxNetDhcpdTestFree(PVBOXNETDHCPDTEST pTest) +{ + AssertPtrReturnVoid(pTest); + Assert(pTest->hThread == NIL_RTTHREAD); + + Config *pConfig = pTest->pDhcpd != NULL ? pTest->pDhcpd->testTakeConfig() : NULL; + delete pTest->pDhcpd; + pTest->pDhcpd = NULL; + delete pConfig; + + if (pTest->hEvtStartup != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pTest->hEvtStartup); + pTest->hEvtStartup = NIL_RTSEMEVENT; + } + + if (pTest->papszArgs != NULL) + { + for (int i = 0; i < pTest->cArgs; i++) + RTStrFree(pTest->papszArgs[i]); + RTMemFree(pTest->papszArgs); + pTest->papszArgs = NULL; + } + + pTest->u32Magic = VBOXNETDHCPDTEST_MAGIC_DEAD; + RTMemFree(pTest); +} + + +/** + * Runs the production VBoxNetDHCP daemon in-process. + * + * @returns VBox status code. + * @param hThreadSelf Thread handle, unused. + * @param pvUser In-process daemon context. + */ +static DECLCALLBACK(int) vboxNetDhcpdTestThread(RTTHREAD hThreadSelf, void *pvUser) +{ + RT_NOREF(hThreadSelf); + PVBOXNETDHCPDTEST pTest = (PVBOXNETDHCPDTEST)pvUser; + AssertPtrReturn(pTest, VERR_INVALID_POINTER); + AssertReturn(pTest->u32Magic == VBOXNETDHCPDTEST_MAGIC, VERR_INVALID_HANDLE); + + int rc = pTest->pDhcpd->main(pTest->cArgs, pTest->papszArgs); + if (rc == VERR_SEM_DESTROYED) + rc = VINF_SUCCESS; + return rc; +} + + +/** + * Duplicates an argument vector for the daemon thread. + * + * @returns VBox status code. + * @param pTest In-process daemon context. + * @param argc Argument count. + * @param argv Argument vector. + */ +static int vboxNetDhcpdTestDupArgv(PVBOXNETDHCPDTEST pTest, int argc, char **argv) +{ + pTest->papszArgs = (char **)RTMemAllocZ((argc + 1) * sizeof(pTest->papszArgs[0])); + if (pTest->papszArgs == NULL) + return VERR_NO_MEMORY; + + for (int i = 0; i < argc; i++) + { + AssertPtrReturn(argv[i], VERR_INVALID_POINTER); + pTest->papszArgs[i] = RTStrDup(argv[i]); + if (pTest->papszArgs[i] == NULL) + return VERR_NO_MEMORY; + pTest->cArgs = i + 1; + } + return VINF_SUCCESS; +} + + +/** + * Starts VBoxNetDHCP in-process and waits for its IntNet/lwIP initialization. + * + * @returns VBox status code. + * @param argc Argument count for VBoxNetDHCP. + * @param argv Argument vector for VBoxNetDHCP. + * @param ppvHandle Where to return the opaque daemon handle. + */ +extern "C" int VBoxNetDhcpdTestStart(int argc, char **argv, void **ppvHandle) +{ + AssertPtrReturn(ppvHandle, VERR_INVALID_POINTER); + *ppvHandle = NULL; + AssertReturn(argc > 0, VERR_INVALID_PARAMETER); + AssertPtrReturn(argv, VERR_INVALID_POINTER); + + PVBOXNETDHCPDTEST pTest = (PVBOXNETDHCPDTEST)RTMemAllocZ(sizeof(*pTest)); + if (pTest == NULL) + return VERR_NO_MEMORY; + pTest->u32Magic = VBOXNETDHCPDTEST_MAGIC; + pTest->hThread = NIL_RTTHREAD; + pTest->hEvtStartup = NIL_RTSEMEVENT; + + int rc = vboxNetDhcpdTestDupArgv(pTest, argc, argv); + if (RT_SUCCESS(rc)) + rc = RTSemEventCreate(&pTest->hEvtStartup); + if (RT_SUCCESS(rc)) + { + pTest->pDhcpd = new (std::nothrow) VBoxNetDhcpd(); + if (pTest->pDhcpd == NULL) + rc = VERR_NO_MEMORY; + } + if (RT_SUCCESS(rc)) + { + pTest->pDhcpd->testSetStartupEvent(pTest->hEvtStartup); + rc = RTThreadCreate(&pTest->hThread, vboxNetDhcpdTestThread, pTest, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "DhcpdTest"); + } + if (RT_FAILURE(rc)) + { + vboxNetDhcpdTestFree(pTest); + return rc; + } + + rc = RTSemEventWait(pTest->hEvtStartup, RT_MS_30SEC); + if (RT_SUCCESS(rc)) + rc = pTest->pDhcpd->testQueryStartupStatus(); + if (RT_SUCCESS(rc)) + { + *ppvHandle = pTest; + return VINF_SUCCESS; + } + + int rcThread = VINF_SUCCESS; + int rcWait = RTThreadWait(pTest->hThread, RT_MS_30SEC, &rcThread); + if (RT_SUCCESS(rcWait)) + { + pTest->hThread = NIL_RTTHREAD; + vboxNetDhcpdTestFree(pTest); + } + else + *ppvHandle = pTest; + return rc; +} + + +/** + * Stops and destroys an in-process VBoxNetDHCP daemon. + * + * @returns VBox status code. + * @param pvHandle Opaque daemon handle returned by VBoxNetDhcpdTestStart. + */ +extern "C" int VBoxNetDhcpdTestStop(void *pvHandle) +{ + PVBOXNETDHCPDTEST pTest = (PVBOXNETDHCPDTEST)pvHandle; + AssertPtrReturn(pTest, VERR_INVALID_HANDLE); + AssertReturn(pTest->u32Magic == VBOXNETDHCPDTEST_MAGIC, VERR_INVALID_HANDLE); + + int rc = pTest->pDhcpd->testStop(); + int rcThread = VINF_SUCCESS; + int rcWait = RTThreadWait(pTest->hThread, RT_MS_30SEC, &rcThread); + if (RT_FAILURE(rcWait)) + return rcWait; + + pTest->hThread = NIL_RTTHREAD; + vboxNetDhcpdTestFree(pTest); + if (RT_FAILURE(rc)) + return rc; + return rcThread; +} + + +/** + * Checks whether an in-process VBoxNetDHCP daemon is running. + * + * @returns true if the daemon is running, false otherwise. + * @param pvHandle Opaque daemon handle returned by VBoxNetDhcpdTestStart. + */ +extern "C" bool VBoxNetDhcpdTestIsRunning(void *pvHandle) +{ + PVBOXNETDHCPDTEST pTest = (PVBOXNETDHCPDTEST)pvHandle; + if ( pTest == NULL + || pTest->u32Magic != VBOXNETDHCPDTEST_MAGIC) + return false; + return pTest->pDhcpd->testIsRunning(); +} diff --git a/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp new file mode 100644 index 000000000000..ecb929b68e43 --- /dev/null +++ b/src/VBox/NetworkServices/Dhcpd/testcase/tstVBoxNetDhcpd.cpp @@ -0,0 +1,1283 @@ +/* $Id: tstVBoxNetDhcpd.cpp 115040 2026-08-14 17:39:40Z andreas.loeffler@oracle.com $ */ +/** @file + * VBoxNetDHCP in-process testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include "../Config.h" +#include +#include "../../NetLib/IntNetIf.h" + +extern "C" int VBoxNetDhcpdTestStart(int argc, char **argv, void **ppvHandle); +extern "C" int VBoxNetDhcpdTestStop(void *pvHandle); +extern "C" bool VBoxNetDhcpdTestIsRunning(void *pvHandle); +extern "C" int VBoxIntNetSwitchTestStart(void **ppvHandle); +extern "C" int VBoxIntNetSwitchTestStop(void *pvHandle); + +static RTTEST g_hTest = NIL_RTTEST; + +#define TST_MAX_FRAME 2048 +#define TST_MAX_RX_FRAMES 64 +#define TST_DHCP_TIMEOUT_MS 3000 +#define TST_ANY_XID UINT32_MAX + +#define DHCP4_DISCOVER 1 +#define DHCP4_OFFER 2 +#define DHCP4_REQUEST 3 +#define DHCP4_ACK 5 + +#define TST_CHECK(a_Expr) \ + do { if (!(a_Expr)) RTTestFailed(g_hTest, "%s:%u: %s", __FILE__, __LINE__, #a_Expr); } while (0) + +#define TST_CHECK_RC_OK(a_rc) \ + do { int rc__ = (a_rc); if (RT_FAILURE(rc__)) RTTestFailed(g_hTest, "%s:%u: %s -> %Rrc", __FILE__, __LINE__, #a_rc, rc__); } while (0) + +static uint16_t tstH2N16(uint16_t u) { return RT_H2N_U16(u); } +static uint32_t tstH2N32(uint32_t u) { return RT_H2N_U32(u); } + +static uint32_t tstIPv4(uint8_t a, uint8_t b, uint8_t c, uint8_t d) +{ + return tstH2N32(((uint32_t)a << 24) | ((uint32_t)b << 16) | ((uint32_t)c << 8) | d); +} + +static int tstMakeUuidName(const char *pszPrefix, char *pszOut, size_t cbOut) +{ + RTUUID Uuid; + char szUuid[RTUUID_STR_LENGTH]; + + int rc = RTUuidCreate(&Uuid); + if (RT_FAILURE(rc)) + return rc; + + RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + + ssize_t cch = RTStrPrintf2(pszOut, cbOut, "%s-%s", pszPrefix, szUuid); + return cch > 0 && (size_t)cch < cbOut ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW; +} + +static int tstMakeTempPath(const char *pszPrefix, const char *pszSuffix, char *pszOut, size_t cbOut) +{ + char szTmp[RTPATH_MAX]; + RTUUID Uuid; + char szUuid[RTUUID_STR_LENGTH]; + + int rc = RTPathTemp(szTmp, sizeof(szTmp)); + if (RT_FAILURE(rc)) + return rc; + + rc = RTUuidCreate(&Uuid); + if (RT_FAILURE(rc)) + return rc; + + RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + + ssize_t cch = RTStrPrintf2(pszOut, cbOut, "%s%c%s-%s%s", + szTmp, RTPATH_DELIMITER, pszPrefix, szUuid, pszSuffix); + return cch > 0 && (size_t)cch < cbOut ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW; +} + +static int tstWriteAll(const char *pszFilename, const void *pvBuf, size_t cbBuf) +{ + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpen(&hFile, + pszFilename, + RTFILE_O_CREATE_REPLACE | RTFILE_O_WRITE | RTFILE_O_DENY_NONE); + if (RT_FAILURE(rc)) + return rc; + + rc = RTFileWrite(hFile, pvBuf, cbBuf, NULL); + + int rcClose = RTFileClose(hFile); + if (RT_SUCCESS(rc) && RT_FAILURE(rcClose)) + rc = rcClose; + + return rc; +} + +static int tstWriteConfigFile(const char *pszNetwork, + const char *pszServerIp, + const char *pszMask, + const char *pszLower, + const char *pszUpper, + char *pszCfgFile, + size_t cbCfgFile) +{ + char szLeases[RTPATH_MAX]; + char szXml[8192]; + + int rc = tstMakeTempPath("VBoxNetDhcpd", ".xml", pszCfgFile, cbCfgFile); + if (RT_FAILURE(rc)) + return rc; + + rc = tstMakeTempPath("VBoxNetDhcpd", ".leases", szLeases, sizeof(szLeases)); + if (RT_FAILURE(rc)) + return rc; + + ssize_t cch = RTStrPrintf2(szXml, sizeof(szXml), + "\n" + "\n" + " \n" + " \n" + "\n", + pszNetwork, + pszServerIp, + pszMask, + pszLower, + pszUpper, + szLeases, + pszMask, + pszServerIp, + pszServerIp); + + if (cch <= 0 || (size_t)cch >= sizeof(szXml)) + return VERR_BUFFER_OVERFLOW; + + return tstWriteAll(pszCfgFile, szXml, (size_t)cch); +} + +static bool tstConfigCreateFromFile(const char *pszCfgFile) +{ + char *argv[3]; + argv[0] = (char *)"tstVBoxNetDhcpd-config"; + argv[1] = (char *)"--config"; + argv[2] = (char *)pszCfgFile; + + Config *pConfig = Config::create(3, argv); + if (pConfig != NULL) + { + delete pConfig; + return true; + } + + return false; +} + +static void tstConfigValidation(void) +{ + RTTestSub(g_hTest, "configuration validation"); + + char szNetwork[128]; + char szCfg[RTPATH_MAX]; + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdCfg", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "10.37.0.1", "255.255.255.0", "10.37.0.10", "10.37.0.12", + szCfg, sizeof(szCfg))); + TST_CHECK(tstConfigCreateFromFile(szCfg)); + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdBadOutside", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "10.37.0.1", "255.255.255.0", "10.38.0.10", "10.38.0.12", + szCfg, sizeof(szCfg))); + TST_CHECK(!tstConfigCreateFromFile(szCfg)); + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdBadReversed", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "10.37.0.1", "255.255.255.0", "10.37.0.12", "10.37.0.10", + szCfg, sizeof(szCfg))); + TST_CHECK(!tstConfigCreateFromFile(szCfg)); + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdBadMask", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "10.37.0.1", "255.255.0.255", "10.37.0.10", "10.37.0.12", + szCfg, sizeof(szCfg))); + TST_CHECK(!tstConfigCreateFromFile(szCfg)); + + TST_CHECK_RC_OK(tstMakeUuidName("VBoxNetDhcpdIPv6Unsupported", szNetwork, sizeof(szNetwork))); + TST_CHECK_RC_OK(tstWriteConfigFile(szNetwork, "fd00:1::1", "ffff:ffff:ffff:ffff::", + "fd00:1::100", "fd00:1::1ff", szCfg, sizeof(szCfg))); + TST_CHECK(!tstConfigCreateFromFile(szCfg)); + + { + char *argv[1]; + argv[0] = (char *)"tstVBoxNetDhcpd-no-config"; + Config *pConfig = Config::create(1, argv); + TST_CHECK(pConfig == NULL); + if (pConfig) + delete pConfig; + } +} + +typedef struct TSTPKT +{ + uint32_t cb; + uint8_t ab[TST_MAX_FRAME]; +} TSTPKT; +typedef TSTPKT *PTSTPKT; +typedef const TSTPKT *PCTSTPKT; + +typedef struct TSTRXFRAME +{ + uint32_t cb; + uint8_t ab[TST_MAX_FRAME]; +} TSTRXFRAME; +typedef TSTRXFRAME *PTSTRXFRAME; + +typedef struct TSTCLIENT +{ + INTNETIFCTX hIf; + RTTHREAD hThread; + /** Receive thread status, VINF_SUCCESS while it is running normally. */ + int32_t volatile rcThread; + RTCRITSECT CritSect; + RTSEMEVENT hEvtRx; + uint32_t iRead; + uint32_t iWrite; + uint32_t cFrames; + TSTRXFRAME aFrames[TST_MAX_RX_FRAMES]; +} TSTCLIENT; +typedef TSTCLIENT *PTSTCLIENT; + +#pragma pack(1) +typedef struct TSTETHHDR +{ + uint8_t abDst[6]; + uint8_t abSrc[6]; + uint16_t uType; +} TSTETHHDR; + +typedef struct TSTIP4HDR +{ + uint8_t uVerIhl; + uint8_t uTos; + uint16_t uLen; + uint16_t uId; + uint16_t uFragOff; + uint8_t uTtl; + uint8_t uProto; + uint16_t uChecksum; + uint32_t uSrc; + uint32_t uDst; +} TSTIP4HDR; + +typedef struct TSTIP6HDR +{ + uint32_t uVerTcFl; + uint16_t uPayloadLen; + uint8_t uNextHeader; + uint8_t uHopLimit; + uint8_t abSrc[16]; + uint8_t abDst[16]; +} TSTIP6HDR; + +typedef struct TSTUDPHDR +{ + uint16_t uSrcPort; + uint16_t uDstPort; + uint16_t uLen; + uint16_t uChecksum; +} TSTUDPHDR; + +typedef struct TSTDHCP4HDR +{ + uint8_t uOp; + uint8_t uHType; + uint8_t uHLen; + uint8_t uHops; + uint32_t uXid; + uint16_t uSecs; + uint16_t uFlags; + uint32_t uCiAddr; + uint32_t uYiAddr; + uint32_t uSiAddr; + uint32_t uGiAddr; + uint8_t abChAddr[16]; + uint8_t abSName[64]; + uint8_t abFile[128]; + uint32_t uCookie; +} TSTDHCP4HDR; +#pragma pack() + +typedef struct TSTDHCP4REPLY +{ + uint8_t uMsgType; + uint32_t uYiAddrBe; + uint32_t uServerIdBe; + uint32_t uXid; +} TSTDHCP4REPLY; +typedef TSTDHCP4REPLY *PTSTDHCP4REPLY; + +static uint32_t tstChecksumAdd(uint32_t uSum, const void *pv, size_t cb) +{ + const uint8_t *pb = (const uint8_t *)pv; + + while (cb > 1) + { + uSum += ((uint16_t)pb[0] << 8) | pb[1]; + pb += 2; + cb -= 2; + } + + if (cb) + uSum += ((uint16_t)pb[0] << 8); + + return uSum; +} + +static uint16_t tstChecksumFinish(uint32_t uSum) +{ + while (uSum >> 16) + uSum = (uSum & 0xffff) + (uSum >> 16); + return (uint16_t)~uSum; +} + +static int tstPktAppend(PTSTPKT pPkt, const void *pv, uint32_t cb) +{ + if (pPkt->cb + cb > sizeof(pPkt->ab)) + return VERR_BUFFER_OVERFLOW; + + memcpy(&pPkt->ab[pPkt->cb], pv, cb); + pPkt->cb += cb; + return VINF_SUCCESS; +} + +static int tstDhcp4AppendOpt(PTSTPKT pPkt, uint8_t uOpt, const void *pv, uint8_t cb) +{ + uint8_t abHdr[2]; + abHdr[0] = uOpt; + abHdr[1] = cb; + + int rc = tstPktAppend(pPkt, abHdr, sizeof(abHdr)); + if (RT_SUCCESS(rc)) + rc = tstPktAppend(pPkt, pv, cb); + return rc; +} + +static int tstDhcp4AppendOptU32(PTSTPKT pPkt, uint8_t uOpt, uint32_t uValueBe) +{ + return tstDhcp4AppendOpt(pPkt, uOpt, &uValueBe, sizeof(uValueBe)); +} + +static int tstBuildDhcp4(PTSTPKT pDhcp, + uint8_t uMsgType, + uint32_t uXid, + PCRTMAC pMac, + uint32_t uRequestedIpBe, + uint32_t uServerIdBe, + bool fBadCookie) +{ + RT_ZERO(*pDhcp); + pDhcp->cb = sizeof(TSTDHCP4HDR); + + TSTDHCP4HDR *pHdr = (TSTDHCP4HDR *)&pDhcp->ab[0]; + + pHdr->uOp = 1; /* BOOTREQUEST */ + pHdr->uHType = 1; /* Ethernet */ + pHdr->uHLen = 6; + pHdr->uXid = tstH2N32(uXid); + pHdr->uFlags = tstH2N16(0x8000); + pHdr->uCookie = fBadCookie ? tstH2N32(0xdeadbeef) : tstH2N32(0x63825363); + memcpy(pHdr->abChAddr, pMac, sizeof(*pMac)); + + uint8_t b = uMsgType; + int rc = tstDhcp4AppendOpt(pDhcp, 53, &b, sizeof(b)); + if (RT_FAILURE(rc)) + return rc; + + uint8_t abClientId[7]; + abClientId[0] = 1; + memcpy(&abClientId[1], pMac, sizeof(*pMac)); + rc = tstDhcp4AppendOpt(pDhcp, 61, abClientId, sizeof(abClientId)); + if (RT_FAILURE(rc)) + return rc; + + { + uint8_t abPrl[] = { 1, 3, 6, 51, 54 }; + rc = tstDhcp4AppendOpt(pDhcp, 55, abPrl, sizeof(abPrl)); + if (RT_FAILURE(rc)) + return rc; + } + + if (uRequestedIpBe) + { + rc = tstDhcp4AppendOptU32(pDhcp, 50, uRequestedIpBe); + if (RT_FAILURE(rc)) + return rc; + } + + if (uServerIdBe) + { + rc = tstDhcp4AppendOptU32(pDhcp, 54, uServerIdBe); + if (RT_FAILURE(rc)) + return rc; + } + + b = 255; + rc = tstPktAppend(pDhcp, &b, sizeof(b)); + if (RT_FAILURE(rc)) + return rc; + + while (pDhcp->cb < 300) + { + b = 0; + rc = tstPktAppend(pDhcp, &b, sizeof(b)); + if (RT_FAILURE(rc)) + return rc; + } + + return VINF_SUCCESS; +} + +static int tstBuildUdp4Frame(PTSTPKT pFrame, + PCRTMAC pSrcMac, + uint32_t uSrcIpBe, + uint32_t uDstIpBe, + uint16_t uSrcPort, + uint16_t uDstPort, + PCTSTPKT pPayload) +{ + static const uint8_t s_abBcast[6] = { 0xff, 0xff, 0xff, 0xff, 0xff, 0xff }; + + RT_ZERO(*pFrame); + + TSTETHHDR Eth; + memcpy(Eth.abDst, s_abBcast, sizeof(Eth.abDst)); + memcpy(Eth.abSrc, pSrcMac, sizeof(Eth.abSrc)); + Eth.uType = tstH2N16(0x0800); + + int rc = tstPktAppend(pFrame, &Eth, sizeof(Eth)); + if (RT_FAILURE(rc)) + return rc; + + TSTIP4HDR Ip; + RT_ZERO(Ip); + Ip.uVerIhl = 0x45; + Ip.uLen = tstH2N16((uint16_t)(sizeof(TSTIP4HDR) + sizeof(TSTUDPHDR) + pPayload->cb)); + Ip.uId = tstH2N16((uint16_t)RTRandU32()); + Ip.uTtl = 64; + Ip.uProto = 17; + Ip.uSrc = uSrcIpBe; + Ip.uDst = uDstIpBe; + Ip.uChecksum = tstH2N16(tstChecksumFinish(tstChecksumAdd(0, &Ip, sizeof(Ip)))); + + rc = tstPktAppend(pFrame, &Ip, sizeof(Ip)); + if (RT_FAILURE(rc)) + return rc; + + TSTUDPHDR Udp; + RT_ZERO(Udp); + Udp.uSrcPort = tstH2N16(uSrcPort); + Udp.uDstPort = tstH2N16(uDstPort); + Udp.uLen = tstH2N16((uint16_t)(sizeof(TSTUDPHDR) + pPayload->cb)); + Udp.uChecksum = 0; /* legal for IPv4 */ + + rc = tstPktAppend(pFrame, &Udp, sizeof(Udp)); + if (RT_FAILURE(rc)) + return rc; + + return tstPktAppend(pFrame, pPayload->ab, pPayload->cb); +} + +static int tstBuildDhcp6SolicitFrame(PTSTPKT pFrame, PCRTMAC pSrcMac) +{ + static const uint8_t s_abDstMac[6] = { 0x33, 0x33, 0x00, 0x01, 0x00, 0x02 }; + static const uint8_t s_abSrcIp[16] = { 0xfe, 0x80, 0, 0, 0, 0, 0, 0, + 0x02, 0x00, 0x27, 0xff, 0xfe, 0x12, 0x34, 0x56 }; + static const uint8_t s_abDstIp[16] = { 0xff, 0x02, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0x01, 0x02 }; + + RT_ZERO(*pFrame); + + TSTETHHDR Eth; + memcpy(Eth.abDst, s_abDstMac, sizeof(Eth.abDst)); + memcpy(Eth.abSrc, pSrcMac, sizeof(Eth.abSrc)); + Eth.uType = tstH2N16(0x86dd); + + int rc = tstPktAppend(pFrame, &Eth, sizeof(Eth)); + if (RT_FAILURE(rc)) + return rc; + + TSTIP6HDR Ip6; + RT_ZERO(Ip6); + Ip6.uVerTcFl = tstH2N32(6U << 28); + Ip6.uPayloadLen = tstH2N16((uint16_t)(sizeof(TSTUDPHDR) + 4)); + Ip6.uNextHeader = 17; + Ip6.uHopLimit = 1; + memcpy(Ip6.abSrc, s_abSrcIp, sizeof(Ip6.abSrc)); + memcpy(Ip6.abDst, s_abDstIp, sizeof(Ip6.abDst)); + + rc = tstPktAppend(pFrame, &Ip6, sizeof(Ip6)); + if (RT_FAILURE(rc)) + return rc; + + TSTUDPHDR Udp; + RT_ZERO(Udp); + Udp.uSrcPort = tstH2N16(546); + Udp.uDstPort = tstH2N16(547); + Udp.uLen = tstH2N16((uint16_t)(sizeof(TSTUDPHDR) + 4)); + + uint8_t abDhcp6[4]; + abDhcp6[0] = 1; /* SOLICIT */ + abDhcp6[1] = 0x12; + abDhcp6[2] = 0x34; + abDhcp6[3] = 0x56; + + uint32_t uSum = 0; + uint32_t uLenBe = tstH2N32(sizeof(TSTUDPHDR) + sizeof(abDhcp6)); + uint8_t abNextHdr[4] = { 0, 0, 0, 17 }; + + uSum = tstChecksumAdd(uSum, s_abSrcIp, sizeof(s_abSrcIp)); + uSum = tstChecksumAdd(uSum, s_abDstIp, sizeof(s_abDstIp)); + uSum = tstChecksumAdd(uSum, &uLenBe, sizeof(uLenBe)); + uSum = tstChecksumAdd(uSum, abNextHdr, sizeof(abNextHdr)); + uSum = tstChecksumAdd(uSum, &Udp, sizeof(Udp)); + uSum = tstChecksumAdd(uSum, abDhcp6, sizeof(abDhcp6)); + + uint16_t uCsum = tstChecksumFinish(uSum); + if (uCsum == 0) + uCsum = 0xffff; + Udp.uChecksum = tstH2N16(uCsum); + + rc = tstPktAppend(pFrame, &Udp, sizeof(Udp)); + if (RT_FAILURE(rc)) + return rc; + + return tstPktAppend(pFrame, abDhcp6, sizeof(abDhcp6)); +} + +static DECLCALLBACK(void) tstClientRx(void *pvUser, void *pvFrame, uint32_t cbFrame) +{ + PTSTCLIENT pThis = (PTSTCLIENT)pvUser; + + if (cbFrame > TST_MAX_FRAME) + return; + + RTCritSectEnter(&pThis->CritSect); + + if (pThis->cFrames == TST_MAX_RX_FRAMES) + { + pThis->iRead = (pThis->iRead + 1) % TST_MAX_RX_FRAMES; + pThis->cFrames--; + } + + PTSTRXFRAME pDst = &pThis->aFrames[pThis->iWrite]; + memcpy(pDst->ab, pvFrame, cbFrame); + pDst->cb = cbFrame; + + pThis->iWrite = (pThis->iWrite + 1) % TST_MAX_RX_FRAMES; + pThis->cFrames++; + + RTCritSectLeave(&pThis->CritSect); + RTSemEventSignal(pThis->hEvtRx); +} + +static DECLCALLBACK(int) tstClientThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PTSTCLIENT pThis = (PTSTCLIENT)pvUser; + int const rc = IntNetR3IfPumpPkts(pThis->hIf, tstClientRx, pThis, NULL, NULL); + ASMAtomicWriteS32(&pThis->rcThread, rc); + RTSemEventSignal(pThis->hEvtRx); + return rc; +} + +static void tstClientDestroy(PTSTCLIENT pThis); + +static int tstClientInit(PTSTCLIENT pThis, const char *pszNetwork) +{ + RT_ZERO(*pThis); + pThis->hThread = NIL_RTTHREAD; + pThis->hEvtRx = NIL_RTSEMEVENT; + pThis->rcThread = VINF_SUCCESS; + + int rc = RTCritSectInit(&pThis->CritSect); + if (RT_FAILURE(rc)) + return rc; + + rc = RTSemEventCreate(&pThis->hEvtRx); + if (RT_FAILURE(rc)) + { + tstClientDestroy(pThis); + return rc; + } + + rc = IntNetR3IfCreateEx(&pThis->hIf, pszNetwork, kIntNetTrunkType_WhateverNone, "", _256K, _256K, 0); + if (RT_FAILURE(rc)) + { + tstClientDestroy(pThis); + return rc; + } + + rc = IntNetR3IfSetActive(pThis->hIf, true); + if (RT_FAILURE(rc)) + { + tstClientDestroy(pThis); + return rc; + } + + rc = RTThreadCreate(&pThis->hThread, + tstClientThread, + pThis, + 0, + RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, + "DhcpCli"); + if (RT_FAILURE(rc)) + tstClientDestroy(pThis); + return rc; +} + +static void tstClientDestroy(PTSTCLIENT pThis) +{ + if (pThis->hIf != NULL) + { + int const rc = IntNetR3IfWaitAbort(pThis->hIf); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Aborting the test client receive wait failed: %Rrc", rc); + } + + if (pThis->hThread != NIL_RTTHREAD) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rc = RTThreadWait(pThis->hThread, 30000, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Joining the test client receive thread failed: %Rrc", rc); + return; + } + pThis->hThread = NIL_RTTHREAD; + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Test client receive thread returned %Rrc, expected %Rrc", + rcThread, VERR_SEM_DESTROYED); + } + + if (pThis->hIf != NULL) + { + IntNetR3IfSetActive(pThis->hIf, false); + IntNetR3IfDestroy(pThis->hIf); + pThis->hIf = NULL; + } + + if (pThis->hEvtRx != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pThis->hEvtRx); + pThis->hEvtRx = NIL_RTSEMEVENT; + } + + if (pThis->CritSect.u32Magic == RTCRITSECT_MAGIC) + RTCritSectDelete(&pThis->CritSect); +} + +static bool tstClientPopFrame(PTSTCLIENT pThis, PTSTRXFRAME pFrame) +{ + bool fHave = false; + + RTCritSectEnter(&pThis->CritSect); + + if (pThis->cFrames > 0) + { + *pFrame = pThis->aFrames[pThis->iRead]; + pThis->iRead = (pThis->iRead + 1) % TST_MAX_RX_FRAMES; + pThis->cFrames--; + fHave = true; + } + + RTCritSectLeave(&pThis->CritSect); + return fHave; +} + +static uint32_t tstClientQueuedFrameCount(PTSTCLIENT pThis) +{ + RTCritSectEnter(&pThis->CritSect); + uint32_t const cFrames = pThis->cFrames; + RTCritSectLeave(&pThis->CritSect); + return cFrames; +} + +static int tstClientSendFrame(PTSTCLIENT pThis, PCTSTPKT pFrame) +{ + INTNETFRAME Frame; + int rc = IntNetR3IfQueryOutputFrame(pThis->hIf, pFrame->cb, &Frame); + if (RT_FAILURE(rc)) + return rc; + + memcpy(Frame.pvFrame, pFrame->ab, pFrame->cb); + return IntNetR3IfOutputFrameCommit(pThis->hIf, &Frame); +} + +static bool tstParseDhcp4Reply(const uint8_t *pbFrame, uint32_t cbFrame, uint32_t uWantXid, PTSTDHCP4REPLY pReply) +{ + if (cbFrame < sizeof(TSTETHHDR) + sizeof(TSTIP4HDR) + sizeof(TSTUDPHDR) + sizeof(TSTDHCP4HDR)) + return false; + + const TSTETHHDR *pEth = (const TSTETHHDR *)pbFrame; + if (RT_N2H_U16(pEth->uType) != 0x0800) + return false; + + const TSTIP4HDR *pIp = (const TSTIP4HDR *)(pbFrame + sizeof(TSTETHHDR)); + if ((pIp->uVerIhl >> 4) != 4 || pIp->uProto != 17) + return false; + + uint32_t cbIp = (pIp->uVerIhl & 0x0f) * 4; + if (cbIp < sizeof(TSTIP4HDR)) + return false; + + if (cbFrame < sizeof(TSTETHHDR) + cbIp + sizeof(TSTUDPHDR) + sizeof(TSTDHCP4HDR)) + return false; + + const TSTUDPHDR *pUdp = (const TSTUDPHDR *)(pbFrame + sizeof(TSTETHHDR) + cbIp); + if (RT_N2H_U16(pUdp->uSrcPort) != 67 || RT_N2H_U16(pUdp->uDstPort) != 68) + return false; + + const TSTDHCP4HDR *pDhcp = (const TSTDHCP4HDR *)(pbFrame + sizeof(TSTETHHDR) + cbIp + sizeof(TSTUDPHDR)); + + if (pDhcp->uOp != 2) + return false; + + uint32_t uXid = RT_N2H_U32(pDhcp->uXid); + if (uWantXid != TST_ANY_XID && uXid != uWantXid) + return false; + + if (RT_N2H_U32(pDhcp->uCookie) != 0x63825363) + return false; + + uint8_t uMsgType = 0; + uint32_t uServerIdBe = 0; + + const uint8_t *pbOpt = (const uint8_t *)(pDhcp + 1); + const uint8_t *pbEnd = pbFrame + cbFrame; + + while (pbOpt < pbEnd) + { + uint8_t uOpt = *pbOpt++; + + if (uOpt == 255) + break; + if (uOpt == 0) + continue; + + if (pbOpt >= pbEnd) + return false; + + uint8_t cbOpt = *pbOpt++; + if (pbOpt + cbOpt > pbEnd) + return false; + + if (uOpt == 53 && cbOpt == 1) + uMsgType = pbOpt[0]; + else if (uOpt == 54 && cbOpt == 4) + memcpy(&uServerIdBe, pbOpt, sizeof(uServerIdBe)); + + pbOpt += cbOpt; + } + + if (uMsgType == 0) + return false; + + pReply->uMsgType = uMsgType; + pReply->uYiAddrBe = pDhcp->uYiAddr; + pReply->uServerIdBe = uServerIdBe; + pReply->uXid = uXid; + return true; +} + +static bool tstFrameIsDhcp6Reply(const uint8_t *pbFrame, uint32_t cbFrame) +{ + if (cbFrame < sizeof(TSTETHHDR) + sizeof(TSTIP6HDR) + sizeof(TSTUDPHDR) + 4) + return false; + + const TSTETHHDR *pEth = (const TSTETHHDR *)pbFrame; + if (RT_N2H_U16(pEth->uType) != 0x86dd) + return false; + + const TSTIP6HDR *pIp6 = (const TSTIP6HDR *)(pbFrame + sizeof(TSTETHHDR)); + if ((RT_N2H_U32(pIp6->uVerTcFl) >> 28) != 6 || pIp6->uNextHeader != 17) + return false; + + const TSTUDPHDR *pUdp = (const TSTUDPHDR *)(pbFrame + sizeof(TSTETHHDR) + sizeof(TSTIP6HDR)); + return RT_N2H_U16(pUdp->uSrcPort) == 547 && RT_N2H_U16(pUdp->uDstPort) == 546; +} + +static bool tstClientWaitForDhcp4(PTSTCLIENT pClient, + uint32_t uXid, + uint8_t uMsgType, + PTSTDHCP4REPLY pReply, + uint32_t cMs) +{ + uint64_t uDeadline = RTTimeMilliTS() + cMs; + + while (RTTimeMilliTS() < uDeadline) + { + TSTRXFRAME Frame; + if (tstClientPopFrame(pClient, &Frame)) + { + TSTDHCP4REPLY Reply; + if (tstParseDhcp4Reply(Frame.ab, Frame.cb, uXid, &Reply) && Reply.uMsgType == uMsgType) + { + if (pReply) + *pReply = Reply; + return true; + } + } + else + RTSemEventWait(pClient->hEvtRx, 50); + } + + return false; +} + +static bool tstClientWaitForDhcp6Reply(PTSTCLIENT pClient, uint32_t cMs) +{ + uint64_t uDeadline = RTTimeMilliTS() + cMs; + + while (RTTimeMilliTS() < uDeadline) + { + TSTRXFRAME Frame; + if (tstClientPopFrame(pClient, &Frame)) + { + if (tstFrameIsDhcp6Reply(Frame.ab, Frame.cb)) + return true; + } + else + RTSemEventWait(pClient->hEvtRx, 50); + } + + return false; +} + +static int tstDhcp4DiscoverOnce(PTSTCLIENT pClient, + PCRTMAC pMac, + uint32_t uXid, + PTSTDHCP4REPLY pOffer, + uint32_t cMs) +{ + TSTPKT Dhcp; + TSTPKT Frame; + + int rc = tstBuildDhcp4(&Dhcp, DHCP4_DISCOVER, uXid, pMac, 0, 0, false); + if (RT_FAILURE(rc)) + return rc; + + rc = tstBuildUdp4Frame(&Frame, pMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); + if (RT_FAILURE(rc)) + return rc; + + rc = tstClientSendFrame(pClient, &Frame); + if (RT_FAILURE(rc)) + return rc; + + return tstClientWaitForDhcp4(pClient, uXid, DHCP4_OFFER, pOffer, cMs) ? VINF_SUCCESS : VERR_TIMEOUT; +} + +static bool tstDhcp4Discover(PTSTCLIENT pClient, PCRTMAC pMac, PTSTDHCP4REPLY pOffer) +{ + /* Retransmissions belong to one DHCP transaction, so delayed replies from + an earlier attempt must remain acceptable on a loaded testbox. */ + uint32_t const uXid = RTRandU32(); + for (unsigned i = 0; i < 8; i++) + { + int const rc = tstDhcp4DiscoverOnce(pClient, pMac, uXid, pOffer, 600); + if (RT_SUCCESS(rc)) + return true; + if (rc != VERR_TIMEOUT) + { + RTTestFailed(g_hTest, "Sending DHCPDISCOVER for %RTmac (xid %#RX32) failed: %Rrc", pMac, uXid, rc); + return false; + } + RTThreadSleep(100); + } + RTTestFailed(g_hTest, "No DHCPOFFER for %RTmac (xid %#RX32) after 8 attempts", pMac, uXid); + RTTestFailureDetails(g_hTest, "client receive status: %Rrc; queued frames: %RU32\n", + ASMAtomicReadS32(&pClient->rcThread), tstClientQueuedFrameCount(pClient)); + return false; +} + +static bool tstDhcp4Request(PTSTCLIENT pClient, + PCRTMAC pMac, + const TSTDHCP4REPLY *pOffer, + PTSTDHCP4REPLY pAck) +{ + /* A DHCPREQUEST selecting an offer continues the DISCOVER transaction. */ + uint32_t const uXid = pOffer->uXid; + TSTPKT Dhcp; + TSTPKT Frame; + + int rc = tstBuildDhcp4(&Dhcp, + DHCP4_REQUEST, + uXid, + pMac, + pOffer->uYiAddrBe, + pOffer->uServerIdBe, + false); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building DHCPREQUEST for %RTmac failed: %Rrc", pMac, rc); + return false; + } + + rc = tstBuildUdp4Frame(&Frame, pMac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building DHCPREQUEST frame for %RTmac failed: %Rrc", pMac, rc); + return false; + } + + rc = tstClientSendFrame(pClient, &Frame); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Sending DHCPREQUEST for %RTmac (xid %#RX32) failed: %Rrc", pMac, uXid, rc); + return false; + } + + if (tstClientWaitForDhcp4(pClient, uXid, DHCP4_ACK, pAck, TST_DHCP_TIMEOUT_MS)) + return true; + + RTTestFailed(g_hTest, "No DHCPACK for %RTmac (xid %#RX32, requested %RTnaipv4)", + pMac, uXid, pOffer->uYiAddrBe); + RTTestFailureDetails(g_hTest, "client receive status: %Rrc; queued frames: %RU32\n", + ASMAtomicReadS32(&pClient->rcThread), tstClientQueuedFrameCount(pClient)); + return false; +} + +static bool tstIPv4InPool(uint32_t uIpBe, uint8_t uFirst, uint8_t uLast) +{ + uint32_t uIp = RT_N2H_U32(uIpBe); + uint32_t uLo = RT_N2H_U32(tstIPv4(10,37,0,uFirst)); + uint32_t uHi = RT_N2H_U32(tstIPv4(10,37,0,uLast)); + + return uIp >= uLo && uIp <= uHi; +} + +static bool tstSendBadCookieNoReply(PTSTCLIENT pClient) +{ + static const RTMAC s_Mac = { { 0x08, 0x00, 0x27, 0xde, 0xad, 0x01 } }; + + uint32_t uXid = RTRandU32(); + TSTPKT Dhcp; + TSTPKT Frame; + + int rc = tstBuildDhcp4(&Dhcp, DHCP4_DISCOVER, uXid, &s_Mac, 0, 0, true); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building bad-cookie DHCPDISCOVER failed: %Rrc", rc); + return false; + } + + rc = tstBuildUdp4Frame(&Frame, &s_Mac, tstIPv4(0,0,0,0), tstIPv4(255,255,255,255), 68, 67, &Dhcp); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building bad-cookie DHCPDISCOVER frame failed: %Rrc", rc); + return false; + } + + rc = tstClientSendFrame(pClient, &Frame); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Sending bad-cookie DHCPDISCOVER failed: %Rrc", rc); + return false; + } + + if (tstClientWaitForDhcp4(pClient, uXid, DHCP4_OFFER, NULL, 750)) + { + RTTestFailed(g_hTest, "Server replied to bad-cookie DHCPDISCOVER (xid %#RX32)", uXid); + return false; + } + + int const rcThread = ASMAtomicReadS32(&pClient->rcThread); + if (RT_FAILURE(rcThread)) + { + RTTestFailed(g_hTest, "Client receive thread failed during bad-cookie test: %Rrc", rcThread); + return false; + } + return true; +} + +static bool tstSendDhcp6SolicitNoReply(PTSTCLIENT pClient) +{ + static const RTMAC s_Mac = { { 0x08, 0x00, 0x27, 0x66, 0x66, 0x66 } }; + + TSTPKT Frame; + int rc = tstBuildDhcp6SolicitFrame(&Frame, &s_Mac); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Building DHCPv6 SOLICIT frame failed: %Rrc", rc); + return false; + } + + rc = tstClientSendFrame(pClient, &Frame); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Sending DHCPv6 SOLICIT failed: %Rrc", rc); + return false; + } + + if (tstClientWaitForDhcp6Reply(pClient, 1000)) + { + RTTestFailed(g_hTest, "IPv4-only server unexpectedly replied to DHCPv6 SOLICIT"); + return false; + } + + int const rcThread = ASMAtomicReadS32(&pClient->rcThread); + if (RT_FAILURE(rcThread)) + { + RTTestFailed(g_hTest, "Client receive thread failed during DHCPv6 test: %Rrc", rcThread); + return false; + } + return true; +} + +static bool tstLeaseClient(PTSTCLIENT pClient, + PCRTMAC pMac, + PTSTDHCP4REPLY pAck) +{ + TSTDHCP4REPLY Offer; + if (!tstDhcp4Discover(pClient, pMac, &Offer)) + return false; + if (!tstIPv4InPool(Offer.uYiAddrBe, 10, 12)) + { + RTTestFailed(g_hTest, "DHCPOFFER for %RTmac contains out-of-pool address %RTnaipv4", pMac, Offer.uYiAddrBe); + return false; + } + if (Offer.uServerIdBe != tstIPv4(10,37,0,1)) + { + RTTestFailed(g_hTest, "DHCPOFFER for %RTmac has server ID %RTnaipv4, expected 10.37.0.1", pMac, Offer.uServerIdBe); + return false; + } + if (!tstDhcp4Request(pClient, pMac, &Offer, pAck)) + return false; + if (pAck->uYiAddrBe != Offer.uYiAddrBe) + { + RTTestFailed(g_hTest, "DHCPACK for %RTmac assigns %RTnaipv4, offered %RTnaipv4", + pMac, pAck->uYiAddrBe, Offer.uYiAddrBe); + return false; + } + return true; +} + +static bool tstStartInProcessServer(char *pszNetwork, size_t cbNetwork, PTSTCLIENT pClient, void **ppvDhcpd) +{ + char szCfg[RTPATH_MAX]; + + int rc = tstMakeUuidName("VBoxNetDhcpdWire", pszNetwork, cbNetwork); + if (RT_FAILURE(rc)) + return false; + + rc = tstWriteConfigFile(pszNetwork, + "10.37.0.1", + "255.255.255.0", + "10.37.0.10", + "10.37.0.12", + szCfg, + sizeof(szCfg)); + if (RT_FAILURE(rc)) + return false; + + char *argv[5]; + argv[0] = (char *)"VBoxNetDHCP-inproc"; + argv[1] = (char *)"--config"; + argv[2] = szCfg; + argv[3] = (char *)"--comment"; + argv[4] = pszNetwork; + + rc = VBoxNetDhcpdTestStart(5, argv, ppvDhcpd); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "VBoxNetDhcpdTestStart failed: %Rrc", rc); + return false; + } + + rc = tstClientInit(pClient, pszNetwork); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "test client IntNet attach failed: %Rrc", rc); + tstClientDestroy(pClient); + return false; + } + + return true; +} + +static void tstWireDhcp(void) +{ + RTTestSub(g_hTest, "in-process daemon setup"); + + char szNetwork[128]; + TSTCLIENT Client; + void *pvDhcpd = NULL; + + RT_ZERO(Client); + + if (!tstStartInProcessServer(szNetwork, sizeof(szNetwork), &Client, &pvDhcpd)) + { + if (pvDhcpd) + VBoxNetDhcpdTestStop(pvDhcpd); + return; + } + + if (!VBoxNetDhcpdTestIsRunning(pvDhcpd)) + RTTestFailed(g_hTest, "VBoxNetDHCP stopped after reporting successful startup"); + + static const RTMAC s_Mac1 = { { 0x08, 0x00, 0x27, 0x12, 0x34, 0x56 } }; + static const RTMAC s_Mac2 = { { 0x08, 0x00, 0x27, 0x00, 0x00, 0x02 } }; + static const RTMAC s_Mac3 = { { 0x08, 0x00, 0x27, 0x00, 0x00, 0x03 } }; + static const RTMAC s_Mac4 = { { 0x08, 0x00, 0x27, 0x00, 0x00, 0x04 } }; + + TSTDHCP4REPLY Ack1; + TSTDHCP4REPLY Ack1Again; + TSTDHCP4REPLY Ack2; + TSTDHCP4REPLY Ack3; + TSTDHCP4REPLY Offer4; + + RT_ZERO(Ack1); + RT_ZERO(Ack1Again); + RT_ZERO(Ack2); + RT_ZERO(Ack3); + RT_ZERO(Offer4); + + RTTestSub(g_hTest, "first client lease"); + bool const fAck1 = tstLeaseClient(&Client, &s_Mac1, &Ack1); + + RTTestSub(g_hTest, "first client lease reuse"); + bool fAck1Again = false; + if (fAck1) + { + fAck1Again = tstLeaseClient(&Client, &s_Mac1, &Ack1Again); + if (fAck1Again && Ack1Again.uYiAddrBe != Ack1.uYiAddrBe) + RTTestFailed(g_hTest, "Repeated lease changed from %RTnaipv4 to %RTnaipv4", + Ack1.uYiAddrBe, Ack1Again.uYiAddrBe); + } + else + RTTestSkipped(g_hTest, "Initial lease failed"); + + RTTestSub(g_hTest, "invalid DHCP cookie rejection"); + tstSendBadCookieNoReply(&Client); + + RTTestSub(g_hTest, "DHCPv6 packet rejection"); + tstSendDhcp6SolicitNoReply(&Client); + + RTTestSub(g_hTest, "second client lease"); + bool const fAck2 = tstLeaseClient(&Client, &s_Mac2, &Ack2); + + RTTestSub(g_hTest, "third client lease"); + bool const fAck3 = tstLeaseClient(&Client, &s_Mac3, &Ack3); + + RTTestSub(g_hTest, "unique client leases"); + if (fAck1 && fAck2 && fAck3) + { + if (Ack1.uYiAddrBe == Ack2.uYiAddrBe) + RTTestFailed(g_hTest, "First and second clients both received %RTnaipv4", Ack1.uYiAddrBe); + if (Ack1.uYiAddrBe == Ack3.uYiAddrBe) + RTTestFailed(g_hTest, "First and third clients both received %RTnaipv4", Ack1.uYiAddrBe); + if (Ack2.uYiAddrBe == Ack3.uYiAddrBe) + RTTestFailed(g_hTest, "Second and third clients both received %RTnaipv4", Ack2.uYiAddrBe); + } + else + RTTestSkipped(g_hTest, "One or more prerequisite lease exchanges failed"); + + RTTestSub(g_hTest, "exhausted address pool"); + if (fAck1 && fAck2 && fAck3) + { + uint32_t const uXid = RTRandU32(); + int const rc = tstDhcp4DiscoverOnce(&Client, &s_Mac4, uXid, &Offer4, 1000); + if (RT_SUCCESS(rc)) + RTTestFailed(g_hTest, "Exhausted pool offered %RTnaipv4 to fourth client (xid %#RX32)", + Offer4.uYiAddrBe, uXid); + else if (rc != VERR_TIMEOUT) + RTTestFailed(g_hTest, "Fourth-client DHCPDISCOVER failed with %Rrc instead of timing out", rc); + else + { + int const rcThread = ASMAtomicReadS32(&Client.rcThread); + if (RT_FAILURE(rcThread)) + RTTestFailed(g_hTest, "Client receive thread failed while checking pool exhaustion: %Rrc", rcThread); + } + } + else + RTTestSkipped(g_hTest, "Address pool was not successfully filled"); + + RTTestSub(g_hTest, "in-process daemon teardown"); + int const rcClientBeforeTeardown = ASMAtomicReadS32(&Client.rcThread); + tstClientDestroy(&Client); + + int rc = VBoxNetDhcpdTestStop(pvDhcpd); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "VBoxNetDhcpdTestStop failed: %Rrc", rc); + RTTestFailureDetails(g_hTest, "client receive status before teardown: %Rrc; after teardown: %Rrc\n", + rcClientBeforeTeardown, ASMAtomicReadS32(&Client.rcThread)); + } +} + +int main(int argc, char **argv) +{ + /* The embedded-switch build forces the driverless R3 path, so SUPLib must + not be initialized by the testcase process. */ + int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + RTTestInitAndCreate("tstVBoxNetDhcpd", &g_hTest); + if (g_hTest == NIL_RTTEST) + return 1; + + RTTestBanner(g_hTest); + + tstConfigValidation(); + /* Config::create() installs a process-global release logger. Destroy it before + the in-process daemon replaces it with its own logger. */ + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + + void *pvSwitch = NULL; + rc = VBoxIntNetSwitchTestStart(&pvSwitch); + if (RT_SUCCESS(rc)) + { + tstWireDhcp(); + rc = VBoxIntNetSwitchTestStop(pvSwitch); + TST_CHECK_RC_OK(rc); + } + else + RTTestFailed(g_hTest, "Embedded IntNet switch startup failed: %Rrc", rc); + + /* Destroy the release logger installed by the in-process daemon. */ + RTLogDestroy(RTLogRelSetDefaultInstance(NULL)); + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk b/src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk index d7718d3369ca..2e68157f32ff 100644 --- a/src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk +++ b/src/VBox/NetworkServices/IntNetSwitch/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114877 2026-08-06 21:53:20Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for the Ring-3 based network switch process. # @@ -31,7 +31,9 @@ include $(KBUILD_PATH)/subheader.kmk # # The internal network switch module. # -PROGRAMS += VBoxIntNetSwitch +if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) && ("$(KBUILD_TARGET)" == "darwin" || defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC)) + PROGRAMS += VBoxIntNetSwitch +endif VBoxIntNetSwitch_TEMPLATE := VBoxR3Exe ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING @@ -39,7 +41,9 @@ ifdef VBOX_WITH_AUTOMATIC_DEFS_QUOTING else VBoxIntNetSwitch_DEFS = KBUILD_TYPE=\"$(KBUILD_TYPE)\" endif -VBoxIntNetSwitch_DEFS += VBOX_WITH_INTNET_SERVICE_IN_R3 +VBoxIntNetSwitch_DEFS += \ + VBOX_WITH_INTNET_SERVICE_IN_R3 \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) VBoxIntNetSwitch_INST.darwin = $(VBoxIntNetSwitch.xpc_INST)/MacOS/ VBoxIntNetSwitch_SOURCES = \ VBoxIntNetSwitch.cpp \ diff --git a/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp b/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp index e8adafc59290..9af735fc934a 100644 --- a/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp +++ b/src/VBox/NetworkServices/IntNetSwitch/VBoxIntNetSwitch.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxIntNetSwitch.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxIntNetSwitch.cpp 114962 2026-08-10 15:04:45Z andreas.loeffler@oracle.com $ */ /** @file * Internal networking - Wrapper for the R0 network service. * @@ -34,26 +34,72 @@ *********************************************************************************************************************************/ #define IN_INTNET_TESTCASE #define IN_INTNET_R3 +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# if !defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) +# error "The local IPC R3 IntNet service implementation is not enabled!" +# endif +#endif #include "IntNetSwitchInternal.h" #include +#include #include #include #include +#include +#include +#include #include #include #include +#include #include #include #include #include -#include +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# include +# include +#else +# include +# include +# include +# include +# include +# include +#endif + +#ifndef RT_OS_WINDOWS +# include +#endif + +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +static int intnetR3LocalIpcSendPoke(struct SUPDRVSESSION *pSession); +#endif /********************************************************************************************************************************* * Defined Constants And Macros * *********************************************************************************************************************************/ +/** A client has registered an asynchronous receive wait. */ +#define INTNETR3RECVSTATE_F_WAITING RT_BIT_32(0) +/** Receive data arrived before a client registered a wait. */ +#define INTNETR3RECVSTATE_F_AVAILABLE RT_BIT_32(1) +/** The client aborted receive waits permanently for this interface session. */ +#define INTNETR3RECVSTATE_F_NO_MORE_WAITS RT_BIT_32(2) +/** Maximum combined IntNet shared-buffer allocation accepted over an R3 transport. */ +#define INTNETR3_MAX_BUFFER_SIZE UINT64_C(134217728) +/** Maximum number of active transport connections. */ +#define INTNETR3_MAX_CONNECTIONS UINT32_C(128) +/** Maximum number of local IPC request and notification worker threads. */ +#define INTNETR3_MAX_THREADS UINT32_C(256) +/** Number of worker threads reserved by each local IPC session. */ +#define INTNETR3_THREADS_PER_LOCALIPC_SESSION UINT32_C(2) +/** Maximum aggregate shared memory allocated by the service. */ +#define INTNETR3_MAX_AGGREGATE_SHMEM_SIZE UINT64_C(1073741824) +/** Number of fresh UUID names tried if a shared-memory object already exists. */ +#define INTNETR3_SHMEM_CREATE_RETRIES 16 /********************************************************************************************************************************* @@ -99,8 +145,20 @@ typedef struct SUPDRVUSAGE */ typedef struct SUPDRVDEVEXT { - /** Number of references to this service. */ + /** Number of active transport connections. */ uint32_t volatile cRefs; + /** Number of active local IPC worker threads. */ + uint32_t volatile cThreads; + /** Aggregate size of all shared-memory allocations. */ + uint64_t cbShMem; + /** Maximum number of active transport connections. */ + uint32_t cMaxConnections; + /** Maximum number of active local IPC worker threads. */ + uint32_t cMaxThreads; + /** Maximum aggregate size of all shared-memory allocations. */ + uint64_t cbMaxShMem; + /** Maximum time allowed to receive the first frame or complete a started frame. */ + uint32_t cMsReadTimeout; /** Critical section to serialize the initialization, usage counting and objects. */ RTCRITSECT CritSect; /** List of registered objects. Protected by the spinlock. */ @@ -109,6 +167,22 @@ typedef struct SUPDRVDEVEXT typedef SUPDRVDEVEXT *PSUPDRVDEVEXT; +typedef struct INTNETR3SHMEM +{ + struct INTNETR3SHMEM *pNext; + /** Original mapping address used as the allocation lookup key. */ + void *pvKey; + /** Mapping address while it still needs to be unmapped. */ + void *pv; + size_t cb; +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + RTSHMEM hShMem; + char szName[INTNET_R3_IPC_MAX_SHMEM_NAME]; +#endif +} INTNETR3SHMEM; +typedef INTNETR3SHMEM *PINTNETR3SHMEM; + + /** * Per session data. * This is mainly for memory tracking. @@ -118,21 +192,366 @@ typedef struct SUPDRVSESSION PSUPDRVDEVEXT pDevExt; /** List of generic usage records. (protected by SUPDRVDEVEXT::CritSect) */ PSUPDRVUSAGE volatile pUsage; +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) /** The XPC connection handle for this session. */ xpc_connection_t hXpcCon; +#else + /** Local IPC session handle for this session. */ + RTLOCALIPCSESSION hIpcSession; + /** Request worker thread serving this session. */ + RTTHREAD hThread; + /** Notification worker thread serving this session. */ + RTTHREAD hIpcPokeThread; + /** Wakes the notification worker without blocking an IntNet delivery callback. */ + RTSEMEVENT hIpcPokeEvt; + /** Whether the notification worker must stop. */ + bool volatile fIpcPokeStopping; + /** Serializes complete outbound IPC frames. */ + RTSEMMUTEX hIpcIoMtx; +#endif + /** Shared memory objects created for this session. */ + PINTNETR3SHMEM pShMemHead; /** The intnet interface handle to wait on. */ INTNETIFHANDLE hIfWait; - /** Flag whether a receive wait was initiated. */ - bool volatile fRecvWait; - /** Flag whether there is something to receive. */ - bool volatile fRecvAvail; + /** INTNETR3RECVSTATE_F_XXX state, updated as one atomic value to avoid lost wakeups. */ + uint32_t volatile fRecvState; } SUPDRVSESSION; +/** Request/reply scratch buffer large enough for all supported IntNet service requests. */ +typedef union INTNETR3REQREPLY +{ + INTNETOPENREQ OpenReq; + INTNETIFCLOSEREQ IfCloseReq; + INTNETIFGETBUFFERPTRSREQ IfGetBufferPtrsReq; + INTNETIFSETPROMISCUOUSMODEREQ IfSetPromiscuousModeReq; + INTNETIFSETMACADDRESSREQ IfSetMacAddressReq; + INTNETIFSETACTIVEREQ IfSetActiveReq; + INTNETIFSENDREQ IfSendReq; + INTNETIFWAITREQ IfWaitReq; + INTNETIFABORTWAITREQ IfAbortWaitReq; +} INTNETR3REQREPLY; +typedef INTNETR3REQREPLY *PINTNETR3REQREPLY; + + +/** Transport-independent result of processing an IntNet service request. */ +typedef struct INTNETR3REQRESULT +{ + /** Ring-3 buffer pointer for VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, NULL otherwise. */ + PINTNETBUF pRing3Buf; + /** Size of the request/reply payload to return for synchronous replies. */ + size_t cbReply; + /** Whether the transport should send a synchronous reply. */ + bool fSendReply; + /** Whether the transport should immediately emit a receive-available poke. */ + bool fSendPokeNow; +} INTNETR3REQRESULT; +typedef INTNETR3REQRESULT *PINTNETR3REQRESULT; + + +/********************************************************************************************************************************* +* Internal Helpers * +*********************************************************************************************************************************/ +static DECLCALLBACK(void) intnetR3RecvAvail(INTNETIFHANDLE hIf, void *pvUser); + + +/** Atomically reserves @a cSlots slots without exceeding @a cMax. */ +static bool intnetR3TryReserveSlots(uint32_t volatile *pcSlots, uint32_t cMax, uint32_t cSlots) +{ + AssertReturn(cSlots > 0, false); + + uint32_t cSlotsOld; + do + { + cSlotsOld = ASMAtomicReadU32(pcSlots); + if ( cSlotsOld > cMax + || cSlots > cMax - cSlotsOld) + return false; + } while (!ASMAtomicCmpXchgU32(pcSlots, cSlotsOld + cSlots, cSlotsOld)); + return true; +} + + +/** Atomically reserves one slot without exceeding @a cMax. */ +static bool intnetR3TryReserveSlot(uint32_t volatile *pcSlots, uint32_t cMax) +{ + return intnetR3TryReserveSlots(pcSlots, cMax, 1); +} + + +#ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH +/** Reads a positive testcase-only 32-bit limit override. */ +static uint32_t intnetR3TestGetLimitU32(const char *pszVar, uint32_t uDefault) +{ + const char *pszValue = RTEnvGet(pszVar); + if (pszValue && *pszValue) + { + uint32_t uValue = 0; + if ( RT_SUCCESS(RTStrToUInt32Full(pszValue, 10, &uValue)) + && uValue > 0) + return uValue; + } + return uDefault; +} + + +/** Reads a positive testcase-only 64-bit limit override. */ +static uint64_t intnetR3TestGetLimitU64(const char *pszVar, uint64_t uDefault) +{ + const char *pszValue = RTEnvGet(pszVar); + if (pszValue && *pszValue) + { + uint64_t uValue = 0; + if ( RT_SUCCESS(RTStrToUInt64Full(pszValue, 10, &uValue)) + && uValue > 0) + return uValue; + } + return uDefault; +} +#endif + + +/** Initializes service-wide resource accounting and limits. */ +static void intnetR3InitLimits(PSUPDRVDEVEXT pDevExt) +{ + pDevExt->cRefs = 0; + pDevExt->cThreads = 0; + pDevExt->cbShMem = 0; + pDevExt->cMaxConnections = INTNETR3_MAX_CONNECTIONS; + pDevExt->cMaxThreads = INTNETR3_MAX_THREADS; + pDevExt->cbMaxShMem = INTNETR3_MAX_AGGREGATE_SHMEM_SIZE; + pDevExt->cMsReadTimeout = INTNET_R3_IPC_FRAME_TIMEOUT_MS; +#ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + pDevExt->cMaxConnections = intnetR3TestGetLimitU32("VBOX_INTNET_R3_TEST_MAX_CONNECTIONS", + pDevExt->cMaxConnections); + pDevExt->cMaxThreads = intnetR3TestGetLimitU32("VBOX_INTNET_R3_TEST_MAX_THREADS", pDevExt->cMaxThreads); + pDevExt->cbMaxShMem = intnetR3TestGetLimitU64("VBOX_INTNET_R3_TEST_MAX_SHMEM", pDevExt->cbMaxShMem); + pDevExt->cMsReadTimeout = intnetR3TestGetLimitU32("VBOX_INTNET_R3_TEST_READ_TIMEOUT_MS", + pDevExt->cMsReadTimeout); +#endif +} + + +/** Reserves service-wide shared-memory quota. */ +static int intnetR3ReserveShMem(PSUPDRVDEVEXT pDevExt, size_t cb) +{ + AssertReturn(cb > 0, VERR_INVALID_PARAMETER); + + int rc = RTCritSectEnter(&pDevExt->CritSect); + if (RT_SUCCESS(rc)) + { + if ( pDevExt->cbShMem <= pDevExt->cbMaxShMem + && (uint64_t)cb <= pDevExt->cbMaxShMem - pDevExt->cbShMem) + pDevExt->cbShMem += cb; + else + rc = VERR_OUT_OF_RESOURCES; + int const rc2 = RTCritSectLeave(&pDevExt->CritSect); + AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); + } + return rc; +} + + +/** Releases service-wide shared-memory quota. */ +static void intnetR3ReleaseShMem(PSUPDRVDEVEXT pDevExt, size_t cb) +{ + int const rc = RTCritSectEnter(&pDevExt->CritSect); + AssertRC(rc); + if (RT_SUCCESS(rc)) + { + Assert(pDevExt->cbShMem >= cb); + if (pDevExt->cbShMem >= cb) + pDevExt->cbShMem -= cb; + else + pDevExt->cbShMem = pDevExt->cbMaxShMem; + int const rc2 = RTCritSectLeave(&pDevExt->CritSect); + AssertRC(rc2); + } +} + + +/** Validates and bounds the allocation implied by an IntNet open request. */ +static int intnetR3ValidateOpenBufferSizes(uint32_t cbSend, uint32_t cbRecv) +{ + uint64_t const cbMinRing = sizeof(INTNETHDR) * UINT64_C(4); + uint64_t const cbSendAligned = RT_ALIGN_64(RT_MAX((uint64_t)cbSend, cbMinRing), INTNETRINGBUF_ALIGNMENT); + uint64_t const cbRecvAligned = RT_ALIGN_64(RT_MAX((uint64_t)cbRecv, cbMinRing), INTNETRINGBUF_ALIGNMENT); + uint64_t const cbHdrAligned = RT_ALIGN_64(sizeof(INTNETBUF), INTNETRINGBUF_ALIGNMENT); + uint64_t const cbTotal = cbHdrAligned + cbSendAligned + cbRecvAligned; + return cbTotal <= INTNETR3_MAX_BUFFER_SIZE ? VINF_SUCCESS : VERR_OUT_OF_RANGE; +} + + +/** + * Transport-agnostic IntNet request processor used by both XPC (Darwin) and local IPC (non-Darwin). + * + * This helper validates the header, dispatches the request to the existing IntNetR3/R0 handlers, + * updates the in/out request buffer, and indicates whether a reply should be sent and/or a + * transport-specific poke should be emitted immediately. + * + * @returns VBox status code for the request processing. + * @param pSession The session context. + * @param uOperation The VMMR0_DO_INTNET_* operation. + * @param pReqReply In/out pointer to the request/reply union buffer. + * @param cbReqReply Size of the request buffer. + * @param pResult Transport-independent processing result. + */ +static int intnetR3ProcessRequestCore(PSUPDRVSESSION pSession, uint32_t uOperation, + PINTNETR3REQREPLY pReqReply, size_t cbReqReply, + PINTNETR3REQRESULT pResult) +{ + pResult->pRing3Buf = NULL; + pResult->cbReply = 0; + pResult->fSendReply = true; + pResult->fSendPokeNow = false; + + PSUPVMMR0REQHDR pReqHdr = (PSUPVMMR0REQHDR)pReqReply; + AssertReturn(pReqHdr->u32Magic == SUPVMMR0REQHDR_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pReqHdr->cbReq == cbReqReply, VERR_INVALID_PARAMETER); + + int rc = VERR_INVALID_PARAMETER; + + switch (uOperation) + { + case VMMR0_DO_INTNET_OPEN: + if (cbReqReply == sizeof(INTNETOPENREQ)) + { + PINTNETOPENREQ p = &pReqReply->OpenReq; + rc = intnetR3ValidateOpenBufferSizes(p->cbSend, p->cbRecv); + if (RT_SUCCESS(rc)) + rc = IntNetR3Open(pSession, &p->szNetwork[0], p->enmTrunkType, p->szTrunk, + p->fFlags, p->cbSend, p->cbRecv, intnetR3RecvAvail, pSession, &p->hIf); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_CLOSE: + if (cbReqReply == sizeof(INTNETIFCLOSEREQ)) + { + PINTNETIFCLOSEREQ p = &pReqReply->IfCloseReq; + rc = IntNetR0IfCloseReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS: + if (cbReqReply == sizeof(INTNETIFGETBUFFERPTRSREQ)) + { + PINTNETIFGETBUFFERPTRSREQ p = &pReqReply->IfGetBufferPtrsReq; + rc = IntNetR0IfGetBufferPtrsReq(pSession, p); + if (RT_SUCCESS(rc)) + pResult->pRing3Buf = p->pRing3Buf; + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE: + if (cbReqReply == sizeof(INTNETIFSETPROMISCUOUSMODEREQ)) + { + PINTNETIFSETPROMISCUOUSMODEREQ p = &pReqReply->IfSetPromiscuousModeReq; + rc = IntNetR0IfSetPromiscuousModeReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS: + if (cbReqReply == sizeof(INTNETIFSETMACADDRESSREQ)) + { + PINTNETIFSETMACADDRESSREQ p = &pReqReply->IfSetMacAddressReq; + rc = IntNetR0IfSetMacAddressReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_SET_ACTIVE: + if (cbReqReply == sizeof(INTNETIFSETACTIVEREQ)) + { + PINTNETIFSETACTIVEREQ p = &pReqReply->IfSetActiveReq; + rc = IntNetR0IfSetActiveReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_SEND: + if (cbReqReply == sizeof(INTNETIFSENDREQ)) + { + PINTNETIFSENDREQ p = &pReqReply->IfSendReq; + rc = IntNetR0IfSendReq(pSession, p); + pResult->cbReply = sizeof(*p); + } + break; + + case VMMR0_DO_INTNET_IF_WAIT: + if (cbReqReply == sizeof(INTNETIFWAITREQ)) + { + uint32_t fOld; + uint32_t fNew; + do + { + fOld = ASMAtomicReadU32(&pSession->fRecvState); + if (fOld & INTNETR3RECVSTATE_F_NO_MORE_WAITS) + fNew = fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE); + else if (fOld & INTNETR3RECVSTATE_F_AVAILABLE) + fNew = fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE); + else + fNew = fOld | INTNETR3RECVSTATE_F_WAITING; + } while (!ASMAtomicCmpXchgU32(&pSession->fRecvState, fNew, fOld)); + if (fOld & (INTNETR3RECVSTATE_F_AVAILABLE | INTNETR3RECVSTATE_F_NO_MORE_WAITS)) + pResult->fSendPokeNow = true; + pResult->fSendReply = false; /* async */ + rc = VINF_SUCCESS; + } + break; + + case VMMR0_DO_INTNET_IF_ABORT_WAIT: + if (cbReqReply == sizeof(INTNETIFABORTWAITREQ)) + { + PINTNETIFABORTWAITREQ p = &pReqReply->IfAbortWaitReq; + RT_NOREF(p); + uint32_t fOld; + uint32_t fNew; + do + { + fOld = ASMAtomicReadU32(&pSession->fRecvState); + fNew = (fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE)) + | INTNETR3RECVSTATE_F_NO_MORE_WAITS; + } while (!ASMAtomicCmpXchgU32(&pSession->fRecvState, fNew, fOld)); + if (fOld & (INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE)) + pResult->fSendPokeNow = true; + rc = VINF_SUCCESS; + pResult->cbReply = sizeof(*p); + } + break; + + default: + rc = VERR_INVALID_PARAMETER; + break; + } + + return rc; +} + + /********************************************************************************************************************************* * Global Variables * *********************************************************************************************************************************/ static SUPDRVDEVEXT g_DevExt; +#ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH +/** Local IPC listener cancelled when the testcase helper loses its last client. */ +static RTLOCALIPCSERVER g_hTestIpcServer = NIL_RTLOCALIPCSERVER; +#endif + + +/** Releases one connection slot and wakes the one-shot listener after the last client. */ +static uint32_t intnetR3ReleaseConnectionSlot(PSUPDRVDEVEXT pDevExt) +{ + uint32_t const cRefs = ASMAtomicDecU32(&pDevExt->cRefs); +#ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + if (!cRefs && g_hTestIpcServer != NIL_RTLOCALIPCSERVER) + RTLocalIpcServerCancel(g_hTestIpcServer); +#endif + return cRefs; +} INTNETR3DECL(void *) SUPR0ObjRegister(PSUPDRVSESSION pSession, SUPDRVOBJTYPE enmType, @@ -320,51 +739,220 @@ INTNETR3DECL(int) SUPR0ObjVerifyAccess(void *pvObj, PSUPDRVSESSION pSession, con } +/** Releases the backing resources of one tracked shared-memory allocation. */ +static int intnetR3DestroyShMem(PINTNETR3SHMEM pShMem) +{ + int rc = VINF_SUCCESS; +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + if (pShMem->pv) + { + if (munmap(pShMem->pv, pShMem->cb) == 0) + pShMem->pv = NULL; + else + rc = RTErrConvertFromErrno(errno); + } +#else + if (pShMem->pv) + { + int const rc2 = RTShMemUnmapRegion(pShMem->hShMem, pShMem->pv); + if (RT_SUCCESS(rc2)) + pShMem->pv = NULL; + else + rc = rc2; + } + if (!pShMem->pv && pShMem->hShMem != NIL_RTSHMEM) + { + int const rc2 = RTShMemClose(pShMem->hShMem); + if (RT_SUCCESS(rc2)) + pShMem->hShMem = NIL_RTSHMEM; + else if (RT_SUCCESS(rc)) + rc = rc2; + } + if (!pShMem->pv && pShMem->hShMem == NIL_RTSHMEM && pShMem->szName[0]) + { + int const rc2 = RTShMemDelete(pShMem->szName); + if ( RT_SUCCESS(rc2) + || rc2 == VERR_FILE_NOT_FOUND + || rc2 == VERR_PATH_NOT_FOUND + || rc2 == VERR_NOT_SUPPORTED) + pShMem->szName[0] = '\0'; + else if (RT_SUCCESS(rc)) + rc = rc2; + } +#endif + return rc; +} + + INTNETR3DECL(int) SUPR0MemAlloc(PSUPDRVSESSION pSession, uint32_t cb, PRTR0PTR ppvR0, PRTR3PTR ppvR3) { - RT_NOREF(pSession); + AssertPtr(pSession); - /* - * This is used to allocate and map the send/receive buffers into the callers process space, meaning - * we have to mmap it with the shareable attribute. - */ - void *pv = mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); - if (pv == MAP_FAILED) + PINTNETR3SHMEM pShMem = (PINTNETR3SHMEM)RTMemAllocZ(sizeof(*pShMem)); + if (!pShMem) return VERR_NO_MEMORY; + pShMem->cb = cb; + + int rc = intnetR3ReserveShMem(pSession->pDevExt, cb); + if (RT_FAILURE(rc)) + { + RTMemFree(pShMem); + return rc; + } + +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + /* The XPC transport requires a shareable mapping for the send/receive buffer. */ + pShMem->pv = mmap(NULL, cb, PROT_READ | PROT_WRITE, MAP_ANON | MAP_SHARED, -1, 0); + if (pShMem->pv == MAP_FAILED) + { + pShMem->pv = NULL; + rc = VERR_NO_MEMORY; + } +#else + pShMem->hShMem = NIL_RTSHMEM; - *ppvR0 = (RTR0PTR)pv; + rc = VERR_ALREADY_EXISTS; + for (uint32_t iTry = 0; iTry < INTNETR3_SHMEM_CREATE_RETRIES && rc == VERR_ALREADY_EXISTS; iTry++) + { + RTUUID Uuid; + char szUuid[32]; + rc = RTUuidCreate(&Uuid); + if (RT_SUCCESS(rc)) + { + size_t cchUuid = 0; + rc = RTBase64EncodeEx(&Uuid, sizeof(Uuid), RTBASE64_FLAGS_NO_LINE_BREAKS, + szUuid, sizeof(szUuid), &cchUuid); + if (RT_SUCCESS(rc)) + { + while (cchUuid > 0 && szUuid[cchUuid - 1] == '=') + szUuid[--cchUuid] = '\0'; + for (size_t off = 0; off < cchUuid; off++) + if (szUuid[off] == '+') + szUuid[off] = '-'; + else if (szUuid[off] == '/') + szUuid[off] = '_'; + } + } + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pShMem->szName, sizeof(pShMem->szName), "vbi-%s", szUuid); + if (cch <= 0 || (size_t)cch >= sizeof(pShMem->szName)) + rc = VERR_BUFFER_OVERFLOW; + } + if (RT_SUCCESS(rc)) + rc = RTShMemOpen(&pShMem->hShMem, pShMem->szName, + RTSHMEM_O_F_CREATE_EXCL | RTSHMEM_O_F_READWRITE, cb, 2 /*cMappingsHint*/); + } + if (RT_SUCCESS(rc)) + rc = RTShMemMapRegion(pShMem->hShMem, 0 /*off*/, cb, RTSHMEM_MAP_F_READ | RTSHMEM_MAP_F_WRITE, &pShMem->pv); +#endif + if (RT_FAILURE(rc)) + { +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + if (pShMem->hShMem != NIL_RTSHMEM) + intnetR3DestroyShMem(pShMem); + else + pShMem->szName[0] = '\0'; +#endif + if ( !pShMem->pv +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + && pShMem->hShMem == NIL_RTSHMEM + && !pShMem->szName[0] +#endif + ) + { + intnetR3ReleaseShMem(pSession->pDevExt, cb); + RTMemFree(pShMem); + } + else + { + pShMem->pNext = pSession->pShMemHead; + pSession->pShMemHead = pShMem; + } + return rc; + } + + pShMem->pvKey = pShMem->pv; + pShMem->pNext = pSession->pShMemHead; + pSession->pShMemHead = pShMem; + *ppvR0 = (RTR0PTR)pShMem->pv; if (ppvR3) - *ppvR3 = pv; + *ppvR3 = pShMem->pv; return VINF_SUCCESS; } INTNETR3DECL(int) SUPR0MemFree(PSUPDRVSESSION pSession, RTHCUINTPTR uPtr) { - RT_NOREF(pSession); + AssertPtr(pSession); + PINTNETR3SHMEM pPrev = NULL; + PINTNETR3SHMEM pCur = pSession->pShMemHead; + while (pCur) + { + if (pCur->pvKey == (void *)(uintptr_t)uPtr) + { + int const rc = intnetR3DestroyShMem(pCur); + if (RT_FAILURE(rc)) + return rc; + if (pPrev) + pPrev->pNext = pCur->pNext; + else + pSession->pShMemHead = pCur->pNext; + intnetR3ReleaseShMem(pSession->pDevExt, pCur->cb); + RTMemFree(pCur); + return VINF_SUCCESS; + } + pPrev = pCur; + pCur = pCur->pNext; + } + return VERR_NOT_FOUND; +} - PINTNETBUF pBuf = (PINTNETBUF)uPtr; /// @todo Hack hack hack! - munmap((void *)uPtr, pBuf->cbBuf); - return VINF_SUCCESS; + +static PINTNETR3SHMEM intnetR3FindShMemByPtr(PSUPDRVSESSION pSession, void *pv) +{ + for (PINTNETR3SHMEM pCur = pSession->pShMemHead; pCur; pCur = pCur->pNext) + if (pCur->pvKey == pv) + return pCur; + return NULL; +} + + +static void intnetR3FreeShMems(PSUPDRVSESSION pSession) +{ + PINTNETR3SHMEM pCur = pSession->pShMemHead; + pSession->pShMemHead = NULL; + while (pCur) + { + PINTNETR3SHMEM pNext = pCur->pNext; + int const rc = intnetR3DestroyShMem(pCur); + AssertRC(rc); + if (RT_SUCCESS(rc)) + intnetR3ReleaseShMem(pSession->pDevExt, pCur->cb); + RTMemFree(pCur); + pCur = pNext; + } } /** - * Destroys the given internal network XPC connection session freeing all allocated resources. + * Destroys the given internal network service session, freeing all allocated resources. * - * @returns Reference count of the device extension.. - * @param pSession The ession to destroy. + * @returns Reference count of the device extension. + * @param pSession The session to destroy. */ static uint32_t intnetR3SessionDestroy(PSUPDRVSESSION pSession) { PSUPDRVDEVEXT pDevExt = pSession->pDevExt; - uint32_t cRefs = ASMAtomicDecU32(&pDevExt->cRefs); - xpc_transaction_end(); - xpc_connection_set_context(pSession->hXpcCon, NULL); - xpc_connection_cancel(pSession->hXpcCon); - pSession->hXpcCon = NULL; - ASMAtomicXchgBool(&pSession->fRecvAvail, true); + /* Prevent new notification work, but keep its synchronization objects and + transport alive until interface destruction has quiesced callbacks. */ + ASMAtomicWriteU32(&pSession->fRecvState, INTNETR3RECVSTATE_F_NO_MORE_WAITS); +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + ASMAtomicWriteBool(&pSession->fIpcPokeStopping, true); + if (pSession->hIpcSession != NIL_RTLOCALIPCSESSION) + RTLocalIpcSessionCancel(pSession->hIpcSession); +#endif if (pSession->pUsage) { @@ -415,6 +1003,36 @@ static uint32_t intnetR3SessionDestroy(PSUPDRVSESSION pSession) AssertMsg(!pSession->pUsage, ("Some buster reregistered an object during destruction!\n")); } + /* Interface destructors release their shared buffers via SUPR0MemFree. */ + intnetR3FreeShMems(pSession); +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + xpc_transaction_end(); + xpc_connection_set_context(pSession->hXpcCon, NULL); + xpc_connection_cancel(pSession->hXpcCon); + pSession->hXpcCon = NULL; +#else + if (pSession->hIpcPokeEvt != NIL_RTSEMEVENT) + RTSemEventSignal(pSession->hIpcPokeEvt); + if (pSession->hIpcPokeThread != NIL_RTTHREAD) + { + int const rcThread = RTThreadWait(pSession->hIpcPokeThread, RT_INDEFINITE_WAIT, NULL /*prc*/); + AssertRC(rcThread); + pSession->hIpcPokeThread = NIL_RTTHREAD; + } + if (pSession->hIpcSession != NIL_RTLOCALIPCSESSION) + { + RTLocalIpcSessionClose(pSession->hIpcSession); + pSession->hIpcSession = NIL_RTLOCALIPCSESSION; + } + if (pSession->hIpcPokeEvt != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pSession->hIpcPokeEvt); + pSession->hIpcPokeEvt = NIL_RTSEMEVENT; + } + if (pSession->hIpcIoMtx != NIL_RTSEMMUTEX) + RTSemMutexDestroy(pSession->hIpcIoMtx); +#endif + uint32_t const cRefs = intnetR3ReleaseConnectionSlot(pDevExt); RTMemFree(pSession); return cRefs; } @@ -428,186 +1046,112 @@ static DECLCALLBACK(void) intnetR3RecvAvail(INTNETIFHANDLE hIf, void *pvUser) RT_NOREF(hIf); PSUPDRVSESSION pSession = (PSUPDRVSESSION)pvUser; - if (ASMAtomicXchgBool(&pSession->fRecvWait, false)) + uint32_t fOld; + uint32_t fNew; + do { + fOld = ASMAtomicReadU32(&pSession->fRecvState); + if (fOld & INTNETR3RECVSTATE_F_NO_MORE_WAITS) + fNew = fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE); + else if (fOld & INTNETR3RECVSTATE_F_WAITING) + fNew = fOld & ~(INTNETR3RECVSTATE_F_WAITING | INTNETR3RECVSTATE_F_AVAILABLE); + else + fNew = fOld | INTNETR3RECVSTATE_F_AVAILABLE; + } while (!ASMAtomicCmpXchgU32(&pSession->fRecvState, fNew, fOld)); + + if (fOld & INTNETR3RECVSTATE_F_WAITING) + { +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) /* Send an empty message. */ xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0); xpc_connection_send_message(pSession->hXpcCon, hObjPoke); xpc_release(hObjPoke); +#else + int const rc = RTSemEventSignal(pSession->hIpcPokeEvt); + AssertRC(rc); +#endif } - else - ASMAtomicXchgBool(&pSession->fRecvAvail, true); } +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) static void intnetR3RequestProcess(xpc_connection_t hCon, xpc_object_t hObj, PSUPDRVSESSION pSession) { - int rc = VINF_SUCCESS; - uint64_t iReq = xpc_dictionary_get_uint64(hObj, "req-id"); + uint64_t const uOperation = xpc_dictionary_get_uint64(hObj, "req-id"); size_t cbReq = 0; const void *pvReq = xpc_dictionary_get_data(hObj, "req", &cbReq); - union - { - INTNETOPENREQ OpenReq; - INTNETIFCLOSEREQ IfCloseReq; - INTNETIFGETBUFFERPTRSREQ IfGetBufferPtrsReq; - INTNETIFSETPROMISCUOUSMODEREQ IfSetPromiscuousModeReq; - INTNETIFSETMACADDRESSREQ IfSetMacAddressReq; - INTNETIFSETACTIVEREQ IfSetActiveReq; - INTNETIFSENDREQ IfSendReq; - INTNETIFWAITREQ IfWaitReq; - INTNETIFABORTWAITREQ IfAbortWaitReq; - } ReqReply; + if (cbReq > INTNET_R3_IPC_MAX_REQ) + { + xpc_connection_cancel(hCon); + return; + } - memcpy(&ReqReply, pvReq, RT_MIN(sizeof(ReqReply), cbReq)); - size_t cbReply = 0; + xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj); + if (!hObjReply && uOperation != VMMR0_DO_INTNET_IF_WAIT) + { + xpc_connection_cancel(hCon); + return; + } - if (pvReq) + INTNETR3REQREPLY ReqReply; + INTNETR3REQRESULT Result; + + RT_ZERO(ReqReply); + int rc = VERR_INVALID_PARAMETER; + RT_ZERO(Result); + Result.fSendReply = true; + + if ( pvReq + && cbReq >= sizeof(SUPVMMR0REQHDR) + && uOperation == (uint32_t)uOperation) { - switch (iReq) - { - case VMMR0_DO_INTNET_OPEN: - { - if (cbReq == sizeof(INTNETOPENREQ)) - { - rc = IntNetR3Open(pSession, &ReqReply.OpenReq.szNetwork[0], ReqReply.OpenReq.enmTrunkType, ReqReply.OpenReq.szTrunk, - ReqReply.OpenReq.fFlags, ReqReply.OpenReq.cbSend, ReqReply.OpenReq.cbRecv, - intnetR3RecvAvail, pSession, &ReqReply.OpenReq.hIf); - cbReply = sizeof(INTNETOPENREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_CLOSE: - { - if (cbReq == sizeof(INTNETIFCLOSEREQ)) - { - rc = IntNetR0IfCloseReq(pSession, &ReqReply.IfCloseReq); - cbReply = sizeof(INTNETIFCLOSEREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS: - { - if (cbReq == sizeof(INTNETIFGETBUFFERPTRSREQ)) - { - rc = IntNetR0IfGetBufferPtrsReq(pSession, &ReqReply.IfGetBufferPtrsReq); - /* This is special as we need to return a shared memory segment. */ - xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj); - if (RT_SUCCESS(rc)) - { - xpc_object_t hObjShMem = xpc_shmem_create(ReqReply.IfGetBufferPtrsReq.pRing3Buf, ReqReply.IfGetBufferPtrsReq.pRing3Buf->cbBuf); - if (hObjShMem) - { - xpc_dictionary_set_value(hObjReply, "buf-ptr", hObjShMem); - xpc_release(hObjShMem); - } - else - rc = VERR_NO_MEMORY; - } + memcpy(&ReqReply, pvReq, RT_MIN(sizeof(ReqReply), cbReq)); - xpc_dictionary_set_uint64(hObjReply, "rc", INTNET_R3_SVC_SET_RC(rc)); - xpc_connection_send_message(hCon, hObjReply); - xpc_release(hObjReply); - return; - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_SET_PROMISCUOUS_MODE: - { - if (cbReq == sizeof(INTNETIFSETPROMISCUOUSMODEREQ)) - { - rc = IntNetR0IfSetPromiscuousModeReq(pSession, &ReqReply.IfSetPromiscuousModeReq); - cbReply = sizeof(INTNETIFSETPROMISCUOUSMODEREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS: - { - if (cbReq == sizeof(INTNETIFSETMACADDRESSREQ)) - { - rc = IntNetR0IfSetMacAddressReq(pSession, &ReqReply.IfSetMacAddressReq); - cbReply = sizeof(INTNETIFSETMACADDRESSREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_SET_ACTIVE: - { - if (cbReq == sizeof(INTNETIFSETACTIVEREQ)) - { - rc = IntNetR0IfSetActiveReq(pSession, &ReqReply.IfSetActiveReq); - cbReply = sizeof(INTNETIFSETACTIVEREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_SEND: - { - if (cbReq == sizeof(INTNETIFSENDREQ)) - { - rc = IntNetR0IfSendReq(pSession, &ReqReply.IfSendReq); - cbReply = sizeof(INTNETIFSENDREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_WAIT: - { - if (cbReq == sizeof(INTNETIFWAITREQ)) - { - ASMAtomicXchgBool(&pSession->fRecvWait, true); - if (ASMAtomicXchgBool(&pSession->fRecvAvail, false)) - { - ASMAtomicXchgBool(&pSession->fRecvWait, false); + rc = intnetR3ProcessRequestCore(pSession, (uint32_t)uOperation, &ReqReply, cbReq, &Result); + } - /* Send an empty message. */ - xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0); - xpc_connection_send_message(pSession->hXpcCon, hObjPoke); - xpc_release(hObjPoke); - } - return; - } - else - rc = VERR_INVALID_PARAMETER; - break; - } - case VMMR0_DO_INTNET_IF_ABORT_WAIT: + if (Result.fSendPokeNow) + { + /* Send an empty message. */ + xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0); + xpc_connection_send_message(pSession->hXpcCon, hObjPoke); + xpc_release(hObjPoke); + } + + if (!Result.fSendReply) + { + if (hObjReply) + xpc_release(hObjReply); + return; + } + + if (!hObjReply) + { + xpc_connection_cancel(hCon); + return; + } + if (RT_SUCCESS(rc) && Result.pRing3Buf) + { + /* This is special as we need to return a shared memory segment. */ + PINTNETR3SHMEM pShMem = intnetR3FindShMemByPtr(pSession, Result.pRing3Buf); + if (pShMem) + { + xpc_object_t hObjShMem = xpc_shmem_create(pShMem->pv, pShMem->cb); + if (hObjShMem) { - if (cbReq == sizeof(INTNETIFABORTWAITREQ)) - { - ASMAtomicXchgBool(&pSession->fRecvWait, false); - if (ASMAtomicXchgBool(&pSession->fRecvAvail, false)) - { - /* Send an empty message. */ - xpc_object_t hObjPoke = xpc_dictionary_create(NULL, NULL, 0); - xpc_connection_send_message(pSession->hXpcCon, hObjPoke); - xpc_release(hObjPoke); - } - cbReply = sizeof(INTNETIFABORTWAITREQ); - } - else - rc = VERR_INVALID_PARAMETER; - break; + xpc_dictionary_set_value(hObjReply, "buf-ptr", hObjShMem); + xpc_release(hObjShMem); } - default: - rc = VERR_INVALID_PARAMETER; + else + rc = VERR_NO_MEMORY; } + else + rc = VERR_INTERNAL_ERROR; } - xpc_object_t hObjReply = xpc_dictionary_create_reply(hObj); xpc_dictionary_set_uint64(hObjReply, "rc", INTNET_R3_SVC_SET_RC(rc)); - xpc_dictionary_set_data(hObjReply, "reply", &ReqReply, cbReply); + size_t const cbXpcReply = uOperation == VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS ? 0 : Result.cbReply; + xpc_dictionary_set_data(hObjReply, "reply", &ReqReply, cbXpcReply); xpc_connection_send_message(hCon, hObjReply); xpc_release(hObjReply); } @@ -615,18 +1159,37 @@ static void intnetR3RequestProcess(xpc_connection_t hCon, xpc_object_t hObj, PSU static DECLCALLBACK(void) xpcConnHandler(xpc_connection_t hXpcCon) { + if (!intnetR3TryReserveSlot(&g_DevExt.cRefs, g_DevExt.cMaxConnections)) + { + xpc_connection_cancel(hXpcCon); + return; + } + + PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession)); + if (!pSession) + { + intnetR3ReleaseConnectionSlot(&g_DevExt); + xpc_connection_cancel(hXpcCon); + return; + } + pSession->pDevExt = &g_DevExt; + pSession->hXpcCon = hXpcCon; + xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) { - PSUPDRVSESSION pSession = (PSUPDRVSESSION)xpc_connection_get_context(hXpcCon); + PSUPDRVSESSION pSessionCtx = (PSUPDRVSESSION)xpc_connection_get_context(hXpcCon); + if (!pSessionCtx) + return; - if (xpc_get_type(hObj) == XPC_TYPE_ERROR) + xpc_type_t const hType = xpc_get_type(hObj); + if (hType == XPC_TYPE_ERROR) { if (hObj == XPC_ERROR_CONNECTION_INVALID) - intnetR3SessionDestroy(pSession); + intnetR3SessionDestroy(pSessionCtx); else if (hObj == XPC_ERROR_TERMINATION_IMMINENT) { - PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + PSUPDRVDEVEXT pDevExt = pSessionCtx->pDevExt; - uint32_t cRefs = intnetR3SessionDestroy(pSession); + uint32_t cRefs = intnetR3SessionDestroy(pSessionCtx); if (!cRefs) { /* Last one cleans up the global data. */ @@ -634,39 +1197,539 @@ static DECLCALLBACK(void) xpcConnHandler(xpc_connection_t hXpcCon) } } } + else if (hType == XPC_TYPE_DICTIONARY) + intnetR3RequestProcess(hXpcCon, hObj, pSessionCtx); else - intnetR3RequestProcess(hXpcCon, hObj, pSession); + xpc_connection_cancel(hXpcCon); }); - PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession)); - if (pSession) + xpc_connection_set_context(hXpcCon, pSession); + xpc_transaction_begin(); + xpc_connection_resume(hXpcCon); +} + +#else /* !RT_OS_DARWIN */ + +# if !defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) +static int intnetR3LocalIpcGetServiceName(char *pszService, size_t cbService) +{ +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestService = RTEnvGet("VBOX_INTNET_R3_SVC_NAME"); + if (pszTestService && *pszTestService) + return RTStrCopy(pszService, cbService, pszTestService); +# endif + + char szUser[256]; + int rc = RTProcQueryUsername(RTProcSelf(), szUser, sizeof(szUser), NULL /*pcbUser*/); + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pszService, cbService, "%s-%08RX32", INTNET_R3_SVC_NAME, RTStrHash1(szUser)); + if (cch < 0 || (size_t)cch >= cbService) + rc = VERR_BUFFER_OVERFLOW; + } + return rc; +} +# endif + + +/** Verifies that a local IPC client belongs to the user running the switch. */ +static int intnetR3LocalIpcVerifyPeer(RTLOCALIPCSESSION hSession) +{ + int rc = RTLocalIpcSessionVerifySameUser(hSession); +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; +# endif + return rc; +} + + +# if !defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) +static int intnetR3LocalIpcAcquireLock(PRTFILE phLock) +{ + *phLock = NIL_RTFILE; +# ifdef RT_OS_WINDOWS + /* The pipe namespace is scoped to the login session and the first pipe + instance is created atomically, so a user-global file lock is wrong. */ + return VINF_SUCCESS; +# else + char szLock[RTPATH_MAX]; +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestLock = RTEnvGet("VBOX_INTNET_R3_SWITCH_LOCK_FILE"); + int rc; + if (pszTestLock && *pszTestLock) + rc = RTStrCopy(szLock, sizeof(szLock), pszTestLock); + else + { + char szHome[RTPATH_MAX]; + rc = RTPathUserHome(szHome, sizeof(szHome)); + if (RT_SUCCESS(rc) && !RTPathStartsWithRoot(szHome)) + rc = VERR_INVALID_NAME; + if (RT_SUCCESS(rc)) + rc = RTPathReal(szHome, szLock, sizeof(szLock)); + } +# else + char szHome[RTPATH_MAX]; + int rc = RTPathUserHome(szHome, sizeof(szHome)); + if (RT_SUCCESS(rc) && !RTPathStartsWithRoot(szHome)) + rc = VERR_INVALID_NAME; + if (RT_SUCCESS(rc)) + rc = RTPathReal(szHome, szLock, sizeof(szLock)); +# endif +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (RT_SUCCESS(rc) && (!pszTestLock || !*pszTestLock)) +# else + if (RT_SUCCESS(rc)) +# endif + rc = RTPathAppend(szLock, sizeof(szLock), ".VirtualBox"); +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (RT_SUCCESS(rc) && (!pszTestLock || !*pszTestLock)) +# else + if (RT_SUCCESS(rc)) +# endif + rc = RTDirCreateFullPath(szLock, 0700); +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (RT_SUCCESS(rc) && (!pszTestLock || !*pszTestLock)) +# else + if (RT_SUCCESS(rc)) +# endif { - pSession->pDevExt = &g_DevExt; - pSession->hXpcCon = hXpcCon; + RTFSOBJINFO ObjInfo; + rc = RTPathQueryInfoEx(szLock, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + if ( !RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode) + || ObjInfo.Attr.u.Unix.uid != (RTUID)geteuid() + || (ObjInfo.Attr.fMode & (RTFS_UNIX_IWUSR | RTFS_UNIX_IXUSR)) + != (RTFS_UNIX_IWUSR | RTFS_UNIX_IXUSR) + || (ObjInfo.Attr.fMode & (RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH))) + rc = VERR_ACCESS_DENIED; + } + } +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (RT_SUCCESS(rc) && (!pszTestLock || !*pszTestLock)) +# else + if (RT_SUCCESS(rc)) +# endif + rc = RTPathAppend(szLock, sizeof(szLock), "VBoxIntNetSwitch.lock"); + if (RT_SUCCESS(rc)) + rc = RTFileOpen(phLock, szLock, RTFILE_O_READWRITE | RTFILE_O_OPEN_CREATE | RTFILE_O_DENY_NONE + | RTFILE_O_NO_SYMLINKS + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + rc = RTFileLock(*phLock, RTFILE_LOCK_WRITE | RTFILE_LOCK_IMMEDIATELY, 0 /*offLock*/, 1 /*cbLock*/); + if (RT_FAILURE(rc)) + { + RTFileClose(*phLock); + *phLock = NIL_RTFILE; + } + } + return rc; +# endif +} +# endif - xpc_connection_set_context(hXpcCon, pSession); - xpc_connection_resume(hXpcCon); - xpc_transaction_begin(); - ASMAtomicIncU32(&g_DevExt.cRefs); +static int intnetR3LocalIpcSendHdrAndPayload(PSUPDRVSESSION pSession, PCINTNETR3IPCREPLYHDR pHdr, + const void *pvReply, size_t cbReply, + const char *pszShMemName, size_t cbShMemName) +{ + int rc = RTSemMutexRequest(pSession->hIpcIoMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcSessionWrite(pSession->hIpcSession, pHdr, sizeof(*pHdr)); + if (RT_SUCCESS(rc) && cbReply) + rc = RTLocalIpcSessionWrite(pSession->hIpcSession, pvReply, cbReply); + if (RT_SUCCESS(rc) && cbShMemName) + rc = RTLocalIpcSessionWrite(pSession->hIpcSession, pszShMemName, cbShMemName); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(pSession->hIpcSession); + RTSemMutexRelease(pSession->hIpcIoMtx); } + return rc; } -int main(int argc, char **argv) +static int intnetR3LocalIpcSendReply(PSUPDRVSESSION pSession, int rcReq, const void *pvReply, size_t cbReply, + const char *pszShMemName, size_t cbShMem) { - int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); + size_t const cbShMemName = pszShMemName ? strlen(pszShMemName) + 1 : 0; + AssertReturn(cbShMemName <= INTNET_R3_IPC_MAX_SHMEM_NAME, VERR_BUFFER_OVERFLOW); + AssertReturn(cbReply <= INTNET_R3_IPC_MAX_REQ, VERR_BUFFER_OVERFLOW); + INTNETR3IPCREPLYHDR Hdr; + Hdr.u32Magic = INTNET_R3_IPC_REPLY_MAGIC; + Hdr.u16Version = INTNET_R3_IPC_VERSION; + Hdr.cbHdr = sizeof(Hdr); + Hdr.rc = rcReq; + Hdr.cbReply = (uint32_t)cbReply; + Hdr.cbShMemName = (uint32_t)cbShMemName; + Hdr.cbShMem = cbShMem; + return intnetR3LocalIpcSendHdrAndPayload(pSession, &Hdr, pvReply, cbReply, pszShMemName, cbShMemName); +} + + +static int intnetR3LocalIpcSendPoke(PSUPDRVSESSION pSession) +{ +# ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + /* Deterministically models a notification write blocked by a non-reading peer. */ + const char *pszTestBlockFile = RTEnvGet("VBOX_INTNET_R3_TEST_POKE_BLOCK_FILE"); + while ( pszTestBlockFile + && *pszTestBlockFile + && RTFileExists(pszTestBlockFile)) + { + if (ASMAtomicReadBool(&pSession->fIpcPokeStopping)) + return VERR_CANCELLED; + RTThreadSleep(1); + } +# endif + + INTNETR3IPCREPLYHDR Hdr; + Hdr.u32Magic = INTNET_R3_IPC_POKE_MAGIC; + Hdr.u16Version = INTNET_R3_IPC_VERSION; + Hdr.cbHdr = sizeof(Hdr); + Hdr.rc = VINF_SUCCESS; + Hdr.cbReply = 0; + Hdr.cbShMemName = 0; + Hdr.cbShMem = 0; + return intnetR3LocalIpcSendHdrAndPayload(pSession, &Hdr, NULL, 0, NULL, 0); +} + + +/** Writes queued receive notifications outside the IntNet delivery callback. */ +static DECLCALLBACK(int) intnetR3LocalIpcPokeThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PSUPDRVSESSION pSession = (PSUPDRVSESSION)pvUser; + PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + uint32_t const cMaxThreads = pDevExt->cMaxThreads; + + int rc = VINF_SUCCESS; + for (;;) + { + rc = RTSemEventWait(pSession->hIpcPokeEvt, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + { + if (!ASMAtomicReadBool(&pSession->fIpcPokeStopping)) + RTLocalIpcSessionCancel(pSession->hIpcSession); + break; + } + if (ASMAtomicReadBool(&pSession->fIpcPokeStopping)) + { + rc = VINF_SUCCESS; + break; + } + + rc = intnetR3LocalIpcSendPoke(pSession); + if (RT_FAILURE(rc)) + { + RTLocalIpcSessionCancel(pSession->hIpcSession); + break; + } + } + + if (ASMAtomicReadBool(&pSession->fIpcPokeStopping)) + rc = VINF_SUCCESS; + uint32_t const cThreads = ASMAtomicDecU32(&pDevExt->cThreads); + Assert(cThreads < cMaxThreads); + RT_NOREF(cThreads, cMaxThreads); + return rc; +} + + +/** Reads exactly @a cbToRead bytes without letting partial progress reset the deadline. */ +static int intnetR3LocalIpcReadExact(RTLOCALIPCSESSION hSession, void *pvBuf, size_t cbToRead, uint64_t msDeadline) +{ + uint8_t *pbDst = (uint8_t *)pvBuf; + while (cbToRead > 0) + { + size_t cbRead = 0; + int rc = RTLocalIpcSessionReadNB(hSession, pbDst, cbToRead, &cbRead); + if (rc == VINF_SUCCESS && cbRead > 0) + { + AssertReturn(cbRead <= cbToRead, VERR_INTERNAL_ERROR); + pbDst += cbRead; + cbToRead -= cbRead; + continue; + } + if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN) + return rc; + + uint64_t const msNow = RTTimeMilliTS(); + if (msNow >= msDeadline) + return VERR_TIMEOUT; + uint64_t const cMsLeft = msDeadline - msNow; + rc = RTLocalIpcSessionWaitForData(hSession, (uint32_t)RT_MIN(cMsLeft, (uint64_t)UINT32_MAX)); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +static int intnetR3LocalIpcProcessRequest(PSUPDRVSESSION pSession, uint32_t uOperation, const void *pvReq, size_t cbReq, + uint32_t *pcOpenIfs) +{ + AssertPtrReturn(pSession, VERR_INVALID_POINTER); + AssertPtrReturn(pvReq, VERR_INVALID_POINTER); + AssertPtrReturn(pcOpenIfs, VERR_INVALID_POINTER); + AssertReturn(cbReq >= sizeof(SUPVMMR0REQHDR), VERR_INVALID_PARAMETER); + + INTNETR3REQREPLY ReqReply; + INTNETR3REQRESULT Result; + + RT_ZERO(ReqReply); + RT_ZERO(Result); + Result.fSendReply = true; + memcpy(&ReqReply, pvReq, RT_MIN(sizeof(ReqReply), cbReq)); + + PSUPVMMR0REQHDR pReqHdr = (PSUPVMMR0REQHDR)&ReqReply; + AssertReturn(pReqHdr->u32Magic == SUPVMMR0REQHDR_MAGIC, VERR_INVALID_MAGIC); + AssertReturn(pReqHdr->cbReq == cbReq, VERR_INVALID_PARAMETER); + + const char *pszShMemName = NULL; + size_t cbShMem = 0; + + int rcReq = intnetR3ProcessRequestCore(pSession, uOperation, &ReqReply, cbReq, &Result); + if (RT_SUCCESS(rcReq) && Result.pRing3Buf) + { + PINTNETR3SHMEM pShMem = intnetR3FindShMemByPtr(pSession, Result.pRing3Buf); + if (pShMem) + { + pszShMemName = pShMem->szName; + cbShMem = pShMem->cb; + } + else + rcReq = VERR_INTERNAL_ERROR; + } + + if (Result.fSendPokeNow) + { + int const rcPoke = intnetR3LocalIpcSendPoke(pSession); + if (!Result.fSendReply) + return rcPoke; + } + + if (Result.fSendReply) + { + int const rc = intnetR3LocalIpcSendReply(pSession, rcReq, &ReqReply, Result.cbReply, pszShMemName, cbShMem); + if (RT_SUCCESS(rc) && RT_SUCCESS(rcReq)) + { + if (uOperation == VMMR0_DO_INTNET_OPEN) + (*pcOpenIfs)++; + else if (uOperation == VMMR0_DO_INTNET_IF_CLOSE && *pcOpenIfs > 0) + (*pcOpenIfs)--; + } + return rc; + } + return rcReq; +} + + +static DECLCALLBACK(int) intnetR3LocalIpcSessionThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PSUPDRVSESSION pSession = (PSUPDRVSESSION)pvUser; + bool fPeerVerified = false; + uint32_t cOpenIfs = 0; + for (;;) + { + uint32_t const cMsFirstByte = cOpenIfs > 0 ? RT_INDEFINITE_WAIT : pSession->pDevExt->cMsReadTimeout; + int rc = RTLocalIpcSessionWaitForData(pSession->hIpcSession, cMsFirstByte); + if (RT_FAILURE(rc)) + break; + + uint64_t const msDeadline = RTTimeMilliTS() + pSession->pDevExt->cMsReadTimeout; + INTNETR3IPCREQHDR Hdr; + rc = intnetR3LocalIpcReadExact(pSession->hIpcSession, &Hdr, sizeof(Hdr), msDeadline); + if (RT_FAILURE(rc)) + break; + if (!fPeerVerified) + { + rc = intnetR3LocalIpcVerifyPeer(pSession->hIpcSession); + if (RT_FAILURE(rc)) + break; + fPeerVerified = true; + } + if ( Hdr.u32Magic != INTNET_R3_IPC_REQ_MAGIC + || Hdr.u16Version != INTNET_R3_IPC_VERSION + || Hdr.cbHdr != sizeof(Hdr) + || Hdr.cbReq < sizeof(SUPVMMR0REQHDR) + || Hdr.cbReq > INTNET_R3_IPC_MAX_REQ) + break; + + void *pvReq = RTMemTmpAlloc(Hdr.cbReq); + if (!pvReq) + break; + rc = intnetR3LocalIpcReadExact(pSession->hIpcSession, pvReq, Hdr.cbReq, msDeadline); + if (RT_SUCCESS(rc)) + rc = intnetR3LocalIpcProcessRequest(pSession, Hdr.uOperation, pvReq, Hdr.cbReq, &cOpenIfs); + RTMemTmpFree(pvReq); + if (RT_FAILURE(rc)) + break; + } + PSUPDRVDEVEXT pDevExt = pSession->pDevExt; + uint32_t const cMaxThreads = pDevExt->cMaxThreads; + intnetR3SessionDestroy(pSession); + uint32_t const cThreads = ASMAtomicDecU32(&pDevExt->cThreads); + Assert(cThreads < cMaxThreads); + RT_NOREF(cThreads, cMaxThreads); + return VINF_SUCCESS; +} + + +# if !defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) +static int intnetR3LocalIpcRun(const char *pszService) +{ + RTFILE hLock = NIL_RTFILE; + int rc = intnetR3LocalIpcAcquireLock(&hLock); + if (RT_FAILURE(rc)) + return rc; + + RTLOCALIPCSERVER hServer = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hServer, pszService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); +# ifndef RT_OS_WINDOWS if (RT_SUCCESS(rc)) + rc = RTLocalIpcServerSetAccessMode(hServer, RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR); +# endif + if (RT_FAILURE(rc)) { - IntNetR0Init(); + if (hServer != NIL_RTLOCALIPCSERVER) + RTLocalIpcServerDestroy(hServer); + RTFileClose(hLock); + return rc; + } +# ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + g_hTestIpcServer = hServer; +# endif - g_DevExt.pObjs = NULL; - rc = RTCritSectInit(&g_DevExt.CritSect); + for (;;) + { + RTLOCALIPCSESSION hClient = NIL_RTLOCALIPCSESSION; + rc = RTLocalIpcServerListen(hServer, &hClient); +# ifdef RT_OS_WINDOWS + if (rc == VERR_TRY_AGAIN) + { + RTThreadSleep(10); + continue; + } +# endif + if (RT_FAILURE(rc)) + break; + if (!intnetR3TryReserveSlot(&g_DevExt.cRefs, g_DevExt.cMaxConnections)) + { + RTLocalIpcSessionClose(hClient); + RTThreadSleep(10); + continue; + } + PSUPDRVSESSION pSession = (PSUPDRVSESSION)RTMemAllocZ(sizeof(*pSession)); + if (!pSession) + { + RTLocalIpcSessionClose(hClient); + intnetR3ReleaseConnectionSlot(&g_DevExt); + continue; + } + pSession->pDevExt = &g_DevExt; + pSession->hIpcSession = hClient; + pSession->hThread = NIL_RTTHREAD; + pSession->hIpcPokeThread = NIL_RTTHREAD; + pSession->hIpcPokeEvt = NIL_RTSEMEVENT; + pSession->fIpcPokeStopping = false; + pSession->hIpcIoMtx = NIL_RTSEMMUTEX; + rc = RTSemMutexCreate(&pSession->hIpcIoMtx); if (RT_SUCCESS(rc)) - xpc_main(xpcConnHandler); /* Never returns. */ + rc = RTSemEventCreate(&pSession->hIpcPokeEvt); + if (RT_FAILURE(rc)) + { + intnetR3SessionDestroy(pSession); + continue; + } - exit(EXIT_FAILURE); + if (!intnetR3TryReserveSlots(&g_DevExt.cThreads, g_DevExt.cMaxThreads, + INTNETR3_THREADS_PER_LOCALIPC_SESSION)) + { + intnetR3SessionDestroy(pSession); + RTThreadSleep(10); + continue; + } + + rc = RTThreadCreate(&pSession->hIpcPokeThread, intnetR3LocalIpcPokeThread, pSession, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetPoke"); + if (RT_FAILURE(rc)) + { + intnetR3SessionDestroy(pSession); + ASMAtomicSubU32(&g_DevExt.cThreads, INTNETR3_THREADS_PER_LOCALIPC_SESSION); + continue; + } + rc = RTThreadCreate(&pSession->hThread, intnetR3LocalIpcSessionThread, pSession, 0 /*cbStack*/, + RTTHREADTYPE_IO, 0 /*fFlags*/, "IntNetIpc"); + if (RT_FAILURE(rc)) + { + intnetR3SessionDestroy(pSession); + ASMAtomicDecU32(&g_DevExt.cThreads); /* Reserved request worker was never started. */ + } } +# ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + g_hTestIpcServer = NIL_RTLOCALIPCSERVER; +# endif + RTLocalIpcServerDestroy(hServer); - return RTMsgInitFailure(rc); + /* + * The session workers are detached. A fatal listener failure must not + * let main tear down the IntNet globals while any worker still uses them. + * Closing the listener prevents new sessions; existing clients can then + * finish normally, however long that takes. + */ + while ( ASMAtomicReadU32(&g_DevExt.cRefs) != 0 + || ASMAtomicReadU32(&g_DevExt.cThreads) != 0) + RTThreadSleep(1); + + RTFileClose(hLock); + return rc; } +# endif /* !VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH */ +#endif /* !RT_OS_DARWIN || VBOX_INTNET_TESTCASE_LOCALIPC */ + +#if !defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) +int main(int argc, char **argv) +{ +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); +#else + int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/); +#endif + if (RT_SUCCESS(rc)) + { + rc = IntNetR0Init(); + if (RT_SUCCESS(rc)) + { + g_DevExt.pObjs = NULL; + rc = RTCritSectInit(&g_DevExt.CritSect); + if (RT_SUCCESS(rc)) + { + intnetR3InitLimits(&g_DevExt); +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + xpc_main(xpcConnHandler); /* Never returns. */ + rc = VERR_INTERNAL_ERROR; +#else + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; + rc = intnetR3LocalIpcGetServiceName(szService, sizeof(szService)); + if (RT_SUCCESS(rc)) + rc = intnetR3LocalIpcRun(szService); /* Normally never returns. */ +# ifdef VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + if (rc == VERR_CANCELLED) + rc = VINF_SUCCESS; +# endif +#endif + int const rc2 = RTCritSectDelete(&g_DevExt.CritSect); + AssertRC(rc2); + if (RT_SUCCESS(rc)) + rc = rc2; + } + IntNetR0Term(); + } + } + + return RT_SUCCESS(rc) ? RTEXITCODE_SUCCESS : RTMsgInitFailure(rc); +} +#endif /* !VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH */ diff --git a/src/VBox/NetworkServices/Makefile.kmk b/src/VBox/NetworkServices/Makefile.kmk index 97303d115eba..7b9990709aed 100644 --- a/src/VBox/NetworkServices/Makefile.kmk +++ b/src/VBox/NetworkServices/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ ## @file # Top-level makefile for the VBox Network Services. # @@ -43,5 +43,9 @@ if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) include $(PATH_SUB_CURRENT)/IntNetSwitch/Makefile.kmk endif +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES) + include $(PATH_SUB_CURRENT)/testcase/Makefile.kmk +endif + include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/NetworkServices/NAT/Makefile.kmk b/src/VBox/NetworkServices/NAT/Makefile.kmk index 1f63168d6283..a6b4fca5a2c5 100644 --- a/src/VBox/NetworkServices/NAT/Makefile.kmk +++ b/src/VBox/NetworkServices/NAT/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114516 2026-06-25 07:04:32Z andreas.loeffler@oracle.com $ +# $Id: Makefile.kmk 114883 2026-08-07 07:12:24Z andreas.loeffler@oracle.com $ ## @file # Sub-makefile for NAT Networking # @@ -45,6 +45,7 @@ ifdef VBOX_WITH_HARDENING else VBoxNetNATHardened_DEFS = SERVICE_NAME=\"VBoxNetNAT\" endif + VBoxNetNATHardened_DEFS += $(if $(and $(VBOX_WITH_DRIVERLESS_NEM_FALLBACK),$(VBOX_WITH_INTNET_SERVICE_IN_R3)),VBOX_WITH_DRIVERLESS_NEM_FALLBACK,) VBoxNetNATHardened_SOURCES = VBoxNetNATHardened.cpp VBoxNetNATHardened_LDFLAGS.win = /SUBSYSTEM:windows $(call VBOX_SET_VER_INFO_EXE,VBoxNetNATHardened,VirtualBox NAT Engine,$(VBOX_WINDOWS_ICON_FILE)) # Version info / description. @@ -63,7 +64,8 @@ VBoxNetNAT_TEMPLATE := $(if-expr defined(VBOX_WITH_HARDENING),VBoxMainDll,VBoxMa VBoxNetNAT_NAME := VBoxNetNAT VBoxNetNAT_DEFS = \ IPv6 \ - $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3),VBOX_WITH_INTNET_SERVICE_IN_R3,) \ + $(if $(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC),VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC,) VBoxNetNAT_DEFS.win = VBOX_COM_OUTOFPROC_MODULE VBoxNetNAT_INCS += \ $(PATH_ROOT)/src/libs/libslirp-4.9.3/include diff --git a/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp b/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp index ead4ea51351b..4bda5d2a8825 100644 --- a/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp +++ b/src/VBox/NetworkServices/NAT/VBoxNetNATHardened.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetNATHardened.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxNetNATHardened.cpp 114883 2026-08-07 07:12:24Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetNAT - Hardened main(). */ @@ -33,5 +33,9 @@ int main(int argc, char **argv, char **envp) { +#ifdef VBOX_WITH_DRIVERLESS_NEM_FALLBACK + return SUPR3HardenedMain(SERVICE_NAME, SUPSECMAIN_FLAGS_DRIVERLESS_NEM_FALLBACK, argc, argv, envp); +#else return SUPR3HardenedMain(SERVICE_NAME, 0 /* fFlags */, argc, argv, envp); +#endif } diff --git a/src/VBox/NetworkServices/NAT/VBoxNetSlirpNAT.cpp b/src/VBox/NetworkServices/NAT/VBoxNetSlirpNAT.cpp index d2385a5050bd..20ace9a8f52b 100644 --- a/src/VBox/NetworkServices/NAT/VBoxNetSlirpNAT.cpp +++ b/src/VBox/NetworkServices/NAT/VBoxNetSlirpNAT.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxNetSlirpNAT.cpp 114457 2026-06-19 11:55:32Z andreas.loeffler@oracle.com $ */ +/* $Id: VBoxNetSlirpNAT.cpp 114883 2026-08-07 07:12:24Z andreas.loeffler@oracle.com $ */ /** @file * VBoxNetNAT - NAT Service for connecting to IntNet. */ @@ -3090,7 +3090,11 @@ extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp) #ifndef VBOX_WITH_HARDENING int main(int argc, char **argv, char **envp) { +#ifdef VBOX_WITH_INTNET_SERVICE_IN_R3 + int rc = RTR3InitExe(argc, &argv, 0 /* fFlags */); +#else int rc = RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_SUPLIB); +#endif if (RT_SUCCESS(rc)) return TrustedMain(argc, argv, envp); return RTMsgInitFailure(rc); diff --git a/src/VBox/NetworkServices/NAT/testcase/tstNATLibslirpVBox.cpp b/src/VBox/NetworkServices/NAT/testcase/tstNATLibslirpVBox.cpp index 2dcb88af3cef..b1c4b5cb174b 100644 --- a/src/VBox/NetworkServices/NAT/testcase/tstNATLibslirpVBox.cpp +++ b/src/VBox/NetworkServices/NAT/testcase/tstNATLibslirpVBox.cpp @@ -1,4 +1,4 @@ -/* $Id: tstNATLibslirpVBox.cpp 114518 2026-06-25 08:29:23Z andreas.loeffler@oracle.com $ */ +/* $Id: tstNATLibslirpVBox.cpp 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ */ /** @file * NAT libslirp VBox testcase. */ @@ -37,6 +37,7 @@ #include +#include #include #include #include @@ -47,11 +48,28 @@ #include +/** Captured frame emitted by libslirp toward the guest. */ +typedef struct TSTNATOUTPUT +{ + uint8_t abFrame[2048]; + size_t cbFrame; + uint32_t cFrames; +} TSTNATOUTPUT; +/** Pointer to a captured libslirp output frame. */ +typedef TSTNATOUTPUT *PTSTNATOUTPUT; + + static DECLCALLBACK(slirp_ssize_t) tstSendPacket(const void *pvBuf, ssize_t cbBuf, void *pvOpaque) { - RT_NOREF(pvBuf, pvOpaque); + PTSTNATOUTPUT pOutput = (PTSTNATOUTPUT)pvOpaque; + if (pOutput != NULL && cbBuf > 0) + { + pOutput->cbFrame = RT_MIN((size_t)cbBuf, sizeof(pOutput->abFrame)); + memcpy(pOutput->abFrame, pvBuf, pOutput->cbFrame); + pOutput->cFrames++; + } return cbBuf; } @@ -185,7 +203,9 @@ static void tstVBoxAbi(void) Cb.timer_free = tstTimerFree; Cb.timer_mod = tstTimerMod; - Slirp *pSlirp = slirp_new(&Cfg, &Cb, NULL); + TSTNATOUTPUT Output; + RT_ZERO(Output); + Slirp *pSlirp = slirp_new(&Cfg, &Cb, &Output); if (!pSlirp) { RTTestIFailed("slirp_new failed"); @@ -230,6 +250,47 @@ static void tstVBoxAbi(void) } RTTESTI_CHECK(slirp_version_string() != NULL); + + RTTestISub("Data path: IPv4 gateway ARP"); + + uint8_t abRequest[sizeof(RTNETETHERHDR) + sizeof(RTNETARPIPV4)]; + RT_ZERO(abRequest); + PRTNETETHERHDR pEth = (PRTNETETHERHDR)&abRequest[0]; + memset(&pEth->DstMac, 0xff, sizeof(pEth->DstMac)); + pEth->SrcMac.au8[0] = 0x08; + pEth->SrcMac.au8[1] = 0x00; + pEth->SrcMac.au8[2] = 0x27; + pEth->SrcMac.au8[3] = 0x12; + pEth->SrcMac.au8[4] = 0x34; + pEth->SrcMac.au8[5] = 0x56; + pEth->EtherType = RT_H2N_U16(RTNET_ETHERTYPE_ARP); + + PRTNETARPIPV4 pArp = (PRTNETARPIPV4)&abRequest[sizeof(*pEth)]; + pArp->Hdr.ar_htype = RT_H2N_U16(RTNET_ARP_ETHER); + pArp->Hdr.ar_ptype = RT_H2N_U16(RTNET_ETHERTYPE_IPV4); + pArp->Hdr.ar_hlen = sizeof(RTMAC); + pArp->Hdr.ar_plen = sizeof(RTNETADDRIPV4); + pArp->Hdr.ar_oper = RT_H2N_U16(RTNET_ARPOP_REQUEST); + pArp->ar_sha = pEth->SrcMac; + pArp->ar_spa.u = tstParseIPv4("10.0.2.15").s_addr; + pArp->ar_tpa.u = Cfg.vhost.s_addr; + + RT_ZERO(Output); + slirp_input(pSlirp, abRequest, sizeof(abRequest)); + RTTESTI_CHECK_MSG(Output.cFrames > 0, ("No frame returned for gateway ARP request")); + RTTESTI_CHECK_MSG(Output.cbFrame >= sizeof(RTNETETHERHDR) + sizeof(RTNETARPIPV4), + ("ARP reply is too small: %zu", Output.cbFrame)); + if (Output.cbFrame >= sizeof(RTNETETHERHDR) + sizeof(RTNETARPIPV4)) + { + PCRTNETETHERHDR pReplyEth = (PCRTNETETHERHDR)&Output.abFrame[0]; + PCRTNETARPIPV4 pReplyArp = (PCRTNETARPIPV4)&Output.abFrame[sizeof(*pReplyEth)]; + RTTESTI_CHECK(RT_N2H_U16(pReplyEth->EtherType) == RTNET_ETHERTYPE_ARP); + RTTESTI_CHECK(RT_N2H_U16(pReplyArp->Hdr.ar_oper) == RTNET_ARPOP_REPLY); + RTTESTI_CHECK(pReplyArp->ar_spa.u == Cfg.vhost.s_addr); + RTTESTI_CHECK(pReplyArp->ar_tpa.u == pArp->ar_spa.u); + RTTESTI_CHECK(memcmp(&pReplyArp->ar_tha, &pEth->SrcMac, sizeof(RTMAC)) == 0); + } + slirp_cleanup(pSlirp); } diff --git a/src/VBox/NetworkServices/NetLib/IntNetIf.cpp b/src/VBox/NetworkServices/NetLib/IntNetIf.cpp index 867de809d828..5d9d273b0e1d 100644 --- a/src/VBox/NetworkServices/NetLib/IntNetIf.cpp +++ b/src/VBox/NetworkServices/NetLib/IntNetIf.cpp @@ -1,4 +1,4 @@ -/* $Id: IntNetIf.cpp 114205 2026-05-29 13:56:57Z andreas.loeffler@oracle.com $ */ +/* $Id: IntNetIf.cpp 114964 2026-08-10 15:18:23Z andreas.loeffler@oracle.com $ */ /** @file * IntNetIfCtx - Abstract API implementing an IntNet connection using the R0 support driver or some R3 IPC variant. */ @@ -30,16 +30,32 @@ * Header Files * *********************************************************************************************************************************/ #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) -# if defined(RT_OS_DARWIN) +# if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# define INTNETIF_WITH_R3_SVC_XPC # include /* This needs to be here because it drags PVM in and cdefs.h needs to undefine it... */ +# elif defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC) \ + && (defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(VBOX_INTNET_TESTCASE_LOCALIPC)) +# define INTNETIF_WITH_R3_SVC_LOCALIPC # else -# error "R3 internal networking not implemented for this platform yet!" +# error "No enabled R3 internal networking transport for this platform!" # endif #endif #include +#include +#include #include +#include #include +#include +#include +#include +#if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) +# include +# include +# include +# include +#endif #include #include @@ -70,16 +86,49 @@ typedef struct INTNETIFCTXINT INTNETIFHANDLE hIf; /** The internal network buffer. */ PINTNETBUF pBuf; + /** Whether this context owns one SUPR3Init reference. */ + bool fSupInited; #if defined (VBOX_WITH_INTNET_SERVICE_IN_R3) /** Flag whether this interface is using the internal network switch in userspace path. */ bool fIntNetR3Svc; /** Receive event semaphore. */ RTSEMEVENT hEvtRecv; -# if defined(RT_OS_DARWIN) + /** Whether the context created and owns hEvtRecv. */ + bool fOwnEvtRecv; + /** Set by IntNetR3IfWaitAbort to prevent any subsequent waits. */ + bool volatile fNoMoreWaits; +# if defined(INTNETIF_WITH_R3_SVC_XPC) /** XPC connection handle to the R3 internal network switch service. */ xpc_connection_t hXpcCon; + /** Signalled by XPC's final invalid event after connection cancellation has quiesced callbacks. */ + RTSEMEVENT hEvtXpcCancelled; + /** Size of the communication buffer in bytes. */ + size_t cbBuf; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + /** Local IPC session to the R3 internal network switch service. */ + RTLOCALIPCSESSION hIpcSession; + /** Thread receiving and demultiplexing local IPC replies and notifications. */ + RTTHREAD hIpcRecvThread; + /** Serializes local IPC request/reply calls. */ + RTSEMMUTEX hIpcCallMtx; + /** Serializes short socket read and write operations; data waits happen outside it. */ + RTSEMMUTEX hIpcIoMtx; + /** Signalled by the receiver thread when a synchronous reply arrives. */ + RTSEMEVENT hEvtReply; + /** Shared memory handle backing the communication buffer. */ + RTSHMEM hShMemBuf; /** Size of the communication buffer in bytes. */ size_t cbBuf; + /** Most recently received synchronous reply header. */ + INTNETR3IPCREPLYHDR ReplyHdr; + /** Most recently received synchronous reply payload. */ + uint8_t abReply[INTNET_R3_IPC_MAX_REQ]; + /** Shared memory name accompanying the most recent reply. */ + char szReplyShMemName[INTNET_R3_IPC_MAX_SHMEM_NAME]; + /** Status carried by the most recently received notification. */ + int32_t volatile rcRecvPoke; + /** Receiver thread status, set to a failure when the stream terminates. */ + int32_t volatile rcIpcRecv; # endif #endif } INTNETIFCTXINT; @@ -87,10 +136,606 @@ typedef struct INTNETIFCTXINT typedef INTNETIFCTXINT *PINTNETIFCTXINT; +#ifdef VBOX_INTNET_TESTCASE_LOCALIPC +/** Interface paused by the deterministic Wait/Abort race testcase. */ +static PINTNETIFCTXINT g_pTestWaitRaceIf = NULL; +/** Signalled after the test wait has performed its initial no-more-waits check. */ +static RTSEMEVENT g_hTestWaitRaceReached = NIL_RTSEMEVENT; +/** Releases the paused test wait so it performs the serialized check and send. */ +static RTSEMEVENT g_hTestWaitRaceContinue = NIL_RTSEMEVENT; + + +/** Configures the deterministic Wait/Abort race hook used by tstVBoxIntNetR3Switch. */ +DECLHIDDEN(void) intnetR3IfTestSetWaitRace(INTNETIFCTX hIfCtx, RTSEMEVENT hReached, RTSEMEVENT hContinue) +{ + g_hTestWaitRaceReached = hReached; + g_hTestWaitRaceContinue = hContinue; + g_pTestWaitRaceIf = (PINTNETIFCTXINT)hIfCtx; +} +#endif + + /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ +#if defined(INTNETIF_WITH_R3_SVC_XPC) +/** Cancels the XPC connection and waits until its event handler cannot run again. */ +static void intnetR3IfXpcDisconnect(PINTNETIFCTXINT pThis) +{ + if (pThis->hXpcCon) + { + Assert(pThis->hEvtXpcCancelled != NIL_RTSEMEVENT); + xpc_connection_cancel(pThis->hXpcCon); + int const rc = RTSemEventWait(pThis->hEvtXpcCancelled, RT_INDEFINITE_WAIT); + AssertReleaseRC(rc); + xpc_release(pThis->hXpcCon); + pThis->hXpcCon = NULL; + } + if (pThis->hEvtXpcCancelled != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pThis->hEvtXpcCancelled); + pThis->hEvtXpcCancelled = NIL_RTSEMEVENT; + } +} + + +/** Validates an XPC reply and decodes the VBox status code. */ +static int intnetR3IfXpcGetReplyStatus(xpc_object_t hObjReply, int *prcReq) +{ + AssertPtrReturn(prcReq, VERR_INVALID_POINTER); + *prcReq = VERR_INVALID_STATE; + AssertReturn(hObjReply != NULL, VERR_INVALID_STATE); + + xpc_type_t hType = xpc_get_type(hObjReply); + if (hType == XPC_TYPE_ERROR) + return hObjReply == XPC_ERROR_CONNECTION_INTERRUPTED ? VERR_INTERRUPTED : VERR_NET_CONNECTION_REFUSED; + AssertReturn(hType == XPC_TYPE_DICTIONARY, VERR_INVALID_STATE); + + uint64_t const u64Rc = xpc_dictionary_get_uint64(hObjReply, "rc"); + AssertReturn(INTNET_R3_SVC_IS_VALID_RC(u64Rc), VERR_INVALID_STATE); + *prcReq = INTNET_R3_SVC_GET_RC(u64Rc); + return VINF_SUCCESS; +} +#endif + + +#if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) +# ifndef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH +static int intnetR3IfLocalIpcGetServiceName(char *pszService, size_t cbService) +{ +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestService = RTEnvGet("VBOX_INTNET_R3_SVC_NAME"); + if (pszTestService && *pszTestService) + return RTStrCopy(pszService, cbService, pszTestService); +# endif + + char szUser[256]; + int rc = RTProcQueryUsername(RTProcSelf(), szUser, sizeof(szUser), NULL /*pcbUser*/); + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pszService, cbService, "%s-%08RX32", INTNET_R3_SVC_NAME, RTStrHash1(szUser)); + if (cch < 0 || (size_t)cch >= cbService) + rc = VERR_BUFFER_OVERFLOW; + } + return rc; +} +# endif + + +# ifndef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH +static bool intnetR3IfLocalIpcIsServiceAbsent(int rc) +{ + return rc == VERR_FILE_NOT_FOUND + || rc == VERR_PATH_NOT_FOUND + || rc == VERR_NET_CONNECTION_REFUSED + || rc == VERR_PIPE_NOT_CONNECTED; +} + + +static int intnetR3IfLocalIpcStartService(void) +{ + char szExec[RTPATH_MAX]; +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestExec = RTEnvGet("VBOX_INTNET_R3_SWITCH_EXE"); + int rc = pszTestExec && *pszTestExec ? RTStrCopy(szExec, sizeof(szExec), pszTestExec) + : RTPathExecDir(szExec, sizeof(szExec)); +# else + int rc = RTPathExecDir(szExec, sizeof(szExec)); +# endif + if (RT_FAILURE(rc)) + return rc; +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (!pszTestExec || !*pszTestExec) +# endif + { +# ifdef RT_OS_WINDOWS + rc = RTPathAppend(szExec, sizeof(szExec), "VBoxIntNetSwitch.exe"); +# else + rc = RTPathAppend(szExec, sizeof(szExec), "VBoxIntNetSwitch"); +# endif + if (RT_FAILURE(rc)) + return rc; + } + + const char *apszArgs[] = { szExec, NULL }; + RTHANDLE hStdNil; + hStdNil.enmType = RTHANDLETYPE_FILE; + hStdNil.u.hFile = NIL_RTFILE; + uint32_t fFlags = RTPROC_FLAGS_DETACHED; +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestPidFile = RTEnvGet("VBOX_INTNET_R3_SWITCH_PID_FILE"); + bool const fTestWaitable = pszTestPidFile && *pszTestPidFile; + RTPROCESS hTestProcess = NIL_RTPROCESS; + if (fTestWaitable) + fFlags &= ~RTPROC_FLAGS_DETACHED; +# endif +# ifdef RT_OS_WINDOWS + fFlags |= RTPROC_FLAGS_NO_WINDOW; +# endif + rc = RTProcCreateEx(szExec, apszArgs, RTENV_DEFAULT, fFlags, + &hStdNil, &hStdNil, &hStdNil, NULL /*pszAsUser*/, NULL /*pszPassword*/, + NULL /*pvExtraData*/, +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + fTestWaitable ? &hTestProcess : NULL /*phProcess*/); + if (RT_SUCCESS(rc) && fTestWaitable) + { + RTFILE hPidFile = NIL_RTFILE; + rc = RTFileOpen(&hPidFile, pszTestPidFile, RTFILE_O_WRITE | RTFILE_O_CREATE | RTFILE_O_DENY_ALL + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + char szPid[32]; + ssize_t const cchPid = RTStrPrintf2(szPid, sizeof(szPid), "%RU32", hTestProcess); + if (cchPid > 0 && (size_t)cchPid < sizeof(szPid)) + rc = RTFileWrite(hPidFile, szPid, (size_t)cchPid, NULL /*pcbWritten*/); + else + rc = VERR_BUFFER_OVERFLOW; + int const rc2 = RTFileClose(hPidFile); + if (RT_SUCCESS(rc)) + rc = rc2; + } + if (RT_FAILURE(rc)) + { + RTProcTerminate(hTestProcess); + RTProcWait(hTestProcess, RTPROCWAIT_FLAGS_BLOCK, NULL /*pProcStatus*/); + } + } +# else + NULL /*phProcess*/); +# endif + return rc; +} +# endif + + +/** Returns the absolute frame-read timeout, shortened only by local IPC testcases. */ +static uint32_t intnetR3IfLocalIpcGetFrameTimeout(void) +{ +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestTimeout = RTEnvGet("VBOX_INTNET_R3_TEST_READ_TIMEOUT_MS"); + if (pszTestTimeout && *pszTestTimeout) + { + uint32_t cMsTimeout = 0; + if ( RT_SUCCESS(RTStrToUInt32Full(pszTestTimeout, 10, &cMsTimeout)) + && cMsTimeout > 0) + return cMsTimeout; + } +# endif + return INTNET_R3_IPC_FRAME_TIMEOUT_MS; +} + + +/** Returns the synchronous reply wait timeout, overridden independently only by local IPC testcases. */ +static uint32_t intnetR3IfLocalIpcGetReplyTimeout(void) +{ +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + const char *pszTestTimeout = RTEnvGet("VBOX_INTNET_R3_TEST_REPLY_TIMEOUT_MS"); + if (pszTestTimeout && *pszTestTimeout) + { + uint32_t cMsTimeout = 0; + if ( RT_SUCCESS(RTStrToUInt32Full(pszTestTimeout, 10, &cMsTimeout)) + && cMsTimeout > 0) + return cMsTimeout; + } +# endif + return intnetR3IfLocalIpcGetFrameTimeout(); +} + + +/** Reads exactly @a cbToRead bytes without letting partial progress reset the deadline. */ +static int intnetR3IfLocalIpcReadExact(PINTNETIFCTXINT pThis, void *pvBuf, size_t cbToRead, uint64_t msDeadline) +{ + uint8_t *pbDst = (uint8_t *)pvBuf; + while (cbToRead > 0) + { + size_t cbRead = 0; + int rc = RTLocalIpcSessionReadNB(pThis->hIpcSession, pbDst, cbToRead, &cbRead); + if (rc == VINF_SUCCESS && cbRead > 0) + { + AssertReturn(cbRead <= cbToRead, VERR_INTERNAL_ERROR); + pbDst += cbRead; + cbToRead -= cbRead; + continue; + } + if (rc != VINF_SUCCESS && rc != VINF_TRY_AGAIN) + return rc; + + uint64_t const msNow = RTTimeMilliTS(); + if (msNow >= msDeadline) + return VERR_TIMEOUT; + uint64_t const cMsLeft = msDeadline - msNow; + rc = RTLocalIpcSessionWaitForData(pThis->hIpcSession, + (uint32_t)RT_MIN(cMsLeft, (uint64_t)UINT32_MAX)); + if (RT_FAILURE(rc)) + return rc; + } + return VINF_SUCCESS; +} + + +/** Local IPC receiver thread. */ +static DECLCALLBACK(int) intnetR3IfLocalIpcRecvThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + PINTNETIFCTXINT pThis = (PINTNETIFCTXINT)pvUser; + uint32_t const cMsFrameTimeout = intnetR3IfLocalIpcGetFrameTimeout(); + int rc = VINF_SUCCESS; + + for (;;) + { + rc = RTLocalIpcSessionWaitForData(pThis->hIpcSession, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + rc = RTSemMutexRequest(pThis->hIpcIoMtx, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + break; + + uint64_t const msDeadline = RTTimeMilliTS() + cMsFrameTimeout; + INTNETR3IPCREPLYHDR Hdr; + rc = intnetR3IfLocalIpcReadExact(pThis, &Hdr, sizeof(Hdr), msDeadline); + if (RT_FAILURE(rc)) + { + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + if ( Hdr.u16Version != INTNET_R3_IPC_VERSION + || Hdr.cbHdr != sizeof(Hdr)) + { + rc = VERR_VERSION_MISMATCH; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + + if (Hdr.u32Magic == INTNET_R3_IPC_POKE_MAGIC) + { + if (Hdr.cbReply != 0 || Hdr.cbShMemName != 0 || Hdr.cbShMem != 0) + { + rc = VERR_INVALID_PARAMETER; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + ASMAtomicWriteS32(&pThis->rcRecvPoke, Hdr.rc); + RTSemEventSignal(pThis->hEvtRecv); + } + else if (Hdr.u32Magic == INTNET_R3_IPC_REPLY_MAGIC) + { + if ( Hdr.cbReply > sizeof(pThis->abReply) + || Hdr.cbShMemName > sizeof(pThis->szReplyShMemName)) + { + rc = VERR_BUFFER_OVERFLOW; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + + if (Hdr.cbReply) + { + rc = intnetR3IfLocalIpcReadExact(pThis, pThis->abReply, Hdr.cbReply, msDeadline); + if (RT_FAILURE(rc)) + { + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + } + pThis->szReplyShMemName[0] = '\0'; + if (Hdr.cbShMemName) + { + rc = intnetR3IfLocalIpcReadExact(pThis, pThis->szReplyShMemName, Hdr.cbShMemName, msDeadline); + if (RT_FAILURE(rc)) + { + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + if (pThis->szReplyShMemName[Hdr.cbShMemName - 1] != '\0') + { + rc = VERR_INVALID_PARAMETER; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + } + pThis->ReplyHdr = Hdr; + RTSemEventSignal(pThis->hEvtReply); + } + else + { + rc = VERR_INVALID_MAGIC; + RTSemMutexRelease(pThis->hIpcIoMtx); + break; + } + RTSemMutexRelease(pThis->hIpcIoMtx); + } + + ASMAtomicWriteS32(&pThis->rcIpcRecv, rc); + RTSemEventSignal(pThis->hEvtRecv); + RTSemEventSignal(pThis->hEvtReply); + return rc; +} + + +/** Stops the receiver and closes a failed or no-longer-needed local IPC transport. */ +static void intnetR3IfLocalIpcRetireSession(PINTNETIFCTXINT pThis) +{ + if ( pThis->hIpcSession != NIL_RTLOCALIPCSESSION + && pThis->hIpcRecvThread != NIL_RTTHREAD) + RTLocalIpcSessionCancel(pThis->hIpcSession); + if (pThis->hIpcRecvThread != NIL_RTTHREAD) + { + int const rcThread = RTThreadWait(pThis->hIpcRecvThread, RT_INDEFINITE_WAIT, NULL); + AssertRC(rcThread); + pThis->hIpcRecvThread = NIL_RTTHREAD; + } + if (pThis->hIpcSession != NIL_RTLOCALIPCSESSION) + { + RTLocalIpcSessionClose(pThis->hIpcSession); + pThis->hIpcSession = NIL_RTLOCALIPCSESSION; + } +} + + +static int intnetR3IfLocalIpcSendReq(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMMR0REQHDR pReqHdr) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + AssertPtrReturn(pReqHdr, VERR_INVALID_POINTER); + int const rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + { + intnetR3IfLocalIpcRetireSession(pThis); + return rcRecv; + } + AssertReturn(pThis->hIpcSession != NIL_RTLOCALIPCSESSION, VERR_INVALID_HANDLE); + AssertReturn(pReqHdr->cbReq >= sizeof(*pReqHdr), VERR_INVALID_PARAMETER); + AssertReturn(pReqHdr->cbReq <= INTNET_R3_IPC_MAX_REQ, VERR_OUT_OF_RANGE); + + size_t const cbMsg = sizeof(INTNETR3IPCREQHDR) + pReqHdr->cbReq; + uint8_t *pbMsg = (uint8_t *)RTMemTmpAlloc(cbMsg); + AssertReturn(pbMsg, VERR_NO_TMP_MEMORY); + + PINTNETR3IPCREQHDR pHdr = (PINTNETR3IPCREQHDR)pbMsg; + pHdr->u32Magic = INTNET_R3_IPC_REQ_MAGIC; + pHdr->u16Version = INTNET_R3_IPC_VERSION; + pHdr->cbHdr = sizeof(*pHdr); + pHdr->cbReq = pReqHdr->cbReq; + pHdr->uOperation = uOperation; + memcpy(pbMsg + sizeof(*pHdr), pReqHdr, pReqHdr->cbReq); + + int rc = RTSemMutexRequest(pThis->hIpcIoMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + int const rcRecvNow = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecvNow)) + rc = rcRecvNow; + else + { + rc = RTLocalIpcSessionWrite(pThis->hIpcSession, pbMsg, cbMsg); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(pThis->hIpcSession); + } + RTSemMutexRelease(pThis->hIpcIoMtx); + if (RT_FAILURE(rcRecvNow)) + intnetR3IfLocalIpcRetireSession(pThis); + } + RTMemTmpFree(pbMsg); + return rc; +} + +static int intnetR3IfLocalIpcReadReply(PINTNETIFCTXINT pThis, void *pvReply, size_t cbReplyMax, size_t *pcbReply, + char *pszShMemName, size_t cbShMemName, size_t *pcbShMem) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + if (pcbReply) + *pcbReply = 0; + if (pszShMemName && cbShMemName) + *pszShMemName = '\0'; + if (pcbShMem) + *pcbShMem = 0; + + int rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + { + intnetR3IfLocalIpcRetireSession(pThis); + return rcRecv; + } + + int rc = RTSemEventWait(pThis->hEvtReply, intnetR3IfLocalIpcGetReplyTimeout()); + if (rc == VERR_TIMEOUT) + { + ASMAtomicCmpXchgS32(&pThis->rcIpcRecv, VERR_TIMEOUT, VINF_SUCCESS); + intnetR3IfLocalIpcRetireSession(pThis); + return rc; + } + if (RT_FAILURE(rc)) + return rc; + + rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + { + intnetR3IfLocalIpcRetireSession(pThis); + return rcRecv; + } + + PCINTNETR3IPCREPLYHDR pHdr = &pThis->ReplyHdr; + AssertReturn(pHdr->cbReply <= cbReplyMax, VERR_BUFFER_OVERFLOW); + AssertReturn(!pHdr->cbShMemName || (pszShMemName && pHdr->cbShMemName <= cbShMemName), VERR_BUFFER_OVERFLOW); + AssertReturn(pHdr->cbShMem == (size_t)pHdr->cbShMem, VERR_OUT_OF_RANGE); + if (pHdr->cbReply) + { + AssertPtrReturn(pvReply, VERR_INVALID_POINTER); + memcpy(pvReply, pThis->abReply, pHdr->cbReply); + } + if (pcbReply) + *pcbReply = pHdr->cbReply; + if (pHdr->cbShMemName) + memcpy(pszShMemName, pThis->szReplyShMemName, pHdr->cbShMemName); + if (pcbShMem) + *pcbShMem = (size_t)pHdr->cbShMem; + return pHdr->rc; +} + +static int intnetR3IfLocalIpcReadPoke(PINTNETIFCTXINT pThis, uint32_t cMillies) +{ + AssertPtrReturn(pThis, VERR_INVALID_POINTER); + int rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + return rcRecv; + int rc = RTSemEventWait(pThis->hEvtRecv, cMillies); + if (RT_SUCCESS(rc)) + { + rcRecv = ASMAtomicReadS32(&pThis->rcIpcRecv); + if (RT_FAILURE(rcRecv)) + return rcRecv; + rc = ASMAtomicReadS32(&pThis->rcRecvPoke); + } + return rc; +} + + +/** Executes a synchronous local IPC call while the caller owns hIpcCallMtx. */ +static int intnetR3IfLocalIpcCallLocked(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMMR0REQHDR pReqHdr) +{ + size_t const cbReq = pReqHdr->cbReq; + int rc = intnetR3IfLocalIpcSendReq(pThis, uOperation, pReqHdr); + if (RT_SUCCESS(rc)) + { + size_t cbReply = 0; + rc = intnetR3IfLocalIpcReadReply(pThis, pReqHdr, cbReq, &cbReply, NULL, 0, NULL); + AssertStmt(RT_FAILURE(rc) || cbReply == cbReq, rc = VERR_INVALID_PARAMETER); + } + return rc; +} + + +static bool intnetR3IfLocalIpcIsRingValid(PCINTNETBUF pBuf, INTNETRINGBUF const *pRing, + uint32_t offRing, uint32_t cbRing, uint32_t offExpected) +{ + uint64_t const offStart = (uint64_t)offRing + pRing->offStart; + uint64_t const offEnd = (uint64_t)offRing + pRing->offEnd; + return offStart == offExpected + && offEnd == offStart + cbRing + && offEnd <= pBuf->cbBuf + && pRing->offReadX >= pRing->offStart + && pRing->offReadX < pRing->offEnd + && pRing->offWriteCom >= pRing->offStart + && pRing->offWriteCom < pRing->offEnd + && pRing->offWriteInt >= pRing->offStart + && pRing->offWriteInt < pRing->offEnd + && RT_ALIGN_32(pRing->offReadX, INTNETHDR_ALIGNMENT) == pRing->offReadX + && RT_ALIGN_32(pRing->offWriteCom, INTNETHDR_ALIGNMENT) == pRing->offWriteCom + && RT_ALIGN_32(pRing->offWriteInt, INTNETHDR_ALIGNMENT) == pRing->offWriteInt; +} + + +static bool intnetR3IfLocalIpcIsBufferValid(PCINTNETBUF pBuf, size_t cbMapped) +{ + if ( pBuf->u32Magic != INTNETBUF_MAGIC + || pBuf->cbBuf != cbMapped + || pBuf->cbBuf < sizeof(*pBuf) + || pBuf->cbRecv < INTNETHDR_ALIGNMENT + || pBuf->cbSend < INTNETHDR_ALIGNMENT + || RT_ALIGN_32(pBuf->cbRecv, INTNETRINGBUF_ALIGNMENT) != pBuf->cbRecv + || RT_ALIGN_32(pBuf->cbSend, INTNETRINGBUF_ALIGNMENT) != pBuf->cbSend) + return false; + + uint32_t const offRecv = RT_UOFFSETOF(INTNETBUF, Recv); + uint32_t const offSend = RT_UOFFSETOF(INTNETBUF, Send); + uint32_t const offRecvStart = RT_ALIGN_32(sizeof(*pBuf), INTNETRINGBUF_ALIGNMENT); + uint64_t const offSendStart = (uint64_t)offRecvStart + pBuf->cbRecv; + if (offSendStart > UINT32_MAX) + return false; + return intnetR3IfLocalIpcIsRingValid(pBuf, &pBuf->Recv, offRecv, pBuf->cbRecv, offRecvStart) + && intnetR3IfLocalIpcIsRingValid(pBuf, &pBuf->Send, offSend, pBuf->cbSend, (uint32_t)offSendStart); +} + + +static void intnetR3IfLocalIpcDisconnect(PINTNETIFCTXINT pThis) +{ + intnetR3IfLocalIpcRetireSession(pThis); + if (pThis->hEvtReply != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pThis->hEvtReply); + pThis->hEvtReply = NIL_RTSEMEVENT; + } + if (pThis->hIpcCallMtx != NIL_RTSEMMUTEX) + { + RTSemMutexDestroy(pThis->hIpcCallMtx); + pThis->hIpcCallMtx = NIL_RTSEMMUTEX; + } + if (pThis->hIpcIoMtx != NIL_RTSEMMUTEX) + { + RTSemMutexDestroy(pThis->hIpcIoMtx); + pThis->hIpcIoMtx = NIL_RTSEMMUTEX; + } +} + + +static int intnetR3IfLocalIpcConnect(PINTNETIFCTXINT pThis, const char *pszService) +{ + int rc = RTSemEventCreate(&pThis->hEvtReply); + if (RT_SUCCESS(rc)) + rc = RTSemMutexCreate(&pThis->hIpcCallMtx); + if (RT_SUCCESS(rc)) + rc = RTSemMutexCreate(&pThis->hIpcIoMtx); + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcSessionConnect(&pThis->hIpcSession, pszService, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); +# ifndef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH + if (intnetR3IfLocalIpcIsServiceAbsent(rc)) + { + int const rcStart = intnetR3IfLocalIpcStartService(); + if (RT_SUCCESS(rcStart)) + { + uint64_t const msStart = RTTimeMilliTS(); + do + { + RTThreadSleep(10); + rc = RTLocalIpcSessionConnect(&pThis->hIpcSession, pszService, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + } while ( ( intnetR3IfLocalIpcIsServiceAbsent(rc) + || rc == VERR_ACCESS_DENIED /* Endpoint security setup may still be completing. */) + && RTTimeMilliTS() - msStart < RT_MS_5SEC); + } + else + rc = rcStart; + } +# endif + } + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionVerifySameUser(pThis->hIpcSession); +# ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; +# endif + if (RT_SUCCESS(rc)) + rc = RTThreadCreate(&pThis->hIpcRecvThread, intnetR3IfLocalIpcRecvThread, pThis, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetRx"); + if (RT_FAILURE(rc)) + intnetR3IfLocalIpcDisconnect(pThis); + return rc; +} +#endif /* INTNETIF_WITH_R3_SVC_LOCALIPC */ + /** * Calls the internal networking switch service living in either R0 or in another R3 process. * @@ -104,23 +749,42 @@ static int intnetR3IfCallSvc(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMM #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) if (pThis->fIntNetR3Svc) { -# if defined(RT_OS_DARWIN) - size_t cbReq = pReqHdr->cbReq; +# if defined(INTNETIF_WITH_R3_SVC_XPC) + size_t const cbReq = pReqHdr->cbReq; xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); + AssertReturn(hObj != NULL, VERR_NO_MEMORY); xpc_dictionary_set_uint64(hObj, "req-id", uOperation); xpc_dictionary_set_data(hObj, "req", pReqHdr, pReqHdr->cbReq); xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj); xpc_release(hObj); - int rc = (int)xpc_dictionary_get_int64(hObjReply, "rc"); - - size_t cbReply = 0; - const void *pvData = xpc_dictionary_get_data(hObjReply, "reply", &cbReply); - AssertRelease(cbReply == cbReq); - memcpy(pReqHdr, pvData, cbReq); - xpc_release(hObjReply); - + int rcReq = VERR_INVALID_STATE; + int rc = intnetR3IfXpcGetReplyStatus(hObjReply, &rcReq); + if (RT_SUCCESS(rc)) + { + size_t cbReply = 0; + const void *pvData = xpc_dictionary_get_data(hObjReply, "reply", &cbReply); + if (pvData && cbReply == cbReq) + { + memcpy(pReqHdr, pvData, cbReq); + rc = rcReq; + } + else + rc = VERR_INVALID_PARAMETER; + } + if (hObjReply) + xpc_release(hObjReply); return rc; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + int rc = RTSemMutexRequest(pThis->hIpcCallMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + rc = intnetR3IfLocalIpcCallLocked(pThis, uOperation, pReqHdr); + RTSemMutexRelease(pThis->hIpcCallMtx); + } + return rc; +# else + return VERR_SUP_DRIVERLESS; # endif } else @@ -131,7 +795,7 @@ static int intnetR3IfCallSvc(PINTNETIFCTXINT pThis, uint32_t uOperation, PSUPVMM } -#if defined(RT_OS_DARWIN) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3) +#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) /** * Calls the internal networking switch service living in either R0 or in another R3 process. * @@ -144,12 +808,28 @@ static int intnetR3IfCallSvcAsync(PINTNETIFCTXINT pThis, uint32_t uOperation, PS { if (pThis->fIntNetR3Svc) { +# if defined(INTNETIF_WITH_R3_SVC_XPC) xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); xpc_dictionary_set_uint64(hObj, "req-id", uOperation); xpc_dictionary_set_data(hObj, "req", pReqHdr, pReqHdr->cbReq); xpc_connection_send_message(pThis->hXpcCon, hObj); xpc_release(hObj); return VINF_SUCCESS; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + int rc = RTSemMutexRequest(pThis->hIpcCallMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + if ( uOperation == VMMR0_DO_INTNET_IF_WAIT + && ASMAtomicReadBool(&pThis->fNoMoreWaits)) + rc = VERR_SEM_DESTROYED; + else + rc = intnetR3IfLocalIpcSendReq(pThis, uOperation, pReqHdr); + RTSemMutexRelease(pThis->hIpcCallMtx); + } + return rc; +# else + return VERR_SUP_DRIVERLESS; +# endif } else return SUPR3CallVMMR0Ex(NIL_RTR0PTR, NIL_VMCPUID, uOperation, 0, pReqHdr); @@ -178,26 +858,94 @@ static int intnetR3IfMapBufferPointers(PINTNETIFCTXINT pThis) #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) if (pThis->fIntNetR3Svc) { -#if defined(RT_OS_DARWIN) +# if defined(INTNETIF_WITH_R3_SVC_XPC) xpc_object_t hObj = xpc_dictionary_create(NULL, NULL, 0); + AssertReturn(hObj != NULL, VERR_NO_MEMORY); xpc_dictionary_set_uint64(hObj, "req-id", VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS); xpc_dictionary_set_data(hObj, "req", &GetBufferPtrsReq, sizeof(GetBufferPtrsReq)); xpc_object_t hObjReply = xpc_connection_send_message_with_reply_sync(pThis->hXpcCon, hObj); xpc_release(hObj); - rc = (int)xpc_dictionary_get_int64(hObjReply, "rc"); + int rcReq = VERR_INVALID_STATE; + rc = intnetR3IfXpcGetReplyStatus(hObjReply, &rcReq); if (RT_SUCCESS(rc)) { - /* Get the shared memory object. */ - xpc_object_t hObjShMem = xpc_dictionary_get_value(hObjReply, "buf-ptr"); - size_t cbMem = xpc_shmem_map(hObjShMem, (void **)&pThis->pBuf); - if (!cbMem) - rc = VERR_NO_MEMORY; - else - pThis->cbBuf = cbMem; + rc = rcReq; + if (RT_SUCCESS(rc)) + { + /* Get the shared memory object. */ + xpc_object_t hObjShMem = xpc_dictionary_get_value(hObjReply, "buf-ptr"); + if (hObjShMem && xpc_get_type(hObjShMem) == XPC_TYPE_SHMEM) + { + size_t const cbMem = xpc_shmem_map(hObjShMem, (void **)&pThis->pBuf); + if (cbMem) + pThis->cbBuf = cbMem; + else + rc = VERR_NO_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + } } - xpc_release(hObjReply); -#endif + if (hObjReply) + xpc_release(hObjReply); +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + char szShMemName[INTNET_R3_IPC_MAX_SHMEM_NAME]; + size_t cbReply = 0; + size_t cbShMem = 0; + rc = RTSemMutexRequest(pThis->hIpcCallMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + rc = intnetR3IfLocalIpcSendReq(pThis, VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS, &GetBufferPtrsReq.Hdr); + if (RT_SUCCESS(rc)) + rc = intnetR3IfLocalIpcReadReply(pThis, &GetBufferPtrsReq, sizeof(GetBufferPtrsReq), &cbReply, + szShMemName, sizeof(szShMemName), &cbShMem); + RTSemMutexRelease(pThis->hIpcCallMtx); + } + if (RT_FAILURE(rc)) + return rc; + AssertReturn(cbReply == sizeof(GetBufferPtrsReq), VERR_INVALID_PARAMETER); + AssertReturn(cbShMem >= sizeof(INTNETBUF), VERR_INVALID_PARAMETER); + AssertReturn(cbShMem <= UINT32_MAX, VERR_OUT_OF_RANGE); + AssertReturn(szShMemName[0] != '\0', VERR_INVALID_PARAMETER); + + RTSHMEM hShMem = NIL_RTSHMEM; + rc = RTShMemOpen(&hShMem, szShMemName, RTSHMEM_O_F_READWRITE, 0 /*cbMax*/, 1 /*cMappingsHint*/); + if (RT_FAILURE(rc)) + return rc; + + size_t cbShMemActual = 0; + rc = RTShMemQuerySize(hShMem, &cbShMemActual); + if (RT_SUCCESS(rc) && cbShMemActual < cbShMem) + rc = VERR_INVALID_PARAMETER; + if (RT_FAILURE(rc)) + { + RTShMemClose(hShMem); + return rc; + } + + void *pvBuf = NULL; + rc = RTShMemMapRegion(hShMem, 0 /*off*/, cbShMem, RTSHMEM_MAP_F_READ | RTSHMEM_MAP_F_WRITE, &pvBuf); + if (RT_FAILURE(rc)) + { + RTShMemClose(hShMem); + return rc; + } + + PINTNETBUF pBuf = (PINTNETBUF)pvBuf; + if (!intnetR3IfLocalIpcIsBufferValid(pBuf, cbShMem)) + { + RTShMemUnmapRegion(hShMem, pvBuf); + RTShMemClose(hShMem); + return VERR_INVALID_PARAMETER; + } + + pThis->hShMemBuf = hShMem; + pThis->pBuf = pBuf; + pThis->cbBuf = cbShMem; +# else + rc = VERR_SUP_DRIVERLESS; +# endif } else #endif @@ -226,7 +974,12 @@ static void intnetR3IfClose(PINTNETIFCTXINT pThis) pThis->hIf = INTNET_HANDLE_INVALID; int rc = intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_CLOSE, &CloseReq.Hdr); +#if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + AssertMsg(RT_SUCCESS(rc) || RT_FAILURE(ASMAtomicReadS32(&pThis->rcIpcRecv)), ("%Rrc\n", rc)); +#else AssertRC(rc); +#endif + RT_NOREF(rc); } } @@ -238,48 +991,123 @@ DECLHIDDEN(int) IntNetR3IfCreate(PINTNETIFCTX phIfCtx, const char *pszNetwork) } -DECLHIDDEN(int) IntNetR3IfCreateEx(PINTNETIFCTX phIfCtx, const char *pszNetwork, INTNETTRUNKTYPE enmTrunkType, - const char *pszTrunk, uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags) +/** Worker for IntNetR3IfCreateEx and IntNetR3IfCreateExWithRecvEvent. */ +static int intnetR3IfCreateExWorker(PINTNETIFCTX phIfCtx, const char *pszNetwork, INTNETTRUNKTYPE enmTrunkType, + const char *pszTrunk, uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags, + RTSEMEVENT hEvtRecv) { AssertPtrReturn(phIfCtx, VERR_INVALID_POINTER); AssertPtrReturn(pszNetwork, VERR_INVALID_POINTER); AssertPtrReturn(pszTrunk, VERR_INVALID_POINTER); + *phIfCtx = NULL; + +#if !defined(VBOX_WITH_INTNET_SERVICE_IN_R3) + RT_NOREF(hEvtRecv); +#endif PSUPDRVSESSION pSession = NIL_RTR0PTR; + bool fSupInited = false; +#if defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) || defined(VBOX_INTNET_TESTCASE_FORCE_R3) + /* For R3-only testcases, avoid SUPR3Init to prevent SCM operations + and force the driverless R3 service path. */ + int rc = VINF_SUCCESS; +#else int rc = SUPR3Init(&pSession); + if (RT_SUCCESS(rc)) + fSupInited = true; +#endif if (RT_SUCCESS(rc)) { PINTNETIFCTXINT pThis = (PINTNETIFCTXINT)RTMemAllocZ(sizeof(*pThis)); if (RT_LIKELY(pThis)) { pThis->pSupDrvSession = pSession; + pThis->hIf = INTNET_HANDLE_INVALID; + pThis->fSupInited = fSupInited; #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) - pThis->hEvtRecv = NIL_RTSEMEVENT; + pThis->hEvtRecv = hEvtRecv; +# if defined(INTNETIF_WITH_R3_SVC_XPC) + pThis->hEvtXpcCancelled = NIL_RTSEMEVENT; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + pThis->hIpcSession = NIL_RTLOCALIPCSESSION; + pThis->hIpcRecvThread = NIL_RTTHREAD; + pThis->hIpcCallMtx = NIL_RTSEMMUTEX; + pThis->hIpcIoMtx = NIL_RTSEMMUTEX; + pThis->hEvtReply = NIL_RTSEMEVENT; + pThis->hShMemBuf = NIL_RTSHMEM; + pThis->rcRecvPoke = VINF_SUCCESS; + pThis->rcIpcRecv = VINF_SUCCESS; +# endif #endif /* Driverless operation needs support for running the internal network switch using IPC. */ - if (SUPR3IsDriverless()) +#if defined(VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH) || defined(VBOX_INTNET_TESTCASE_FORCE_R3) + bool const fDriverless = true; +#else + bool const fDriverless = SUPR3IsDriverless(); +#endif + if (fDriverless) { #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) -# if defined(RT_OS_DARWIN) - xpc_connection_t hXpcCon = xpc_connection_create(INTNET_R3_SVC_NAME, NULL); - xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) { - if (xpc_get_type(hObj) == XPC_TYPE_ERROR) + if (pThis->hEvtRecv == NIL_RTSEMEVENT) + { + rc = RTSemEventCreate(&pThis->hEvtRecv); + if (RT_SUCCESS(rc)) + pThis->fOwnEvtRecv = true; + } + if (RT_SUCCESS(rc)) + { +# if defined(INTNETIF_WITH_R3_SVC_XPC) + rc = RTSemEventCreate(&pThis->hEvtXpcCancelled); + if (RT_SUCCESS(rc)) { - /** @todo Error handling - reconnecting. */ + xpc_connection_t hXpcCon = xpc_connection_create(INTNET_R3_SVC_NAME, NULL); + if (hXpcCon) + { + pThis->hXpcCon = hXpcCon; + xpc_connection_set_event_handler(hXpcCon, ^(xpc_object_t hObj) { + if (xpc_get_type(hObj) == XPC_TYPE_ERROR) + { + if (hObj == XPC_ERROR_CONNECTION_INVALID) + { + int const rc2 = RTSemEventSignal(pThis->hEvtXpcCancelled); + AssertRC(rc2); + } + /** @todo Error handling - reconnecting. */ + } + else + RTSemEventSignal(pThis->hEvtRecv); + }); + xpc_connection_resume(hXpcCon); + } + else + rc = VERR_NO_MEMORY; } +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) +# ifdef VBOX_INTNET_TESTCASE_EMBEDDED_SWITCH + const char *pszService = RTEnvGet("VBOX_INTNET_R3_SVC_NAME"); + if (!pszService || !*pszService) + pszService = INTNET_R3_SVC_NAME; + rc = intnetR3IfLocalIpcConnect(pThis, pszService); +# else + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; + rc = intnetR3IfLocalIpcGetServiceName(szService, sizeof(szService)); + if (RT_SUCCESS(rc)) + rc = intnetR3IfLocalIpcConnect(pThis, szService); +# endif +# else + rc = VERR_SUP_DRIVERLESS; +# endif + if (RT_SUCCESS(rc)) + pThis->fIntNetR3Svc = true; else { - /* Out of band messages should only come when there is something to receive. */ - RTSemEventSignal(pThis->hEvtRecv); + if (pThis->fOwnEvtRecv) + RTSemEventDestroy(pThis->hEvtRecv); + pThis->hEvtRecv = NIL_RTSEMEVENT; + pThis->fOwnEvtRecv = false; } - }); - - xpc_connection_resume(hXpcCon); - pThis->hXpcCon = hXpcCon; -# endif - pThis->fIntNetR3Svc = true; - rc = RTSemEventCreate(&pThis->hEvtRecv); + } #else rc = VERR_SUP_DRIVERLESS; #endif @@ -338,27 +1166,58 @@ DECLHIDDEN(int) IntNetR3IfCreateEx(PINTNETIFCTX phIfCtx, const char *pszNetwork, #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) if (pThis->fIntNetR3Svc) { -# if defined(RT_OS_DARWIN) - if (pThis->hXpcCon) - xpc_connection_cancel(pThis->hXpcCon); - pThis->hXpcCon = NULL; +# if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + if (pThis->pBuf && pThis->hShMemBuf != NIL_RTSHMEM) + { + RTShMemUnmapRegion(pThis->hShMemBuf, pThis->pBuf); + pThis->pBuf = NULL; + pThis->cbBuf = 0; + } + if (pThis->hShMemBuf != NIL_RTSHMEM) + { + RTShMemClose(pThis->hShMemBuf); + pThis->hShMemBuf = NIL_RTSHMEM; + } + intnetR3IfLocalIpcDisconnect(pThis); # endif - - if (pThis->hEvtRecv != NIL_RTSEMEVENT) - RTSemEventDestroy(pThis->hEvtRecv); } +# if defined(INTNETIF_WITH_R3_SVC_XPC) + intnetR3IfXpcDisconnect(pThis); +# endif + if (pThis->fOwnEvtRecv && pThis->hEvtRecv != NIL_RTSEMEVENT) + RTSemEventDestroy(pThis->hEvtRecv); #endif - RTMemFree(pThis); } + else + rc = VERR_NO_MEMORY; + } + if (fSupInited) SUPR3Term(); - } return rc; } +DECLHIDDEN(int) IntNetR3IfCreateEx(PINTNETIFCTX phIfCtx, const char *pszNetwork, INTNETTRUNKTYPE enmTrunkType, + const char *pszTrunk, uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags) +{ + return intnetR3IfCreateExWorker(phIfCtx, pszNetwork, enmTrunkType, pszTrunk, cbSend, cbRecv, fFlags, + NIL_RTSEMEVENT); +} + + +DECLHIDDEN(int) IntNetR3IfCreateExWithRecvEvent(PINTNETIFCTX phIfCtx, const char *pszNetwork, + INTNETTRUNKTYPE enmTrunkType, const char *pszTrunk, + uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags, + RTSEMEVENT hEvtRecv) +{ + AssertReturn(hEvtRecv != NIL_RTSEMEVENT, VERR_INVALID_HANDLE); + return intnetR3IfCreateExWorker(phIfCtx, pszNetwork, enmTrunkType, pszTrunk, cbSend, cbRecv, fFlags, hEvtRecv); +} + + DECLHIDDEN(int) IntNetR3IfDestroy(INTNETIFCTX hIfCtx) { PINTNETIFCTXINT pThis = hIfCtx; @@ -369,18 +1228,51 @@ DECLHIDDEN(int) IntNetR3IfDestroy(INTNETIFCTX hIfCtx) #if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) if (pThis->fIntNetR3Svc) { -# if defined(RT_OS_DARWIN) +# if defined(INTNETIF_WITH_R3_SVC_XPC) /* Unmap the shared buffer. */ munmap(pThis->pBuf, pThis->cbBuf); - xpc_connection_cancel(pThis->hXpcCon); - pThis->hXpcCon = NULL; + pThis->pBuf = NULL; + pThis->cbBuf = 0; +# elif defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + if (pThis->pBuf && pThis->hShMemBuf != NIL_RTSHMEM) + { + RTShMemUnmapRegion(pThis->hShMemBuf, pThis->pBuf); + pThis->pBuf = NULL; + pThis->cbBuf = 0; + } + if (pThis->hShMemBuf != NIL_RTSHMEM) + { + RTShMemClose(pThis->hShMemBuf); + pThis->hShMemBuf = NIL_RTSHMEM; + } + intnetR3IfLocalIpcDisconnect(pThis); +# endif +# if defined(INTNETIF_WITH_R3_SVC_XPC) + intnetR3IfXpcDisconnect(pThis); # endif - RTSemEventDestroy(pThis->hEvtRecv); + if (pThis->fOwnEvtRecv) + RTSemEventDestroy(pThis->hEvtRecv); + pThis->hEvtRecv = NIL_RTSEMEVENT; + pThis->fOwnEvtRecv = false; pThis->fIntNetR3Svc = false; } #endif + bool const fSupInited = pThis->fSupInited; RTMemFree(pThis); + if (fSupInited) + SUPR3Term(); + return VINF_SUCCESS; +} + + +DECLHIDDEN(int) IntNetR3IfQueryHandle(INTNETIFCTX hIfCtx, PINTNETIFHANDLE phIf) +{ + PINTNETIFCTXINT pThis = hIfCtx; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(phIf, VERR_INVALID_POINTER); + + *phIf = pThis->hIf; return VINF_SUCCESS; } @@ -426,6 +1318,22 @@ DECLHIDDEN(int) IntNetR3IfSetPromiscuous(INTNETIFCTX hIfCtx, bool fPromiscuous) } +DECLHIDDEN(int) IntNetR3IfSetMacAddress(INTNETIFCTX hIfCtx, PCRTMAC pMac) +{ + PINTNETIFCTXINT pThis = hIfCtx; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertPtrReturn(pMac, VERR_INVALID_POINTER); + + INTNETIFSETMACADDRESSREQ Req; + Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + Req.Hdr.cbReq = sizeof(Req); + Req.pSession = pThis->pSupDrvSession; + Req.hIf = pThis->hIf; + Req.Mac = *pMac; + return intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_SET_MAC_ADDRESS, &Req.Hdr); +} + + DECLHIDDEN(int) IntNetR3IfSend(INTNETIFCTX hIfCtx) { PINTNETIFCTXINT pThis = hIfCtx; @@ -444,8 +1352,22 @@ DECLHIDDEN(int) IntNetR3IfWait(INTNETIFCTX hIfCtx, uint32_t cMillies) { PINTNETIFCTXINT pThis = hIfCtx; AssertPtrReturn(pThis, VERR_INVALID_HANDLE); +#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) + if (pThis->fIntNetR3Svc && ASMAtomicReadBool(&pThis->fNoMoreWaits)) + return VERR_SEM_DESTROYED; +#endif int rc = VINF_SUCCESS; +#ifdef VBOX_INTNET_TESTCASE_LOCALIPC + if (g_pTestWaitRaceIf == pThis) + { + rc = RTSemEventSignal(g_hTestWaitRaceReached); + if (RT_SUCCESS(rc)) + rc = RTSemEventWait(g_hTestWaitRaceContinue, RT_INDEFINITE_WAIT); + if (RT_FAILURE(rc)) + return rc; + } +#endif INTNETIFWAITREQ WaitReq; WaitReq.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; WaitReq.Hdr.cbReq = sizeof(WaitReq); @@ -459,8 +1381,16 @@ DECLHIDDEN(int) IntNetR3IfWait(INTNETIFCTX hIfCtx, uint32_t cMillies) rc = intnetR3IfCallSvcAsync(pThis, VMMR0_DO_INTNET_IF_WAIT, &WaitReq.Hdr); if (RT_SUCCESS(rc)) { +# if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + rc = RTSemEventWait(pThis->hEvtRecv, 0 /*cMillies*/); + if (rc == VERR_TIMEOUT) + rc = intnetR3IfLocalIpcReadPoke(pThis, cMillies); +# else /* Wait on the receive semaphore. */ rc = RTSemEventWait(pThis->hEvtRecv, cMillies); +# endif + if (ASMAtomicReadBool(&pThis->fNoMoreWaits)) + rc = VERR_SEM_DESTROYED; } } else @@ -482,6 +1412,25 @@ DECLHIDDEN(int) IntNetR3IfWaitAbort(INTNETIFCTX hIfCtx) AbortWaitReq.pSession = pThis->pSupDrvSession; AbortWaitReq.hIf = pThis->hIf; AbortWaitReq.fNoMoreWaits = true; +#if defined(VBOX_WITH_INTNET_SERVICE_IN_R3) + if (pThis->fIntNetR3Svc) + { +# if defined(INTNETIF_WITH_R3_SVC_LOCALIPC) + int rc = RTSemMutexRequest(pThis->hIpcCallMtx, RT_INDEFINITE_WAIT); + if (RT_SUCCESS(rc)) + { + ASMAtomicWriteBool(&pThis->fNoMoreWaits, true); + RTSemEventSignal(pThis->hEvtRecv); + rc = intnetR3IfLocalIpcCallLocked(pThis, VMMR0_DO_INTNET_IF_ABORT_WAIT, &AbortWaitReq.Hdr); + RTSemMutexRelease(pThis->hIpcCallMtx); + } + return rc; +# else + ASMAtomicWriteBool(&pThis->fNoMoreWaits, true); + RTSemEventSignal(pThis->hEvtRecv); +# endif + } +#endif return intnetR3IfCallSvc(pThis, VMMR0_DO_INTNET_IF_ABORT_WAIT, &AbortWaitReq.Hdr); } diff --git a/src/VBox/NetworkServices/NetLib/IntNetIf.h b/src/VBox/NetworkServices/NetLib/IntNetIf.h index a914fc4652c9..547fee8cae71 100644 --- a/src/VBox/NetworkServices/NetLib/IntNetIf.h +++ b/src/VBox/NetworkServices/NetLib/IntNetIf.h @@ -1,4 +1,4 @@ -/* $Id: IntNetIf.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: IntNetIf.h 114878 2026-08-06 21:59:49Z andreas.loeffler@oracle.com $ */ /** @file * IntNetIf - Convenience class implementing an IntNet connection. */ @@ -34,6 +34,7 @@ #include #include +#include #include #include @@ -43,7 +44,7 @@ /** - * Low-level internal network access helpers to hide away the different variants (R0 SUP or R3 XPC on macOS). + * Low-level internal network access helpers hiding the R0 SUP and R3 service transports. */ /** Internal networking interface context handle. */ typedef struct INTNETIFCTXINT *INTNETIFCTX; @@ -96,10 +97,16 @@ typedef const INTNETFRAME *PCINTNETFRAME; DECLHIDDEN(int) IntNetR3IfCreate(PINTNETIFCTX phIfCtx, const char *pszNetwork); DECLHIDDEN(int) IntNetR3IfCreateEx(PINTNETIFCTX phIfCtx, const char *pszNetwork, INTNETTRUNKTYPE enmTrunkType, const char *pszTrunk, uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags); +DECLHIDDEN(int) IntNetR3IfCreateExWithRecvEvent(PINTNETIFCTX phIfCtx, const char *pszNetwork, + INTNETTRUNKTYPE enmTrunkType, const char *pszTrunk, + uint32_t cbSend, uint32_t cbRecv, uint32_t fFlags, + RTSEMEVENT hEvtRecv); DECLHIDDEN(int) IntNetR3IfDestroy(INTNETIFCTX hIfCtx); +DECLHIDDEN(int) IntNetR3IfQueryHandle(INTNETIFCTX hIfCtx, PINTNETIFHANDLE phIf); DECLHIDDEN(int) IntNetR3IfQueryBufferPtr(INTNETIFCTX hIfCtx, PINTNETBUF *ppIfBuf); DECLHIDDEN(int) IntNetR3IfSetActive(INTNETIFCTX hIfCtx, bool fActive); DECLHIDDEN(int) IntNetR3IfSetPromiscuous(INTNETIFCTX hIfCtx, bool fPromiscuous); +DECLHIDDEN(int) IntNetR3IfSetMacAddress(INTNETIFCTX hIfCtx, PCRTMAC pMac); DECLHIDDEN(int) IntNetR3IfSend(INTNETIFCTX hIfCtx); DECLHIDDEN(int) IntNetR3IfWait(INTNETIFCTX hIfCtx, uint32_t cMillies); DECLHIDDEN(int) IntNetR3IfWaitAbort(INTNETIFCTX hIfCtx); diff --git a/src/VBox/NetworkServices/testcase/Makefile.kmk b/src/VBox/NetworkServices/testcase/Makefile.kmk new file mode 100644 index 000000000000..21367ee02484 --- /dev/null +++ b/src/VBox/NetworkServices/testcase/Makefile.kmk @@ -0,0 +1,124 @@ +# $Id: Makefile.kmk 114960 2026-08-10 14:50:43Z andreas.loeffler@oracle.com $ +## @file +# Network Services testcases. +# + +# +# Copyright (C) 2026 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../../.. +include $(KBUILD_PATH)/subheader.kmk + +if defined(VBOX_WITH_TESTCASES) && defined(VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES) + + if !defined(VBOX_ONLY_BUILD) \ + && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SUPPORTED_HOST_ARCHS))" != "" + PROGRAMS += tstVBoxNatGuestSide + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstVBoxNatGuestSide_0_OUTDIR)/tstVBoxNatGuestSide.run + endif + tstVBoxNatGuestSide_TEMPLATE = VBoxR3TstExe + tstVBoxNatGuestSide_DEFS = \ + VBOX_WITH_TESTCASES \ + VBOX_WITH_INTNET_SERVICE_IN_R3 \ + VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC \ + VBOX_INTNET_TESTCASE_FORCE_R3 \ + VBOX_INTNET_TESTCASE_LOCALIPC + tstVBoxNatGuestSide_INCS = \ + $(PATH_SUB_CURRENT) \ + $(PATH_SUB_CURRENT)/../NetLib \ + $(PATH_SUB_CURRENT)/../IntNetSwitch \ + $(PATH_ROOT)/src/libs/libslirp-4.9.3/include + tstVBoxNatGuestSide_SOURCES = \ + tstVBoxNatGuestSide.cpp \ + VBoxNetSlirpNATTest.cpp \ + ../NetLib/IntNetIf.cpp + tstVBoxNatGuestSide_LIBS = \ + $(PATH_STAGE_LIB)/VBox-libslirp$(VBOX_SUFF_LIB) \ + $(LIB_RUNTIME) + tstVBoxNatGuestSide_LIBS.solaris += socket nsl + tstVBoxNatGuestSide_LIBS.darwin += resolv + tstVBoxNatGuestSide_ORDERDEPS = \ + $(PATH_STAGE_BIN)/testcase/VBoxIntNetR3SwitchTestHelper$(SUFF_EXE) + tstVBoxNatGuestSide_CLEAN = \ + $(tstVBoxNatGuestSide_0_OUTDIR)/tstVBoxNatGuestSide.run + + $$(tstVBoxNatGuestSide_0_OUTDIR)/tstVBoxNatGuestSide.run: \ + $$(tstVBoxNatGuestSide_1_STAGE_TARGET) \ + $$(VBoxIntNetR3SwitchTestHelper_1_STAGE_TARGET) \ + | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstVBoxNatGuestSide_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + endif + + # IntNet R3 Switch cross-process communication testcase and service helper. + PROGRAMS += \ + tstVBoxIntNetR3Switch \ + VBoxIntNetR3SwitchTestHelper + tstVBoxIntNetR3Switch_TEMPLATE = VBoxR3TstExe + tstVBoxIntNetR3Switch_DEFS += \ + VBOX_WITH_INTNET_SERVICE_IN_R3 \ + VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC \ + VBOX_INTNET_TESTCASE_FORCE_R3 \ + VBOX_INTNET_TESTCASE_LOCALIPC + tstVBoxIntNetR3Switch_INCS = \ + $(PATH_SUB_CURRENT) \ + $(PATH_SUB_CURRENT)/../NetLib \ + $(PATH_SUB_CURRENT)/../IntNetSwitch + tstVBoxIntNetR3Switch_SOURCES = \ + tstVBoxIntNetR3Switch.cpp \ + ../NetLib/IntNetIf.cpp + tstVBoxIntNetR3Switch_LIBS = \ + $(LIB_RUNTIME) + tstVBoxIntNetR3Switch_ORDERDEPS = \ + $(PATH_STAGE_BIN)/testcase/VBoxIntNetR3SwitchTestHelper$(SUFF_EXE) + tstVBoxIntNetR3Switch_CLEAN = \ + $(tstVBoxIntNetR3Switch_0_OUTDIR)/tstVBoxIntNetR3Switch.run + + ifeq ($(KBUILD_TARGET).$(KBUILD_TARGET_ARCH),$(KBUILD_HOST).$(KBUILD_HOST_ARCH)) + TESTING += $(tstVBoxIntNetR3Switch_0_OUTDIR)/tstVBoxIntNetR3Switch.run + + $$(tstVBoxIntNetR3Switch_0_OUTDIR)/tstVBoxIntNetR3Switch.run: \ + $$(tstVBoxIntNetR3Switch_1_STAGE_TARGET) \ + $$(VBoxIntNetR3SwitchTestHelper_1_STAGE_TARGET) \ + | $$(dir $$@) $$(VBOX_RUN_TARGET_ORDER_DEPS) + export VBOX_LOG_DEST=nofile; $(tstVBoxIntNetR3Switch_1_STAGE_TARGET) quiet + $(QUIET)$(APPEND) -t "$@" "done" + endif + + VBoxIntNetR3SwitchTestHelper_TEMPLATE = VBoxR3TstExe + VBoxIntNetR3SwitchTestHelper_DEFS += \ + VBOX_WITH_INTNET_SERVICE_IN_R3 \ + VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC \ + VBOX_INTNET_TESTCASE_LOCALIPC \ + VBOX_INTNET_TESTCASE_ONESHOT_SWITCH + VBoxIntNetR3SwitchTestHelper_INCS = \ + $(PATH_SUB_CURRENT)/../IntNetSwitch + VBoxIntNetR3SwitchTestHelper_SOURCES = \ + ../IntNetSwitch/VBoxIntNetSwitch.cpp \ + ../IntNetSwitch/SrvIntNetWrapper.cpp + VBoxIntNetR3SwitchTestHelper_LIBS = \ + $(LIB_RUNTIME) + +endif # VBOX_WITH_TESTCASES && VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC_TESTCASES + +include $(FILE_KBUILD_SUB_FOOTER) diff --git a/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp b/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp new file mode 100644 index 000000000000..e15c55fcaf9a --- /dev/null +++ b/src/VBox/NetworkServices/testcase/VBoxNetSlirpNATTest.cpp @@ -0,0 +1,554 @@ +/* $Id: VBoxNetSlirpNATTest.cpp 114965 2026-08-10 15:39:37Z andreas.loeffler@oracle.com $ */ +/** @file + * VBoxNetSlirpNAT - Wrapper for guest-side tests. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#ifdef RT_OS_WINDOWS +# include +# include +# include +#else +# include +# include +#endif + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "tstVBoxNatGuestSideInternal.h" + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** One classic libslirp timer. */ +typedef struct VBOXNETSLIRPNATTESTTIMER +{ + /** The next timer. */ + struct VBOXNETSLIRPNATTESTTIMER *pNext; + /** Absolute expiration time in milliseconds; zero means stopped. */ + int64_t msExpire; + /** The libslirp timer callback. */ + SlirpTimerCb pfnCallback; + /** The callback argument. */ + void *pvCallback; + /** Poll generation in which this timer was most recently dispatched. */ + uint64_t uRunGeneration; +} VBOXNETSLIRPNATTESTTIMER; +/** Pointer to a classic libslirp timer. */ +typedef VBOXNETSLIRPNATTESTTIMER *PVBOXNETSLIRPNATTESTTIMER; + +/** Restricted, single-thread-affine libslirp test instance. */ +struct VBOXNETSLIRPNATTEST +{ + /** The libslirp instance. */ + Slirp *pSlirp; + /** Callbacks retained for the complete libslirp lifetime. */ + SlirpCb Callbacks; + /** Frame callback supplied by the testcase. */ + PFNVBOXNETSLIRPNATTESTOUTPUT pfnOutput; + /** Frame callback argument. */ + void *pvOutputUser; + /** First output callback error from the current operation. */ + int rcOutput; + /** First poll-array construction error from the current poll. */ + int rcPoll; + /** Classic libslirp timers. */ + PVBOXNETSLIRPNATTESTTIMER pTimerHead; + /** Generation counter used to dispatch timers safely across list changes. */ + uint64_t uTimerRunGeneration; + /** Poll descriptors. */ + struct pollfd *paPollFds; + /** Number of allocated poll descriptors. */ + uint32_t cPollFdsAllocated; + /** Number of descriptors used by the current poll. */ + uint32_t cPollFds; +#ifdef RT_OS_WINDOWS + /** Whether this instance owns one WSAStartup reference. */ + bool fWsaStarted; +#endif +}; + + +/********************************************************************************************************************************* +* Internal Functions * +*********************************************************************************************************************************/ +/** Converts libslirp poll events to the host poll representation. */ +static short vboxNetSlirpNATTestPollEventsToHost(int fEvents) +{ + short fRet = 0; +#ifndef RT_OS_WINDOWS + if (fEvents & SLIRP_POLL_IN) fRet |= POLLIN; + if (fEvents & SLIRP_POLL_OUT) fRet |= POLLOUT; + if (fEvents & SLIRP_POLL_PRI) fRet |= POLLPRI; + if (fEvents & SLIRP_POLL_ERR) fRet |= POLLERR; + if (fEvents & SLIRP_POLL_HUP) fRet |= POLLHUP; +#else + if (fEvents & SLIRP_POLL_IN) fRet |= POLLRDNORM | POLLRDBAND; + if (fEvents & SLIRP_POLL_OUT) fRet |= POLLWRNORM; + if (fEvents & SLIRP_POLL_PRI) fRet |= POLLIN; +#endif + return fRet; +} + + +/** Converts host poll results to the libslirp representation. */ +static int vboxNetSlirpNATTestPollEventsFromHost(short fEvents) +{ + int fRet = 0; +#ifndef RT_OS_WINDOWS + if (fEvents & POLLIN) fRet |= SLIRP_POLL_IN; + if (fEvents & POLLOUT) fRet |= SLIRP_POLL_OUT; + if (fEvents & POLLPRI) fRet |= SLIRP_POLL_PRI; +#else + if (fEvents & (POLLRDNORM | POLLRDBAND)) fRet |= SLIRP_POLL_IN; + if (fEvents & POLLWRNORM) fRet |= SLIRP_POLL_OUT; + if (fEvents & POLLPRI) fRet |= SLIRP_POLL_PRI; +#endif + if (fEvents & POLLERR) fRet |= SLIRP_POLL_ERR; + if (fEvents & POLLHUP) fRet |= SLIRP_POLL_HUP; + return fRet; +} + + +/** Delivers one libslirp frame to the testcase. */ +static DECLCALLBACK(slirp_ssize_t) +vboxNetSlirpNATTestSendPacket(const void *pvFrame, ssize_t cbFrame, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + AssertPtrReturn(pThis, -1); + AssertPtrReturn(pvFrame, -1); + AssertReturn(cbFrame > 0, -1); + + int const rc = pThis->pfnOutput(pvFrame, (size_t)cbFrame, pThis->pvOutputUser); + if (RT_FAILURE(rc)) + { + if (RT_SUCCESS(pThis->rcOutput)) + pThis->rcOutput = rc; + return -1; + } + return cbFrame; +} + + +/** Ignores libslirp diagnostics caused by deliberately malformed guest input. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestGuestError(const char *pszMessage, void *pvUser) +{ + RT_NOREF(pszMessage, pvUser); +} + + +/** Returns libslirp's monotonic virtual clock. */ +static DECLCALLBACK(int64_t) vboxNetSlirpNATTestClockGetNs(void *pvUser) +{ + RT_NOREF(pvUser); + return (int64_t)RTTimeNanoTS(); +} + + +/** Allocates and links a classic libslirp timer. */ +static DECLCALLBACK(void *) +vboxNetSlirpNATTestTimerNew(SlirpTimerCb pfnCallback, void *pvCallback, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + AssertPtrReturn(pThis, NULL); + AssertReturn(pfnCallback != NULL, NULL); + + PVBOXNETSLIRPNATTESTTIMER pTimer = (PVBOXNETSLIRPNATTESTTIMER)RTMemAllocZ(sizeof(*pTimer)); + if (pTimer) + { + pTimer->pfnCallback = pfnCallback; + pTimer->pvCallback = pvCallback; + pTimer->pNext = pThis->pTimerHead; + pThis->pTimerHead = pTimer; + } + return pTimer; +} + + +/** Unlinks and frees a classic libslirp timer. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestTimerFree(void *pvTimer, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + PVBOXNETSLIRPNATTESTTIMER pTimer = (PVBOXNETSLIRPNATTESTTIMER)pvTimer; + AssertPtrReturnVoid(pThis); + + PVBOXNETSLIRPNATTESTTIMER *ppCur = &pThis->pTimerHead; + while (*ppCur) + { + if (*ppCur == pTimer) + { + *ppCur = pTimer->pNext; + RTMemFree(pTimer); + return; + } + ppCur = &(*ppCur)->pNext; + } + AssertFailed(); +} + + +/** Arms or stops a classic libslirp timer. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestTimerMod(void *pvTimer, int64_t msExpire, void *pvUser) +{ + PVBOXNETSLIRPNATTESTTIMER pTimer = (PVBOXNETSLIRPNATTESTTIMER)pvTimer; + AssertPtrReturnVoid(pTimer); + pTimer->msExpire = msExpire; + RT_NOREF(pvUser); +} + + +/** Notification is unnecessary because Input and Poll are serialized. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestNotify(void *pvUser) +{ + RT_NOREF(pvUser); +} + + +/** Socket registration is handled by slirp_pollfds_fill_socket. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestRegisterSocket(slirp_os_socket hSocket, void *pvUser) +{ + RT_NOREF(hSocket, pvUser); +} + + +/** Socket unregistration is handled by slirp_pollfds_fill_socket. */ +static DECLCALLBACK(void) vboxNetSlirpNATTestUnregisterSocket(slirp_os_socket hSocket, void *pvUser) +{ + RT_NOREF(hSocket, pvUser); +} + + +/** Adds a descriptor requested by slirp_pollfds_fill_socket. */ +static DECLCALLBACK(int) vboxNetSlirpNATTestAddPollFd(slirp_os_socket hSocket, int fEvents, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + AssertPtrReturn(pThis, -1); + + if (pThis->cPollFds == pThis->cPollFdsAllocated) + { + uint32_t const cNew = pThis->cPollFdsAllocated ? pThis->cPollFdsAllocated * 2 : 64; + void *pvNew = RTMemRealloc(pThis->paPollFds, cNew * sizeof(pThis->paPollFds[0])); + if (!pvNew) + { + pThis->rcPoll = VERR_NO_MEMORY; + return -1; + } + pThis->paPollFds = (struct pollfd *)pvNew; + pThis->cPollFdsAllocated = cNew; + } + + uint32_t const iPoll = pThis->cPollFds++; + AssertReturn(iPoll < INT_MAX, -1); + pThis->paPollFds[iPoll].fd = hSocket; + pThis->paPollFds[iPoll].events = vboxNetSlirpNATTestPollEventsToHost(fEvents); + pThis->paPollFds[iPoll].revents = 0; + return (int)iPoll; +} + + +/** Returns the events observed for a descriptor. */ +static DECLCALLBACK(int) vboxNetSlirpNATTestGetPollEvents(int iPoll, void *pvUser) +{ + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)pvUser; + AssertPtrReturn(pThis, SLIRP_POLL_ERR); + AssertReturn(iPoll >= 0 && (uint32_t)iPoll < pThis->cPollFds, SLIRP_POLL_ERR); + return vboxNetSlirpNATTestPollEventsFromHost(pThis->paPollFds[iPoll].revents); +} + + +/** Lowers a poll timeout to the earliest classic timer deadline. */ +static uint32_t vboxNetSlirpNATTestAdjustTimeout(PVBOXNETSLIRPNATTEST pThis, uint32_t cMsTimeout) +{ + int64_t msDeadline = INT64_MAX; + for (PVBOXNETSLIRPNATTESTTIMER pTimer = pThis->pTimerHead; pTimer; pTimer = pTimer->pNext) + if (pTimer->msExpire > 0 && pTimer->msExpire < msDeadline) + msDeadline = pTimer->msExpire; + + if (msDeadline != INT64_MAX) + { + int64_t const msNow = (int64_t)(RTTimeNanoTS() / RT_NS_1MS); + if (msDeadline <= msNow) + return 0; + uint64_t const cMsToDeadline = (uint64_t)(msDeadline - msNow); + if (cMsToDeadline < cMsTimeout) + cMsTimeout = (uint32_t)cMsToDeadline; + } + return cMsTimeout; +} + + +/** Runs all expired classic timers. */ +static void vboxNetSlirpNATTestRunExpiredTimers(PVBOXNETSLIRPNATTEST pThis) +{ + int64_t const msNow = (int64_t)(RTTimeNanoTS() / RT_NS_1MS); + uint64_t const uGeneration = ++pThis->uTimerRunGeneration; + for (PVBOXNETSLIRPNATTESTTIMER pTimer = pThis->pTimerHead; pTimer; pTimer = pTimer->pNext) + if (pTimer->msExpire > 0 && pTimer->msExpire <= msNow) + pTimer->uRunGeneration = uGeneration; + + for (;;) + { + PVBOXNETSLIRPNATTESTTIMER pTimer = pThis->pTimerHead; + while (pTimer && pTimer->uRunGeneration != uGeneration) + pTimer = pTimer->pNext; + if (!pTimer) + break; + + SlirpTimerCb const pfnCallback = pTimer->pfnCallback; + void * const pvCallback = pTimer->pvCallback; + pTimer->uRunGeneration = 0; + pTimer->msExpire = 0; + pfnCallback(pvCallback); + } +} + + +/** Validates that an address belongs to the configured IPv4 network. */ +static bool vboxNetSlirpNATTestAddrIsInNetwork(PCVBOXNETSLIRPNATTESTCFG pCfg, RTNETADDRIPV4 Addr) +{ + return (Addr.u & pCfg->IPv4Netmask.u) == pCfg->IPv4Network.u; +} + + +/** Validates the restricted IPv4 configuration accepted by the wrapper. */ +static int vboxNetSlirpNATTestValidateConfig(PCVBOXNETSLIRPNATTESTCFG pCfg) +{ + AssertPtrReturn(pCfg, VERR_INVALID_POINTER); + if (!pCfg->fRestricted) + return VERR_ACCESS_DENIED; + + int iPrefix = 0; + int rc = RTNetMaskToPrefixIPv4(&pCfg->IPv4Netmask, &iPrefix); + if (RT_FAILURE(rc) || iPrefix < 1 || iPrefix > 30) + return VERR_INVALID_PARAMETER; + if ( (pCfg->IPv4Network.u & pCfg->IPv4Netmask.u) != pCfg->IPv4Network.u + || !vboxNetSlirpNATTestAddrIsInNetwork(pCfg, pCfg->IPv4Host) + || !vboxNetSlirpNATTestAddrIsInNetwork(pCfg, pCfg->IPv4DhcpStart) + || !vboxNetSlirpNATTestAddrIsInNetwork(pCfg, pCfg->IPv4Nameserver)) + return VERR_INVALID_PARAMETER; + + uint32_t const uBroadcast = pCfg->IPv4Network.u | ~pCfg->IPv4Netmask.u; + if ( pCfg->IPv4Host.u == pCfg->IPv4Network.u + || pCfg->IPv4Host.u == uBroadcast + || pCfg->IPv4DhcpStart.u == pCfg->IPv4Network.u + || pCfg->IPv4DhcpStart.u == uBroadcast + || pCfg->IPv4Nameserver.u == pCfg->IPv4Network.u + || pCfg->IPv4Nameserver.u == uBroadcast) + return VERR_INVALID_PARAMETER; + return VINF_SUCCESS; +} + + +/********************************************************************************************************************************* +* Exported Test Functions * +*********************************************************************************************************************************/ +DECLHIDDEN(int) VBoxNetSlirpNATTestCreate(PCVBOXNETSLIRPNATTESTCFG pCfg, + PFNVBOXNETSLIRPNATTESTOUTPUT pfnOutput, + void *pvOutputUser, PVBOXNETSLIRPNATTEST *phNat) +{ + AssertPtrReturn(phNat, VERR_INVALID_POINTER); + *phNat = NULL; + AssertPtrReturn(pfnOutput, VERR_INVALID_POINTER); + + int rc = vboxNetSlirpNATTestValidateConfig(pCfg); + if (RT_FAILURE(rc)) + return rc; + + PVBOXNETSLIRPNATTEST pThis = (PVBOXNETSLIRPNATTEST)RTMemAllocZ(sizeof(*pThis)); + if (!pThis) + return VERR_NO_MEMORY; + pThis->pfnOutput = pfnOutput; + pThis->pvOutputUser = pvOutputUser; + pThis->rcOutput = VINF_SUCCESS; + pThis->rcPoll = VINF_SUCCESS; + +#ifdef RT_OS_WINDOWS + WSADATA WsaData; + int const iWsaErr = WSAStartup(MAKEWORD(2, 2), &WsaData); + if (iWsaErr != 0) + { + RTMemFree(pThis); + return RTErrConvertFromWin32(iWsaErr); + } + pThis->fWsaStarted = true; +#endif + + pThis->Callbacks.send_packet = vboxNetSlirpNATTestSendPacket; + pThis->Callbacks.guest_error = vboxNetSlirpNATTestGuestError; + pThis->Callbacks.clock_get_ns = vboxNetSlirpNATTestClockGetNs; + pThis->Callbacks.timer_new = vboxNetSlirpNATTestTimerNew; + pThis->Callbacks.timer_free = vboxNetSlirpNATTestTimerFree; + pThis->Callbacks.timer_mod = vboxNetSlirpNATTestTimerMod; + pThis->Callbacks.notify = vboxNetSlirpNATTestNotify; + pThis->Callbacks.register_poll_socket = vboxNetSlirpNATTestRegisterSocket; + pThis->Callbacks.unregister_poll_socket = vboxNetSlirpNATTestUnregisterSocket; + + SlirpConfig Cfg; + RT_ZERO(Cfg); + Cfg.version = SLIRP_CONFIG_VERSION_MAX; + Cfg.restricted = 1; + Cfg.in_enabled = true; + Cfg.vnetwork.s_addr = pCfg->IPv4Network.u; + Cfg.vnetmask.s_addr = pCfg->IPv4Netmask.u; + Cfg.vhost.s_addr = pCfg->IPv4Host.u; + Cfg.vdhcp_start.s_addr = pCfg->IPv4DhcpStart.u; + Cfg.vnameserver.s_addr = pCfg->IPv4Nameserver.u; + Cfg.in6_enabled = false; + Cfg.if_mtu = 1500; + Cfg.if_mru = 1500; + Cfg.if_mtu_v6 = 1280; + Cfg.if_mru_v6 = 1280; + Cfg.disable_host_loopback = true; + Cfg.enable_emu = false; + Cfg.disable_dns = !pCfg->fDns; + Cfg.disable_dhcp = !pCfg->fDhcp; + Cfg.fForwardBroadcast = false; + Cfg.iSoMaxConn = 10; + Cfg.fDisableIPv6RA = true; + + pThis->pSlirp = slirp_new(&Cfg, &pThis->Callbacks, pThis); + if (!pThis->pSlirp) + { +#ifdef RT_OS_WINDOWS + WSACleanup(); +#endif + RTMemFree(pThis); + return VERR_NO_MEMORY; + } + + *phNat = pThis; + return VINF_SUCCESS; +} + + +DECLHIDDEN(void) VBoxNetSlirpNATTestDestroy(PVBOXNETSLIRPNATTEST hNat) +{ + if (!hNat) + return; + + if (hNat->pSlirp) + { + slirp_cleanup(hNat->pSlirp); + hNat->pSlirp = NULL; + } + while (hNat->pTimerHead) + { + PVBOXNETSLIRPNATTESTTIMER pFree = hNat->pTimerHead; + hNat->pTimerHead = pFree->pNext; + RTMemFree(pFree); + } + RTMemFree(hNat->paPollFds); +#ifdef RT_OS_WINDOWS + if (hNat->fWsaStarted) + WSACleanup(); +#endif + RTMemFree(hNat); +} + + +DECLHIDDEN(int) VBoxNetSlirpNATTestInput(PVBOXNETSLIRPNATTEST hNat, + const void *pvFrame, size_t cbFrame) +{ + AssertPtrReturn(hNat, VERR_INVALID_HANDLE); + if (!pvFrame || cbFrame == 0 || cbFrame > _64K) + return VERR_INVALID_PARAMETER; + + /* Keep the testcase seam aligned with VBoxNetSlirpNAT::processFrame. */ + if (cbFrame < sizeof(RTNETETHERHDR) || cbFrame > 1522) + return VINF_SUCCESS; + + hNat->rcOutput = VINF_SUCCESS; + slirp_input(hNat->pSlirp, (uint8_t const *)pvFrame, (int)cbFrame); + return hNat->rcOutput; +} + + +DECLHIDDEN(int) VBoxNetSlirpNATTestPoll(PVBOXNETSLIRPNATTEST hNat, uint32_t cMsMax) +{ + AssertPtrReturn(hNat, VERR_INVALID_HANDLE); + + hNat->rcOutput = VINF_SUCCESS; + hNat->rcPoll = VINF_SUCCESS; + hNat->cPollFds = 0; + + uint32_t cMsTimeout = RT_MIN(cMsMax, (uint32_t)INT_MAX); + slirp_pollfds_fill_socket(hNat->pSlirp, &cMsTimeout, vboxNetSlirpNATTestAddPollFd, hNat); + if (RT_FAILURE(hNat->rcPoll)) + return hNat->rcPoll; + cMsTimeout = vboxNetSlirpNATTestAdjustTimeout(hNat, cMsTimeout); + + int cReady = 0; + if (hNat->cPollFds == 0) + { + if (cMsTimeout > 0) + RTThreadSleep(cMsTimeout); + } + else + { +#ifdef RT_OS_WINDOWS + cReady = WSAPoll(hNat->paPollFds, hNat->cPollFds, (int)cMsTimeout); + if (cReady == SOCKET_ERROR) + { + int const rc = RTErrConvertFromWin32(WSAGetLastError()); + slirp_pollfds_poll(hNat->pSlirp, true, vboxNetSlirpNATTestGetPollEvents, hNat); + return rc; + } +#else + cReady = poll(hNat->paPollFds, hNat->cPollFds, (int)cMsTimeout); + if (cReady < 0) + { + if (errno == EINTR) + cReady = 0; + else + { + int const rc = RTErrConvertFromErrno(errno); + slirp_pollfds_poll(hNat->pSlirp, true, vboxNetSlirpNATTestGetPollEvents, hNat); + return rc; + } + } +#endif + } + + RT_NOREF(cReady); + slirp_pollfds_poll(hNat->pSlirp, false, vboxNetSlirpNATTestGetPollEvents, hNat); + vboxNetSlirpNATTestRunExpiredTimers(hNat); + return hNat->rcOutput; +} diff --git a/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp b/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp new file mode 100644 index 000000000000..12f6e741e32c --- /dev/null +++ b/src/VBox/NetworkServices/testcase/tstVBoxIntNetR3Switch.cpp @@ -0,0 +1,1650 @@ +/* $Id: tstVBoxIntNetR3Switch.cpp 115038 2026-08-14 12:14:02Z andreas.loeffler@oracle.com $ */ +/** @file + * tstVBoxIntNetR3Switch - Self-contained testcase for R3 IntNet/IntNetSwitch communication. + * + * This RTTest auto-starts a standalone helper built from the production Local + * IPC switch service and R3 wrapper sources. This exercises the same process, + * IPC, shared-memory, and switching boundaries used on non-Darwin hosts. + * + * The client side uses NetLib/IntNetIf (IntNetR3If*) to exercise the production + * driverless path which talks to the R3 service via local IPC. + * + * Covered scenarios (non-exhaustive, but focused on IPC correctness): + * - A squatter occupying the legacy global endpoint cannot block service startup. + * - Missing-service detection, automatic process startup, client attach, and shared-memory mapping. + * - Basic broadcast send on interface A and receive on interface B (same network name). + * - Concurrent Wait/Abort semantics: a blocked IntNetR3IfWait is woken by IntNetR3IfWaitAbort. + * - Repeated send/wait notification registration without lost wakeups. + * - Network isolation: interface C on a different network name does not receive frames. + * - Connection, worker-thread, and aggregate shared-memory limits with recovery. + * - Idle-unverified, partial-request, and partial-reply read timeouts. + * + * The production R3 path on macOS uses XPC. To keep this testcase self-contained + * while still exercising the production local IPC implementation, its build + * selects local IPC on all host platforms, including Darwin. + * + * Usage: + * - Build with VBOX_WITH_TESTCASES=1 and VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC defined. + * kmk -C src/VBox/NetworkServices/testcase tstVBoxIntNetR3Switch + * - Run the produced binary from the staged testcase directory. + * + * Security: No external inputs; all frames, names, and shared memory are local-only. + */ + +/** + * @page pg_tstVBoxIntNetR3Switch R3 IntNet/IntNetSwitch IPC self-contained testcase + * + * Purpose: + * - Validate the production IntNet R3 service local IPC path across a real + * client/service process boundary, including automatic service startup. + * + * Scope: + * - Automatic server startup, client attach/map via IntNetR3If* APIs. + * - Broadcast delivery between two interfaces on the same internal network name. + * - Isolation for a third interface on a different internal network name. + * - Concurrent Wait/Abort and repeated notification coverage for the async notification path. + * - Deferred local IPC notification output that cannot block IntNet delivery. + * + * Build: + * - Ensure VBOX_WITH_TESTCASES=1 and VBOX_WITH_INTNET_SERVICE_IN_R3_LOCALIPC are set. + * - Build just this testcase: kmk -C src/VBox/NetworkServices/testcase tstVBoxIntNetR3Switch + * + * Run: + * - Run tstVBoxIntNetR3Switch from out/.//testcase or the + * platform-specific staged testcase directory. + * + * Notes: + * - Darwin production builds use XPC. This testcase explicitly selects the same + * local IPC implementation used by the other supported hosts. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include +#include +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# include +#endif +#include "../NetLib/IntNetIf.h" + +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +# include +#endif + + +/********************************************************************************************************************************* +* Constants & Helpers * +*********************************************************************************************************************************/ +static RTTEST g_hTest = NIL_RTTEST; + +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) +DECLHIDDEN(void) intnetR3IfTestSetWaitRace(INTNETIFCTX hIfCtx, RTSEMEVENT hReached, RTSEMEVENT hContinue); + +# define TST_MAX_FRAME 1600 +# define TST_WAIT_MS 1000 +# define TST_STRESS_FRAMES 128 +# define TST_READ_TIMEOUT_MS 1000 + +# define TST_ENV_MAX_CONNECTIONS "VBOX_INTNET_R3_TEST_MAX_CONNECTIONS" +# define TST_ENV_MAX_THREADS "VBOX_INTNET_R3_TEST_MAX_THREADS" +# define TST_ENV_MAX_SHMEM "VBOX_INTNET_R3_TEST_MAX_SHMEM" +# define TST_ENV_READ_TIMEOUT "VBOX_INTNET_R3_TEST_READ_TIMEOUT_MS" +# define TST_ENV_REPLY_TIMEOUT "VBOX_INTNET_R3_TEST_REPLY_TIMEOUT_MS" +# define TST_ENV_POKE_BLOCK_FILE "VBOX_INTNET_R3_TEST_POKE_BLOCK_FILE" + +#define TST_CHECK(a_Expr) \ + do { if (!(a_Expr)) RTTestFailed(g_hTest, "%s:%u: %s", __FILE__, __LINE__, #a_Expr); } while (0) + +#define TST_CHECK_RC_OK(a_rc) \ + do { int rc__ = (a_rc); if (RT_FAILURE(rc__)) RTTestFailed(g_hTest, "%s:%u: %s -> %Rrc", __FILE__, __LINE__, #a_rc, rc__); } while (0) + +static int tstMakeUuidName(const char *pszPrefix, char *pszOut, size_t cbOut) +{ + RTUUID Uuid; char szUuid[RTUUID_STR_LENGTH]; + int rc = RTUuidCreate(&Uuid); if (RT_FAILURE(rc)) return rc; + RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + ssize_t cch = RTStrPrintf2(pszOut, cbOut, "%s-%s", pszPrefix, szUuid); + return cch > 0 && (size_t)cch < cbOut ? VINF_SUCCESS : VERR_BUFFER_OVERFLOW; +} + + +/********************************************************************************************************************************* +* External R3 Switch Service * +*********************************************************************************************************************************/ +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + +typedef struct TSTSWITCHSVC +{ + RTPROCESS hProcess; + bool fProcessReaped; + bool fTempDirCreated; +# ifdef RT_OS_WINDOWS + RTLOCALIPCSERVER hLegacySquatter; +# else + bool fLegacySquatterCreated; + bool fRuntimeDirCreated; + bool fRuntimeDirChanged; + bool fRuntimeDirWasSet; + char *pszSavedRuntimeDir; + char szLegacySquatter[RTPATH_MAX]; + char szRuntimeDir[RTPATH_MAX]; +# endif + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; + char szExec[RTPATH_MAX]; + char szTempDir[RTPATH_MAX]; + char szPidFile[RTPATH_MAX]; + char szLockFile[RTPATH_MAX]; +} TSTSWITCHSVC; + +static bool svcIsAbsent(int rc) +{ + return rc == VERR_FILE_NOT_FOUND + || rc == VERR_PATH_NOT_FOUND + || rc == VERR_NET_CONNECTION_REFUSED + || rc == VERR_PIPE_NOT_CONNECTED; +} + + +/** Returns whether @a rc is a transport-disconnect status from a rejected or closed peer. */ +static bool svcIsDisconnected(int rc) +{ + return rc == VERR_BROKEN_PIPE + || rc == VERR_PIPE_NOT_CONNECTED + || rc == VERR_NET_NOT_CONNECTED + || rc == VERR_NET_CONNECTION_RESET + || rc == VERR_NET_CONNECTION_RESET_BY_PEER + || rc == VERR_NET_CONNECTION_REFUSED; +} + + +static int svcReadPid(TSTSWITCHSVC *pSvc) +{ + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpen(&hFile, pSvc->szPidFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + char szPid[32]; + size_t cbRead = 0; + rc = RTFileRead(hFile, szPid, sizeof(szPid) - 1, &cbRead); + int const rc2 = RTFileClose(hFile); + if (RT_SUCCESS(rc)) + rc = rc2; + if (RT_SUCCESS(rc)) + { + szPid[cbRead] = '\0'; + uint32_t uPid = NIL_RTPROCESS; + rc = RTStrToUInt32Full(szPid, 10, &uPid); + if (RT_SUCCESS(rc)) + { + if (uPid == NIL_RTPROCESS || uPid == RTProcSelf()) + rc = VERR_INVALID_PARAMETER; + else + pSvc->hProcess = uPid; + } + } + } + return rc; +} + + +static int svcStart(TSTSWITCHSVC *pSvc) +{ + RT_ZERO(*pSvc); + pSvc->hProcess = NIL_RTPROCESS; +# ifdef RT_OS_WINDOWS + pSvc->hLegacySquatter = NIL_RTLOCALIPCSERVER; +# endif + + int rc = RTPathTemp(pSvc->szTempDir, sizeof(pSvc->szTempDir)); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pSvc->szTempDir, sizeof(pSvc->szTempDir), "tstVBoxIntNetR3Switch-XXXXXX"); + if (RT_SUCCESS(rc)) + { + rc = RTDirCreateTemp(pSvc->szTempDir, 0700); + if (RT_SUCCESS(rc)) + pSvc->fTempDirCreated = true; + } + if (RT_SUCCESS(rc)) + rc = tstMakeUuidName("tst-vbi", pSvc->szService, sizeof(pSvc->szService)); +# ifndef RT_OS_WINDOWS + if (RT_SUCCESS(rc)) + { + pSvc->fRuntimeDirWasSet = RTEnvExist("XDG_RUNTIME_DIR"); + if (pSvc->fRuntimeDirWasSet) + { + pSvc->pszSavedRuntimeDir = RTEnvDupEx(RTENV_DEFAULT, "XDG_RUNTIME_DIR"); + if (!pSvc->pszSavedRuntimeDir) + rc = VERR_NO_MEMORY; + } + } + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pSvc->szRuntimeDir, sizeof(pSvc->szRuntimeDir), + "/tmp/tst-vbi-runtime-%u-XXXXXX", (unsigned)RTProcSelf()); + if (cch <= 0 || (size_t)cch >= sizeof(pSvc->szRuntimeDir)) + rc = VERR_BUFFER_OVERFLOW; + else + { + rc = RTDirCreateTemp(pSvc->szRuntimeDir, 0700); + if (RT_SUCCESS(rc)) + pSvc->fRuntimeDirCreated = true; + } + } + if (RT_SUCCESS(rc)) + { + rc = RTEnvSet("XDG_RUNTIME_DIR", pSvc->szRuntimeDir); + if (RT_SUCCESS(rc)) + pSvc->fRuntimeDirChanged = true; + } +# endif + if (RT_SUCCESS(rc)) + { +# ifdef RT_OS_WINDOWS + rc = RTLocalIpcServerCreate(&pSvc->hLegacySquatter, pSvc->szService, 0 /*fFlags*/); +# else + ssize_t const cch = RTStrPrintf2(pSvc->szLegacySquatter, sizeof(pSvc->szLegacySquatter), + "/tmp/.iprt-localipc-%s", pSvc->szService); + if (cch <= 0 || (size_t)cch >= sizeof(pSvc->szLegacySquatter)) + rc = VERR_BUFFER_OVERFLOW; + else + { + RTDirRemove(pSvc->szLegacySquatter); + rc = RTDirCreate(pSvc->szLegacySquatter, 0700, 0 /*fCreate*/); + if (RT_SUCCESS(rc)) + pSvc->fLegacySquatterCreated = true; + } +# endif + } + if (RT_SUCCESS(rc)) + rc = RTPathExecDir(pSvc->szExec, sizeof(pSvc->szExec)); + if (RT_SUCCESS(rc)) +# ifdef RT_OS_WINDOWS + rc = RTPathAppend(pSvc->szExec, sizeof(pSvc->szExec), "VBoxIntNetR3SwitchTestHelper.exe"); +# else + rc = RTPathAppend(pSvc->szExec, sizeof(pSvc->szExec), "VBoxIntNetR3SwitchTestHelper"); +# endif + if (RT_SUCCESS(rc)) + rc = RTStrCopy(pSvc->szPidFile, sizeof(pSvc->szPidFile), pSvc->szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pSvc->szPidFile, sizeof(pSvc->szPidFile), "switch.pid"); + if (RT_SUCCESS(rc)) + rc = RTStrCopy(pSvc->szLockFile, sizeof(pSvc->szLockFile), pSvc->szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pSvc->szLockFile, sizeof(pSvc->szLockFile), "switch.lock"); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SVC_NAME", pSvc->szService); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_EXE", pSvc->szExec); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_PID_FILE", pSvc->szPidFile); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_LOCK_FILE", pSvc->szLockFile); + if (RT_SUCCESS(rc)) + { + RTLOCALIPCSESSION hExisting = NIL_RTLOCALIPCSESSION; + rc = RTLocalIpcSessionConnect(&hExisting, pSvc->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + { + RTLocalIpcSessionClose(hExisting); + rc = VERR_ALREADY_EXISTS; + } + else if (svcIsAbsent(rc)) + rc = VINF_SUCCESS; + } + return rc; +} + + +static void svcCleanupEndpoint(TSTSWITCHSVC *pSvc) +{ + if (!pSvc->szService[0]) + return; + + RTLOCALIPCSESSION hExisting = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcSessionConnect(&hExisting, pSvc->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + { + RTLocalIpcSessionClose(hExisting); + RTTestFailed(g_hTest, "Switch IPC endpoint still accepts clients after service exit"); + return; + } + if (rc == VERR_NET_CONNECTION_REFUSED) + RTTestFailed(g_hTest, "Switch IPC endpoint pathname was left behind after service exit"); + else if (!svcIsAbsent(rc)) + { + RTTestFailed(g_hTest, "Checking switch IPC endpoint failed: %Rrc", rc); + return; + } + + RTLOCALIPCSERVER hCleanup = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hCleanup, pSvc->szService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + RTLocalIpcServerDestroy(hCleanup); + else + RTTestFailed(g_hTest, "Cleaning switch IPC endpoint failed: %Rrc", rc); +} + + +static void svcStop(TSTSWITCHSVC *pSvc) +{ + if ( !pSvc->fProcessReaped + && pSvc->hProcess == NIL_RTPROCESS + && pSvc->szPidFile[0]) + svcReadPid(pSvc); + + if (pSvc->hProcess != NIL_RTPROCESS) + { + RTPROCSTATUS Status; + RT_ZERO(Status); + int rc = VERR_PROCESS_RUNNING; + uint64_t const msStart = RTTimeMilliTS(); + while ( rc == VERR_PROCESS_RUNNING + && RTTimeMilliTS() - msStart < RT_MS_5SEC) + { + rc = RTProcWait(pSvc->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &Status); + if (rc == VERR_PROCESS_RUNNING) + RTThreadSleep(10); + } + if (rc == VERR_PROCESS_RUNNING) + { + RTTestFailed(g_hTest, "Switch helper did not exit after its last client disconnected"); + int const rcTerm = RTProcTerminate(pSvc->hProcess); + if (RT_SUCCESS(rcTerm)) + rc = RTProcWait(pSvc->hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status); + else + rc = rcTerm; + } + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Waiting for switch helper failed: %Rrc", rc); + else if ( Status.enmReason != RTPROCEXITREASON_NORMAL + || Status.iStatus != RTEXITCODE_SUCCESS) + RTTestFailed(g_hTest, "Switch helper exit reason/status: %d/%d", Status.enmReason, Status.iStatus); + if (RT_SUCCESS(rc)) + pSvc->fProcessReaped = true; + pSvc->hProcess = NIL_RTPROCESS; + } + + svcCleanupEndpoint(pSvc); + + RTEnvUnset("VBOX_INTNET_R3_SWITCH_LOCK_FILE"); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_PID_FILE"); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_EXE"); + RTEnvUnset("VBOX_INTNET_R3_SVC_NAME"); + RTEnvUnset(TST_ENV_MAX_CONNECTIONS); + RTEnvUnset(TST_ENV_MAX_THREADS); + RTEnvUnset(TST_ENV_MAX_SHMEM); + RTEnvUnset(TST_ENV_READ_TIMEOUT); + RTEnvUnset(TST_ENV_REPLY_TIMEOUT); + RTEnvUnset(TST_ENV_POKE_BLOCK_FILE); + +# ifndef RT_OS_WINDOWS + if (pSvc->fRuntimeDirChanged) + { + int const rc = pSvc->fRuntimeDirWasSet + ? RTEnvSet("XDG_RUNTIME_DIR", pSvc->pszSavedRuntimeDir) + : RTEnvUnset("XDG_RUNTIME_DIR"); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Restoring XDG_RUNTIME_DIR failed: %Rrc", rc); + pSvc->fRuntimeDirChanged = false; + } + RTStrFree(pSvc->pszSavedRuntimeDir); + pSvc->pszSavedRuntimeDir = NULL; +# endif + +# ifdef RT_OS_WINDOWS + if (pSvc->hLegacySquatter != NIL_RTLOCALIPCSERVER) + { + int const rc = RTLocalIpcServerDestroy(pSvc->hLegacySquatter); + if (rc != VINF_OBJECT_DESTROYED) + RTTestFailed(g_hTest, "Destroying legacy endpoint squatter failed: %Rrc", rc); + pSvc->hLegacySquatter = NIL_RTLOCALIPCSERVER; + } +# else + if (pSvc->fLegacySquatterCreated) + { + int const rc = RTDirRemove(pSvc->szLegacySquatter); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Removing legacy endpoint squatter failed: %Rrc", rc); + else + pSvc->fLegacySquatterCreated = false; + } + if (pSvc->fRuntimeDirCreated) + { + int const rc = RTDirRemove(pSvc->szRuntimeDir); + if (RT_FAILURE(rc) && rc != VERR_PATH_NOT_FOUND && rc != VERR_FILE_NOT_FOUND) + RTTestFailed(g_hTest, "Removing local IPC runtime directory failed: %Rrc", rc); + else + pSvc->fRuntimeDirCreated = false; + } +# endif + + if (pSvc->szPidFile[0]) + RTFileDelete(pSvc->szPidFile); + if (pSvc->szLockFile[0]) + RTFileDelete(pSvc->szLockFile); + if (pSvc->fTempDirCreated) + { + int const rc = RTDirRemove(pSvc->szTempDir); + if (RT_FAILURE(rc) && rc != VERR_PATH_NOT_FOUND && rc != VERR_FILE_NOT_FOUND) + RTTestFailed(g_hTest, "Removing switch helper temp directory failed: %Rrc", rc); + else + pSvc->fTempDirCreated = false; + } +} + +#endif /* !RT_OS_DARWIN || VBOX_INTNET_TESTCASE_LOCALIPC */ + + +/********************************************************************************************************************************* +* Simple client-side RX collector * +*********************************************************************************************************************************/ +typedef struct TSTRXCOLLECT +{ + INTNETIFCTX hIf; + RTSEMEVENT hEvt; + RTTHREAD hThread; + uint32_t cFrames; + uint8_t abLast[TST_MAX_FRAME]; + uint32_t cbLast; +} TSTRXCOLLECT; + +static DECLCALLBACK(void) tstRxCb(void *pvUser, void *pvFrame, uint32_t cbFrame) +{ + TSTRXCOLLECT *p = (TSTRXCOLLECT *)pvUser; + if (cbFrame > sizeof(p->abLast)) + cbFrame = sizeof(p->abLast); + memcpy(p->abLast, pvFrame, cbFrame); + p->cbLast = cbFrame; + ASMAtomicIncU32(&p->cFrames); + RTSemEventSignal(p->hEvt); +} + +static DECLCALLBACK(int) tstRxThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + TSTRXCOLLECT *p = (TSTRXCOLLECT *)pvUser; + return IntNetR3IfPumpPkts(p->hIf, tstRxCb, p, NULL, NULL); +} + +static int tstRxStart(TSTRXCOLLECT *p, INTNETIFCTX hIf) +{ + RT_ZERO(*p); p->hIf = hIf; p->hEvt = NIL_RTSEMEVENT; p->hThread = NIL_RTTHREAD; + int rc = RTSemEventCreate(&p->hEvt); if (RT_FAILURE(rc)) return rc; + return RTThreadCreate(&p->hThread, tstRxThread, p, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetRx"); +} + +static bool tstRxStop(TSTRXCOLLECT *p) +{ + if (p->hIf) + { + int const rc = IntNetR3IfWaitAbort(p->hIf); + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Failed to abort receive thread wait: %Rrc", rc); + } + if (p->hThread != NIL_RTTHREAD) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rc = RTThreadWait(p->hThread, RT_MS_5SEC, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Receive thread did not terminate: %Rrc", rc); + return false; /* The thread may still be using the event and interface. */ + } + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Receive thread returned %Rrc, expected %Rrc", rcThread, VERR_SEM_DESTROYED); + p->hThread = NIL_RTTHREAD; + } + if (p->hEvt != NIL_RTSEMEVENT) + { + RTSemEventDestroy(p->hEvt); + p->hEvt = NIL_RTSEMEVENT; + } + return true; +} + + +/** Joins a receive thread after svcStop has forced its IPC connection closed. */ +static bool tstRxJoinAfterServiceStop(TSTRXCOLLECT *p) +{ + if (p->hThread != NIL_RTTHREAD) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rc = RTThreadWait(p->hThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Joining receive thread after service stop failed: %Rrc", rc); + return false; + } + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Receive thread returned %Rrc after service stop, expected %Rrc", + rcThread, VERR_SEM_DESTROYED); + p->hThread = NIL_RTTHREAD; + } + if (p->hEvt != NIL_RTSEMEVENT) + { + RTSemEventDestroy(p->hEvt); + p->hEvt = NIL_RTSEMEVENT; + } + return true; +} + + +static DECLCALLBACK(int) tstWaitThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + return IntNetR3IfWait((INTNETIFCTX)pvUser, RT_INDEFINITE_WAIT); +} + +static bool tstConcurrentWaitAbort(INTNETIFCTX hIf, PRTTHREAD phThread) +{ + *phThread = NIL_RTTHREAD; + int rc = RTThreadCreate(phThread, tstWaitThread, hIf, 0 /*cbStack*/, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "IntNetWait"); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Failed to create wait thread: %Rrc", rc); + return true; + } + + /* Once the client is sleeping on its receive event, its WAIT request has + already been written to the ordered IPC stream. A following ABORT must + therefore cancel a registered wait, not merely win a scheduling race. */ + uint64_t const msStart = RTTimeMilliTS(); + RTTHREADSTATE enmState; + do + { + enmState = RTThreadGetState(*phThread); + if (enmState == RTTHREADSTATE_EVENT || enmState == RTTHREADSTATE_TERMINATED) + break; + RTThreadSleep(1); + } while (RTTimeMilliTS() - msStart < RT_MS_5SEC); + + if (enmState != RTTHREADSTATE_EVENT) + RTTestFailed(g_hTest, "Wait thread did not block on the receive event (state %d)", enmState); + + rc = IntNetR3IfWaitAbort(hIf); + TST_CHECK_RC_OK(rc); + + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(*phThread, RT_MS_5SEC, &rcThread); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + return false; /* The thread may still be using the interface. */ + *phThread = NIL_RTTHREAD; + if (RT_SUCCESS(rc) && rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Wait thread returned %Rrc, expected %Rrc", rcThread, VERR_SEM_DESTROYED); + + rc = IntNetR3IfWait(hIf, 0 /*cMillies*/); + if (rc != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Wait after abort returned %Rrc, expected %Rrc", rc, VERR_SEM_DESTROYED); + return true; +} + + +typedef struct TSTWAITRACE +{ + RTTHREAD hThread; + RTSEMEVENT hReached; + RTSEMEVENT hContinue; +} TSTWAITRACE; + + +/** Exercises Abort completing between Wait's initial and serialized no-more-waits checks. */ +static bool tstInverseWaitAbortRace(INTNETIFCTX hIf, TSTWAITRACE *pRace) +{ + RT_ZERO(*pRace); + pRace->hThread = NIL_RTTHREAD; + pRace->hReached = NIL_RTSEMEVENT; + pRace->hContinue = NIL_RTSEMEVENT; + + int rc = RTSemEventCreate(&pRace->hReached); + if (RT_SUCCESS(rc)) + rc = RTSemEventCreate(&pRace->hContinue); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Creating inverse-race events failed: %Rrc", rc); + if (pRace->hReached != NIL_RTSEMEVENT) + RTSemEventDestroy(pRace->hReached); + pRace->hReached = NIL_RTSEMEVENT; + return true; + } + + intnetR3IfTestSetWaitRace(hIf, pRace->hReached, pRace->hContinue); + rc = RTThreadCreate(&pRace->hThread, tstWaitThread, hIf, 0 /*cbStack*/, RTTHREADTYPE_IO, + RTTHREADFLAGS_WAITABLE, "IntNetRace"); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Creating inverse-race wait thread failed: %Rrc", rc); + intnetR3IfTestSetWaitRace(NULL, NIL_RTSEMEVENT, NIL_RTSEMEVENT); + RTSemEventDestroy(pRace->hContinue); + RTSemEventDestroy(pRace->hReached); + pRace->hContinue = NIL_RTSEMEVENT; + pRace->hReached = NIL_RTSEMEVENT; + return true; + } + + rc = RTSemEventWait(pRace->hReached, RT_MS_5SEC); + TST_CHECK_RC_OK(rc); + + int const rcAbort = IntNetR3IfWaitAbort(hIf); + TST_CHECK_RC_OK(rcAbort); + TST_CHECK_RC_OK(RTSemEventSignal(pRace->hContinue)); + intnetR3IfTestSetWaitRace(NULL, NIL_RTSEMEVENT, NIL_RTSEMEVENT); + + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(pRace->hThread, RT_MS_5SEC, &rcThread); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + return false; + pRace->hThread = NIL_RTTHREAD; + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Inverse-race wait returned %Rrc, expected %Rrc", rcThread, VERR_SEM_DESTROYED); + + rc = IntNetR3IfWait(hIf, 0 /*cMillies*/); + if (rc != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Post-abort wait returned %Rrc, expected %Rrc", rc, VERR_SEM_DESTROYED); + + RTSemEventDestroy(pRace->hContinue); + RTSemEventDestroy(pRace->hReached); + pRace->hContinue = NIL_RTSEMEVENT; + pRace->hReached = NIL_RTSEMEVENT; + return true; +} + + +/** Joins and releases an inverse-race waiter after the service has been stopped. */ +static bool tstWaitRaceJoinAfterServiceStop(TSTWAITRACE *pRace) +{ + if (pRace->hThread != NIL_RTTHREAD) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + int const rc = RTThreadWait(pRace->hThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Joining inverse-race wait after service stop failed: %Rrc", rc); + return false; + } + if (rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Inverse-race wait returned %Rrc after service stop, expected %Rrc", + rcThread, VERR_SEM_DESTROYED); + pRace->hThread = NIL_RTTHREAD; + } + if (pRace->hContinue != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pRace->hContinue); + pRace->hContinue = NIL_RTSEMEVENT; + } + if (pRace->hReached != NIL_RTSEMEVENT) + { + RTSemEventDestroy(pRace->hReached); + pRace->hReached = NIL_RTSEMEVENT; + } + return true; +} + +static int tstSendFrame(INTNETIFCTX hIf, uint8_t bFill) +{ + INTNETFRAME Frame; + int rc = IntNetR3IfQueryOutputFrame(hIf, 64, &Frame); + if (RT_SUCCESS(rc)) + { + memset(Frame.pvFrame, bFill, 64); + rc = IntNetR3IfOutputFrameCommit(hIf, &Frame); + } + return rc; +} + + +/** Sends one frame from a worker so the test can detect callback blocking. */ +static DECLCALLBACK(int) tstSendThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + return tstSendFrame((INTNETIFCTX)pvUser, 0x5a); +} + + +/** Ensures a blocked notification writer cannot stall the IntNet delivery callback. */ +static void tstDeferredNotification(void) +{ + RTTestSub(g_hTest, "Deferred receive notification"); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing switch helper failed: %Rrc", rc); + svcStop(&Svc); + return; + } + + char szBlockFile[RTPATH_MAX]; + rc = RTStrCopy(szBlockFile, sizeof(szBlockFile), Svc.szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(szBlockFile, sizeof(szBlockFile), "poke-block"); + RTFILE hBlockFile = NIL_RTFILE; + bool fBlockFileCreated = false; + if (RT_SUCCESS(rc)) + { + rc = RTFileOpen(&hBlockFile, szBlockFile, RTFILE_O_WRITE | RTFILE_O_CREATE_REPLACE | RTFILE_O_DENY_ALL + | (0600 << RTFILE_O_CREATE_MODE_SHIFT)); + if (RT_SUCCESS(rc)) + { + fBlockFileCreated = true; + rc = RTFileClose(hBlockFile); + } + } + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + rc = RTEnvSet(TST_ENV_POKE_BLOCK_FILE, szBlockFile); + TST_CHECK_RC_OK(rc); + + INTNETIFCTX hTx = NULL; + INTNETIFCTX hSlow = NULL; + RTTHREAD hWaitThread = NIL_RTTHREAD; + RTTHREAD hSendThread = NIL_RTTHREAD; + bool fNotificationTriggered = false; + bool fServiceStopped = false; + do + { + if (RT_FAILURE(rc)) + break; + + char szNetwork[128]; + RTTESTI_CHECK_RC_OK_BREAK(tstMakeUuidName("tst-IntNet-deferred", szNetwork, sizeof(szNetwork))); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hTx, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hSlow, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hTx, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hSlow, true)); + + RTTESTI_CHECK_RC_OK_BREAK(RTThreadCreate(&hWaitThread, tstWaitThread, hSlow, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetSlow")); + uint64_t const msWaitStart = RTTimeMilliTS(); + RTTHREADSTATE enmState; + do + { + enmState = RTThreadGetState(hWaitThread); + if (enmState == RTTHREADSTATE_EVENT || enmState == RTTHREADSTATE_TERMINATED) + break; + RTThreadSleep(1); + } while (RTTimeMilliTS() - msWaitStart < RT_MS_5SEC); + if (enmState != RTTHREADSTATE_EVENT) + { + RTTestFailed(g_hTest, "Slow receive waiter did not block on its event (state %d)", enmState); + break; + } + + /* This ordered synchronous request proves that the preceding asynchronous + WAIT has reached the service and armed its delivery callback. */ + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hSlow, true)); + + RTTESTI_CHECK_RC_OK_BREAK(RTThreadCreate(&hSendThread, tstSendThread, hTx, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetSend")); + fNotificationTriggered = true; + int rcSend = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hSendThread, RT_MS_5SEC, &rcSend); + if (rc == VERR_TIMEOUT) + RTTestFailed(g_hTest, "Frame delivery remained blocked behind the notification writer"); + else + { + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + hSendThread = NIL_RTTHREAD; + TST_CHECK_RC_OK(rcSend); + } + } + } while (0); + + if (fNotificationTriggered && hWaitThread != NIL_RTTHREAD) + { + int rcWait = VERR_IPE_UNINITIALIZED_STATUS; + int const rc2 = RTThreadWait(hWaitThread, 100 /*cMillies*/, &rcWait); + if (rc2 != VERR_TIMEOUT) + { + RTTestFailed(g_hTest, "Notification block gate was ineffective: %Rrc (%Rrc)", rc2, rcWait); + if (RT_SUCCESS(rc2)) + hWaitThread = NIL_RTTHREAD; + } + } + + if (fBlockFileCreated) + { + int const rc2 = RTFileDelete(szBlockFile); + TST_CHECK_RC_OK(rc2); + if ( RT_SUCCESS(rc2) + || rc2 == VERR_FILE_NOT_FOUND + || rc2 == VERR_PATH_NOT_FOUND) + fBlockFileCreated = false; + } + if (fBlockFileCreated) + { + /* The helper gate is still active, so synchronous cleanup requests may + block as well. Stop the helper before joining either client thread. */ + svcStop(&Svc); + fServiceStopped = true; + + int const rc2 = RTFileDelete(szBlockFile); + if ( RT_SUCCESS(rc2) + || rc2 == VERR_FILE_NOT_FOUND + || rc2 == VERR_PATH_NOT_FOUND) + fBlockFileCreated = false; + } + + if (hSendThread != NIL_RTTHREAD) + { + int rcSend = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hSendThread, RT_MS_5SEC, &rcSend); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + { + if (!fServiceStopped) + { + svcStop(&Svc); + fServiceStopped = true; + } + rc = RTThreadWait(hSendThread, RT_INDEFINITE_WAIT, &rcSend); + } + if (RT_SUCCESS(rc)) + { + hSendThread = NIL_RTTHREAD; + if (!fServiceStopped) + TST_CHECK_RC_OK(rcSend); + } + } + if (hWaitThread != NIL_RTTHREAD) + { + int rcWait = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hWaitThread, RT_MS_5SEC, &rcWait); + if (rc == VERR_TIMEOUT) + { + if (fNotificationTriggered) + RTTestFailed(g_hTest, "Slow receive waiter was not notified after releasing the writer"); + if (!fServiceStopped) + { + svcStop(&Svc); + fServiceStopped = true; + } + rc = RTThreadWait(hWaitThread, RT_INDEFINITE_WAIT, &rcWait); + } + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + hWaitThread = NIL_RTTHREAD; + if (!fServiceStopped && rcWait != VINF_SUCCESS) + RTTestFailed(g_hTest, "Slow receive waiter returned %Rrc, expected %Rrc", rcWait, VINF_SUCCESS); + } + } + + if (hSlow) + IntNetR3IfDestroy(hSlow); + if (hTx) + IntNetR3IfDestroy(hTx); + svcStop(&Svc); +} + + +/** Retries interface creation while a just-closed worker releases its slot. */ +static int tstCreateIfRetry(PINTNETIFCTX phIf, const char *pszNetwork, uint32_t cbSend, uint32_t cbRecv) +{ + uint64_t const msStart = RTTimeMilliTS(); + int rc; + do + { + rc = IntNetR3IfCreateEx(phIf, pszNetwork, kIntNetTrunkType_WhateverNone, "", cbSend, cbRecv, 0 /*fFlags*/); + if (RT_SUCCESS(rc)) + return rc; + Assert(*phIf == NULL); + RTThreadSleep(10); + } while (RTTimeMilliTS() - msStart < RT_MS_5SEC); + return rc; +} + + +/** Exercises either the connection or worker-thread admission limit. */ +static void tstSessionSlotLimit(const char *pszSubTest, const char *pszMaxConnections, const char *pszMaxThreads) +{ + RTTestSub(g_hTest, pszSubTest); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing switch helper failed: %Rrc", rc); + svcStop(&Svc); + return; + } + + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_MAX_CONNECTIONS, pszMaxConnections)); + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_MAX_THREADS, pszMaxThreads)); + + char szNetwork[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-limit", szNetwork, sizeof(szNetwork))); + INTNETIFCTX hAnchor = NULL; + INTNETIFCTX hHolder = NULL; + INTNETIFCTX hRejected = NULL; + INTNETIFCTX hRetry = NULL; + do + { + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hAnchor, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hHolder, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + + rc = IntNetR3IfCreateEx(&hRejected, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/); + if (RT_SUCCESS(rc) || hRejected != NULL) + RTTestFailed(g_hTest, "Over-limit interface unexpectedly succeeded: %Rrc (%p)", rc, hRejected); + else if (!svcIsDisconnected(rc)) + RTTestFailed(g_hTest, "Over-limit interface returned unexpected status: %Rrc", rc); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hAnchor, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hAnchor, false)); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfDestroy(hHolder)); + hHolder = NULL; + RTTESTI_CHECK_RC_OK_BREAK(tstCreateIfRetry(&hRetry, szNetwork, _64K, _64K)); + } while (0); + + if (hRejected) + IntNetR3IfDestroy(hRejected); + if (hRetry) + IntNetR3IfDestroy(hRetry); + if (hHolder) + IntNetR3IfDestroy(hHolder); + if (hAnchor) + IntNetR3IfDestroy(hAnchor); + svcStop(&Svc); +} + + +/** Exercises the service-wide shared-memory byte quota and release accounting. */ +static void tstAggregateShMemLimit(void) +{ + RTTestSub(g_hTest, "Aggregate shared-memory limit"); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing switch helper failed: %Rrc", rc); + svcStop(&Svc); + return; + } + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_MAX_SHMEM, "1048576")); + + char szNetwork[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-shmem", szNetwork, sizeof(szNetwork))); + INTNETIFCTX hAnchor = NULL; + INTNETIFCTX hLarge = NULL; + INTNETIFCTX hRejected = NULL; + INTNETIFCTX hRetry = NULL; + do + { + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hAnchor, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hLarge, szNetwork, kIntNetTrunkType_WhateverNone, "", + _256K, _256K, 0 /*fFlags*/)); + + rc = IntNetR3IfCreateEx(&hRejected, szNetwork, kIntNetTrunkType_WhateverNone, "", + _256K, _256K, 0 /*fFlags*/); + if (rc != VERR_OUT_OF_RESOURCES) + RTTestFailed(g_hTest, "Over-quota interface returned %Rrc, expected %Rrc", rc, VERR_OUT_OF_RESOURCES); + if (hRejected != NULL) + RTTestFailed(g_hTest, "Over-quota interface unexpectedly returned a context"); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfDestroy(hLarge)); + hLarge = NULL; + RTTESTI_CHECK_RC_OK_BREAK(tstCreateIfRetry(&hRetry, szNetwork, _256K, _256K)); + } while (0); + + if (hRejected) + IntNetR3IfDestroy(hRejected); + if (hRetry) + IntNetR3IfDestroy(hRetry); + if (hLarge) + IntNetR3IfDestroy(hLarge); + if (hAnchor) + IntNetR3IfDestroy(hAnchor); + svcStop(&Svc); +} + + +/** Sends a deliberately incomplete request and waits for the service to close it. */ +static void tstServerReadTimeoutOne(TSTSWITCHSVC *pSvc, unsigned iPart) +{ + RTLOCALIPCSESSION hSession = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcSessionConnect(&hSession, pSvc->szService, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + return; + + if (iPart == 1) + { + uint8_t bPartial = 0; + rc = RTLocalIpcSessionWrite(hSession, &bPartial, sizeof(bPartial)); + } + else if (iPart == 2 || iPart == 3) + { + INTNETR3IPCREQHDR Hdr; + Hdr.u32Magic = INTNET_R3_IPC_REQ_MAGIC; + Hdr.u16Version = INTNET_R3_IPC_VERSION; + Hdr.cbHdr = sizeof(Hdr); + Hdr.cbReq = sizeof(INTNETIFCLOSEREQ); + Hdr.uOperation = VMMR0_DO_INTNET_IF_CLOSE; + rc = RTLocalIpcSessionWrite(hSession, &Hdr, sizeof(Hdr)); + if (RT_SUCCESS(rc)) + { + uint8_t bPartial = 0; + rc = RTLocalIpcSessionWrite(hSession, &bPartial, sizeof(bPartial)); + } + } + if (RT_SUCCESS(rc) && iPart != 0) + rc = RTLocalIpcSessionFlush(hSession); + + if (RT_SUCCESS(rc) && iPart == 3) + { + uint64_t const msStart = RTTimeMilliTS(); + do + { + rc = RTLocalIpcSessionWaitForData(hSession, 0 /*cMillies*/); + if (svcIsDisconnected(rc)) + break; + if (rc != VERR_TIMEOUT) + break; + RTThreadSleep(TST_READ_TIMEOUT_MS / 4); + uint8_t bPartial = 0; + rc = RTLocalIpcSessionWrite(hSession, &bPartial, sizeof(bPartial)); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(hSession); + } while ( RT_SUCCESS(rc) + && RTTimeMilliTS() - msStart < TST_READ_TIMEOUT_MS * 2); + } + else if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionWaitForData(hSession, RT_MS_5SEC); + + if (!svcIsDisconnected(rc)) + RTTestFailed(g_hTest, "Incomplete request part %u returned %Rrc, expected a disconnect", iPart, rc); + RTLocalIpcSessionClose(hSession); +} + + +/** Ensures a complete but unsuccessful pre-OPEN request does not disable the idle deadline. */ +static void tstServerPreOpenIdleTimeout(TSTSWITCHSVC *pSvc) +{ + RTLOCALIPCSESSION hSession = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcSessionConnect(&hSession, pSvc->szService, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + return; + + INTNETIFCLOSEREQ Req; + RT_ZERO(Req); + Req.Hdr.u32Magic = SUPVMMR0REQHDR_MAGIC; + Req.Hdr.cbReq = sizeof(Req); + Req.hIf = INTNET_HANDLE_INVALID; + + INTNETR3IPCREQHDR Hdr; + Hdr.u32Magic = INTNET_R3_IPC_REQ_MAGIC; + Hdr.u16Version = INTNET_R3_IPC_VERSION; + Hdr.cbHdr = sizeof(Hdr); + Hdr.cbReq = sizeof(Req); + Hdr.uOperation = VMMR0_DO_INTNET_IF_CLOSE; + rc = RTLocalIpcSessionWrite(hSession, &Hdr, sizeof(Hdr)); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionWrite(hSession, &Req, sizeof(Req)); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(hSession); + + INTNETR3IPCREPLYHDR ReplyHdr; + RT_ZERO(ReplyHdr); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionRead(hSession, &ReplyHdr, sizeof(ReplyHdr), NULL /*pcbRead*/); + if (RT_SUCCESS(rc)) + { + if ( ReplyHdr.u32Magic != INTNET_R3_IPC_REPLY_MAGIC + || ReplyHdr.u16Version != INTNET_R3_IPC_VERSION + || ReplyHdr.cbHdr != sizeof(ReplyHdr) + || ReplyHdr.cbReply != sizeof(Req) + || ReplyHdr.cbShMemName != 0 + || ReplyHdr.cbShMem != 0) + rc = VERR_INVALID_PARAMETER; + } + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionRead(hSession, &Req, sizeof(Req), NULL /*pcbRead*/); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionWaitForData(hSession, RT_MS_5SEC); + if (!svcIsDisconnected(rc)) + RTTestFailed(g_hTest, "Idle pre-OPEN session returned %Rrc, expected a disconnect", rc); + RTLocalIpcSessionClose(hSession); +} + + +/** Exercises initial-byte and absolute request-frame deadlines. */ +static void tstServerReadTimeout(void) +{ + RTTestSub(g_hTest, "Server read timeout"); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing switch helper failed: %Rrc", rc); + svcStop(&Svc); + return; + } + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_READ_TIMEOUT, "1000")); + + char szNetwork[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-timeout", szNetwork, sizeof(szNetwork))); + INTNETIFCTX hAnchor = NULL; + INTNETIFCTX hFresh = NULL; + do + { + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hAnchor, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + + /* Established interfaces may be idle for longer than the frame timeout. */ + RTThreadSleep(TST_READ_TIMEOUT_MS * 2); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hAnchor, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hAnchor, false)); + + tstServerReadTimeoutOne(&Svc, 0 /* no bytes */); + tstServerReadTimeoutOne(&Svc, 1 /* partial header */); + tstServerReadTimeoutOne(&Svc, 2 /* partial payload */); + tstServerReadTimeoutOne(&Svc, 3 /* drip-fed payload */); + tstServerPreOpenIdleTimeout(&Svc); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hFresh, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/)); + } while (0); + + if (hFresh) + IntNetR3IfDestroy(hFresh); + if (hAnchor) + IntNetR3IfDestroy(hAnchor); + svcStop(&Svc); +} + + +/** Fake restricted service sending an incomplete reply frame. */ +typedef struct TSTPARTIALREPLYSERVER +{ + RTLOCALIPCSERVER hServer; + RTSEMEVENT hRelease; + unsigned iMode; +} TSTPARTIALREPLYSERVER; + + +/** Reads and validates one request accepted by the fake service. */ +static int tstPartialReplyServerReadRequest(RTLOCALIPCSESSION hSession, bool fVerifyPeer, + PINTNETR3IPCREQHDR pReqHdr, void **ppvReq) +{ + *ppvReq = NULL; + int rc = RTLocalIpcSessionRead(hSession, pReqHdr, sizeof(*pReqHdr), NULL /*pcbRead*/); + if (RT_SUCCESS(rc) && fVerifyPeer) + { + rc = RTLocalIpcSessionVerifySameUser(hSession); + if (rc == VERR_NOT_SUPPORTED) + rc = VINF_SUCCESS; + } + if (RT_SUCCESS(rc)) + { + if ( pReqHdr->u32Magic != INTNET_R3_IPC_REQ_MAGIC + || pReqHdr->u16Version != INTNET_R3_IPC_VERSION + || pReqHdr->cbHdr != sizeof(*pReqHdr) + || pReqHdr->cbReq < sizeof(SUPVMMR0REQHDR) + || pReqHdr->cbReq > INTNET_R3_IPC_MAX_REQ) + rc = VERR_INVALID_PARAMETER; + } + if (RT_SUCCESS(rc)) + { + *ppvReq = RTMemTmpAlloc(pReqHdr->cbReq); + rc = *ppvReq ? RTLocalIpcSessionRead(hSession, *ppvReq, pReqHdr->cbReq, NULL /*pcbRead*/) + : VERR_NO_TMP_MEMORY; + } + return rc; +} + + +/** Sends one complete successful fake-service reply. */ +static int tstPartialReplyServerSendReply(RTLOCALIPCSESSION hSession, INTNETR3IPCREQHDR const *pReqHdr, + const void *pvReq) +{ + INTNETR3IPCREPLYHDR ReplyHdr; + ReplyHdr.u32Magic = INTNET_R3_IPC_REPLY_MAGIC; + ReplyHdr.u16Version = INTNET_R3_IPC_VERSION; + ReplyHdr.cbHdr = sizeof(ReplyHdr); + ReplyHdr.rc = VINF_SUCCESS; + ReplyHdr.cbReply = pReqHdr->cbReq; + ReplyHdr.cbShMemName = 0; + ReplyHdr.cbShMem = 0; + int rc = RTLocalIpcSessionWrite(hSession, &ReplyHdr, sizeof(ReplyHdr)); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionWrite(hSession, pvReq, pReqHdr->cbReq); + if (RT_SUCCESS(rc)) + rc = RTLocalIpcSessionFlush(hSession); + return rc; +} + + +/** Accepts requests and deliberately stalls before or during a reply. */ +static DECLCALLBACK(int) tstPartialReplyServerThread(RTTHREAD hThread, void *pvUser) +{ + RT_NOREF(hThread); + TSTPARTIALREPLYSERVER *pServer = (TSTPARTIALREPLYSERVER *)pvUser; + + RTLOCALIPCSESSION hSession = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcServerListen(pServer->hServer, &hSession); + if (RT_SUCCESS(rc)) + { + INTNETR3IPCREQHDR ReqHdr; + void *pvReq = NULL; + rc = tstPartialReplyServerReadRequest(hSession, true /*fVerifyPeer*/, &ReqHdr, &pvReq); + if (RT_SUCCESS(rc) && pServer->iMode == 3) + { + if (ReqHdr.uOperation != VMMR0_DO_INTNET_OPEN || ReqHdr.cbReq != sizeof(INTNETOPENREQ)) + rc = VERR_INVALID_PARAMETER; + else + { + ((PINTNETOPENREQ)pvReq)->hIf = 1; + rc = tstPartialReplyServerSendReply(hSession, &ReqHdr, pvReq); + } + RTMemTmpFree(pvReq); + pvReq = NULL; + if (RT_SUCCESS(rc)) + rc = tstPartialReplyServerReadRequest(hSession, false /*fVerifyPeer*/, &ReqHdr, &pvReq); + if ( RT_SUCCESS(rc) + && ReqHdr.uOperation != VMMR0_DO_INTNET_IF_GET_BUFFER_PTRS) + rc = VERR_INVALID_PARAMETER; + } + if (RT_SUCCESS(rc)) + { + INTNETR3IPCREPLYHDR ReplyHdr; + ReplyHdr.u32Magic = INTNET_R3_IPC_REPLY_MAGIC; + ReplyHdr.u16Version = INTNET_R3_IPC_VERSION; + ReplyHdr.cbHdr = sizeof(ReplyHdr); + ReplyHdr.rc = VINF_SUCCESS; + ReplyHdr.cbReply = ReqHdr.cbReq; + ReplyHdr.cbShMemName = 0; + ReplyHdr.cbShMem = 0; + if (pServer->iMode == 1) + rc = RTLocalIpcSessionWrite(hSession, &ReplyHdr, 1); + else if (pServer->iMode == 2) + { + rc = RTLocalIpcSessionWrite(hSession, &ReplyHdr, sizeof(ReplyHdr)); + if (RT_SUCCESS(rc)) + { + uint8_t bPartial = 0; + rc = RTLocalIpcSessionWrite(hSession, &bPartial, sizeof(bPartial)); + } + } + if (RT_SUCCESS(rc) && (pServer->iMode == 1 || pServer->iMode == 2)) + rc = RTLocalIpcSessionFlush(hSession); + if (RT_SUCCESS(rc)) + rc = RTSemEventWait(pServer->hRelease, RT_MS_5SEC); + } + RTMemTmpFree(pvReq); + RTLocalIpcSessionClose(hSession); + } + return rc; +} + + +/** Exercises no-reply, partial-frame, and post-OPEN client read deadlines. */ +static void tstClientReadTimeout(void) +{ + RTTestSub(g_hTest, "Client read timeout"); + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Preparing fake service failed: %Rrc", rc); + svcStop(&Svc); + return; + } + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_READ_TIMEOUT, "1000")); + + RTLOCALIPCSERVER hServer = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hServer, Svc.szService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + for (unsigned iMode = 0; iMode < 4; iMode++) + { + /* A partial frame must be retired by the receiver's frame deadline, + not by the indistinguishable synchronous-call deadline. */ + TST_CHECK_RC_OK(RTEnvSet(TST_ENV_REPLY_TIMEOUT, + iMode == 1 || iMode == 2 ? "4294967295" : "1000")); + + TSTPARTIALREPLYSERVER Server; + Server.hServer = hServer; + Server.hRelease = NIL_RTSEMEVENT; + Server.iMode = iMode; + RTTHREAD hThread = NIL_RTTHREAD; + rc = RTSemEventCreate(&Server.hRelease); + if (RT_SUCCESS(rc)) + rc = RTThreadCreate(&hThread, tstPartialReplyServerThread, &Server, 0 /*cbStack*/, + RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, "IntNetFake"); + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc)) + { + char szNetwork[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-client-timeout", szNetwork, sizeof(szNetwork))); + INTNETIFCTX hIf = NULL; + rc = IntNetR3IfCreateEx(&hIf, szNetwork, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0 /*fFlags*/); + if (rc != VERR_TIMEOUT) + RTTestFailed(g_hTest, "Incomplete reply mode %u returned %Rrc, expected %Rrc", + iMode, rc, VERR_TIMEOUT); + if (hIf != NULL) + { + RTTestFailed(g_hTest, "Incomplete reply mode %u unexpectedly returned an interface", iMode); + IntNetR3IfDestroy(hIf); + } + + TST_CHECK_RC_OK(RTSemEventSignal(Server.hRelease)); + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hThread, RT_MS_5SEC, &rcThread); + TST_CHECK_RC_OK(rc); + if (RT_FAILURE(rc)) + rc = RTThreadWait(hThread, RT_INDEFINITE_WAIT, &rcThread); + if (RT_SUCCESS(rc) && RT_FAILURE(rcThread)) + RTTestFailed(g_hTest, "Fake service mode %u returned %Rrc", iMode, rcThread); + } + if (Server.hRelease != NIL_RTSEMEVENT) + RTSemEventDestroy(Server.hRelease); + } + RTLocalIpcServerDestroy(hServer); + } + svcStop(&Svc); +} +#endif /* !RT_OS_DARWIN || VBOX_INTNET_TESTCASE_LOCALIPC */ + + +/********************************************************************************************************************************* +* Test body * +*********************************************************************************************************************************/ +static void tstIntNetR3Switch(void) +{ + RTTestSub(g_hTest, "R3 switch IPC (local IPC)"); + +#if defined(RT_OS_DARWIN) && !defined(VBOX_INTNET_TESTCASE_LOCALIPC) + RTTestSub(g_hTest, "R3 switch IPC (Darwin/XPC)"); + RTTestSkipped(g_hTest, "Darwin uses XPC for R3 IntNet switch; in-process embedding is not implemented in this testcase."); + return; +#else + + TSTSWITCHSVC Svc; + int rc = svcStart(&Svc); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Failed to prepare standalone switch service: %Rrc", rc); + svcStop(&Svc); + return; + } + + char szNetA[128], szNetB[128], szNetWait[128], szNetRace[128]; + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-A", szNetA, sizeof(szNetA))); + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-B", szNetB, sizeof(szNetB))); + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-Wait", szNetWait, sizeof(szNetWait))); + TST_CHECK_RC_OK(tstMakeUuidName("tst-IntNet-Race", szNetRace, sizeof(szNetRace))); + INTNETIFCTX hA = NULL, hB = NULL, hC = NULL, hWait = NULL, hRace = NULL; + RTSEMEVENT hWaitEvt = NIL_RTSEMEVENT; + RTTHREAD hWaitThread = NIL_RTTHREAD; + TSTRXCOLLECT RxB; RT_ZERO(RxB); + TSTRXCOLLECT RxC; RT_ZERO(RxC); + TSTWAITRACE WaitRace; RT_ZERO(WaitRace); + WaitRace.hThread = NIL_RTTHREAD; + WaitRace.hReached = NIL_RTSEMEVENT; + WaitRace.hContinue = NIL_RTSEMEVENT; + bool fWaitThreadStopped = true; + bool fRaceThreadStopped = true; + do + { + /* No service is running yet. This first interface creation must auto-start it. */ + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hA, szNetA, kIntNetTrunkType_WhateverNone, "", _256K, _256K, 0)); + RTTESTI_CHECK_RC_OK_BREAK(svcReadPid(&Svc)); + RTPROCSTATUS Status; + rc = RTProcWait(Svc.hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &Status); + if (rc != VERR_PROCESS_RUNNING) + { + RTTestFailed(g_hTest, "Auto-started switch helper is not running: %Rrc", rc); + if (RT_SUCCESS(rc)) + { + Svc.fProcessReaped = true; + Svc.hProcess = NIL_RTPROCESS; + } + break; + } + + /* Reject hostile sizes before they reach IntNet's uint32_t alignment arithmetic. */ + INTNETIFCTX hOversized = NULL; + rc = IntNetR3IfCreateEx(&hOversized, szNetA, kIntNetTrunkType_WhateverNone, "", + UINT32_MAX, UINT32_MAX, 0); + if (rc != VERR_OUT_OF_RANGE) + RTTestFailed(g_hTest, "Oversized open returned %Rrc, expected %Rrc", rc, VERR_OUT_OF_RANGE); + if (hOversized != NULL) + { + RTTestFailed(g_hTest, "Oversized open unexpectedly returned an interface context"); + IntNetR3IfDestroy(hOversized); + } + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hB, szNetA, kIntNetTrunkType_WhateverNone, "", _256K, _256K, 0)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hC, szNetB, kIntNetTrunkType_WhateverNone, "", _128K, _128K, 0)); + RTTESTI_CHECK_RC_OK_BREAK(RTSemEventCreate(&hWaitEvt)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateExWithRecvEvent(&hWait, szNetWait, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0, hWaitEvt)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfCreateEx(&hRace, szNetRace, kIntNetTrunkType_WhateverNone, "", + _64K, _64K, 0)); + + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hA, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hB, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hC, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hWait, true)); + RTTESTI_CHECK_RC_OK_BREAK(IntNetR3IfSetActive(hRace, true)); + + RTTESTI_CHECK_RC_OK_BREAK(tstRxStart(&RxB, hB)); + RTTESTI_CHECK_RC_OK_BREAK(tstRxStart(&RxC, hC)); + + /* Build and send a small broadcast-like frame on A. */ + RTTESTI_CHECK_RC_OK_BREAK(tstSendFrame(hA, 0x42)); /* content isn't parsed by switch */ + + /* Wait for B to receive, ensure C did not (isolation by network name). */ + uint64_t msStart = RTTimeMilliTS(); + bool fGotB = false; bool fGotC = false; + while (RTTimeMilliTS() - msStart < TST_WAIT_MS) + { + fGotB = ASMAtomicReadU32(&RxB.cFrames) != 0; + fGotC = ASMAtomicReadU32(&RxC.cFrames) != 0; + if (fGotC) + break; + RTThreadSleep(10); + } + TST_CHECK(fGotB); + TST_CHECK(!fGotC); + + /* Send the next frame as soon as the preceding callback runs. This + repeatedly overlaps delivery with the collector re-registering its + wait and exercises the server's lost-wakeup avoidance. */ + uint32_t const cFramesBefore = ASMAtomicReadU32(&RxB.cFrames); + uint64_t const msStressStart = RTTimeMilliTS(); + uint32_t cFramesSent = 0; + while (cFramesSent < TST_STRESS_FRAMES) + { + rc = tstSendFrame(hA, (uint8_t)cFramesSent); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "Stress frame %RU32 failed: %Rrc", cFramesSent, rc); + break; + } + cFramesSent++; + while ( ASMAtomicReadU32(&RxB.cFrames) < cFramesBefore + cFramesSent + && RTTimeMilliTS() - msStressStart < RT_MS_5SEC) + RTThreadSleep(1); + if (ASMAtomicReadU32(&RxB.cFrames) < cFramesBefore + cFramesSent) + { + RTTestFailed(g_hTest, "Stress receive stalled after %RU32 frames", cFramesSent); + break; + } + } + TST_CHECK(ASMAtomicReadU32(&RxB.cFrames) >= cFramesBefore + cFramesSent); + TST_CHECK(ASMAtomicReadU32(&RxC.cFrames) == 0); + + fWaitThreadStopped = tstConcurrentWaitAbort(hWait, &hWaitThread); + fRaceThreadStopped = tstInverseWaitAbortRace(hRace, &WaitRace); + + } while (0); + + /* Cleanup RX helpers before tearing down interfaces, including error paths. */ + bool const fRxBStopped = tstRxStop(&RxB); + bool const fRxCStopped = tstRxStop(&RxC); + + if (hA) { IntNetR3IfSetActive(hA, false); IntNetR3IfDestroy(hA); } + if (hB && fRxBStopped) { IntNetR3IfSetActive(hB, false); IntNetR3IfDestroy(hB); } + if (hC && fRxCStopped) { IntNetR3IfSetActive(hC, false); IntNetR3IfDestroy(hC); } + if (hWait && fWaitThreadStopped) + { + IntNetR3IfSetActive(hWait, false); + IntNetR3IfDestroy(hWait); + hWait = NULL; + + /* The caller-owned event must remain valid after interface teardown. */ + TST_CHECK_RC_OK(RTSemEventSignal(hWaitEvt)); + TST_CHECK_RC_OK(RTSemEventWait(hWaitEvt, 0 /*cMillies*/)); + } + if (hWaitEvt != NIL_RTSEMEVENT && fWaitThreadStopped) + RTSemEventDestroy(hWaitEvt); + if (hRace && fRaceThreadStopped) + { + IntNetR3IfSetActive(hRace, false); + IntNetR3IfDestroy(hRace); + hRace = NULL; + } + + svcStop(&Svc); + + /* If a bounded pre-stop join timed out, forcing the helper down closes the + IPC stream. Join before stack storage, events, or interfaces disappear. */ + if (!fRxBStopped && tstRxJoinAfterServiceStop(&RxB) && hB) + { + IntNetR3IfDestroy(hB); + hB = NULL; + } + if (!fRxCStopped && tstRxJoinAfterServiceStop(&RxC) && hC) + { + IntNetR3IfDestroy(hC); + hC = NULL; + } + if (!fWaitThreadStopped) + { + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(hWaitThread, RT_INDEFINITE_WAIT, &rcThread); + TST_CHECK_RC_OK(rc); + if (RT_SUCCESS(rc) && rcThread != VERR_SEM_DESTROYED) + RTTestFailed(g_hTest, "Wait thread returned %Rrc after service stop, expected %Rrc", + rcThread, VERR_SEM_DESTROYED); + if (RT_SUCCESS(rc) && hWait) + { + IntNetR3IfDestroy(hWait); + hWait = NULL; + } + if (RT_SUCCESS(rc) && hWaitEvt != NIL_RTSEMEVENT) + { + RTSemEventDestroy(hWaitEvt); + hWaitEvt = NIL_RTSEMEVENT; + } + } + if (!fRaceThreadStopped && tstWaitRaceJoinAfterServiceStop(&WaitRace) && hRace) + { + IntNetR3IfDestroy(hRace); + hRace = NULL; + } +#endif +} + + +int main(int argc, char **argv) +{ + /* The testcase build of IntNetIf forces the driverless R3 path and does not initialize SUPLib. */ + int rc = RTR3InitExe(argc, &argv, 0 /*fFlags*/); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + + RTTEST hTest = NIL_RTTEST; + rc = RTTestCreate("tstVBoxIntNetR3Switch", &hTest); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + g_hTest = hTest; + + tstIntNetR3Switch(); +#if !defined(RT_OS_DARWIN) || defined(VBOX_INTNET_TESTCASE_LOCALIPC) + tstDeferredNotification(); + tstSessionSlotLimit("Connection limit", "2", "8"); + tstSessionSlotLimit("Worker-thread limit", "8", "4"); + tstAggregateShMemLimit(); + tstServerReadTimeout(); + tstClientReadTimeout(); +#endif + + return RTTestSummaryAndDestroy(hTest); +} diff --git a/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp new file mode 100644 index 000000000000..4d3f5b5f0d34 --- /dev/null +++ b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSide.cpp @@ -0,0 +1,1213 @@ +/* $Id: tstVBoxNatGuestSide.cpp 114964 2026-08-10 15:18:23Z andreas.loeffler@oracle.com $ */ +/** @file + * tstVBoxNatGuestSide - Guest-side NAT over Ring-3 IntNet testcase. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include "../NetLib/IntNetIf.h" +#include "tstVBoxNatGuestSideInternal.h" + + +/********************************************************************************************************************************* +* Constants * +*********************************************************************************************************************************/ +#define TST_MAX_FRAME 2048 +#define TST_MAX_QUEUED_FRAMES 16 +#define TST_DHCP_XID UINT32_C(0x33445566) +#define TST_WAIT_MS 3000 +#define TST_POLL_MS 5 + + +/********************************************************************************************************************************* +* Structures and Typedefs * +*********************************************************************************************************************************/ +/** One captured or synthetic Ethernet frame. */ +typedef struct TSTFRAME +{ + /** Frame bytes. */ + uint8_t ab[TST_MAX_FRAME]; + /** Number of valid frame bytes. */ + size_t cb; +} TSTFRAME; +/** Pointer to a frame. */ +typedef TSTFRAME *PTSTFRAME; +/** Pointer to a const frame. */ +typedef const TSTFRAME *PCTSTFRAME; + +/** Bounded FIFO for frames emitted by libslirp. */ +typedef struct TSTFRAMEQUEUE +{ + /** Captured frames. */ + TSTFRAME aFrames[TST_MAX_QUEUED_FRAMES]; + /** Index of the oldest frame. */ + uint32_t iHead; + /** Number of queued frames. */ + uint32_t cFrames; +} TSTFRAMEQUEUE; +/** Pointer to a frame queue. */ +typedef TSTFRAMEQUEUE *PTSTFRAMEQUEUE; + +/** Parsed fields from a DHCP server response. */ +typedef struct TSTDHCPOFFER +{ + /** DHCP message type. */ + uint8_t uMsgType; + /** Offered or acknowledged client address. */ + RTNETADDRIPV4 YiAddr; + /** Subnet mask option. */ + RTNETADDRIPV4 Netmask; + /** Server identifier option. */ + RTNETADDRIPV4 ServerId; + /** Whether a subnet mask option was present. */ + bool fNetmask; + /** Whether a server identifier option was present. */ + bool fServerId; + /** Whether a router option was present. */ + bool fRouter; + /** Whether a DNS option was present. */ + bool fDns; +} TSTDHCPOFFER; +/** Pointer to parsed DHCP fields. */ +typedef TSTDHCPOFFER *PTSTDHCPOFFER; + +/** State for the standalone R3 switch helper. */ +typedef struct TSTSWITCHSERVICE +{ + /** Helper process ID once the auto-start path has published it. */ + RTPROCESS hProcess; + /** Whether the helper was successfully reaped. */ + bool fProcessReaped; + /** Whether the temporary directory was created. */ + bool fTempDirCreated; + /** Unique local IPC service name. */ + char szService[INTNET_R3_IPC_MAX_SERVICE_NAME]; + /** Absolute helper executable path. */ + char szExec[RTPATH_MAX]; + /** Per-test temporary directory. */ + char szTempDir[RTPATH_MAX]; + /** Helper PID file. */ + char szPidFile[RTPATH_MAX]; + /** Helper lock file. */ + char szLockFile[RTPATH_MAX]; +} TSTSWITCHSERVICE; +/** Pointer to switch helper state. */ +typedef TSTSWITCHSERVICE *PTSTSWITCHSERVICE; + +/** NAT-facing IntNet receive pump. */ +typedef struct TSTNATPUMP +{ + /** NAT-facing IntNet interface. */ + INTNETIFCTX hIf; + /** Restricted libslirp wrapper. */ + PVBOXNETSLIRPNATTEST hNat; + /** Pump thread. */ + RTTHREAD hThread; + /** First input failure reported by the pump callback. */ + int rcInput; +} TSTNATPUMP; +/** Pointer to NAT pump state. */ +typedef TSTNATPUMP *PTSTNATPUMP; + +/** Guest-facing IntNet receive collector. */ +typedef struct TSTGUESTRX +{ + /** Guest-facing IntNet interface. */ + INTNETIFCTX hIf; + /** Receive thread. */ + RTTHREAD hThread; + /** Signalled when the expected ARP reply is captured. */ + RTSEMEVENT hReplyEvent; + /** Expected gateway address. */ + RTNETADDRIPV4 GatewayIp; + /** Expected guest address. */ + RTNETADDRIPV4 GuestIp; + /** Expected guest MAC address. */ + RTMAC GuestMac; + /** Captured reply. */ + TSTFRAME Reply; + /** Whether Reply contains the expected ARP response. */ + bool fReply; +} TSTGUESTRX; +/** Pointer to guest receive state. */ +typedef TSTGUESTRX *PTSTGUESTRX; + +/** IntNet destination used by the libslirp output callback. */ +typedef struct TSTNATOUTPUT +{ + /** NAT-facing IntNet interface. */ + INTNETIFCTX hIf; +} TSTNATOUTPUT; +/** Pointer to the IntNet output state. */ +typedef TSTNATOUTPUT *PTSTNATOUTPUT; + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Test handle used by fixture cleanup diagnostics. */ +static RTTEST g_hTest = NIL_RTTEST; + + +/********************************************************************************************************************************* +* Packet Helpers * +*********************************************************************************************************************************/ +/** Constructs a MAC address from literal octets. */ +static RTMAC tstMac(uint8_t b0, uint8_t b1, uint8_t b2, uint8_t b3, uint8_t b4, uint8_t b5) +{ + RTMAC Mac; + Mac.au8[0] = b0; + Mac.au8[1] = b1; + Mac.au8[2] = b2; + Mac.au8[3] = b3; + Mac.au8[4] = b4; + Mac.au8[5] = b5; + return Mac; +} + + +/** Returns the Ethernet broadcast address. */ +static RTMAC tstMacBroadcast(void) +{ + return tstMac(0xff, 0xff, 0xff, 0xff, 0xff, 0xff); +} + + +/** Compares two MAC addresses. */ +static bool tstMacEqual(PCRTMAC pLeft, PCRTMAC pRight) +{ + return memcmp(pLeft, pRight, sizeof(*pLeft)) == 0; +} + + +/** Constructs an IPv4 address from dotted-decimal octets. */ +static RTNETADDRIPV4 tstIPv4(uint8_t b3, uint8_t b2, uint8_t b1, uint8_t b0) +{ + return RTNetIPv4AddrFromU8(b3, b2, b1, b0); +} + + +/** Populates the restricted configuration shared by the tests. */ +static void tstNatConfigInit(PVBOXNETSLIRPNATTESTCFG pCfg, bool fDhcp) +{ + RT_ZERO(*pCfg); + pCfg->IPv4Network = tstIPv4(10, 0, 2, 0); + pCfg->IPv4Netmask = tstIPv4(255, 255, 255, 0); + pCfg->IPv4Host = tstIPv4(10, 0, 2, 2); + pCfg->IPv4DhcpStart = tstIPv4(10, 0, 2, 15); + pCfg->IPv4Nameserver = tstIPv4(10, 0, 2, 3); + pCfg->fDhcp = fDhcp; + pCfg->fDns = true; + pCfg->fRestricted = true; +} + + +/** Starts an Ethernet frame. */ +static void tstEthernetBegin(PTSTFRAME pFrame, PCRTMAC pDstMac, PCRTMAC pSrcMac, uint16_t uEtherType) +{ + RT_ZERO(*pFrame); + PRTNETETHERHDR pEthernet = (PRTNETETHERHDR)pFrame->ab; + pEthernet->DstMac = *pDstMac; + pEthernet->SrcMac = *pSrcMac; + pEthernet->EtherType = RT_H2N_U16(uEtherType); + pFrame->cb = sizeof(*pEthernet); +} + + +/** Builds a guest ARP request. */ +static void tstBuildArpRequest(PTSTFRAME pFrame, PCRTMAC pGuestMac, + RTNETADDRIPV4 GuestIp, RTNETADDRIPV4 TargetIp) +{ + RTMAC const Broadcast = tstMacBroadcast(); + tstEthernetBegin(pFrame, &Broadcast, pGuestMac, RTNET_ETHERTYPE_ARP); + + PRTNETARPIPV4 pArp = (PRTNETARPIPV4)&pFrame->ab[pFrame->cb]; + RT_ZERO(*pArp); + pArp->Hdr.ar_htype = RT_H2N_U16(RTNET_ARP_ETHER); + pArp->Hdr.ar_ptype = RT_H2N_U16(RTNET_ETHERTYPE_IPV4); + pArp->Hdr.ar_hlen = sizeof(RTMAC); + pArp->Hdr.ar_plen = sizeof(RTNETADDRIPV4); + pArp->Hdr.ar_oper = RT_H2N_U16(RTNET_ARPOP_REQUEST); + pArp->ar_sha = *pGuestMac; + pArp->ar_spa = GuestIp; + pArp->ar_tpa = TargetIp; + pFrame->cb += sizeof(*pArp); +} + + +/** Recognizes an internally consistent ARP reply for the expected guest. */ +static bool tstIsArpReply(PCTSTFRAME pFrame, RTNETADDRIPV4 SenderIp, RTNETADDRIPV4 TargetIp, + PCRTMAC pTargetMac, PRTMAC pSenderMac) +{ + if (pFrame->cb < sizeof(RTNETETHERHDR) + sizeof(RTNETARPIPV4)) + return false; + + PCRTNETETHERHDR pEthernet = (PCRTNETETHERHDR)pFrame->ab; + PCRTNETARPIPV4 pArp = (PCRTNETARPIPV4)(pEthernet + 1); + if ( RT_N2H_U16(pEthernet->EtherType) != RTNET_ETHERTYPE_ARP + || RT_N2H_U16(pArp->Hdr.ar_htype) != RTNET_ARP_ETHER + || RT_N2H_U16(pArp->Hdr.ar_ptype) != RTNET_ETHERTYPE_IPV4 + || pArp->Hdr.ar_hlen != sizeof(RTMAC) + || pArp->Hdr.ar_plen != sizeof(RTNETADDRIPV4) + || RT_N2H_U16(pArp->Hdr.ar_oper) != RTNET_ARPOP_REPLY + || pArp->ar_spa.u != SenderIp.u + || pArp->ar_tpa.u != TargetIp.u + || !tstMacEqual(&pEthernet->SrcMac, &pArp->ar_sha) + || !tstMacEqual(&pEthernet->DstMac, &pArp->ar_tha) + || (pTargetMac && !tstMacEqual(&pArp->ar_tha, pTargetMac))) + return false; + + if (pSenderMac) + *pSenderMac = pArp->ar_sha; + return true; +} + + +/** Appends a one-byte DHCP option. */ +static void tstDhcpOptionU8(uint8_t **ppb, uint8_t uOption, uint8_t uValue) +{ + *(*ppb)++ = uOption; + *(*ppb)++ = 1; + *(*ppb)++ = uValue; +} + + +/** Appends an IPv4-valued DHCP option. */ +static void tstDhcpOptionIPv4(uint8_t **ppb, uint8_t uOption, RTNETADDRIPV4 Addr) +{ + *(*ppb)++ = uOption; + *(*ppb)++ = sizeof(Addr); + memcpy(*ppb, &Addr, sizeof(Addr)); + *ppb += sizeof(Addr); +} + + +/** Builds a DHCP DISCOVER or REQUEST Ethernet frame. */ +static void tstBuildDhcpRequest(PTSTFRAME pFrame, PCRTMAC pGuestMac, uint8_t uMsgType, + RTNETADDRIPV4 RequestedAddr, RTNETADDRIPV4 ServerId) +{ + RTMAC const Broadcast = tstMacBroadcast(); + RTNETADDRIPV4 const Zero = tstIPv4(0, 0, 0, 0); + RTNETADDRIPV4 const All = tstIPv4(255, 255, 255, 255); + tstEthernetBegin(pFrame, &Broadcast, pGuestMac, RTNET_ETHERTYPE_IPV4); + + PRTNETIPV4 pIp = (PRTNETIPV4)&pFrame->ab[pFrame->cb]; + RT_ZERO(*pIp); + pIp->ip_v = 4; + pIp->ip_hl = 5; + pIp->ip_len = RT_H2N_U16(RTNETIPV4_MIN_LEN + RTNETUDP_MIN_LEN + sizeof(RTNETBOOTP)); + pIp->ip_id = RT_H2N_U16(0x4400); + pIp->ip_off = RT_H2N_U16(RTNETIPV4_FLAGS_DF); + pIp->ip_ttl = 64; + pIp->ip_p = RTNETIPV4_PROT_UDP; + pIp->ip_src = Zero; + pIp->ip_dst = All; + pIp->ip_sum = RTNetIPv4HdrChecksum(pIp); + pFrame->cb += RTNETIPV4_MIN_LEN; + + PRTNETUDP pUdp = (PRTNETUDP)&pFrame->ab[pFrame->cb]; + RT_ZERO(*pUdp); + pUdp->uh_sport = RT_H2N_U16(RTNETIPV4_PORT_BOOTPC); + pUdp->uh_dport = RT_H2N_U16(RTNETIPV4_PORT_BOOTPS); + pUdp->uh_ulen = RT_H2N_U16(RTNETUDP_MIN_LEN + sizeof(RTNETBOOTP)); + pFrame->cb += RTNETUDP_MIN_LEN; + + PRTNETBOOTP pDhcp = (PRTNETBOOTP)&pFrame->ab[pFrame->cb]; + RT_ZERO(*pDhcp); + pDhcp->bp_op = RTNETBOOTP_OP_REQUEST; + pDhcp->bp_htype = RTNET_ARP_ETHER; + pDhcp->bp_hlen = sizeof(RTMAC); + pDhcp->bp_xid = RT_H2N_U32(TST_DHCP_XID); + pDhcp->bp_flags = RT_H2N_U16(RTNET_DHCP_FLAG_BROADCAST); + pDhcp->bp_chaddr.Mac = *pGuestMac; + pDhcp->bp_vend.Dhcp.dhcp_cookie = RT_H2N_U32(RTNET_DHCP_COOKIE); + + uint8_t *pbOption = pDhcp->bp_vend.Dhcp.dhcp_opts; + tstDhcpOptionU8(&pbOption, RTNET_DHCP_OPT_MSG_TYPE, uMsgType); + if (uMsgType == RTNET_DHCP_MT_REQUEST) + { + tstDhcpOptionIPv4(&pbOption, RTNET_DHCP_OPT_REQ_ADDR, RequestedAddr); + tstDhcpOptionIPv4(&pbOption, RTNET_DHCP_OPT_SERVER_ID, ServerId); + } + *pbOption++ = RTNET_DHCP_OPT_PARAM_REQ_LIST; + *pbOption++ = 4; + *pbOption++ = RTNET_DHCP_OPT_SUBNET_MASK; + *pbOption++ = RTNET_DHCP_OPT_ROUTERS; + *pbOption++ = RTNET_DHCP_OPT_DNS; + *pbOption++ = RTNET_DHCP_OPT_DOMAIN_NAME; + *pbOption++ = RTNET_DHCP_OPT_END; + + pFrame->cb += sizeof(*pDhcp); + pUdp->uh_sum = RTNetIPv4UDPChecksum(pIp, pUdp, pDhcp); +} + + +/** Parses a bounded IPv4/UDP DHCP server response. */ +static bool tstParseDhcpResponse(PCTSTFRAME pFrame, PCRTMAC pGuestMac, PTSTDHCPOFFER pOffer) +{ + if (pFrame->cb < sizeof(RTNETETHERHDR) + RTNETIPV4_MIN_LEN + RTNETUDP_MIN_LEN + RTNETBOOTP_DHCP_MIN_LEN) + return false; + + PCRTNETETHERHDR pEthernet = (PCRTNETETHERHDR)pFrame->ab; + if (RT_N2H_U16(pEthernet->EtherType) != RTNET_ETHERTYPE_IPV4) + return false; + PCRTNETIPV4 pIp = (PCRTNETIPV4)(pEthernet + 1); + if (pIp->ip_v != 4 || pIp->ip_hl < 5 || pIp->ip_p != RTNETIPV4_PROT_UDP) + return false; + + size_t const cbIpHdr = pIp->ip_hl * 4; + size_t const cbIpMax = pFrame->cb - sizeof(*pEthernet); + size_t const cbIp = RT_N2H_U16(pIp->ip_len); + if ( !RTNetIPv4IsHdrValid(pIp, cbIpMax, cbIpMax, true /* fChecksum */) + || cbIp < cbIpHdr + RTNETUDP_MIN_LEN + || cbIp > cbIpMax + || (RT_N2H_U16(pIp->ip_off) & (RTNETIPV4_FLAGS_MF | UINT16_C(0x1fff)))) + return false; + PCRTNETUDP pUdp = (PCRTNETUDP)((uint8_t const *)pIp + cbIpHdr); + size_t const cbUdp = RT_N2H_U16(pUdp->uh_ulen); + if ( cbUdp < RTNETUDP_MIN_LEN + RTNETBOOTP_DHCP_MIN_LEN + || cbIpHdr + cbUdp > cbIp + || !RTNetIPv4IsUDPValid(pIp, pUdp, pUdp + 1, cbIp - cbIpHdr, true /* fChecksum */) + || RT_N2H_U16(pUdp->uh_sport) != RTNETIPV4_PORT_BOOTPS + || RT_N2H_U16(pUdp->uh_dport) != RTNETIPV4_PORT_BOOTPC) + return false; + + size_t const cbDhcp = cbUdp - RTNETUDP_MIN_LEN; + PCRTNETBOOTP pDhcp = (PCRTNETBOOTP)(pUdp + 1); + if ( pDhcp->bp_op != RTNETBOOTP_OP_REPLY + || pDhcp->bp_htype != RTNET_ARP_ETHER + || pDhcp->bp_hlen != sizeof(RTMAC) + || pDhcp->bp_xid != RT_H2N_U32(TST_DHCP_XID) + || !tstMacEqual(&pDhcp->bp_chaddr.Mac, pGuestMac) + || pDhcp->bp_vend.Dhcp.dhcp_cookie != RT_H2N_U32(RTNET_DHCP_COOKIE)) + return false; + + RT_ZERO(*pOffer); + pOffer->YiAddr = pDhcp->bp_yiaddr; + uint8_t const *pbOption = pDhcp->bp_vend.Dhcp.dhcp_opts; + uint8_t const *pbEnd = (uint8_t const *)pDhcp + cbDhcp; + while (pbOption < pbEnd) + { + uint8_t const uOption = *pbOption++; + if (uOption == RTNET_DHCP_OPT_END) + break; + if (uOption == RTNET_DHCP_OPT_PAD) + continue; + if (pbOption >= pbEnd) + return false; + uint8_t const cbOption = *pbOption++; + if ((size_t)(pbEnd - pbOption) < cbOption) + return false; + + if (uOption == RTNET_DHCP_OPT_MSG_TYPE && cbOption == 1) + pOffer->uMsgType = pbOption[0]; + else if (uOption == RTNET_DHCP_OPT_SUBNET_MASK && cbOption == sizeof(RTNETADDRIPV4)) + { + memcpy(&pOffer->Netmask, pbOption, sizeof(pOffer->Netmask)); + pOffer->fNetmask = true; + } + else if (uOption == RTNET_DHCP_OPT_SERVER_ID && cbOption == sizeof(RTNETADDRIPV4)) + { + memcpy(&pOffer->ServerId, pbOption, sizeof(pOffer->ServerId)); + pOffer->fServerId = true; + } + else if (uOption == RTNET_DHCP_OPT_ROUTERS && cbOption >= sizeof(RTNETADDRIPV4)) + pOffer->fRouter = true; + else if (uOption == RTNET_DHCP_OPT_DNS && cbOption >= sizeof(RTNETADDRIPV4)) + pOffer->fDns = true; + pbOption += cbOption; + } + return pOffer->uMsgType != 0; +} + + +/********************************************************************************************************************************* +* Direct libslirp Test Helpers * +*********************************************************************************************************************************/ +/** Captures a frame emitted by the restricted libslirp wrapper. */ +static DECLCALLBACK(int) tstCaptureOutput(const void *pvFrame, size_t cbFrame, void *pvUser) +{ + PTSTFRAMEQUEUE pQueue = (PTSTFRAMEQUEUE)pvUser; + if (!pQueue || cbFrame > TST_MAX_FRAME || pQueue->cFrames >= TST_MAX_QUEUED_FRAMES) + return VERR_BUFFER_OVERFLOW; + + uint32_t const iFrame = (pQueue->iHead + pQueue->cFrames) % TST_MAX_QUEUED_FRAMES; + memcpy(pQueue->aFrames[iFrame].ab, pvFrame, cbFrame); + pQueue->aFrames[iFrame].cb = cbFrame; + pQueue->cFrames++; + return VINF_SUCCESS; +} + + +/** Pops the oldest captured frame. */ +static bool tstQueuePop(PTSTFRAMEQUEUE pQueue, PTSTFRAME pFrame) +{ + if (!pQueue->cFrames) + return false; + *pFrame = pQueue->aFrames[pQueue->iHead]; + pQueue->iHead = (pQueue->iHead + 1) % TST_MAX_QUEUED_FRAMES; + pQueue->cFrames--; + return true; +} + + +/** Finds and consumes a matching ARP reply. */ +static bool tstFindArpReply(PTSTFRAMEQUEUE pQueue, RTNETADDRIPV4 SenderIp, RTNETADDRIPV4 TargetIp, + PCRTMAC pTargetMac, PRTMAC pSenderMac) +{ + uint32_t cLeft = pQueue->cFrames; + while (cLeft--) + { + TSTFRAME Frame; + tstQueuePop(pQueue, &Frame); + if (tstIsArpReply(&Frame, SenderIp, TargetIp, pTargetMac, pSenderMac)) + return true; + } + return false; +} + + +/** Finds and consumes a DHCP response of the requested type. */ +static bool tstFindDhcpResponse(PTSTFRAMEQUEUE pQueue, PCRTMAC pGuestMac, uint8_t uMsgType, + PTSTDHCPOFFER pOffer) +{ + uint32_t cLeft = pQueue->cFrames; + while (cLeft--) + { + TSTFRAME Frame; + tstQueuePop(pQueue, &Frame); + if (tstParseDhcpResponse(&Frame, pGuestMac, pOffer) && pOffer->uMsgType == uMsgType) + return true; + } + return false; +} + + +/** Polls libslirp until a matching ARP reply is captured or the deadline expires. */ +static int tstWaitForArpReply(PVBOXNETSLIRPNATTEST hNat, PTSTFRAMEQUEUE pQueue, + RTNETADDRIPV4 SenderIp, RTNETADDRIPV4 TargetIp, + PCRTMAC pTargetMac, PRTMAC pSenderMac) +{ + uint64_t const msStart = RTTimeMilliTS(); + for (;;) + { + if (tstFindArpReply(pQueue, SenderIp, TargetIp, pTargetMac, pSenderMac)) + return VINF_SUCCESS; + if (RTTimeMilliTS() - msStart >= TST_WAIT_MS) + return VERR_TIMEOUT; + int const rc = VBoxNetSlirpNATTestPoll(hNat, TST_POLL_MS); + if (RT_FAILURE(rc)) + return rc; + } +} + + +/** Polls libslirp until a matching DHCP response is captured or the deadline expires. */ +static int tstWaitForDhcpResponse(PVBOXNETSLIRPNATTEST hNat, PTSTFRAMEQUEUE pQueue, + PCRTMAC pGuestMac, uint8_t uMsgType, PTSTDHCPOFFER pOffer) +{ + uint64_t const msStart = RTTimeMilliTS(); + for (;;) + { + if (tstFindDhcpResponse(pQueue, pGuestMac, uMsgType, pOffer)) + return VINF_SUCCESS; + if (RTTimeMilliTS() - msStart >= TST_WAIT_MS) + return VERR_TIMEOUT; + int const rc = VBoxNetSlirpNATTestPoll(hNat, TST_POLL_MS); + if (RT_FAILURE(rc)) + return rc; + } +} + + +/** Verifies the restricted wrapper rejects unsafe or inconsistent configuration. */ +static void tstConfigValidation(void) +{ + RTTestSub(g_hTest, "restricted configuration validation"); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, false); + TSTFRAMEQUEUE Queue; + RT_ZERO(Queue); + PVBOXNETSLIRPNATTEST hNat = NULL; + + Cfg.fRestricted = false; + int rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC(g_hTest, rc, VERR_ACCESS_DENIED); + RTTEST_CHECK(g_hTest, hNat == NULL); + + tstNatConfigInit(&Cfg, false); + Cfg.IPv4Host = tstIPv4(192, 0, 2, 1); + rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC(g_hTest, rc, VERR_INVALID_PARAMETER); + RTTEST_CHECK(g_hTest, hNat == NULL); +} + + +/** Verifies the full restricted DHCP DISCOVER/OFFER/REQUEST/ACK exchange. */ +static void tstDhcp(void) +{ + RTTestSub(g_hTest, "restricted libslirp DHCPv4 lease"); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, true); + TSTFRAMEQUEUE Queue; + RT_ZERO(Queue); + PVBOXNETSLIRPNATTEST hNat = NULL; + int rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC_RETV(g_hTest, rc, VINF_SUCCESS); + + RTMAC const GuestMac = tstMac(0x08, 0x00, 0x27, 0xaa, 0xbb, 0x15); + TSTFRAME Frame; + tstBuildDhcpRequest(&Frame, &GuestMac, RTNET_DHCP_MT_DISCOVER, + tstIPv4(0, 0, 0, 0), tstIPv4(0, 0, 0, 0)); + rc = VBoxNetSlirpNATTestInput(hNat, Frame.ab, Frame.cb); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + TSTDHCPOFFER Offer; + RT_ZERO(Offer); + if (RT_SUCCESS(rc)) + rc = tstWaitForDhcpResponse(hNat, &Queue, &GuestMac, RTNET_DHCP_MT_OFFER, &Offer); + bool const fOffer = RT_SUCCESS(rc); + RTTEST_CHECK_MSG(g_hTest, fOffer, (g_hTest, "No valid DHCPOFFER was returned\n")); + if (fOffer) + { + RTTEST_CHECK(g_hTest, Offer.YiAddr.u != 0); + RTTEST_CHECK(g_hTest, (Offer.YiAddr.u & Cfg.IPv4Netmask.u) == Cfg.IPv4Network.u); + RTTEST_CHECK(g_hTest, Offer.fNetmask && Offer.Netmask.u == Cfg.IPv4Netmask.u); + RTTEST_CHECK(g_hTest, Offer.fServerId && Offer.ServerId.u == Cfg.IPv4Host.u); + RTTEST_CHECK_MSG(g_hTest, !Offer.fRouter && !Offer.fDns, + (g_hTest, "Restricted DHCP unexpectedly advertised host routing or DNS access\n")); + + RT_ZERO(Queue); + tstBuildDhcpRequest(&Frame, &GuestMac, RTNET_DHCP_MT_REQUEST, Offer.YiAddr, Offer.ServerId); + rc = VBoxNetSlirpNATTestInput(hNat, Frame.ab, Frame.cb); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + TSTDHCPOFFER Ack; + RT_ZERO(Ack); + if (RT_SUCCESS(rc)) + rc = tstWaitForDhcpResponse(hNat, &Queue, &GuestMac, RTNET_DHCP_MT_ACK, &Ack); + bool const fAck = RT_SUCCESS(rc); + RTTEST_CHECK_MSG(g_hTest, fAck, (g_hTest, "No valid DHCPACK was returned\n")); + if (fAck) + { + RTTEST_CHECK(g_hTest, Ack.YiAddr.u == Offer.YiAddr.u); + RTTEST_CHECK(g_hTest, Ack.fNetmask && Ack.Netmask.u == Cfg.IPv4Netmask.u); + RTTEST_CHECK(g_hTest, Ack.fServerId && Ack.ServerId.u == Cfg.IPv4Host.u); + } + } + + VBoxNetSlirpNATTestDestroy(hNat); +} + + +/** Verifies gateway ARP and negative ARP behavior directly through libslirp. */ +static void tstArp(void) +{ + RTTestSub(g_hTest, "restricted libslirp ARP"); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, false); + TSTFRAMEQUEUE Queue; + RT_ZERO(Queue); + PVBOXNETSLIRPNATTEST hNat = NULL; + int rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC_RETV(g_hTest, rc, VINF_SUCCESS); + + RTMAC const GuestMac = tstMac(0x08, 0x00, 0x27, 0xaa, 0xbb, 0x16); + RTNETADDRIPV4 const GuestIp = tstIPv4(10, 0, 2, 16); + TSTFRAME Frame; + tstBuildArpRequest(&Frame, &GuestMac, GuestIp, Cfg.IPv4Host); + rc = VBoxNetSlirpNATTestInput(hNat, Frame.ab, Frame.cb); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + RTMAC GatewayMac; + RT_ZERO(GatewayMac); + if (RT_SUCCESS(rc)) + rc = tstWaitForArpReply(hNat, &Queue, Cfg.IPv4Host, GuestIp, &GuestMac, &GatewayMac); + RTTEST_CHECK_MSG(g_hTest, RT_SUCCESS(rc), + (g_hTest, "No valid gateway ARP reply was returned\n")); + RTTEST_CHECK(g_hTest, !tstMacEqual(&GatewayMac, &GuestMac)); + + RT_ZERO(Queue); + tstBuildArpRequest(&Frame, &GuestMac, GuestIp, tstIPv4(10, 0, 2, 99)); + rc = VBoxNetSlirpNATTestInput(hNat, Frame.ab, Frame.cb); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = VBoxNetSlirpNATTestPoll(hNat, 20); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + RTTEST_CHECK_MSG(g_hTest, Queue.cFrames == 0, + (g_hTest, "libslirp answered ARP for an unowned guest-network address\n")); + + VBoxNetSlirpNATTestDestroy(hNat); +} + + +/** Verifies production-sized frame filtering and malformed-input drops. */ +static void tstMalformedFrames(void) +{ + RTTestSub(g_hTest, "malformed and oversized Ethernet input"); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, false); + TSTFRAMEQUEUE Queue; + RT_ZERO(Queue); + PVBOXNETSLIRPNATTEST hNat = NULL; + int rc = VBoxNetSlirpNATTestCreate(&Cfg, tstCaptureOutput, &Queue, &hNat); + RTTEST_CHECK_RC_RETV(g_hTest, rc, VINF_SUCCESS); + + uint8_t abFrame[1523]; + RT_ZERO(abFrame); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, NULL, sizeof(abFrame)), VERR_INVALID_PARAMETER); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, 0), VERR_INVALID_PARAMETER); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, _64K + 1), VERR_INVALID_PARAMETER); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, 8), VINF_SUCCESS); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, sizeof(abFrame)), VINF_SUCCESS); + + RTNETETHERHDR *pEthernet = (RTNETETHERHDR *)abFrame; + pEthernet->DstMac = tstMacBroadcast(); + pEthernet->SrcMac = tstMac(0x08, 0x00, 0x27, 0xaa, 0xbb, 0x17); + pEthernet->EtherType = RT_H2N_U16(0x88b5); + RTTEST_CHECK_RC(g_hTest, VBoxNetSlirpNATTestInput(hNat, abFrame, 60), VINF_SUCCESS); + RTTEST_CHECK(g_hTest, Queue.cFrames == 0); + + VBoxNetSlirpNATTestDestroy(hNat); +} + + +/********************************************************************************************************************************* +* Standalone Ring-3 Switch Fixture * +*********************************************************************************************************************************/ +/** Creates a unique name for a service or network. */ +static int tstMakeUuidName(const char *pszPrefix, char *pszName, size_t cbName) +{ + RTUUID Uuid; + int rc = RTUuidCreate(&Uuid); + if (RT_SUCCESS(rc)) + { + char szUuid[RTUUID_STR_LENGTH]; + rc = RTUuidToStr(&Uuid, szUuid, sizeof(szUuid)); + if (RT_SUCCESS(rc)) + { + ssize_t const cch = RTStrPrintf2(pszName, cbName, "%s-%s", pszPrefix, szUuid); + if (cch < 0 || (size_t)cch >= cbName) + rc = VERR_BUFFER_OVERFLOW; + } + } + return rc; +} + + +/** Returns whether a local IPC connection failure means no server is present. */ +static bool tstServiceIsAbsent(int rc) +{ + return rc == VERR_FILE_NOT_FOUND + || rc == VERR_PATH_NOT_FOUND + || rc == VERR_NET_CONNECTION_REFUSED + || rc == VERR_PIPE_NOT_CONNECTED; +} + + +/** Reads the auto-started switch process ID. */ +static int tstServiceReadPid(PTSTSWITCHSERVICE pService) +{ + RTFILE hFile = NIL_RTFILE; + int rc = RTFileOpen(&hFile, pService->szPidFile, RTFILE_O_READ | RTFILE_O_OPEN | RTFILE_O_DENY_WRITE); + if (RT_SUCCESS(rc)) + { + char szPid[32]; + size_t cbRead = 0; + rc = RTFileRead(hFile, szPid, sizeof(szPid) - 1, &cbRead); + int const rcClose = RTFileClose(hFile); + if (RT_SUCCESS(rc)) + rc = rcClose; + if (RT_SUCCESS(rc)) + { + szPid[cbRead] = '\0'; + uint32_t uPid = NIL_RTPROCESS; + rc = RTStrToUInt32Full(szPid, 10, &uPid); + if (RT_SUCCESS(rc)) + { + if (uPid == NIL_RTPROCESS || uPid == RTProcSelf()) + rc = VERR_INVALID_PARAMETER; + else + pService->hProcess = uPid; + } + } + } + return rc; +} + + +/** Waits for the auto-started switch to publish its process ID. */ +static int tstServiceWaitForPid(PTSTSWITCHSERVICE pService) +{ + uint64_t const msStart = RTTimeMilliTS(); + int rc; + do + { + rc = tstServiceReadPid(pService); + if (RT_SUCCESS(rc)) + return rc; + if ( rc != VERR_FILE_NOT_FOUND + && rc != VERR_PATH_NOT_FOUND + && rc != VERR_NO_DIGITS) + return rc; + RTThreadSleep(10); + } while (RTTimeMilliTS() - msStart < TST_WAIT_MS); + return rc; +} + + +/** Prepares unique environment variables used by the production auto-start path. */ +static int tstServiceStart(PTSTSWITCHSERVICE pService) +{ + RT_ZERO(*pService); + pService->hProcess = NIL_RTPROCESS; + + int rc = RTPathTemp(pService->szTempDir, sizeof(pService->szTempDir)); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pService->szTempDir, sizeof(pService->szTempDir), "tstVBoxNatGuestSide-XXXXXX"); + if (RT_SUCCESS(rc)) + { + rc = RTDirCreateTemp(pService->szTempDir, 0700); + if (RT_SUCCESS(rc)) + pService->fTempDirCreated = true; + } + if (RT_SUCCESS(rc)) + rc = tstMakeUuidName("tst-vbox-nat", pService->szService, sizeof(pService->szService)); + if (RT_SUCCESS(rc)) + rc = RTPathExecDir(pService->szExec, sizeof(pService->szExec)); + if (RT_SUCCESS(rc)) +#ifdef RT_OS_WINDOWS + rc = RTPathAppend(pService->szExec, sizeof(pService->szExec), "VBoxIntNetR3SwitchTestHelper.exe"); +#else + rc = RTPathAppend(pService->szExec, sizeof(pService->szExec), "VBoxIntNetR3SwitchTestHelper"); +#endif + if (RT_SUCCESS(rc)) + rc = RTStrCopy(pService->szPidFile, sizeof(pService->szPidFile), pService->szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pService->szPidFile, sizeof(pService->szPidFile), "switch.pid"); + if (RT_SUCCESS(rc)) + rc = RTStrCopy(pService->szLockFile, sizeof(pService->szLockFile), pService->szTempDir); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pService->szLockFile, sizeof(pService->szLockFile), "switch.lock"); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SVC_NAME", pService->szService); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_EXE", pService->szExec); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_PID_FILE", pService->szPidFile); + if (RT_SUCCESS(rc)) + rc = RTEnvSet("VBOX_INTNET_R3_SWITCH_LOCK_FILE", pService->szLockFile); + if (RT_SUCCESS(rc)) + { + RTLOCALIPCSESSION hExisting = NIL_RTLOCALIPCSESSION; + rc = RTLocalIpcSessionConnect(&hExisting, pService->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + { + RTLocalIpcSessionClose(hExisting); + rc = VERR_ALREADY_EXISTS; + } + else if (tstServiceIsAbsent(rc)) + rc = VINF_SUCCESS; + } + return rc; +} + + +/** Removes a stale POSIX local IPC filesystem node after the helper exits. */ +static void tstServiceCleanupEndpoint(PTSTSWITCHSERVICE pService) +{ + if (!pService->szService[0]) + return; + + RTLOCALIPCSESSION hExisting = NIL_RTLOCALIPCSESSION; + int rc = RTLocalIpcSessionConnect(&hExisting, pService->szService, + RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + { + RTLocalIpcSessionClose(hExisting); + RTTestFailed(g_hTest, "Switch IPC endpoint still accepts clients after helper exit"); + return; + } + if (rc == VERR_NET_CONNECTION_REFUSED) + RTTestFailed(g_hTest, "Switch IPC endpoint pathname was left behind after helper exit"); + else if (!tstServiceIsAbsent(rc)) + { + RTTestFailed(g_hTest, "Checking switch IPC endpoint failed: %Rrc", rc); + return; + } + + RTLOCALIPCSERVER hCleanup = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hCleanup, pService->szService, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + if (RT_SUCCESS(rc)) + RTLocalIpcServerDestroy(hCleanup); + else + RTTestFailed(g_hTest, "Cleaning switch IPC endpoint failed: %Rrc", rc); +} + + +/** Waits for the oneshot helper and removes all fixture state. */ +static void tstServiceStop(PTSTSWITCHSERVICE pService) +{ + if ( !pService->fProcessReaped + && pService->hProcess == NIL_RTPROCESS + && pService->szPidFile[0]) + { + uint64_t const msStart = RTTimeMilliTS(); + int rc; + do + { + rc = tstServiceReadPid(pService); + if (RT_SUCCESS(rc) || (rc != VERR_FILE_NOT_FOUND && rc != VERR_PATH_NOT_FOUND)) + break; + RTThreadSleep(10); + } while (RTTimeMilliTS() - msStart < RT_MS_1SEC); + } + + if (pService->hProcess != NIL_RTPROCESS) + { + RTPROCSTATUS Status; + RT_ZERO(Status); + int rc = VERR_PROCESS_RUNNING; + uint64_t const msStart = RTTimeMilliTS(); + while (rc == VERR_PROCESS_RUNNING && RTTimeMilliTS() - msStart < RT_MS_5SEC) + { + rc = RTProcWait(pService->hProcess, RTPROCWAIT_FLAGS_NOBLOCK, &Status); + if (rc == VERR_PROCESS_RUNNING) + RTThreadSleep(10); + } + if (rc == VERR_PROCESS_RUNNING) + { + RTTestFailed(g_hTest, "Switch helper did not exit after its last client disconnected"); + int const rcTerminate = RTProcTerminate(pService->hProcess); + if (RT_SUCCESS(rcTerminate)) + rc = RTProcWait(pService->hProcess, RTPROCWAIT_FLAGS_BLOCK, &Status); + else + rc = rcTerminate; + } + if (RT_FAILURE(rc)) + RTTestFailed(g_hTest, "Waiting for switch helper failed: %Rrc", rc); + else if (Status.enmReason != RTPROCEXITREASON_NORMAL || Status.iStatus != RTEXITCODE_SUCCESS) + RTTestFailed(g_hTest, "Switch helper exit reason/status: %d/%d", Status.enmReason, Status.iStatus); + else + pService->fProcessReaped = true; + pService->hProcess = NIL_RTPROCESS; + } + + tstServiceCleanupEndpoint(pService); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_LOCK_FILE"); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_PID_FILE"); + RTEnvUnset("VBOX_INTNET_R3_SWITCH_EXE"); + RTEnvUnset("VBOX_INTNET_R3_SVC_NAME"); + + if (pService->szPidFile[0]) + RTFileDelete(pService->szPidFile); + if (pService->szLockFile[0]) + RTFileDelete(pService->szLockFile); + if (pService->fTempDirCreated) + { + int const rc = RTDirRemove(pService->szTempDir); + if (RT_FAILURE(rc) && rc != VERR_PATH_NOT_FOUND && rc != VERR_FILE_NOT_FOUND) + RTTestFailed(g_hTest, "Removing switch helper temporary directory failed: %Rrc", rc); + } +} + + +/********************************************************************************************************************************* +* Ring-3 IntNet / NAT Integration * +*********************************************************************************************************************************/ +/** Commits one libslirp output frame to the NAT-facing IntNet interface. */ +static DECLCALLBACK(int) tstIntNetOutput(const void *pvFrame, size_t cbFrame, void *pvUser) +{ + PTSTNATOUTPUT pOutput = (PTSTNATOUTPUT)pvUser; + if (!pOutput || cbFrame > UINT32_MAX) + return VERR_INVALID_PARAMETER; + + INTNETFRAME Frame; + int rc = IntNetR3IfQueryOutputFrame(pOutput->hIf, (uint32_t)cbFrame, &Frame); + if (RT_SUCCESS(rc)) + { + memcpy(Frame.pvFrame, pvFrame, cbFrame); + rc = IntNetR3IfOutputFrameCommit(pOutput->hIf, &Frame); + } + return rc; +} + + +/** Injects one NAT-facing IntNet frame into libslirp. */ +static DECLCALLBACK(void) tstNatInput(void *pvUser, void *pvFrame, uint32_t cbFrame) +{ + PTSTNATPUMP pPump = (PTSTNATPUMP)pvUser; + int const rc = VBoxNetSlirpNATTestInput(pPump->hNat, pvFrame, cbFrame); + if (RT_FAILURE(rc) && RT_SUCCESS(pPump->rcInput)) + pPump->rcInput = rc; +} + + +/** Runs the NAT-facing IntNet receive pump. */ +static DECLCALLBACK(int) tstNatPumpThread(RTTHREAD hThread, void *pvUser) +{ + PTSTNATPUMP pPump = (PTSTNATPUMP)pvUser; + RTThreadUserSignal(hThread); + return IntNetR3IfPumpPkts(pPump->hIf, tstNatInput, pPump, NULL, NULL); +} + + +/** Captures only the expected gateway ARP reply on the guest interface. */ +static DECLCALLBACK(void) tstGuestInput(void *pvUser, void *pvFrame, uint32_t cbFrame) +{ + PTSTGUESTRX pRx = (PTSTGUESTRX)pvUser; + if (!pRx->fReply && cbFrame <= sizeof(pRx->Reply.ab)) + { + TSTFRAME Frame; + Frame.cb = cbFrame; + memcpy(Frame.ab, pvFrame, cbFrame); + if (tstIsArpReply(&Frame, pRx->GatewayIp, pRx->GuestIp, &pRx->GuestMac, NULL)) + { + pRx->Reply = Frame; + pRx->fReply = true; + RTSemEventSignal(pRx->hReplyEvent); + } + } +} + + +/** Runs the guest-facing IntNet receive pump. */ +static DECLCALLBACK(int) tstGuestRxThread(RTTHREAD hThread, void *pvUser) +{ + PTSTGUESTRX pRx = (PTSTGUESTRX)pvUser; + RTThreadUserSignal(hThread); + return IntNetR3IfPumpPkts(pRx->hIf, tstGuestInput, pRx, NULL, NULL); +} + + +/** Starts one waitable IntNet pump thread. */ +static int tstPumpStart(PRTTHREAD phThread, PFNRTTHREAD pfnThread, void *pvUser, const char *pszName) +{ + int rc = RTThreadCreate(phThread, pfnThread, pvUser, 0, RTTHREADTYPE_IO, RTTHREADFLAGS_WAITABLE, pszName); + if (RT_SUCCESS(rc)) + rc = RTThreadUserWait(*phThread, RT_MS_1SEC); + return rc; +} + + +/** Aborts and joins one IntNet pump. */ +static void tstPumpStop(INTNETIFCTX hIf, PRTTHREAD phThread, const char *pszName) +{ + if (*phThread == NIL_RTTHREAD) + return; + + int rc = IntNetR3IfWaitAbort(hIf); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + int rcThread = VERR_IPE_UNINITIALIZED_STATUS; + rc = RTThreadWait(*phThread, RT_MS_5SEC, &rcThread); + if (RT_FAILURE(rc)) + { + RTTestFailed(g_hTest, "%s did not stop within five seconds: %Rrc", pszName, rc); + int const rcAbort = IntNetR3IfWaitAbort(hIf); + RTTEST_CHECK_RC(g_hTest, rcAbort, VINF_SUCCESS); + rc = RTThreadWait(*phThread, RT_INDEFINITE_WAIT, &rcThread); + } + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + exit(RTEXITCODE_FAILURE); + RTTEST_CHECK_MSG(g_hTest, rcThread == VERR_SEM_DESTROYED, + (g_hTest, "%s returned %Rrc instead of %Rrc\n", pszName, rcThread, VERR_SEM_DESTROYED)); + *phThread = NIL_RTTHREAD; +} + + +/** Sends one Ethernet frame through an IntNet interface. */ +static int tstIntNetSend(INTNETIFCTX hIf, PCTSTFRAME pFrame) +{ + INTNETFRAME Frame; + int rc = IntNetR3IfQueryOutputFrame(hIf, (uint32_t)pFrame->cb, &Frame); + if (RT_SUCCESS(rc)) + { + memcpy(Frame.pvFrame, pFrame->ab, pFrame->cb); + rc = IntNetR3IfOutputFrameCommit(hIf, &Frame); + } + return rc; +} + + +/** Verifies an ARP request/reply through the external local IPC R3 switch. */ +static void tstR3IntNetNatArp(void) +{ + RTTestSub(g_hTest, "R3 IntNet switch -> restricted NAT -> R3 IntNet"); + + TSTSWITCHSERVICE Service; + int rc = tstServiceStart(&Service); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { + tstServiceStop(&Service); + return; + } + + INTNETIFCTX hGuestIf = NULL; + INTNETIFCTX hNatIf = NULL; + PVBOXNETSLIRPNATTEST hNat = NULL; + TSTNATPUMP NatPump; + RT_ZERO(NatPump); + NatPump.hThread = NIL_RTTHREAD; + NatPump.rcInput = VINF_SUCCESS; + TSTGUESTRX GuestRx; + RT_ZERO(GuestRx); + GuestRx.hThread = NIL_RTTHREAD; + GuestRx.hReplyEvent = NIL_RTSEMEVENT; + + char szNetwork[INTNET_MAX_NETWORK_NAME]; + rc = tstMakeUuidName("tst-nat-r3", szNetwork, sizeof(szNetwork)); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfCreate(&hGuestIf, szNetwork); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = tstServiceWaitForPid(&Service); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfCreate(&hNatIf, szNetwork); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + RTMAC const GuestMac = tstMac(0x08, 0x00, 0x27, 0xaa, 0xbb, 0x18); + RTMAC const NatMac = tstMac(0x52, 0x54, 0x00, 0x12, 0x35, 0x00); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfSetMacAddress(hGuestIf, &GuestMac); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfSetMacAddress(hNatIf, &NatMac); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfSetActive(hGuestIf, true); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = IntNetR3IfSetActive(hNatIf, true); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + VBOXNETSLIRPNATTESTCFG Cfg; + tstNatConfigInit(&Cfg, false); + TSTNATOUTPUT NatOutput; + NatOutput.hIf = hNatIf; + if (RT_SUCCESS(rc)) + rc = VBoxNetSlirpNATTestCreate(&Cfg, tstIntNetOutput, &NatOutput, &hNat); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + NatPump.hIf = hNatIf; + NatPump.hNat = hNat; + if (RT_SUCCESS(rc)) + rc = tstPumpStart(&NatPump.hThread, tstNatPumpThread, &NatPump, "NatR3Pump"); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + GuestRx.hIf = hGuestIf; + GuestRx.GatewayIp = Cfg.IPv4Host; + GuestRx.GuestIp = tstIPv4(10, 0, 2, 18); + GuestRx.GuestMac = GuestMac; + if (RT_SUCCESS(rc)) + rc = RTSemEventCreate(&GuestRx.hReplyEvent); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = tstPumpStart(&GuestRx.hThread, tstGuestRxThread, &GuestRx, "GuestR3Rx"); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + + if (RT_SUCCESS(rc)) + { + TSTFRAME Request; + tstBuildArpRequest(&Request, &GuestMac, GuestRx.GuestIp, Cfg.IPv4Host); + rc = tstIntNetSend(hGuestIf, &Request); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + rc = RTSemEventWait(GuestRx.hReplyEvent, TST_WAIT_MS); + RTTEST_CHECK_RC(g_hTest, rc, VINF_SUCCESS); + if (RT_SUCCESS(rc)) + RTTEST_CHECK_MSG(g_hTest, GuestRx.fReply, + (g_hTest, "Guest did not receive the NAT gateway ARP reply through the R3 switch\n")); + } + + tstPumpStop(hGuestIf, &GuestRx.hThread, "Guest R3 receive pump"); + tstPumpStop(hNatIf, &NatPump.hThread, "NAT R3 receive pump"); + RTTEST_CHECK_RC(g_hTest, NatPump.rcInput, VINF_SUCCESS); + if (GuestRx.hReplyEvent != NIL_RTSEMEVENT) + RTSemEventDestroy(GuestRx.hReplyEvent); + VBoxNetSlirpNATTestDestroy(hNat); + if (hNatIf) + IntNetR3IfDestroy(hNatIf); + if (hGuestIf) + IntNetR3IfDestroy(hGuestIf); + tstServiceStop(&Service); +} + + +/********************************************************************************************************************************* +* Main * +*********************************************************************************************************************************/ +int main(int argc, char **argv) +{ + RT_NOREF(argc, argv); + + int rc = RTTestInitAndCreate("tstVBoxNatGuestSide", &g_hTest); + if (RT_FAILURE(rc)) + return rc; + RTTestBanner(g_hTest); + + tstConfigValidation(); + tstDhcp(); + tstArp(); + tstMalformedFrames(); + tstR3IntNetNatArp(); + + return RTTestSummaryAndDestroy(g_hTest); +} diff --git a/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSideInternal.h b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSideInternal.h new file mode 100644 index 000000000000..0c190f8d0d0b --- /dev/null +++ b/src/VBox/NetworkServices/testcase/tstVBoxNatGuestSideInternal.h @@ -0,0 +1,148 @@ +/* $Id: tstVBoxNatGuestSideInternal.h 114884 2026-08-07 07:22:52Z andreas.loeffler@oracle.com $ */ +/** @file + * VBoxNetSlirpNAT guest-side testcase hooks. + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * SPDX-License-Identifier: GPL-3.0-only + */ + +#ifndef VBOX_INCLUDED_SRC_testcase_tstVBoxNatGuestSideInternal_h +#define VBOX_INCLUDED_SRC_testcase_tstVBoxNatGuestSideInternal_h +#ifndef RT_WITHOUT_PRAGMA_ONCE +# pragma once +#endif + +#include +#include +#include + +RT_C_DECLS_BEGIN + +/** Opaque test instance owning one restricted libslirp datapath. */ +typedef struct VBOXNETSLIRPNATTEST *PVBOXNETSLIRPNATTEST; + +/** + * Callback for an Ethernet frame emitted toward the guest. + * + * The frame is valid only for the duration of the callback. The callback is + * made synchronously by VBoxNetSlirpNATTestInput or VBoxNetSlirpNATTestPoll; + * no callback may occur after either function returns. + * + * @returns VINF_SUCCESS on success, or an IPRT error to propagate to the + * currently executing test-hook call. + * @param pvFrame Frame bytes emitted toward the guest. + * @param cbFrame Number of valid bytes in @a pvFrame. + * @param pvUser User pointer supplied to VBoxNetSlirpNATTestCreate. + */ +typedef DECLCALLBACKTYPE(int, FNVBOXNETSLIRPNATTESTOUTPUT,(const void *pvFrame, size_t cbFrame, void *pvUser)); +/** Pointer to a guest-output callback. */ +typedef FNVBOXNETSLIRPNATTESTOUTPUT *PFNVBOXNETSLIRPNATTESTOUTPUT; + +/** Deterministic IPv4 configuration for the test-only NAT datapath wrapper. */ +typedef struct VBOXNETSLIRPNATTESTCFG +{ + /** IPv4 network address in IPRT/network representation. */ + RTNETADDRIPV4 IPv4Network; + /** IPv4 netmask in IPRT/network representation. */ + RTNETADDRIPV4 IPv4Netmask; + /** Guest-visible gateway address. */ + RTNETADDRIPV4 IPv4Host; + /** First address in the DHCP pool. */ + RTNETADDRIPV4 IPv4DhcpStart; + /** Guest-visible DNS proxy address advertised by DHCP. */ + RTNETADDRIPV4 IPv4Nameserver; + /** Whether libslirp DHCP is enabled. */ + bool fDhcp; + /** Whether the libslirp DNS proxy is enabled. */ + bool fDns; + /** Whether host-network access is disabled. Tests require true. */ + bool fRestricted; +} VBOXNETSLIRPNATTESTCFG; +/** Pointer to a NAT testcase configuration. */ +typedef VBOXNETSLIRPNATTESTCFG *PVBOXNETSLIRPNATTESTCFG; +/** Pointer to a const NAT testcase configuration. */ +typedef const VBOXNETSLIRPNATTESTCFG *PCVBOXNETSLIRPNATTESTCFG; + +/** + * Create one guest-side test context (without COM). + * + * The implementation must validate that @a pCfg requests a restricted IPv4 + * network and leave frame transport to @a pfnOutput. It intentionally does + * not instantiate the COM VBoxNetSlirpNAT service; its configuration is + * locked down for gest packet tests. The returned handle is + * single-thread affine: Input and Poll must not be called concurrently. + * + * @returns VINF_SUCCESS on success, or an IPRT/VBox error. + * @param pCfg NAT configuration. + * @param pfnOutput Required callback for frames sent toward the guest. + * @param pvOutputUser User pointer passed to @a pfnOutput. + * @param phNat Where to return the opaque test handle. Set to NULL + * on failure. + */ +DECLHIDDEN(int) VBoxNetSlirpNATTestCreate(PCVBOXNETSLIRPNATTESTCFG pCfg, + PFNVBOXNETSLIRPNATTESTOUTPUT pfnOutput, + void *pvOutputUser, PVBOXNETSLIRPNATTEST *phNat); + +/** + * Destroy a test instance. + * + * The implementation must cancel timers, release libslirp state, and guarantee + * that no output callback remains in flight when it returns. + * + * @param hNat Test instance. NULL is accepted as a no-op. + */ +DECLHIDDEN(void) VBoxNetSlirpNATTestDestroy(PVBOXNETSLIRPNATTEST hNat); + +/** + * Inject one complete guest Ethernet frame through the same validation and + * slirp-input path used by VBoxNetSlirpNAT::processFrame. + * + * Immediate output must be delivered before this function returns. Like + * VBoxNetSlirpNAT::processFrame, frames smaller than an Ethernet header or + * larger than 1522 bytes are dropped successfully. NULL input, zero size, or + * input larger than 64 KiB returns VERR_INVALID_PARAMETER. + * + * @returns VINF_SUCCESS when accepted or deliberately dropped, an error from + * argument validation/libslirp, or an output-callback error. + * @param hNat Test instance. + * @param pvFrame Guest Ethernet frame bytes. + * @param cbFrame Number of bytes in @a pvFrame. + */ +DECLHIDDEN(int) VBoxNetSlirpNATTestInput(PVBOXNETSLIRPNATTEST hNat, + const void *pvFrame, size_t cbFrame); + +/** + * Progress production libslirp timers and pending socket work for at most the + * requested interval. + * + * This call must not access the unrestricted host network when fRestricted is + * set. Output callbacks are completed before the function returns. + * + * @returns VINF_SUCCESS, an IPRT/VBox poll error, or an output-callback error. + * @param hNat Test instance. + * @param cMsMax Maximum poll duration in milliseconds. + */ +DECLHIDDEN(int) VBoxNetSlirpNATTestPoll(PVBOXNETSLIRPNATTEST hNat, uint32_t cMsMax); + +RT_C_DECLS_END + +#endif /* !VBOX_INCLUDED_SRC_testcase_tstVBoxNatGuestSideInternal_h */ diff --git a/src/VBox/RDP/server/Makefile.kmk b/src/VBox/RDP/server/Makefile.kmk index 23e9cdc36827..f8b2a339774e 100644 --- a/src/VBox/RDP/server/Makefile.kmk +++ b/src/VBox/RDP/server/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ ## @file # Sub-Makefile for the VBox RDP server. # @@ -40,11 +40,8 @@ VBoxVRDP_LIBS = $(LIB_RUNTIME) VBoxVRDP_SDKS = VBoxOpenSsl VBoxVRDP_DEFS = IN_VRDP VBoxVRDP_DEFS += NOVOL -VBoxVRDP_INCS += $(VBOX_JPEG_INCS) $(VBOX_GRAPHICS_INCS) -VBoxVRDP_LIBS += \ - $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) +VBoxVRDP_INCS += $(SDK_VBoxLibJpeg_INCS) $(VBOX_GRAPHICS_INCS) +VBoxVRDP_LIBS += $(SDK_VBoxLibJpeg_LIBS) ifdef VBOX_WITH_VRDP_ENABLE_LOGREL VBoxVRDP_DEFS += VRDP_ENABLE_LOGREL diff --git a/src/VBox/RDP/server/bmpcache.h b/src/VBox/RDP/server/bmpcache.h index e3c0e6a40b95..f537a0b23c0a 100644 --- a/src/VBox/RDP/server/bmpcache.h +++ b/src/VBox/RDP/server/bmpcache.h @@ -1,4 +1,4 @@ -/* $Id: bmpcache.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: bmpcache.h 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -88,4 +88,24 @@ void *BCBitmapHeapBlockQuery(PBMPCACHE pbc, const BCHEAPHANDLE *pHandle, int32_t void BCBitmapHeapBlockRelease(PBMPCACHE pbc, const BCHEAPHANDLE *pHandle); void BCBitmapHeapBlockFree(PBMPCACHE pbc, const BCHEAPHANDLE *pHandle); +DECLINLINE(bool) isValidBitsHdr(VRDEDATABITS const *pBitsHdr) +{ + switch (pBitsHdr->cbPixel) + { + case 2: + case 3: + case 4: break; + + default: + return false; + } + + /* uint16_t * uint16_t * uint8_t */ + uint64_t const u64BitmapSize = (uint64_t)pBitsHdr->cWidth * pBitsHdr->cHeight * pBitsHdr->cbPixel; + if (pBitsHdr->cb < u64BitmapSize) + return false; + + return true; +} + #endif /* !VRDP_INCLUDED_SRC_bmpcache_h */ diff --git a/src/VBox/RDP/server/jpegtest/Makefile.kmk b/src/VBox/RDP/server/jpegtest/Makefile.kmk index b463dd428bf6..3053770db724 100644 --- a/src/VBox/RDP/server/jpegtest/Makefile.kmk +++ b/src/VBox/RDP/server/jpegtest/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114892 2026-08-07 15:59:20Z klaus.espenlaub@oracle.com $ ## @file # Makefile for jpegtest - not for general consumption, see OTHERS. # @@ -38,11 +38,8 @@ ifdef TJ jpegtest_INCS += $(PATH_ROOT)/src/libs/libjpeg-turbo-1.0.0/win jpegtest_LIBS += $(PATH_STAGE_LIB)/VBox-libjpeg-t$(VBOX_SUFF_LIB) else - jpegtest_INCS += $(VBOX_JPEG_INCS) $(VBOX_GRAPHICS_INCS) - jpegtest_LIBS += \ - $(PATH_STAGE_LIB)/VBox-libjpeg$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg12$(VBOX_SUFF_LIB) \ - $(PATH_STAGE_LIB)/VBox-libjpeg16$(VBOX_SUFF_LIB) + jpegtest_INCS += $(SDK_VBoxLibJpeg_INCS) $(VBOX_GRAPHICS_INCS) + jpegtest_LIBS += $(SDK_VBoxLibJpeg_LIBS) endif ifneq ($(KBUILD_TARGET),win) jpegtest_CXXFLAGS += -Wno-unused-function -Wno-multichar diff --git a/src/VBox/RDP/server/output.cpp b/src/VBox/RDP/server/output.cpp index f6fa3a76b483..193de874f554 100644 --- a/src/VBox/RDP/server/output.cpp +++ b/src/VBox/RDP/server/output.cpp @@ -1,4 +1,4 @@ -/* $Id: output.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: output.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -608,6 +608,11 @@ int VRDPServer::OutputThread (RTTHREAD self, VRDPServerThreadStartCtx *pCtx) SERVERLOG(("OutputThread: VRDE_ORDER_MEMBLT %d,%d %dx%d from %d,%d rop 0x%02X\n", pOrder->x, pOrder->y, pOrder->w, pOrder->h, pOrder->xSrc, pOrder->ySrc, pOrder->rop)); + if (!shadowBufferAreOrderCoordsValid(action.uScreenId, pOrder->x, pOrder->y, pOrder->w, pOrder->h)) + { + break; + } + /* Locate the bitmap in the cache. */ PBMPCACHEENTRY pbce = BCFindBitmap (m_pbc, &pOrder->hash); @@ -852,7 +857,7 @@ int VRDPServer::OutputThread (RTTHREAD self, VRDPServerThreadStartCtx *pCtx) */ TCFONTTEXT2 *pFontText2 = NULL; - bool fSuccess = TCCacheGlyphs (m_ptc, pOrder, &pFontText2); + bool fSuccess = TCCacheGlyphs (m_ptc, pOrder, action.u.order.cbOrder, &pFontText2); if (!fSuccess) { diff --git a/src/VBox/RDP/server/rdpdr.cpp b/src/VBox/RDP/server/rdpdr.cpp index 1e09cbea3cc0..c7c2adc31e66 100644 --- a/src/VBox/RDP/server/rdpdr.cpp +++ b/src/VBox/RDP/server/rdpdr.cpp @@ -1,4 +1,4 @@ -/* $Id: rdpdr.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: rdpdr.cpp 114935 2026-08-10 12:46:46Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol - "RDPDR" static virtual channel: File System Virtual Channel Extension. */ @@ -692,6 +692,45 @@ int VRDPChannelRDPDR::fetchIO(uint32_t u32CompletionId, return rc; } +/** @thread INPUT */ +void VRDPChannelRDPDR::cancelIOForDevice(uint32_t u32DeviceId) +{ + RTLISTANCHOR ListPendingIO; + RTListInit(&ListPendingIO); + + RDPDRIOCompletion *pIter; + RDPDRIOCompletion *pNext; + + bool fLocked = RT_SUCCESS(VRDPLock::Lock(m_pLock)); + + RTListForEachSafe(&m_IOCompletion.ListUsed, pIter, pNext, RDPDRIOCompletion, NodeIOCompletion) + { + if (pIter->u32DeviceId == u32DeviceId) + { + RTListNodeRemove(&pIter->NodeIOCompletion); + RTListAppend(&ListPendingIO, &pIter->NodeIOCompletion); + } + } + + if (fLocked) + { + VRDPLock::Unlock(m_pLock); + } + + RTListForEachSafe(&ListPendingIO, pIter, pNext, RDPDRIOCompletion, NodeIOCompletion) + { + RDPDRIOCTL *pIO = (RDPDRIOCTL *)pIter->pPktHdr; + + RDPDRLOG(("cancelIOForDevice: pending IO %p [%d,%d]\n", + pIO, m_pvrdptp->Client()->Id(), u32DeviceId)); + + rdpdrDispatchIOCompletion(pIO, u32DeviceId, RDPDR_STATUS_UNSUCCESSFUL); + RDPDRPktRelease(&pIO->hdr); + + VRDPMemFree(pIter); + } +} + /** @thread OUTPUT */ int VRDPChannelRDPDR::ProcessOutput (const void *pvData, uint32_t cbData) { @@ -1192,6 +1231,12 @@ int VRDPChannelRDPDR::rdpdrOnDeviceAdd(const DEVICE_ANNOUNCE *pDevHdr, #ifdef DEBUG_sunlover Assert(!m_smartcard.fEnabled); #endif + if (m_smartcard.fEnabled) + { + /* Cancel pending IOCTLs for the existing device */ + cancelIOForDevice(m_smartcard.u32DeviceId); + } + m_smartcard.fEnabled = true; m_smartcard.u32DeviceId = pDevHdr->u32DeviceId; m_pvrdptp->Client()->Server()->SCard()->SCardAttach(m_pvrdptp->Client()->Id(), m_smartcard.u32DeviceId); diff --git a/src/VBox/RDP/server/shadowbuffer.cpp b/src/VBox/RDP/server/shadowbuffer.cpp index 9f9df4431b2b..d3ef82dedead 100644 --- a/src/VBox/RDP/server/shadowbuffer.cpp +++ b/src/VBox/RDP/server/shadowbuffer.cpp @@ -1,4 +1,4 @@ -/* $Id: shadowbuffer.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: shadowbuffer.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -360,11 +360,13 @@ typedef struct _VRDPSBSCREEN static DECLCALLBACK(bool) vscVideoSourceStreamStart(void *pvCallback, uint32_t u32SourceStreamId, const RGNRECT *prect, int64_t timeStart); static DECLCALLBACK(void) vscVideoSourceStreamStop(void *pvCallback, uint32_t u32SourceStreamId, const RGNRECT *prect); +static DECLCALLBACK(bool) vscIsValidRect(void *pvCallback, const RGNRECT *prect); static VIDEOSTREAMCALLBACKS vsCallbacks = { vscVideoSourceStreamStart, - vscVideoSourceStreamStop + vscVideoSourceStreamStop, + vscIsValidRect, }; #define VRDP_SB_TO_SCREEN(__psb) ((VRDPSBSCREEN *)((uint8_t *)(__psb) - RT_UOFFSETOF(VRDPSBSCREEN, sb))) @@ -2385,6 +2387,12 @@ static DECLCALLBACK(void) vscVideoSourceStreamStop(void *pvCallback, uint32_t u3 return; } +static DECLCALLBACK(bool) vscIsValidRect(void *pvCallback, const RGNRECT *prect) +{ + VRDPSBSCREEN *pScreen = (VRDPSBSCREEN *)pvCallback; + AssertPtrReturn(pScreen, false); + return rgnIsRectWithin(&pScreen->sb.pixelBuffer.rect, prect); +} void shadowBufferUpdateComplete(void) { @@ -2998,6 +3006,40 @@ void shadowBufferQueryRect (unsigned uScreenId, RGNRECT *prect) } } +bool shadowBufferAreOrderCoordsValid(unsigned uScreenId, int32_t x, int32_t y, uint32_t w, uint32_t h) +{ + SBLOG(("Enter: uScreenId = %d\n", uScreenId)); + + bool fValid = false; + + if (sbLock (uScreenId)) + { + VRDPSBSCREEN *pScreen = sbResolveScreenId (uScreenId); + + if (pScreen != NULL) + { + RGNRECT rect; + rect.x = x; + rect.y = y; + rect.w = w; + rect.h = h; + + RGNRECT rectFB; + rectFB.x = 0; + rectFB.y = 0; + rectFB.w = pScreen->sb.transform.cFBWidth; + rectFB.h = pScreen->sb.transform.cFBHeight; + + fValid = rgnIsRectWithin(&rectFB, &rect); + } + + sbUnlock (); + } + + return fValid; +} + + void shadowBufferTransformRect (unsigned uScreenId, RGNRECT *prect) { SBLOG(("Enter: uScreenId = %d\n", uScreenId)); @@ -3578,6 +3620,11 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) rectAffected.y = hdr.y; rectAffected.w = hdr.w; rectAffected.h = hdr.h; + if (!rgnIsRectWithin(&pScreen->sb.pixelBuffer.rect, &rectAffected)) + { + sbUnlock (); + return; + } pScreen->sb.transform.pfnTransformRect (&rectAffected, pScreen->sb.transform.cSBWidth, pScreen->sb.transform.cSBHeight); @@ -3606,18 +3653,6 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) pBitsHdr->cb, pBitsHdr->x, pBitsHdr->y, pBitsHdr->cWidth, pBitsHdr->cHeight, pBitsHdr->cbPixel, *(uint32_t *)&pOrder->hash[0], *(uint32_t *)&pOrder->hash[4], *(uint32_t *)&pOrder->hash[8], *(uint32_t *)&pOrder->hash[12])); - switch (pBitsHdr->cbPixel) - { - case 2: - case 3: - case 4: break; - - default: - SBLOG(("Unsupported cbPixel (%d)!!!", pBitsHdr->cbPixel)); - sbUnlock (); - return; - } - /* Verify that the buffer is big enough for those bits. */ if (pBitsHdr->cb > cbSrcRemaining) { @@ -3628,6 +3663,12 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) return; } + if (!isValidBitsHdr(pBitsHdr)) + { + sbUnlock (); + return; + } + BCHEAPHANDLE hBmp; int rc = BCStore(&hBmp, g_pCtx->pServer->BC(), VRDE_ORDER_CACHED_BITMAP, @@ -3693,6 +3734,12 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) SBLOG(("VRDE_ORDER_SAVESCREEN: pBitsHdr cb = %d, x = %d, y = %d, cWidth = %d, cHeight = %d, cbPixel = %d\n", pBitsHdr->cb, pBitsHdr->x, pBitsHdr->y, pBitsHdr->cWidth, pBitsHdr->cHeight, pBitsHdr->cbPixel)); + if (!isValidBitsHdr(pBitsHdr)) + { + sbUnlock (); + return; + } + /* Save bitmap in the bmpcache intermediate heap, if that fails, do a bitmap update. */ BCHEAPHANDLE hBmp; int rc = BCStore(&hBmp, g_pCtx->pServer->BC(), @@ -3752,18 +3799,6 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) SBLOG(("VRDE_ORDER_DIRTY_RECT: cb %d, %d,%d %dx%d, cbPixel %d\n", pBitsHdr->cb, pBitsHdr->x, pBitsHdr->y, pBitsHdr->cWidth, pBitsHdr->cHeight, pBitsHdr->cbPixel)); - switch (pBitsHdr->cbPixel) - { - case 2: - case 3: - case 4: break; - - default: - SBLOG(("Unsupported cbPixel (%d)!!!", pBitsHdr->cbPixel)); - sbUnlock (); - return; - } - /* Verify that the buffer is big enough for those bits. */ if (pBitsHdr->cb > cbSrcRemaining) { @@ -3774,6 +3809,12 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata) return; } + if (!isValidBitsHdr(pBitsHdr)) + { + sbUnlock (); + return; + } + /* Copy bits to pixel buffers. */ uint32_t cbLine = pBitsHdr->cWidth * pBitsHdr->cbPixel; diff --git a/src/VBox/RDP/server/shadowbuffer.h b/src/VBox/RDP/server/shadowbuffer.h index 89495833996e..66f640c25371 100644 --- a/src/VBox/RDP/server/shadowbuffer.h +++ b/src/VBox/RDP/server/shadowbuffer.h @@ -1,4 +1,4 @@ -/* $Id: shadowbuffer.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: shadowbuffer.h 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -98,6 +98,7 @@ void shadowBufferOrder (unsigned uScreenId, void *pdata, uint32_t cbdata); void shadowBufferQueryRect (unsigned uScreenId, RGNRECT *prect); +bool shadowBufferAreOrderCoordsValid(unsigned uScreenId, int32_t x, int32_t y, uint32_t w, uint32_t h); void shadowBufferTransformRect (unsigned uScreenId, RGNRECT *prect); void shadowBufferTransformRectGeneric (unsigned uScreenId, RGNRECT *prect, unsigned w, unsigned h); void shadowBufferTransformWidthHeight(unsigned uScreenId, unsigned *pw, unsigned *ph); diff --git a/src/VBox/RDP/server/textcache.cpp b/src/VBox/RDP/server/textcache.cpp index 2c418d9c598d..2343cbd6b2be 100644 --- a/src/VBox/RDP/server/textcache.cpp +++ b/src/VBox/RDP/server/textcache.cpp @@ -1,4 +1,4 @@ -/* $Id: textcache.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: textcache.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -122,9 +122,8 @@ static uint8_t *tcGlyphBitmap (const TCGLYPHFONT *pFont, int iGlyph) return NULL; } -#ifdef RT_STRICT /* Return maximum size of a bitmap for this font. */ -static int tcGlyphBitmapMaxSize (const TCGLYPHFONT *pFont) +static uint32_t tcGlyphBitmapMaxSize (const TCGLYPHFONT *pFont) { switch (pFont->iRDPFontHandle) { @@ -140,7 +139,6 @@ static int tcGlyphBitmapMaxSize (const TCGLYPHFONT *pFont) AssertFailed (); return 0; } -#endif /* Convert the server font array index to the RDP font handle. */ static int tcRDPHandleFromServerIndex (int index) @@ -222,19 +220,22 @@ static void tcClearFontCache (TCGLYPHFONT *pFont) pFont->cGlyphsCached = 0; } -static TCCACHEDGLYPH *tcCacheGlyph (TCGLYPHFONT *pFont, const VRDEORDERGLYPH *pGlyph) +static TCCACHEDGLYPH *tcCacheGlyph (TCGLYPHFONT *pFont, const VRDEORDERGLYPH *pGlyph, uint32_t cbGlyphBitmap) { - Assert (pFont->cGlyphsCached < pFont->cGlyphsMax); - - /* Allocate place for the new glyph, remember its index. */ - int iGlyph = pFont->cGlyphsCached++; - if (pFont->cGlyphsCached >= pFont->cGlyphsMax) { /* No place for the new glyph. */ return NULL; } + if (cbGlyphBitmap > tcGlyphBitmapMaxSize (pFont)) + { + return NULL; + } + + /* Allocate place for the new glyph, remember its index. */ + int iGlyph = pFont->cGlyphsCached++; + /* Convert the VRDEORDERGLYPH to the cache glyph format. */ TCCACHEDGLYPH *pCachedGlyph = &pFont->aGlyphs[iGlyph]; @@ -250,13 +251,7 @@ static TCCACHEDGLYPH *tcCacheGlyph (TCGLYPHFONT *pFont, const VRDEORDERGLYPH *pG pCachedGlyph->pu8Bitmap = tcGlyphBitmap (pFont, iGlyph); - int cbBitmap = (pCachedGlyph->w + 7) / 8; /* Line size in bytes. */ - cbBitmap *= pCachedGlyph->h; /* Size of bitmap. */ - cbBitmap = (cbBitmap + 3) & ~3; /* 32 bit DWORD align. */ - - Assert (cbBitmap <= tcGlyphBitmapMaxSize (pFont)); - - memcpy (pCachedGlyph->pu8Bitmap, pGlyph->au8Bitmap, cbBitmap); + memcpy (pCachedGlyph->pu8Bitmap, pGlyph->au8Bitmap, cbGlyphBitmap); return pCachedGlyph; } @@ -280,22 +275,51 @@ static TCCACHEDGLYPH *tcFindCachedGlyph (TCGLYPHFONT *pFont, const VRDEORDERGLYP } -static int tcTryCacheGlyphs (const VRDEORDERTEXT *pOrder, TCGLYPHFONT *pFont, TCFONTTEXT2 *pFontText2) +static int tcTryCacheGlyphs (const VRDEORDERTEXT *pOrder, uint32_t cbOrder, TCGLYPHFONT *pFont, TCFONTTEXT2 *pFontText2) { int rc = VINF_SUCCESS; /* Scan the string and check glyphs. */ const VRDEORDERGLYPH *pGlyph = (VRDEORDERGLYPH *)(&pOrder[1]); /* Glyphs follow the order structure. */ + uint32_t cbLeft = cbOrder; unsigned i; for (i = 0; i < pOrder->u8Glyphs; i++) { + if (cbLeft < RT_UOFFSETOF(VRDEORDERGLYPH, au8Bitmap)) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + if (pGlyph->o32NextGlyph > cbLeft) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + uint32_t cbGlyphBitmap = (pGlyph->w + 7) / 8; /* Line size in bytes. */ + cbGlyphBitmap *= pGlyph->h; /* Size of bitmap. */ + cbGlyphBitmap = (cbGlyphBitmap + 3) & ~3; /* 32 bit DWORD align. */ + + if (cbGlyphBitmap > cbLeft - RT_UOFFSETOF(VRDEORDERGLYPH, au8Bitmap)) + { + rc = VERR_INVALID_PARAMETER; + break; + } + + if (cbGlyphBitmap > pOrder->u16MaxGlyph) + { + rc = VERR_INVALID_PARAMETER; + break; + } + /* Find the glyph in the cache. */ TCCACHEDGLYPH *pCachedGlyph = tcFindCachedGlyph (pFont, pGlyph); if (!pCachedGlyph) { - pCachedGlyph = tcCacheGlyph (pFont, pGlyph); + pCachedGlyph = tcCacheGlyph (pFont, pGlyph, cbGlyphBitmap); } if (!pCachedGlyph) @@ -322,6 +346,7 @@ static int tcTryCacheGlyphs (const VRDEORDERTEXT *pOrder, TCGLYPHFONT *pFont, TC pFontText2->cGlyphs++; pGlyph = (const VRDEORDERGLYPH *)((uint8_t *)pGlyph + pGlyph->o32NextGlyph); + cbLeft -= pGlyph->o32NextGlyph; } return rc; @@ -363,7 +388,7 @@ static int tcSetupFontText2 (TCFONTTEXT2 *pFontText2, TCGLYPHFONT *pFont, const } -bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **ppFontText2) +bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, uint32_t cbOrder, TCFONTTEXT2 **ppFontText2) { /* Check which glyphs are already cached. The original order is copied * to the TCFONTTEXT2 structure and the status of each glyph is determined. @@ -378,6 +403,11 @@ bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **p * to the string: ID - the cache index. CB is the string length. */ + if (cbOrder < sizeof(VRDEORDERTEXT)) + { + return false; + } + int iRDPFontHandle = tcSelectRDPHandle (ptc, pOrder); if (iRDPFontHandle == -1) @@ -394,7 +424,7 @@ bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **p TCGLYPHFONT *pFont = &ptc->glyphs.fonts[ tcServerIndexFromRDPHandle (iRDPFontHandle) ]; - int rc = tcTryCacheGlyphs (pOrder, pFont, pFontText2); + int rc = tcTryCacheGlyphs (pOrder, cbOrder, pFont, pFontText2); if (RT_FAILURE(rc)) { @@ -402,7 +432,7 @@ bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **p memset (pFontText2, 0, sizeof (TCFONTTEXT2)); - rc = tcTryCacheGlyphs (pOrder, pFont, pFontText2); + rc = tcTryCacheGlyphs (pOrder, cbOrder, pFont, pFontText2); } if (RT_SUCCESS (rc)) diff --git a/src/VBox/RDP/server/textcache.h b/src/VBox/RDP/server/textcache.h index 1babcccedbff..b747c731fff7 100644 --- a/src/VBox/RDP/server/textcache.h +++ b/src/VBox/RDP/server/textcache.h @@ -1,4 +1,4 @@ -/* $Id: textcache.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: textcache.h 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -171,7 +171,7 @@ typedef struct _TCFONTTEXT2 PTEXTCACHE TCCreate (void); void TCDelete (PTEXTCACHE ptc); -bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, TCFONTTEXT2 **ppFontText2); +bool TCCacheGlyphs (PTEXTCACHE ptc, const VRDEORDERTEXT *pOrder, uint32_t cbOrder, TCFONTTEXT2 **ppFontText2); void TCFreeFontText2 (TCFONTTEXT2 *pFontText2); #endif /* !VRDP_INCLUDED_SRC_textcache_h */ diff --git a/src/VBox/RDP/server/videodetector.cpp b/src/VBox/RDP/server/videodetector.cpp index d7d0b20ff5fc..83e88d144879 100644 --- a/src/VBox/RDP/server/videodetector.cpp +++ b/src/VBox/RDP/server/videodetector.cpp @@ -1,4 +1,4 @@ -/* $Id: videodetector.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: videodetector.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -356,6 +356,11 @@ bool videoDetectorBitmapUpdate(VDCONTEXT *pCtx, return false; } + if (!pCtx->pCallbacks->pfnIsValidRect(pCtx->pvCallback, prectUpdate)) + { + return false; + } + #ifdef DEBUG_sunlover VIDEOLOG(("@%d,%d %dx%d\n", prectUpdate->x, prectUpdate->y, prectUpdate->w, prectUpdate->h)); #endif /* DEBUG_sunlover */ diff --git a/src/VBox/RDP/server/videostream.h b/src/VBox/RDP/server/videostream.h index 4e2810ce64c7..1138b8ac92c5 100644 --- a/src/VBox/RDP/server/videostream.h +++ b/src/VBox/RDP/server/videostream.h @@ -1,4 +1,4 @@ -/* $Id: videostream.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: videostream.h 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -47,6 +47,7 @@ typedef struct VIDEOSTREAMCALLBACKS { DECLR3CALLBACKMEMBER(bool, pfnVideoSourceStreamStart, (void *pvCallback, uint32_t u32SourceStreamId, const RGNRECT *prect, int64_t timeStart)); DECLR3CALLBACKMEMBER(void, pfnVideoSourceStreamStop, (void *pvCallback, uint32_t u32SourceStreamId, const RGNRECT *prect)); + DECLR3CALLBACKMEMBER(bool, pfnIsValidRect, (void *pvCallback, const RGNRECT *prect)); } VIDEOSTREAMCALLBACKS; typedef struct VDCONTEXT VDCONTEXT; diff --git a/src/VBox/RDP/server/vrdp.h b/src/VBox/RDP/server/vrdp.h index 8b88554446f2..5dad7e8abb57 100644 --- a/src/VBox/RDP/server/vrdp.h +++ b/src/VBox/RDP/server/vrdp.h @@ -1,4 +1,4 @@ -/* $Id: vrdp.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: vrdp.h 114935 2026-08-10 12:46:46Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol. */ @@ -658,6 +658,7 @@ class VRDPChannelRDPDR: public VRDPChannel int createIO(uint32_t *pu32CompletionId, RDPDRPKTHDR *pHdr, uint32_t u32MajorFunction, uint32_t u32DeviceId); int fetchIO(uint32_t u32CompletionId, RDPDRPKTHDR **ppHdr, uint32_t *pu32MajorFunction, uint32_t *pu32DeviceId); + void cancelIOForDevice(uint32_t u32DeviceId); int rdpdrSendServerCoreCapability(void); int rdpdrSendServerClientIdConfirm(void); diff --git a/src/VBox/RDP/server/vrdpapi.cpp b/src/VBox/RDP/server/vrdpapi.cpp index 79a5b273557d..c9de92ee52af 100644 --- a/src/VBox/RDP/server/vrdpapi.cpp +++ b/src/VBox/RDP/server/vrdpapi.cpp @@ -1,4 +1,4 @@ -/* $Id: vrdpapi.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: vrdpapi.cpp 114912 2026-08-10 12:03:20Z vitali.pelenjow@oracle.com $ */ /** @file * VBox Remote Desktop Protocol - Public API functions. */ @@ -96,6 +96,7 @@ static DECLCALLBACK(void) VRDPUpdate (HVRDESERVER hServer, if (pServer) { +#if 0 /* obsolete */ if (cbUpdate > sizeof (VRDEORDERHDR)) { /* The update includes VRDP order information. */ @@ -103,6 +104,8 @@ static DECLCALLBACK(void) VRDPUpdate (HVRDESERVER hServer, pServer->ProcessOutputUpdate (uScreenId, pvUpdate, cbUpdate); } else if (cbUpdate == sizeof (VRDEORDERHDR)) +#endif + if (cbUpdate == sizeof (VRDEORDERHDR)) { /* This is just a bitmap update. */ VRDPAPILOG(("%p, %d, %d (bitmap)\n", pServer, cbUpdate, sizeof (VRDEORDERHDR))); diff --git a/src/VBox/Runtime/Makefile.kmk b/src/VBox/Runtime/Makefile.kmk index 361ba9527d75..bb037bb94d64 100644 --- a/src/VBox/Runtime/Makefile.kmk +++ b/src/VBox/Runtime/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114131 2026-05-14 12:27:45Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 114998 2026-08-12 23:06:36Z knut.osmundsen@oracle.com $ ## @file # Sub-Makefile for the IPRT. # @@ -2891,6 +2891,7 @@ RuntimeGuestR3_SOURCES = $(filter-out \ ifndef VBOX_WITH_NOCRT_STATIC RuntimeGuestR3_DEFS.win.x86 = $(RuntimeR3_DEFS.win.x86) \ VCC_FAKES_TARGET_$(VBOX_VCC_TOOL_STEM) VCC_FAKES_TARGET=$(substr $(VBOX_VCC_TOOL_STEM),-3) + RuntimeGuestR3_ASINCS.win.x86 = $(RuntimeR3_ASINCS.win.x86) r3/win/ # NASM crap RuntimeGuestR3_SOURCES.win.x86 = $(RuntimeR3_SOURCES.win.x86) \ r3/win/vcc-fakes-kernel32.cpp \ r3/win/vcc-fakes-kernel32-A.asm \ @@ -3640,6 +3641,7 @@ if ($(VBOX_SOLARIS_11_UPDATE_VERSION) > 2 \ endif RuntimeR0Drv_INCS := $(PATH_SUB_CURRENT) include +RuntimeR0Drv_ASINCS.win.x86 = common/compiler/vcc/ # NASM crap RuntimeR0Drv_INCS.freebsd = \ $(PATH_STAGE)/gen-sys-hdrs RuntimeR0Drv_INCS.solaris = \ @@ -4637,6 +4639,7 @@ endif ifndef VBOX_WITH_NOCRT_STATIC RuntimeR3VccTricks_TEMPLATE = VBoxR3Static RuntimeR3VccTricks_DEFS = VCC_FAKES_TARGET_$(VBOX_VCC_TOOL_STEM) VCC_FAKES_TARGET=$(substr $(VBOX_VCC_TOOL_STEM),-3) + RuntimeR3VccTricks_ASINCS = r3/win/ # NASM crap RuntimeR3VccTricks_SOURCES = \ r3/win/vcc-fakes-kernel32.cpp \ r3/win/vcc-fakes-kernel32-A.asm \ diff --git a/src/VBox/Runtime/VBox/DefToAsmExternsVcc32.sed b/src/VBox/Runtime/VBox/DefToAsmExternsVcc32.sed index df12c51a4d86..69627aedc13f 100644 --- a/src/VBox/Runtime/VBox/DefToAsmExternsVcc32.sed +++ b/src/VBox/Runtime/VBox/DefToAsmExternsVcc32.sed @@ -1,4 +1,4 @@ -# $Id: DefToAsmExternsVcc32.sed 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: DefToAsmExternsVcc32.sed 115000 2026-08-12 23:10:34Z knut.osmundsen@oracle.com $ ## @file # SED script for generating assembly externs from a VBoxRT windows .def file. # @@ -39,6 +39,11 @@ # /not-some-systems/d +# +# Check the external side of function aliases. +# +s/=[^ ;]*// + # # Remove comments and space. Skip empty lines. # @@ -54,7 +59,8 @@ s/[[:space:]][[:space:]]*$//g s/^EXPORTS$// /^$/b end -/^?/b cpp_export +/^?.*[[:space:]]DATA$/b cpp_data_export +/^?.*/b cpp_export /[[:space:]]DATA$/b data # @@ -72,12 +78,16 @@ s/^\(.*\)[[:space:]]*DATA$/EXTERN_IMP2 \1/ b end # -# Mangled C++ . +# Mangled C++ code and data. # :cpp_export s/^\(.*\)$/extern __imp_\1/ b end +:cpp_data_export +s/^\(.*\)[[:space:]]DATA$/extern __imp_\1/ +b end + } d b end diff --git a/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.asm b/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.asm index 6c5528d99cdd..dfde77cd8496 100644 --- a/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.asm +++ b/src/VBox/Runtime/VBox/RTLogWriteVmm-amd64-x86.asm @@ -1,4 +1,4 @@ -; $Id: RTLogWriteVmm-amd64-x86.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTLogWriteVmm-amd64-x86.asm 115012 2026-08-12 23:37:41Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTLogWriteVmm - AMD64 & X86 for VBox. ; @@ -95,3 +95,4 @@ RT_BEGINPROC RTLogWriteVmm ret ENDPROC RTLogWriteVmm +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAddFlags.asm b/src/VBox/Runtime/common/asm/ASMAddFlags.asm index 2052bb004387..d44ad0f05e78 100644 --- a/src/VBox/Runtime/common/asm/ASMAddFlags.asm +++ b/src/VBox/Runtime/common/asm/ASMAddFlags.asm @@ -1,4 +1,4 @@ -; $Id: ASMAddFlags.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAddFlags.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetFlags(). ; @@ -78,3 +78,4 @@ RT_BEGINPROC ASMAddFlags ret ENDPROC ASMAddFlags +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm index 24a0ae38970e..42ae5a00fc6a 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgExU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicCmpXchgExU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicCmpXchgExU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicCmpXchgExU64(). ; @@ -92,3 +92,4 @@ RT_BEGINPROC ASMAtomicCmpXchgExU64 %endif ENDPROC ASMAtomicCmpXchgExU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm index 5fabb9204c3b..76ae4b48a0d8 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU16.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicCmpXchgU16.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicCmpXchgU16.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicCmpXchgU16(). ; @@ -71,3 +71,4 @@ RT_BEGINPROC ASMAtomicCmpXchgU16 ret ENDPROC ASMAtomicCmpXchgU16 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm index 6ef61a2c902e..f75c68d982ac 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicCmpXchgU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicCmpXchgU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicCmpXchgU64(). ; @@ -86,3 +86,4 @@ RT_BEGINPROC ASMAtomicCmpXchgU64 %endif ENDPROC ASMAtomicCmpXchgU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm index 1b4bf81233f5..81e8a1df41e0 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicCmpXchgU8.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicCmpXchgU8.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicCmpXchgU8.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicCmpXchgU8(). ; @@ -71,3 +71,4 @@ RT_BEGINPROC ASMAtomicCmpXchgU8 ret ENDPROC ASMAtomicCmpXchgU8 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm index 2ed9436ed6e1..1b7a1366fcff 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicReadU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicReadU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicReadU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicReadU64(). ; @@ -79,3 +79,4 @@ RT_BEGINPROC ASMAtomicReadU64 %endif ENDPROC ASMAtomicReadU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm index d86a951f1580..e6a3d0a5fb2b 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoAndU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoAndU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoAndU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoAndU32 ret ENDPROC ASMAtomicUoAndU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm index 9b6d52296d90..6c908c7e8123 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoAndU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoAndU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoAndU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoAndU64(). ; @@ -75,6 +75,7 @@ RT_BEGINPROC ASMAtomicUoAndU64 mov ecx, edx and ecx, [ebp + 0ch + 4] jmp .try_again + int3 .done: pop edi @@ -84,3 +85,4 @@ RT_BEGINPROC ASMAtomicUoAndU64 ret ENDPROC ASMAtomicUoAndU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm index 13465e2a7e1c..42436d8c2a3f 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoDecU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoDecU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoDecU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoDecU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoDecU32 ret ENDPROC ASMAtomicUoDecU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm index 186163b7af63..f0e1404a0037 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoIncU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoIncU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoIncU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoIncU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoIncU32 ret ENDPROC ASMAtomicUoIncU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm index 16f47030516d..aec58e2cc05c 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoOrU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoOrU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoOrU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoOrU32 ret ENDPROC ASMAtomicUoOrU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm index d43ca1cd3302..77efb8361d6f 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoOrU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoOrU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoOrU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoOrU64(). ; @@ -75,6 +75,7 @@ RT_BEGINPROC ASMAtomicUoOrU64 mov ecx, edx or ecx, [ebp + 0ch + 4] jmp .try_again + int3 .done: pop edi @@ -84,3 +85,4 @@ RT_BEGINPROC ASMAtomicUoOrU64 ret ENDPROC ASMAtomicUoOrU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm index e86b29d004f9..8adea3e0f072 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoReadU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoReadU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoReadU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoReadU64(). ; @@ -78,3 +78,4 @@ RT_BEGINPROC ASMAtomicUoReadU64 %endif ENDPROC ASMAtomicUoReadU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm b/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm index e82a8e580d58..73e51da64d18 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicUoXorU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicUoXorU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicUoXorU32.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicUoXorU32(). ; @@ -64,3 +64,4 @@ RT_BEGINPROC ASMAtomicUoXorU32 ret ENDPROC ASMAtomicUoXorU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm b/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm index 5b81a233ae99..3feefba7b914 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicXchgU16.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicXchgU16.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicXchgU16.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicXchgU16(). ; @@ -68,3 +68,4 @@ RT_BEGINPROC ASMAtomicXchgU16 ret ENDPROC ASMAtomicXchgU16 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm b/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm index 29b69060814b..4a9800afa481 100644 --- a/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm +++ b/src/VBox/Runtime/common/asm/ASMAtomicXchgU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMAtomicXchgU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMAtomicXchgU64.asm 115010 2026-08-12 23:36:50Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMAtomicXchgU64(). ; @@ -78,3 +78,4 @@ RT_BEGINPROC ASMAtomicXchgU64 %endif ENDPROC ASMAtomicXchgU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm b/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm index f2a7a02ea22c..df4630a29ee2 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstClear.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstClear.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstClear.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstClear(). ; @@ -118,6 +118,7 @@ RT_BEGINPROC ASMBitFirstClear leave %endif ret + int3 ; failure ;} @@ -135,3 +136,4 @@ RT_BEGINPROC ASMBitFirstClear ret ENDPROC ASMBitFirstClear +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm index ee65fb0ea2aa..e4476fb06240 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstSet.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstSet.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstSet.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstSet(). ; @@ -118,6 +118,7 @@ RT_BEGINPROC ASMBitFirstSet leave %endif ret + int3 ; failure ;} @@ -135,3 +136,4 @@ RT_BEGINPROC ASMBitFirstSet ret ENDPROC ASMBitFirstSet +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm index 8835ea1086b4..b20a53c09ad6 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstSetU16.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstSetU16.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstSetU16.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstSetU16(). ; @@ -71,6 +71,7 @@ RT_BEGINPROC ASMBitFirstSetU16 jc .return inc ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -95,9 +96,11 @@ RT_BEGINPROC ASMBitFirstSetU16 inc eax .return: ret + int3 .return_zero: xor eax, eax ret %endif ENDPROC ASMBitFirstSetU16 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm index 6708ff92d333..558de82489f3 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstSetU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstSetU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstSetU32.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstSetU32(). ; @@ -76,6 +76,7 @@ RT_BEGINPROC ASMBitFirstSetU32 jc .return inc ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -99,9 +100,11 @@ RT_BEGINPROC ASMBitFirstSetU32 inc eax .return: ret + int3 .return_zero: xor eax, eax ret %endif ENDPROC ASMBitFirstSetU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm b/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm index aa99e81952d5..0288db5da96c 100644 --- a/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm +++ b/src/VBox/Runtime/common/asm/ASMBitFirstSetU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitFirstSetU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitFirstSetU64.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitFirstSetU64(). ; @@ -86,6 +86,7 @@ RT_BEGINPROC ASMBitFirstSetU64 jc .return inc ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -103,6 +104,7 @@ RT_BEGINPROC ASMBitFirstSetU64 jz .return_zero inc eax ret + int3 %elif ARCH_BITS == 32 ; Check the first dword then the 2nd one. @@ -110,6 +112,7 @@ RT_BEGINPROC ASMBitFirstSetU64 jnz .check_2nd_dword inc eax ret + int3 .check_2nd_dword: bsf eax, dword [esp + 4 + 4] jz .return_zero @@ -124,3 +127,4 @@ RT_BEGINPROC ASMBitFirstSetU64 %endif ENDPROC ASMBitFirstSetU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm b/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm index 2ccc3e881f91..3f97bae64a63 100644 --- a/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm +++ b/src/VBox/Runtime/common/asm/ASMBitLastSetU16.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitLastSetU16.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitLastSetU16.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitLastSetU16(). ; @@ -69,6 +69,7 @@ RT_BEGINPROC ASMBitLastSetU16 jc .return dec ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -93,9 +94,11 @@ RT_BEGINPROC ASMBitLastSetU16 inc eax .return: ret + int3 .return_zero: xor eax, eax ret %endif ENDPROC ASMBitLastSetU16 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm b/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm index ca60ec5c6b57..b33ac29b6628 100644 --- a/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm +++ b/src/VBox/Runtime/common/asm/ASMBitLastSetU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitLastSetU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitLastSetU32.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitLastSetU32(). ; @@ -76,6 +76,7 @@ RT_BEGINPROC ASMBitLastSetU32 jc .return dec ax jmp .next_bit + int3 .return_zero: xor ax, ax @@ -99,9 +100,11 @@ RT_BEGINPROC ASMBitLastSetU32 inc eax .return: ret + int3 .return_zero: xor eax, eax ret %endif ENDPROC ASMBitLastSetU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm b/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm index 8e35542aae3d..28f382bc49b0 100644 --- a/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm +++ b/src/VBox/Runtime/common/asm/ASMBitLastSetU64.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitLastSetU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitLastSetU64.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitLastSetU64(). ; @@ -86,12 +86,14 @@ RT_BEGINPROC ASMBitLastSetU64 jc .return dec ax jmp .next_bit + int3 .return_zero: xor ax, ax .return: pop bp ret + int3 %else %if ARCH_BITS == 64 @@ -104,6 +106,7 @@ RT_BEGINPROC ASMBitLastSetU64 inc eax .return: ret + int3 %elif ARCH_BITS == 32 ; Check the 2nd dword then the first one. @@ -111,12 +114,14 @@ RT_BEGINPROC ASMBitLastSetU64 jz .check_1st_dword add eax, 32 ret + int3 .check_1st_dword: bsr eax, dword [esp + 4 + 0] jz .return_zero inc eax ret + int3 %else %error "Missing or invalid ARCH_BITS." @@ -128,3 +133,4 @@ RT_BEGINPROC ASMBitLastSetU64 %endif ENDPROC ASMBitLastSetU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitNextClear.asm b/src/VBox/Runtime/common/asm/ASMBitNextClear.asm index c3a58e93db29..33fa906dde1a 100644 --- a/src/VBox/Runtime/common/asm/ASMBitNextClear.asm +++ b/src/VBox/Runtime/common/asm/ASMBitNextClear.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitNextClear.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitNextClear.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitNextClear(). ; @@ -115,6 +115,7 @@ RT_BEGINPROC ASMBitNextClear leave %endif ret + int3 ; ; Do dword scan. @@ -175,9 +176,11 @@ RT_BEGINPROC ASMBitNextClear shl edi, 3 ; edi=bit offset of current dword. add eax, edi jmp .return + int3 .return_failure: mov eax, 0ffffffffh jmp .return ENDPROC ASMBitNextClear +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMBitNextSet.asm b/src/VBox/Runtime/common/asm/ASMBitNextSet.asm index fc40e24d9111..60d20fe64c23 100644 --- a/src/VBox/Runtime/common/asm/ASMBitNextSet.asm +++ b/src/VBox/Runtime/common/asm/ASMBitNextSet.asm @@ -1,4 +1,4 @@ -; $Id: ASMBitNextSet.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMBitNextSet.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMBitNextSet(). ; @@ -116,6 +116,7 @@ RT_BEGINPROC ASMBitNextSet leave %endif ret + int3 ; ; Do dword scan. @@ -175,9 +176,11 @@ RT_BEGINPROC ASMBitNextSet shl edi, 3 ; edi=bit offset of current dword. add eax, edi jmp .return + int3 .return_failure: mov eax, 0ffffffffh jmp .return ENDPROC ASMBitNextSet +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMCpuId.asm b/src/VBox/Runtime/common/asm/ASMCpuId.asm index b889072d9b41..1cbfb3db3fb6 100644 --- a/src/VBox/Runtime/common/asm/ASMCpuId.asm +++ b/src/VBox/Runtime/common/asm/ASMCpuId.asm @@ -1,4 +1,4 @@ -; $Id: ASMCpuId.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMCpuId.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMCpuIdExSlow(). ; @@ -119,3 +119,4 @@ RT_BEGINPROC ASMCpuId ret ENDPROC ASMCpuId +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm b/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm index 7a8ecfde0dca..93da869fd8e9 100644 --- a/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm +++ b/src/VBox/Runtime/common/asm/ASMCpuIdExSlow.asm @@ -1,4 +1,4 @@ -; $Id: ASMCpuIdExSlow.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMCpuIdExSlow.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMCpuIdExSlow(). ; @@ -179,3 +179,4 @@ RT_BEGINPROC ASMCpuIdExSlow ret ENDPROC ASMCpuIdExSlow +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm b/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm index 47a93e3520d1..59a8dad16b4a 100644 --- a/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm +++ b/src/VBox/Runtime/common/asm/ASMCpuId_Idx_ECX.asm @@ -1,4 +1,4 @@ -; $Id: ASMCpuId_Idx_ECX.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMCpuId_Idx_ECX.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMCpuId_Idx_ECX(). ; @@ -124,3 +124,4 @@ RT_BEGINPROC ASMCpuId_Idx_ECX %endif ENDPROC ASMCpuId_Idx_ECX +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMFxRstor.asm b/src/VBox/Runtime/common/asm/ASMFxRstor.asm index c0153fad1cec..3afeb18b2859 100644 --- a/src/VBox/Runtime/common/asm/ASMFxRstor.asm +++ b/src/VBox/Runtime/common/asm/ASMFxRstor.asm @@ -1,4 +1,4 @@ -; $Id: ASMFxRstor.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMFxRstor.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMFxRstor(). ; @@ -72,3 +72,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMFxRstor +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMFxSave.asm b/src/VBox/Runtime/common/asm/ASMFxSave.asm index c3bed12dfb13..139db7cc9f87 100644 --- a/src/VBox/Runtime/common/asm/ASMFxSave.asm +++ b/src/VBox/Runtime/common/asm/ASMFxSave.asm @@ -1,4 +1,4 @@ -; $Id: ASMFxSave.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMFxSave.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMFxSave(). ; @@ -72,3 +72,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMFxSave +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetFSBase.asm b/src/VBox/Runtime/common/asm/ASMGetFSBase.asm index ce3119884ae7..b92e73b31263 100644 --- a/src/VBox/Runtime/common/asm/ASMGetFSBase.asm +++ b/src/VBox/Runtime/common/asm/ASMGetFSBase.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetFSBase.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetFSBase.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetFSBase(). ; @@ -52,3 +52,4 @@ RT_BEGINPROC ASMGetFSBase ret ENDPROC ASMGetFSBase +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetFlags.asm b/src/VBox/Runtime/common/asm/ASMGetFlags.asm index 30d0af222d55..ca802b6dd66e 100644 --- a/src/VBox/Runtime/common/asm/ASMGetFlags.asm +++ b/src/VBox/Runtime/common/asm/ASMGetFlags.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetFlags.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetFlags.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetFlags(). ; @@ -51,3 +51,4 @@ RT_BEGINPROC ASMGetFlags ret ENDPROC ASMGetFlags +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetGDTR.asm b/src/VBox/Runtime/common/asm/ASMGetGDTR.asm index 56e657db3502..c1b42a35531e 100644 --- a/src/VBox/Runtime/common/asm/ASMGetGDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMGetGDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetGDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetGDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetGDTR(). ; @@ -60,3 +60,4 @@ RT_BEGINPROC ASMGetGDTR ret ENDPROC ASMGetGDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetGSBase.asm b/src/VBox/Runtime/common/asm/ASMGetGSBase.asm index a2c0250864f3..eca9ca7e1a4c 100644 --- a/src/VBox/Runtime/common/asm/ASMGetGSBase.asm +++ b/src/VBox/Runtime/common/asm/ASMGetGSBase.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetGSBase.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetGSBase.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetGSBase(). ; @@ -52,3 +52,4 @@ RT_BEGINPROC ASMGetGSBase ret ENDPROC ASMGetGSBase +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetIDTR.asm b/src/VBox/Runtime/common/asm/ASMGetIDTR.asm index e41b34804a1c..78f8ebbaa66e 100644 --- a/src/VBox/Runtime/common/asm/ASMGetIDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMGetIDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetIDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetIDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetIDTR(). ; @@ -60,3 +60,4 @@ RT_BEGINPROC ASMGetIDTR ret ENDPROC ASMGetIDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm b/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm index 06f5f50dc730..79760313d2a1 100644 --- a/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm +++ b/src/VBox/Runtime/common/asm/ASMGetIdtrLimit.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetIdtrLimit.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetIdtrLimit.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetIdtrLimit(). ; @@ -56,3 +56,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMGetIdtrLimit +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetLDTR.asm b/src/VBox/Runtime/common/asm/ASMGetLDTR.asm index c5f00a5e9b6e..6c4046b0079f 100644 --- a/src/VBox/Runtime/common/asm/ASMGetLDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMGetLDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetLDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetLDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetLDTR(). ; @@ -51,3 +51,4 @@ RT_BEGINPROC ASMGetLDTR ret ENDPROC ASMGetLDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm b/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm index de76d2e04e6a..2f8e0d21ff9f 100644 --- a/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm +++ b/src/VBox/Runtime/common/asm/ASMGetSegAttr.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetSegAttr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetSegAttr.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetSegAttr(). ; @@ -69,3 +69,4 @@ RT_BEGINPROC ASMGetSegAttr ret ENDPROC ASMGetSegAttr +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetTR.asm b/src/VBox/Runtime/common/asm/ASMGetTR.asm index 8da48479edf9..d7505f547b68 100644 --- a/src/VBox/Runtime/common/asm/ASMGetTR.asm +++ b/src/VBox/Runtime/common/asm/ASMGetTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetTR(). ; @@ -51,3 +51,4 @@ RT_BEGINPROC ASMGetTR ret ENDPROC ASMGetTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMGetXcr0.asm b/src/VBox/Runtime/common/asm/ASMGetXcr0.asm index 436c0b667f26..eb64466836a9 100644 --- a/src/VBox/Runtime/common/asm/ASMGetXcr0.asm +++ b/src/VBox/Runtime/common/asm/ASMGetXcr0.asm @@ -1,4 +1,4 @@ -; $Id: ASMGetXcr0.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMGetXcr0.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMGetXcr0(). ; @@ -64,3 +64,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMGetXcr0 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm b/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm index 5a30cd6f1adc..6b91f49e854c 100644 --- a/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm +++ b/src/VBox/Runtime/common/asm/ASMMemFirstMismatchingU8.asm @@ -1,4 +1,4 @@ -; $Id: ASMMemFirstMismatchingU8.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMMemFirstMismatchingU8.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMMemFirstMismatchingU8(). ; @@ -204,6 +204,7 @@ SEH64_END_PROLOGUE leave %endif ret + int3 ; Return after byte scan mismatch. .return_xDI: @@ -215,6 +216,7 @@ SEH64_END_PROLOGUE leave %endif ret + int3 ; ; Multibyte mismatch. We rewind and do a byte scan of the remainder. @@ -225,6 +227,7 @@ SEH64_END_PROLOGUE lea xCX, [xCX * xCB + xCB] or ecx, edx jmp .byte_by_byte + int3 ; ; Unaligned pointer. If it's worth it, align the pointer, but if the @@ -277,6 +280,7 @@ SEH64_END_PROLOGUE scasb jne .return_xDI jmp .aligned_pv + int3 %else ; ARCH_BITS == 16 @@ -330,6 +334,7 @@ CPU 8086 pop di pop bp ret + int3 .word_mismatch: ; back up a word. @@ -353,3 +358,4 @@ CPU 8086 %endif ; ARCH_BITS == 16 ENDPROC ASMMemFirstMismatchingU8 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm b/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm index 28e753d7bd19..fe9fe2c9a489 100644 --- a/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm +++ b/src/VBox/Runtime/common/asm/ASMMultU32ByU32DivByU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMMultU32ByU32DivByU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMMultU32ByU32DivByU32.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Assembly Functions, ASMMultU32ByU32DivByU32. ; @@ -72,3 +72,5 @@ RT_BEGINPROC ASMMultU32ByU32DivByU32 ret ENDPROC ASMMultU32ByU32DivByU32 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm b/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm index 84597f58f092..62d04cb0613c 100644 --- a/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm +++ b/src/VBox/Runtime/common/asm/ASMMultU64ByU32DivByU32.asm @@ -1,4 +1,4 @@ -; $Id: ASMMultU64ByU32DivByU32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMMultU64ByU32DivByU32.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Assembly Functions, ASMMultU64ByU32DivByU32. ; @@ -139,3 +139,4 @@ RT_BEGINPROC ASMMultU64ByU32DivByU32 ret ENDPROC ASMMultU64ByU32DivByU32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMNopPause.asm b/src/VBox/Runtime/common/asm/ASMNopPause.asm index fdf22913617d..1405a1210348 100644 --- a/src/VBox/Runtime/common/asm/ASMNopPause.asm +++ b/src/VBox/Runtime/common/asm/ASMNopPause.asm @@ -1,4 +1,4 @@ -; $Id: ASMNopPause.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMNopPause.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMNopPause(). ; @@ -49,3 +49,4 @@ RT_BEGINPROC ASMNopPause ret ENDPROC ASMNopPause +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm b/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm index 5fe9c26464d1..8eec3f81f4f4 100644 --- a/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm +++ b/src/VBox/Runtime/common/asm/ASMRdMsrEx.asm @@ -1,4 +1,4 @@ -; $Id: ASMRdMsrEx.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: ASMRdMsrEx.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMRdMsrEx(). ; @@ -91,3 +91,4 @@ SEH64_END_PROLOGUE %endif ENDPROC ASMRdMsrEx +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm index ea0467c2d845..0106a141446d 100644 --- a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm +++ b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-cpuid.asm @@ -1,4 +1,4 @@ -; $Id: ASMSerializeInstruction-cpuid.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSerializeInstruction-cpuid.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSerializeInstruction() using cpuid. ; @@ -57,3 +57,4 @@ RT_BEGINPROC ASMSerializeInstructionCpuId ret ENDPROC ASMSerializeInstructionCpuId +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm index 89eb377e6765..cad369cd8461 100644 --- a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm +++ b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-iret.asm @@ -1,4 +1,4 @@ -; $Id: ASMSerializeInstruction-iret.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSerializeInstruction-iret.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSerializeInstruction() using iret. ; @@ -69,3 +69,4 @@ RT_BEGINPROC ASMSerializeInstructionIRet %endif ENDPROC ASMSerializeInstructionIRet +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm index 4adc645e378c..72e94facb6ee 100644 --- a/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm +++ b/src/VBox/Runtime/common/asm/ASMSerializeInstruction-rdtscp.asm @@ -1,4 +1,4 @@ -; $Id: ASMSerializeInstruction-rdtscp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSerializeInstruction-rdtscp.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSerializeInstruction() using rdtscp. ; @@ -54,3 +54,4 @@ RT_BEGINPROC ASMSerializeInstructionRdTscp ret ENDPROC ASMSerializeInstructionRdTscp +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetFSBase.asm b/src/VBox/Runtime/common/asm/ASMSetFSBase.asm index 0b4bd688e1a1..10992f316cb2 100644 --- a/src/VBox/Runtime/common/asm/ASMSetFSBase.asm +++ b/src/VBox/Runtime/common/asm/ASMSetFSBase.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetFSBase.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetFSBase.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetFSBase(). ; @@ -56,3 +56,4 @@ RT_BEGINPROC ASMSetFSBase ret ENDPROC ASMSetFSBase +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetFlags.asm b/src/VBox/Runtime/common/asm/ASMSetFlags.asm index c8fc77873fbb..4e107bd225ff 100644 --- a/src/VBox/Runtime/common/asm/ASMSetFlags.asm +++ b/src/VBox/Runtime/common/asm/ASMSetFlags.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetFlags.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetFlags.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetFlags(). ; @@ -67,3 +67,4 @@ RT_BEGINPROC ASMSetFlags ret ENDPROC ASMSetFlags +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetGDTR.asm b/src/VBox/Runtime/common/asm/ASMSetGDTR.asm index e4d8c061db30..3f0ab80bf467 100644 --- a/src/VBox/Runtime/common/asm/ASMSetGDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMSetGDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetGDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetGDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetGDTR(). ; @@ -60,3 +60,4 @@ RT_BEGINPROC ASMSetGDTR ret ENDPROC ASMSetGDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetGSBase.asm b/src/VBox/Runtime/common/asm/ASMSetGSBase.asm index 0f1209105e5e..e0b3fcffd18b 100644 --- a/src/VBox/Runtime/common/asm/ASMSetGSBase.asm +++ b/src/VBox/Runtime/common/asm/ASMSetGSBase.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetGSBase.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetGSBase.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetGSBase(). ; @@ -56,3 +56,4 @@ RT_BEGINPROC ASMSetGSBase ret ENDPROC ASMSetGSBase +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetIDTR.asm b/src/VBox/Runtime/common/asm/ASMSetIDTR.asm index fdf2290455f6..60c13a223d17 100644 --- a/src/VBox/Runtime/common/asm/ASMSetIDTR.asm +++ b/src/VBox/Runtime/common/asm/ASMSetIDTR.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetIDTR.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetIDTR.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetIDTR(). ; @@ -60,3 +60,4 @@ RT_BEGINPROC ASMSetIDTR ret ENDPROC ASMSetIDTR +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMSetXcr0.asm b/src/VBox/Runtime/common/asm/ASMSetXcr0.asm index 86c02ac7599c..72a28abd05f8 100644 --- a/src/VBox/Runtime/common/asm/ASMSetXcr0.asm +++ b/src/VBox/Runtime/common/asm/ASMSetXcr0.asm @@ -1,4 +1,4 @@ -; $Id: ASMSetXcr0.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMSetXcr0.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMSetXcr0(). ; @@ -78,3 +78,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMSetXcr0 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMWrMsr.asm b/src/VBox/Runtime/common/asm/ASMWrMsr.asm index e04849f59482..b49672696ff0 100644 --- a/src/VBox/Runtime/common/asm/ASMWrMsr.asm +++ b/src/VBox/Runtime/common/asm/ASMWrMsr.asm @@ -1,4 +1,4 @@ -; $Id: ASMWrMsr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMWrMsr.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMWrMsr(). ; @@ -97,3 +97,4 @@ RT_BEGINPROC ASMWrMsr %endif ENDPROC ASMWrMsr +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm b/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm index 039880958995..db0267af1a53 100644 --- a/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm +++ b/src/VBox/Runtime/common/asm/ASMWrMsrEx.asm @@ -1,4 +1,4 @@ -; $Id: ASMWrMsrEx.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: ASMWrMsrEx.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMWrMsrEx(). ; @@ -86,3 +86,4 @@ SEH64_END_PROLOGUE %endif ENDPROC ASMWrMsrEx +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMXRstor.asm b/src/VBox/Runtime/common/asm/ASMXRstor.asm index f3d482a8e2ba..9b5e6d209baa 100644 --- a/src/VBox/Runtime/common/asm/ASMXRstor.asm +++ b/src/VBox/Runtime/common/asm/ASMXRstor.asm @@ -1,4 +1,4 @@ -; $Id: ASMXRstor.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMXRstor.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMXRstor(). ; @@ -71,3 +71,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMXRstor +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/asm/ASMXSave.asm b/src/VBox/Runtime/common/asm/ASMXSave.asm index e3ce807f8e2e..5d2aecd2c643 100644 --- a/src/VBox/Runtime/common/asm/ASMXSave.asm +++ b/src/VBox/Runtime/common/asm/ASMXSave.asm @@ -1,4 +1,4 @@ -; $Id: ASMXSave.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ASMXSave.asm 115011 2026-08-12 23:37:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - ASMXSave(). ; @@ -71,3 +71,4 @@ SEH64_END_PROLOGUE ret ENDPROC ASMXSave +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm b/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm index 73b652d3c127..864e5bb7d845 100644 --- a/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm +++ b/src/VBox/Runtime/common/compiler/vcc/except-x86-vcc-asm.asm @@ -1,4 +1,4 @@ -; $Id: except-x86-vcc-asm.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: except-x86-vcc-asm.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - x86 Exception Handler Support Code. ; @@ -181,6 +181,7 @@ __NLG_Dispatch: pop ecx pop eax ret + int3 ;; ; NLG call + return2. @@ -195,6 +196,7 @@ GLOBALNAME_RAW __NLG_Call, function, hidden global __NLG_Return2 __NLG_Return2: ret + int3 %endif @@ -356,3 +358,4 @@ BEGINPROC rtVccEh4DoGlobalUnwind ret ENDPROC rtVccEh4DoGlobalUnwind +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: file contains indirect jumps diff --git a/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm index dc3d8a12f6e1..2f87d087c91b 100644 --- a/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm +++ b/src/VBox/Runtime/common/compiler/vcc/ftol2-vcc.asm @@ -1,4 +1,4 @@ -; $Id: ftol2-vcc.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: ftol2-vcc.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Floating Point to Integer related Visual C++ support routines. ; @@ -121,6 +121,7 @@ BEGINPROC_RAW __ftoui2 .return: leave ret + int3 ; ; Negative value. @@ -136,6 +137,7 @@ BEGINPROC_RAW __ftoui2 fisttp dword [esp] ; Raise exceptions as appropriate, pop ST0. xor eax, eax jmp .return + int3 ; Return MAX after maybe raising an exception. .unordered: @@ -183,6 +185,7 @@ BEGINPROC_RAW __ftoul2 .return: leave ret + int3 ; ; We've got a value that so large that fisttp can't handle it, however @@ -223,6 +226,7 @@ BEGINPROC_RAW __ftoul2 frndint ; Clear C1 & raising exceptions as appropriate. ffreep st0 jmp .return + int3 ; ; Negative value. @@ -239,6 +243,7 @@ BEGINPROC_RAW __ftoul2 xor edx, edx xor eax, eax jmp .return + int3 ; ; Unordered or a value in the (-1.0, 0) range. @@ -288,3 +293,4 @@ g_r32TwoToThePowerOf63: g_r32QNaN: dd 0xffc00000 ; Quite negative NaN (RTFLOAT32U_INIT_QNAN) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm index c93ffd37c27a..064545ea5c51 100644 --- a/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm +++ b/src/VBox/Runtime/common/compiler/vcc/guard-vcc.asm @@ -1,4 +1,4 @@ -; $Id: guard-vcc.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: guard-vcc.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Control Flow Guard related Visual C++ support routines. ; @@ -106,3 +106,4 @@ BEGINPROC __guard_xfg_dispatch_icall_nop ENDPROC __guard_xfg_dispatch_icall_nop %endif +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm index 3abcd81f573b..b6d32d52b66e 100644 --- a/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm +++ b/src/VBox/Runtime/common/compiler/vcc/stack-probe-vcc.asm @@ -1,4 +1,4 @@ -; $Id: stack-probe-vcc.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: stack-probe-vcc.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Stack related Visual C++ support routines. ; @@ -89,6 +89,7 @@ BEGINPROC_RAW __chkstk leave %ifndef RT_ARCH_X86 ret + int3 %else ; ; Do the stack space allocation and jump to the return location. @@ -96,6 +97,7 @@ BEGINPROC_RAW __chkstk sub esp, eax add esp, 4 jmp dword [esp + eax - 4] + int3 %endif ; @@ -141,6 +143,7 @@ BEGINPROC_RAW __alloca_probe_ %+ %1 pop ecx jmp __alloca_probe + int3 .bad_alloc_size: %ifdef RT_STRICT @@ -155,3 +158,4 @@ __alloc_probe_xxx 16 __alloc_probe_xxx 8 %endif ; RT_ARCH_X86 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm b/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm index 18b7377ec7b8..128a07826555 100644 --- a/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm +++ b/src/VBox/Runtime/common/compiler/vcc/stack-vcc.asm @@ -1,4 +1,4 @@ -; $Id: stack-vcc.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: stack-vcc.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Stack related Visual C++ support routines. ; @@ -266,6 +266,7 @@ ALIGNCODE(16) BEGINPROC _RTC_CheckEsp jne .unexpected_esp ret + int3 .unexpected_esp: push xBP @@ -637,3 +638,4 @@ BEGINPROC rtVccCaptureContext ret ENDPROC rtVccCaptureContext +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-alldiv.asm b/src/VBox/Runtime/common/compiler/vcc/x86-alldiv.asm index 1dddafb0b3bf..2a27903c110f 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-alldiv.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-alldiv.asm @@ -1,4 +1,4 @@ -; $Id: x86-alldiv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-alldiv.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - signed 64-bit division support, x86. ; @@ -74,6 +74,7 @@ BEGINPROC_RAW __alldiv ; Both positive, so same as unsigned division. jmp __aulldiv + int3 ; @@ -106,6 +107,7 @@ BEGINPROC_RAW __alldiv leave ret 10h + int3 .negative_dividend: push ebp @@ -137,6 +139,7 @@ BEGINPROC_RAW __alldiv leave ret 10h + int3 .negative_dividend_negative_divisor: ; negate both dividend (above) and divisor, do unsigned division(, and negate the remainder). @@ -154,3 +157,4 @@ BEGINPROC_RAW __alldiv ret 10h ENDPROC_RAW __alldiv +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-alldvrm.asm b/src/VBox/Runtime/common/compiler/vcc/x86-alldvrm.asm index 0536a9634f12..03fe829cde86 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-alldvrm.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-alldvrm.asm @@ -1,4 +1,4 @@ -; $Id: x86-alldvrm.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-alldvrm.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - signed 64-bit division support, x86. ; @@ -74,6 +74,7 @@ BEGINPROC_RAW __alldvrm ; Both positive, so same as unsigned division. jmp __aulldvrm + int3 ; @@ -106,6 +107,7 @@ BEGINPROC_RAW __alldvrm leave ret 10h + int3 .negative_dividend: push ebp @@ -142,6 +144,7 @@ BEGINPROC_RAW __alldvrm leave ret 10h + int3 .negative_dividend_negative_divisor: ; negate both dividend (above) and divisor, do unsigned division, and negate the remainder. @@ -158,3 +161,4 @@ BEGINPROC_RAW __alldvrm jmp .return_negated_remainder ENDPROC_RAW __alldvrm +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-allmul.asm b/src/VBox/Runtime/common/compiler/vcc/x86-allmul.asm index becfe7292535..c10942d82857 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-allmul.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-allmul.asm @@ -1,4 +1,4 @@ -; $Id: x86-allmul.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-allmul.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - 64-bit multiplication support, x86. ; @@ -64,6 +64,7 @@ BEGINPROC_RAW __allmul mul dword [esp + 0ch] ret 10h + int3 ; ; Complicated. @@ -88,3 +89,4 @@ BEGINPROC_RAW __allmul ret 10h ENDPROC_RAW __allmul +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-allrem.asm b/src/VBox/Runtime/common/compiler/vcc/x86-allrem.asm index 18e1ad23fe01..0d049b8b3785 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-allrem.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-allrem.asm @@ -1,4 +1,4 @@ -; $Id: x86-allrem.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-allrem.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - signed 64-bit division support, x86. ; @@ -74,6 +74,7 @@ BEGINPROC_RAW __allrem ; Both positive, so same as unsigned division. jmp __aullrem + int3 .negative_divisor_positive_dividend: @@ -84,6 +85,7 @@ BEGINPROC_RAW __allrem mov [esp + 0ch+4], ecx jmp __aullrem + int3 ; @@ -122,6 +124,7 @@ BEGINPROC_RAW __allrem leave ret 10h + int3 .negative_dividend_negative_divisor: ; negate both dividend (above) and divisor, do unsigned division, and negate the remainder. @@ -132,3 +135,4 @@ BEGINPROC_RAW __allrem jmp .negative_dividend_positive_divisor ENDPROC_RAW __allrem +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-allshl.asm b/src/VBox/Runtime/common/compiler/vcc/x86-allshl.asm index 995955f804b0..9a6c459585f4 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-allshl.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-allshl.asm @@ -1,4 +1,4 @@ -; $Id: x86-allshl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-allshl.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - 64-bit left shift support, x86. ; @@ -56,6 +56,7 @@ BEGINPROC_RAW __allshl shld edx, eax, cl shl eax, cl ret + int3 .shift_32_or_more: test cl, ~63 @@ -67,9 +68,11 @@ BEGINPROC_RAW __allshl .return_zero_eax: xor eax, eax ret + int3 .shift_64_or_more: xor edx, edx jmp .return_zero_eax ENDPROC_RAW __allshl +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-allshr.asm b/src/VBox/Runtime/common/compiler/vcc/x86-allshr.asm index 5a5874ff7d67..1ce3874d4ba3 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-allshr.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-allshr.asm @@ -1,4 +1,4 @@ -; $Id: x86-allshr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-allshr.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - signed 64-bit right shift support, x86. ; @@ -56,6 +56,7 @@ BEGINPROC_RAW __allshr shrd eax, edx, cl sar edx, cl ret + int3 .shift_32_or_more: mov eax, edx @@ -67,9 +68,11 @@ BEGINPROC_RAW __allshr and cl, 31 sar eax, cl ret + int3 .shift_64_or_more: mov eax, edx ret ENDPROC_RAW __allshr +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aulldiv.asm b/src/VBox/Runtime/common/compiler/vcc/x86-aulldiv.asm index 9b08a492c3a9..0e6e48ae4ffe 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aulldiv.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aulldiv.asm @@ -1,4 +1,4 @@ -; $Id: x86-aulldiv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aulldiv.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit division support, x86. ; @@ -44,3 +44,4 @@ rtVccUnsignedDivision __aulldiv, 1 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm-core.mac b/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm-core.mac index 4809e332962b..016835641f38 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm-core.mac +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm-core.mac @@ -1,4 +1,4 @@ -; $Id: x86-aulldvrm-core.mac 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aulldvrm-core.mac 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit division support, x86. ; @@ -110,6 +110,7 @@ BEGINPROC_RAW %1 %endif leave ret 10h + int3 ; ; The divisor is larger than 32 bits. @@ -140,6 +141,7 @@ BEGINPROC_RAW %1 shrd eax, edx, cl shr edx, cl jmp .shifted + int3 .shift_32: ; simplified version. mov edi, ebx @@ -185,6 +187,7 @@ BEGINPROC_RAW %1 %endif leave ret 10h + int3 .quotient_is_one_above_and_calc_remainder: %if %2 != 1 @@ -200,6 +203,7 @@ BEGINPROC_RAW %1 dec edi %endif jmp .done + int3 %else ; diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm.asm b/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm.asm index 261edfb1f8ce..a2708c3b5a15 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aulldvrm.asm @@ -1,4 +1,4 @@ -; $Id: x86-aulldvrm.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aulldvrm.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit division support, x86. ; @@ -44,3 +44,4 @@ rtVccUnsignedDivision __aulldvrm, 0 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aullrem.asm b/src/VBox/Runtime/common/compiler/vcc/x86-aullrem.asm index 84e2c20f274a..9e3a4172ce21 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aullrem.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aullrem.asm @@ -1,4 +1,4 @@ -; $Id: x86-aullrem.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aullrem.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit division support, x86. ; @@ -43,3 +43,4 @@ rtVccUnsignedDivision __aullrem, 2 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/compiler/vcc/x86-aullshr.asm b/src/VBox/Runtime/common/compiler/vcc/x86-aullshr.asm index 3f7ac32781b1..334fa47659e6 100644 --- a/src/VBox/Runtime/common/compiler/vcc/x86-aullshr.asm +++ b/src/VBox/Runtime/common/compiler/vcc/x86-aullshr.asm @@ -1,4 +1,4 @@ -; $Id: x86-aullshr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: x86-aullshr.asm 115009 2026-08-12 23:36:02Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ Compiler - unsigned 64-bit right shift support, x86. ; @@ -56,6 +56,7 @@ BEGINPROC_RAW __aullshr shrd eax, edx, cl shr edx, cl ret + int3 .shift_32_or_more: test cl, ~63 @@ -67,9 +68,11 @@ BEGINPROC_RAW __aullshr .return_zero_edx: xor edx, edx ret + int3 .shift_64_or_more: xor eax, eax jmp .return_zero_edx ENDPROC_RAW __aullshr +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/crypto/ssl-openssl.cpp b/src/VBox/Runtime/common/crypto/ssl-openssl.cpp index 67f41ebfa03d..286730c12cb8 100644 --- a/src/VBox/Runtime/common/crypto/ssl-openssl.cpp +++ b/src/VBox/Runtime/common/crypto/ssl-openssl.cpp @@ -422,7 +422,7 @@ RTDECL(int) RTCrSslSessionGetCertIssuerNameAsString(RTCRSSLSESSION hSslSession, X509 *pCert = SSL_get_certificate(pThis->pSsl); if (pCert) { - X509_NAME *pIssuer = X509_get_issuer_name(pCert); + const X509_NAME *pIssuer = X509_get_issuer_name(pCert); if (pIssuer) { char *pszSrc = X509_NAME_oneline(pIssuer, NULL, 0); diff --git a/src/VBox/Runtime/common/crypto/x509-create-sign.cpp b/src/VBox/Runtime/common/crypto/x509-create-sign.cpp index dad632a7ac1a..3705a326600e 100644 --- a/src/VBox/Runtime/common/crypto/x509-create-sign.cpp +++ b/src/VBox/Runtime/common/crypto/x509-create-sign.cpp @@ -1,4 +1,4 @@ -/* $Id: x509-create-sign.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: x509-create-sign.cpp 114868 2026-08-06 15:53:09Z klaus.espenlaub@oracle.com $ */ /** @file * IPRT - Crypto - X.509, Certificate Creation. */ @@ -61,7 +61,7 @@ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType, uint32_t cBits, uint32_t cSecsValidFor, - uint32_t fKeyUsage, uint64_t fExtKeyUsage, const char *pvSubject, + uint32_t fKeyUsage, uint64_t fExtKeyUsage, const char *pszSubject, const char *pszCertFile, const char *pszPrivateKeyFile, PRTERRINFO pErrInfo) { AssertReturn(cSecsValidFor <= (uint32_t)INT32_MAX, VERR_OUT_OF_RANGE); /* larger values are not portable (win) */ @@ -152,13 +152,12 @@ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType # endif /** @todo set other certificate attributes? */ - - /** @todo check what the subject name is... Offer way to specify it? */ - /* Make it self signed: */ - X509_NAME *pX509Name = X509_get_subject_name(pNewCert); - rcOssl = X509_NAME_add_entry_by_txt(pX509Name, "CN", MBSTRING_ASC, (unsigned char *) pvSubject, -1, -1, 0); + X509_NAME *pX509Name = X509_NAME_new(); + rcOssl = X509_NAME_add_entry_by_txt(pX509Name, "CN", MBSTRING_ASC, (const unsigned char *)pszSubject, -1, -1, 0); AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_NAME_add_entry_by_txt failed")); + rcOssl = X509_set_subject_name(pNewCert, pX509Name); + AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_set_subject_name failed")); rcOssl = X509_set_issuer_name(pNewCert, pX509Name); AssertStmt(rcOssl > 0, rc = RTErrInfoSet(pErrInfo, VERR_GENERAL_FAILURE, "X509_set_issuer_name failed")); @@ -211,6 +210,7 @@ RTDECL(int) RTCrX509Certificate_GenerateSelfSignedRsa(RTDIGESTTYPE enmDigestType rc = RTErrInfoSet(pErrInfo, VERR_CR_KEY_GEN_FAILED_RSA, "X509_sign failed"); } + X509_NAME_free(pX509Name); X509_free(pNewCert); } else diff --git a/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm b/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm index e6354558411f..5ef675fbd19f 100644 --- a/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm +++ b/src/VBox/Runtime/common/dbg/dbgstackdumpself-amd64-x86.asm @@ -1,4 +1,4 @@ -; $Id: dbgstackdumpself-amd64-x86.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: dbgstackdumpself-amd64-x86.asm 115012 2026-08-12 23:37:41Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTDbgStackDumpSelf assembly wrapper calling rtDbgStackDumpSelfWorker. ; @@ -155,3 +155,4 @@ SEH64_END_PROLOGUE ret ENDPROC RTDbgStackDumpSelf +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm b/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm index fffafc169b4b..965bbd0bc48a 100644 --- a/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm +++ b/src/VBox/Runtime/common/math/RTUInt128MulByU64.asm @@ -1,4 +1,4 @@ -; $Id: RTUInt128MulByU64.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTUInt128MulByU64.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTUInt128MulByU64 - AMD64 implementation. ; @@ -89,3 +89,4 @@ SEH64_END_PROLOGUE ret ENDPROC RTUInt128MulByU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm b/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm index 02286e5ac577..f899b0812e00 100644 --- a/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm +++ b/src/VBox/Runtime/common/math/RTUInt128MulByU64Ex.asm @@ -1,4 +1,4 @@ -; $Id: RTUInt128MulByU64Ex.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTUInt128MulByU64Ex.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTUInt128MulByU64 - AMD64 implementation. ; @@ -93,3 +93,4 @@ SEH64_END_PROLOGUE ret ENDPROC RTUInt128MulByU64Ex +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/atan.asm b/src/VBox/Runtime/common/math/atan.asm index fe19d497fa07..4ae655cdcbce 100644 --- a/src/VBox/Runtime/common/math/atan.asm +++ b/src/VBox/Runtime/common/math/atan.asm @@ -1,4 +1,4 @@ -; $Id: atan.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: atan.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT atan - AMD64 & X86. ; @@ -75,3 +75,4 @@ RT_NOCRT_BEGINPROC atan ret ENDPROC RT_NOCRT(atan) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/atan2.asm b/src/VBox/Runtime/common/math/atan2.asm index aa8a28d60744..0a867941e441 100644 --- a/src/VBox/Runtime/common/math/atan2.asm +++ b/src/VBox/Runtime/common/math/atan2.asm @@ -1,4 +1,4 @@ -; $Id: atan2.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: atan2.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT atan2 - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC atan2 ret ENDPROC RT_NOCRT(atan2) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/atan2f.asm b/src/VBox/Runtime/common/math/atan2f.asm index 71d934b59bef..20d29ab5da66 100644 --- a/src/VBox/Runtime/common/math/atan2f.asm +++ b/src/VBox/Runtime/common/math/atan2f.asm @@ -1,4 +1,4 @@ -; $Id: atan2f.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: atan2f.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT atan2f - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC atan2f ret ENDPROC RT_NOCRT(atan2f) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/atanf.asm b/src/VBox/Runtime/common/math/atanf.asm index 4817c734d697..9ca7a9232740 100644 --- a/src/VBox/Runtime/common/math/atanf.asm +++ b/src/VBox/Runtime/common/math/atanf.asm @@ -1,4 +1,4 @@ -; $Id: atanf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: atanf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT atanf - AMD64 & X86. ; @@ -75,3 +75,4 @@ RT_NOCRT_BEGINPROC atanf ret ENDPROC RT_NOCRT(atanf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/bignum-amd64-x86.asm b/src/VBox/Runtime/common/math/bignum-amd64-x86.asm index b6c347ddfd67..d0a873c27b4b 100644 --- a/src/VBox/Runtime/common/math/bignum-amd64-x86.asm +++ b/src/VBox/Runtime/common/math/bignum-amd64-x86.asm @@ -1,4 +1,4 @@ -; $Id: bignum-amd64-x86.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: bignum-amd64-x86.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Big Integer Numbers, AMD64 and X86 Assembly Workers ; @@ -137,12 +137,14 @@ SEH64_END_PROLOGUE jz .done sahf ; Restore CF. jmp .small_loop ; Skip CF=1 (clc). + int3 %else jnc .no_carry and cUsed, 7 ; Up to seven odd rounds. jz .done stc jmp .small_loop ; Skip CF=1 (clc). + int3 .no_carry: and cUsed, 7 ; Up to seven odd rounds. jz .done @@ -218,6 +220,7 @@ SEH64_END_PROLOGUE jz .done sahf ; Restore CF. jmp .small_loop ; Skip CF=1 (clc). + int3 .small_job: clc @@ -319,12 +322,14 @@ SEH64_END_PROLOGUE jz .done sahf ; Restore CF. jmp .small_loop ; Skip CF=1 (clc). + int3 %else jnc .no_carry and cUsed, 7 ; Up to seven odd rounds. jz .done stc jmp .small_loop ; Skip CF=1 (clc). + int3 .no_carry: and cUsed, 7 ; Up to seven odd rounds. jz .done @@ -388,6 +393,7 @@ SEH64_END_PROLOGUE jz .done sahf ; Restore CF. jmp .small_loop ; Skip CF=1 (clc). + int3 .small_job: clc @@ -460,6 +466,7 @@ SEH64_END_PROLOGUE test cUsed, cUsed jz .no_elements jmp .small_loop_init + int3 ; Big loop - 8 unrolled loop iterations. .big_loop_init: @@ -506,9 +513,11 @@ SEH64_END_PROLOGUE jz .restore_cf_and_return ; Jump if we're good and done. popf ; Restore CF. jmp .small_loop ; Deal with the odd rounds. + int3 .restore_cf_and_return: popf jmp .carry_to_eax + int3 ; Small loop - One round at the time. .small_loop_init: @@ -534,6 +543,7 @@ SEH64_END_PROLOGUE .return: leave ret + int3 .no_elements: mov eax, uCarry @@ -754,6 +764,7 @@ SEH64_END_PROLOGUE add pauMultiplier, RTBIGNUM_ELEMENT_SIZE add pauResult, RTBIGNUM_ELEMENT_SIZE jmp .multiplier_loop + int3 .done: @@ -889,3 +900,4 @@ SEH64_END_PROLOGUE ret ENDPROC rtBigNumKnuthD4_MulSub +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ceil.asm b/src/VBox/Runtime/common/math/ceil.asm index 84103c3e9d6b..bda72d5d4699 100644 --- a/src/VBox/Runtime/common/math/ceil.asm +++ b/src/VBox/Runtime/common/math/ceil.asm @@ -1,4 +1,4 @@ -; $Id: ceil.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ceil.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ceil - AMD64 & X86. ; @@ -77,3 +77,4 @@ RT_NOCRT_BEGINPROC ceil ret ENDPROC RT_NOCRT(ceil) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ceilf.asm b/src/VBox/Runtime/common/math/ceilf.asm index 78ca6a0b8058..595a5526393d 100644 --- a/src/VBox/Runtime/common/math/ceilf.asm +++ b/src/VBox/Runtime/common/math/ceilf.asm @@ -1,4 +1,4 @@ -; $Id: ceilf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ceilf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ceilf - AMD64 & X86. ; @@ -77,3 +77,4 @@ RT_NOCRT_BEGINPROC ceilf ret ENDPROC RT_NOCRT(ceilf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ceill.asm b/src/VBox/Runtime/common/math/ceill.asm index 0d0b3e41cae7..9f417b148243 100644 --- a/src/VBox/Runtime/common/math/ceill.asm +++ b/src/VBox/Runtime/common/math/ceill.asm @@ -1,4 +1,4 @@ -; $Id: ceill.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ceill.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ceill - AMD64 & X86. ; @@ -68,3 +68,4 @@ RT_NOCRT_BEGINPROC ceill ret ENDPROC RT_NOCRT(ceill) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/cos.asm b/src/VBox/Runtime/common/math/cos.asm index 10345aa32565..0fb31f08a5d2 100644 --- a/src/VBox/Runtime/common/math/cos.asm +++ b/src/VBox/Runtime/common/math/cos.asm @@ -1,4 +1,4 @@ -; $Id: cos.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: cos.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT cos - AMD64 & X86. ; @@ -106,6 +106,7 @@ RT_NOCRT_BEGINPROC cos .do_fcos: fcos jmp .return_val + int3 ; ; Finite number. @@ -143,6 +144,7 @@ RT_NOCRT_BEGINPROC cos fsubp st1, st0 ; st0=3pi/2 fchs ; st0=-3pi/2 jmp .make_sine_adjustment + int3 .adjust_negative_to_sine: ; Calc +pi/2. @@ -174,6 +176,7 @@ RT_NOCRT_BEGINPROC cos .return: leave ret + int3 ; ; cos(+/-0) = +1.0 @@ -184,6 +187,7 @@ RT_NOCRT_BEGINPROC cos ffreep st0 fld1 jmp .return_val + int3 ; ; Input is NaN, output it unmodified as far as we can (FLD changes SNaN @@ -194,6 +198,7 @@ RT_NOCRT_BEGINPROC cos ffreep st0 %endif jmp .return + int3 ; ; Local constants. @@ -211,3 +216,4 @@ ALIGNCODE(8) dq 2.0 ENDPROC RT_NOCRT(cos) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/cosf.asm b/src/VBox/Runtime/common/math/cosf.asm index a14dabd80238..55783a6e5eaa 100644 --- a/src/VBox/Runtime/common/math/cosf.asm +++ b/src/VBox/Runtime/common/math/cosf.asm @@ -1,4 +1,4 @@ -; $Id: cosf.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: cosf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT cosf - AMD64 & X86. ; @@ -106,6 +106,7 @@ RT_NOCRT_BEGINPROC cosf .do_fcos: fcos jmp .return_val + int3 ; ; Finite number. @@ -143,6 +144,7 @@ RT_NOCRT_BEGINPROC cosf fsubp st1, st0 ; st0=3pi/2 fchs ; st0=-3pi/2 jmp .make_sine_adjustment + int3 .adjust_negative_to_sine: ; Calc +pi/2. @@ -174,6 +176,7 @@ RT_NOCRT_BEGINPROC cosf .return: leave ret + int3 ; ; cosf(+/-0) = +1.0 @@ -184,6 +187,7 @@ RT_NOCRT_BEGINPROC cosf ffreep st0 fld1 jmp .return_val + int3 ; ; Input is NaN, output it unmodified as far as we can (FLD changes SNaN @@ -194,6 +198,7 @@ RT_NOCRT_BEGINPROC cosf ffreep st0 %endif jmp .return + int3 ; ; Local constants. @@ -211,3 +216,4 @@ ALIGNCODE(8) dq 2.0 ENDPROC RT_NOCRT(cosf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/cosl.asm b/src/VBox/Runtime/common/math/cosl.asm index ffeff343bc9d..41809b181ca3 100644 --- a/src/VBox/Runtime/common/math/cosl.asm +++ b/src/VBox/Runtime/common/math/cosl.asm @@ -1,4 +1,4 @@ -; $Id: cosl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: cosl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT cosl - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC cosl ret ENDPROC RT_NOCRT(cosl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/exp.asm b/src/VBox/Runtime/common/math/exp.asm index 91e32aef4ee2..cfb781006ad5 100644 --- a/src/VBox/Runtime/common/math/exp.asm +++ b/src/VBox/Runtime/common/math/exp.asm @@ -1,4 +1,4 @@ -; $Id: exp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: exp.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT exp - AMD64 & X86. ; @@ -83,6 +83,7 @@ RT_NOCRT_BEGINPROC exp cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .inf jmp .nan + int3 .finite: ; @@ -119,6 +120,7 @@ RT_NOCRT_BEGINPROC exp .return: leave ret + int3 ; ; +/-0.0: Return +1.0 @@ -127,6 +129,7 @@ RT_NOCRT_BEGINPROC exp ffreep st0 fld1 jmp .return_val + int3 ; ; -Inf: Return +0.0. @@ -138,6 +141,7 @@ RT_NOCRT_BEGINPROC exp ffreep st0 fldz jmp .return_val + int3 ; ; NaN: Return the input NaN value as is, if we can. @@ -149,3 +153,4 @@ RT_NOCRT_BEGINPROC exp jmp .return ENDPROC RT_NOCRT(exp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/exp2.asm b/src/VBox/Runtime/common/math/exp2.asm index ea609fd9cae6..e8e57b33169f 100644 --- a/src/VBox/Runtime/common/math/exp2.asm +++ b/src/VBox/Runtime/common/math/exp2.asm @@ -1,4 +1,4 @@ -; $Id: exp2.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: exp2.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT exp2 - AMD64 & X86. ; @@ -86,6 +86,7 @@ RT_NOCRT_BEGINPROC exp2 %endif fldz ; Signed, so return zero as that's a good approximation for 2**-Inf. jmp .return_val + int3 .input_ok: ; @@ -115,3 +116,4 @@ RT_NOCRT_BEGINPROC exp2 ret ENDPROC RT_NOCRT(exp2) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/exp2f.asm b/src/VBox/Runtime/common/math/exp2f.asm index 51357883047f..a075514fc0a7 100644 --- a/src/VBox/Runtime/common/math/exp2f.asm +++ b/src/VBox/Runtime/common/math/exp2f.asm @@ -1,4 +1,4 @@ -; $Id: exp2f.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: exp2f.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT exp2f - AMD64 & X86. ; @@ -86,6 +86,7 @@ RT_NOCRT_BEGINPROC exp2f %endif fldz ; Signed, so return zero as that's a good approximation for 2**-Inf. jmp .return_val + int3 .input_ok: ; @@ -115,3 +116,4 @@ RT_NOCRT_BEGINPROC exp2f ret ENDPROC RT_NOCRT(exp2f) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/expf.asm b/src/VBox/Runtime/common/math/expf.asm index 1362265a3ada..aeba05e14b07 100644 --- a/src/VBox/Runtime/common/math/expf.asm +++ b/src/VBox/Runtime/common/math/expf.asm @@ -1,4 +1,4 @@ -; $Id: expf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: expf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT expf - AMD64 & X86. ; @@ -83,6 +83,7 @@ RT_NOCRT_BEGINPROC expf cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .inf jmp .nan + int3 .finite: ; @@ -119,6 +120,7 @@ RT_NOCRT_BEGINPROC expf .return: leave ret + int3 ; ; +/-0.0: Return +1.0 @@ -127,6 +129,7 @@ RT_NOCRT_BEGINPROC expf ffreep st0 fld1 jmp .return_val + int3 ; ; -Inf: Return +0.0. @@ -138,6 +141,7 @@ RT_NOCRT_BEGINPROC expf ffreep st0 fldz jmp .return_val + int3 ; ; NaN: Return the input NaN value as is, if we can. @@ -149,3 +153,4 @@ RT_NOCRT_BEGINPROC expf jmp .return ENDPROC RT_NOCRT(expf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fabs.asm b/src/VBox/Runtime/common/math/fabs.asm index cefb18855179..ede73f2cda9a 100644 --- a/src/VBox/Runtime/common/math/fabs.asm +++ b/src/VBox/Runtime/common/math/fabs.asm @@ -1,4 +1,4 @@ -; $Id: fabs.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: fabs.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fabs - AMD64 & X86. ; @@ -71,3 +71,4 @@ g_r64ClearSignMask: dd 0ffffffffh dd 07fffffffh +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fabsf.asm b/src/VBox/Runtime/common/math/fabsf.asm index b6b21491a855..7eefdd308b17 100644 --- a/src/VBox/Runtime/common/math/fabsf.asm +++ b/src/VBox/Runtime/common/math/fabsf.asm @@ -1,4 +1,4 @@ -; $Id: fabsf.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: fabsf.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fabsf - AMD64 & X86. ; @@ -70,3 +70,4 @@ g_r32ClearSignMask: dd 07fffffffh dd 07fffffffh +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fabsl.asm b/src/VBox/Runtime/common/math/fabsl.asm index bd7864c0dfdd..c37d97e7493b 100644 --- a/src/VBox/Runtime/common/math/fabsl.asm +++ b/src/VBox/Runtime/common/math/fabsl.asm @@ -1,4 +1,4 @@ -; $Id: fabsl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fabsl.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fabsl - AMD64 & X86. ; @@ -59,3 +59,4 @@ RT_NOCRT_BEGINPROC fabsl ret ENDPROC RT_NOCRT(fabsl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feclearexcept.asm b/src/VBox/Runtime/common/math/feclearexcept.asm index 2e0f91a61728..9c56e104f6e0 100644 --- a/src/VBox/Runtime/common/math/feclearexcept.asm +++ b/src/VBox/Runtime/common/math/feclearexcept.asm @@ -1,4 +1,4 @@ -; $Id: feclearexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: feclearexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feclearexcept - AMD64 & X86. ; @@ -91,6 +91,7 @@ RT_NOCRT_BEGINPROC feclearexcept jne .partial_mask fnclex jmp .do_sse + int3 .partial_mask: fnstenv [xBP - 20h] @@ -119,3 +120,4 @@ RT_NOCRT_BEGINPROC feclearexcept ret ENDPROC RT_NOCRT(feclearexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fedisableexcept.asm b/src/VBox/Runtime/common/math/fedisableexcept.asm index 6f0f33a47d61..7ef4616a1821 100644 --- a/src/VBox/Runtime/common/math/fedisableexcept.asm +++ b/src/VBox/Runtime/common/math/fedisableexcept.asm @@ -1,4 +1,4 @@ -; $Id: fedisableexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fedisableexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fedisableexcept - AMD64 & X86. ; @@ -74,6 +74,7 @@ RT_NOCRT_BEGINPROC fedisableexcept jz .input_ok int3 jmp .return + int3 .input_ok: %endif @@ -115,3 +116,4 @@ RT_NOCRT_BEGINPROC fedisableexcept ret ENDPROC RT_NOCRT(fedisableexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feenableexcept.asm b/src/VBox/Runtime/common/math/feenableexcept.asm index a010d002f07a..822d7ab6a30a 100644 --- a/src/VBox/Runtime/common/math/feenableexcept.asm +++ b/src/VBox/Runtime/common/math/feenableexcept.asm @@ -1,4 +1,4 @@ -; $Id: feenableexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: feenableexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feenableexcept - AMD64 & X86. ; @@ -74,6 +74,7 @@ RT_NOCRT_BEGINPROC feenableexcept jz .input_ok int3 jmp .return + int3 .input_ok: %endif @@ -119,3 +120,4 @@ RT_NOCRT_BEGINPROC feenableexcept ret ENDPROC RT_NOCRT(feenableexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetenv.asm b/src/VBox/Runtime/common/math/fegetenv.asm index 955b58248c44..e5b8eb9587b9 100644 --- a/src/VBox/Runtime/common/math/fegetenv.asm +++ b/src/VBox/Runtime/common/math/fegetenv.asm @@ -1,4 +1,4 @@ -; $Id: fegetenv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetenv.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetenv - AMD64 & X86. ; @@ -88,3 +88,4 @@ RT_NOCRT_BEGINPROC fegetenv ret ENDPROC RT_NOCRT(fegetenv) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetexcept.asm b/src/VBox/Runtime/common/math/fegetexcept.asm index 861c8c66440e..ecd7d1dc816c 100644 --- a/src/VBox/Runtime/common/math/fegetexcept.asm +++ b/src/VBox/Runtime/common/math/fegetexcept.asm @@ -1,4 +1,4 @@ -; $Id: fegetexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetexcept - AMD64 & X86. ; @@ -80,3 +80,4 @@ RT_NOCRT_BEGINPROC fegetexcept ret ENDPROC RT_NOCRT(fegetexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetexceptflag.asm b/src/VBox/Runtime/common/math/fegetexceptflag.asm index cccb282f5a85..3ee2b002224f 100644 --- a/src/VBox/Runtime/common/math/fegetexceptflag.asm +++ b/src/VBox/Runtime/common/math/fegetexceptflag.asm @@ -1,4 +1,4 @@ -; $Id: fegetexceptflag.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetexceptflag.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetexceptflag - AMD64 & X86. ; @@ -80,6 +80,7 @@ RT_NOCRT_BEGINPROC fegetexceptflag jz .input_ok int3 jmp .return + int3 .input_ok: %endif %endif @@ -115,3 +116,4 @@ RT_NOCRT_BEGINPROC fegetexceptflag ret ENDPROC RT_NOCRT(fegetexceptflag) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetround.asm b/src/VBox/Runtime/common/math/fegetround.asm index 9dc088bd984c..3e9ff2bd23ac 100644 --- a/src/VBox/Runtime/common/math/fegetround.asm +++ b/src/VBox/Runtime/common/math/fegetround.asm @@ -1,4 +1,4 @@ -; $Id: fegetround.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetround.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetround - AMD64 & X86. ; @@ -77,3 +77,4 @@ RT_NOCRT_BEGINPROC fegetround ret ENDPROC RT_NOCRT(fegetround) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fegetx87precision.asm b/src/VBox/Runtime/common/math/fegetx87precision.asm index 4dd5800f1243..51f088256d7d 100644 --- a/src/VBox/Runtime/common/math/fegetx87precision.asm +++ b/src/VBox/Runtime/common/math/fegetx87precision.asm @@ -1,4 +1,4 @@ -; $Id: fegetx87precision.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fegetx87precision.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fegetx87precision - AMD64 & X86. ; @@ -68,3 +68,4 @@ RT_NOCRT_BEGINPROC fegetx87precision ret ENDPROC RT_NOCRT(fegetx87precision) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feholdexcept.asm b/src/VBox/Runtime/common/math/feholdexcept.asm index 3777dbcb7c07..0dea2cc28e27 100644 --- a/src/VBox/Runtime/common/math/feholdexcept.asm +++ b/src/VBox/Runtime/common/math/feholdexcept.asm @@ -1,4 +1,4 @@ -; $Id: feholdexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: feholdexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feholdexcept - AMD64 & X86. ; @@ -97,3 +97,4 @@ RT_NOCRT_BEGINPROC feholdexcept ret ENDPROC RT_NOCRT(feholdexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feraiseexcept.asm b/src/VBox/Runtime/common/math/feraiseexcept.asm index 8c3d1c14c3f3..aab030dffca1 100644 --- a/src/VBox/Runtime/common/math/feraiseexcept.asm +++ b/src/VBox/Runtime/common/math/feraiseexcept.asm @@ -1,4 +1,4 @@ -; $Id: feraiseexcept.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: feraiseexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feraiseexcept - AMD64 & X86. ; @@ -186,3 +186,4 @@ g_r32Tiny: dd 1.0e-37 %endif +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fesetenv.asm b/src/VBox/Runtime/common/math/fesetenv.asm index 22a1e6ab8f73..9f601b0d7028 100644 --- a/src/VBox/Runtime/common/math/fesetenv.asm +++ b/src/VBox/Runtime/common/math/fesetenv.asm @@ -1,4 +1,4 @@ -; $Id: fesetenv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fesetenv.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fesetenv - AMD64 & X86. ; @@ -89,6 +89,7 @@ RT_NOCRT_BEGINPROC fesetenv int3 %endif jmp .return + int3 ; ; Special x87 state. Clear all pending exceptions. @@ -122,6 +123,7 @@ RT_NOCRT_BEGINPROC fesetenv .x87_special_done: mov [xBP - 20h + X86FSTENV32P.FCW], ax jmp .x87_common + int3 ; ; Merge input and current. @@ -169,6 +171,7 @@ RT_NOCRT_BEGINPROC fesetenv jb .sse_special_env ldmxcsr [xCX + 28] jmp .return_okay + int3 .sse_special_env: stmxcsr [xBP - 10h] @@ -192,3 +195,4 @@ RT_NOCRT_BEGINPROC fesetenv ret ENDPROC RT_NOCRT(fesetenv) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fesetexceptflag.asm b/src/VBox/Runtime/common/math/fesetexceptflag.asm index ba75c4607007..10f8b0159553 100644 --- a/src/VBox/Runtime/common/math/fesetexceptflag.asm +++ b/src/VBox/Runtime/common/math/fesetexceptflag.asm @@ -1,4 +1,4 @@ -; $Id: fesetexceptflag.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fesetexceptflag.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fesetexceptflag - AMD64 & X86. ; @@ -125,3 +125,4 @@ RT_NOCRT_BEGINPROC fesetexceptflag ret ENDPROC RT_NOCRT(fesetexceptflag) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fesetround.asm b/src/VBox/Runtime/common/math/fesetround.asm index f61963b91da2..c8e7288546d2 100644 --- a/src/VBox/Runtime/common/math/fesetround.asm +++ b/src/VBox/Runtime/common/math/fesetround.asm @@ -1,4 +1,4 @@ -; $Id: fesetround.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fesetround.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fesetround - AMD64 & X86. ; @@ -105,3 +105,4 @@ RT_NOCRT_BEGINPROC fesetround ret ENDPROC RT_NOCRT(fesetround) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fesetx87precision.asm b/src/VBox/Runtime/common/math/fesetx87precision.asm index 4e9a4ef4c751..e76790f07d71 100644 --- a/src/VBox/Runtime/common/math/fesetx87precision.asm +++ b/src/VBox/Runtime/common/math/fesetx87precision.asm @@ -1,4 +1,4 @@ -; $Id: fesetx87precision.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fesetx87precision.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fesetx87precision - AMD64 & X86. ; @@ -86,3 +86,4 @@ RT_NOCRT_BEGINPROC fesetx87precision ret ENDPROC RT_NOCRT(fesetx87precision) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fetestexcept.asm b/src/VBox/Runtime/common/math/fetestexcept.asm index 4fa53bce7aba..8527b4747796 100644 --- a/src/VBox/Runtime/common/math/fetestexcept.asm +++ b/src/VBox/Runtime/common/math/fetestexcept.asm @@ -1,4 +1,4 @@ -; $Id: fetestexcept.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fetestexcept.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fetestexcept - AMD64 & X86. ; @@ -105,3 +105,4 @@ RT_NOCRT_BEGINPROC fetestexcept ret ENDPROC RT_NOCRT(fetestexcept) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/feupdateenv.asm b/src/VBox/Runtime/common/math/feupdateenv.asm index 4ef0533e1a2f..2adb4e2e7b51 100644 --- a/src/VBox/Runtime/common/math/feupdateenv.asm +++ b/src/VBox/Runtime/common/math/feupdateenv.asm @@ -1,4 +1,4 @@ -; $Id: feupdateenv.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: feupdateenv.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT feupdateenv - AMD64 & X86. ; @@ -126,3 +126,4 @@ RT_NOCRT_BEGINPROC feupdateenv ret ENDPROC RT_NOCRT(feupdateenv) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/floor.asm b/src/VBox/Runtime/common/math/floor.asm index b71f5711f9a0..d5ede0e490ec 100644 --- a/src/VBox/Runtime/common/math/floor.asm +++ b/src/VBox/Runtime/common/math/floor.asm @@ -1,4 +1,4 @@ -; $Id: floor.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: floor.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT floor - AMD64 & X86. ; @@ -76,3 +76,4 @@ RT_NOCRT_BEGINPROC floor ret ENDPROC RT_NOCRT(floor) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/floorf.asm b/src/VBox/Runtime/common/math/floorf.asm index f749483a8cc2..342cd8ea60cd 100644 --- a/src/VBox/Runtime/common/math/floorf.asm +++ b/src/VBox/Runtime/common/math/floorf.asm @@ -1,4 +1,4 @@ -; $Id: floorf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: floorf.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT floorf - AMD64 & X86. ; @@ -76,3 +76,4 @@ RT_NOCRT_BEGINPROC floorf ret ENDPROC RT_NOCRT(floorf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/floorl.asm b/src/VBox/Runtime/common/math/floorl.asm index 61033eb97032..7445500d4dfb 100644 --- a/src/VBox/Runtime/common/math/floorl.asm +++ b/src/VBox/Runtime/common/math/floorl.asm @@ -1,4 +1,4 @@ -; $Id: floorl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: floorl.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT floorl - AMD64 & X86. ; @@ -67,3 +67,4 @@ RT_NOCRT_BEGINPROC floorl ret ENDPROC RT_NOCRT(floorl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fma-asm.asm b/src/VBox/Runtime/common/math/fma-asm.asm index 60d0a223f1d3..733ed8d80fa0 100644 --- a/src/VBox/Runtime/common/math/fma-asm.asm +++ b/src/VBox/Runtime/common/math/fma-asm.asm @@ -1,4 +1,4 @@ -; $Id: fma-asm.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fma-asm.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fma alternatives - AMD64 & X86. ; @@ -102,3 +102,4 @@ BEGINPROC rtNoCrtMathFma4 ret ENDPROC rtNoCrtMathFma4 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/fmaf-asm.asm b/src/VBox/Runtime/common/math/fmaf-asm.asm index 06271d0d28eb..62a9f5863c3c 100644 --- a/src/VBox/Runtime/common/math/fmaf-asm.asm +++ b/src/VBox/Runtime/common/math/fmaf-asm.asm @@ -1,4 +1,4 @@ -; $Id: fmaf-asm.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: fmaf-asm.asm 115007 2026-08-12 23:35:12Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT fmaf alternatives - AMD64 & X86. ; @@ -102,3 +102,4 @@ BEGINPROC rtNoCrtMathFma4f ret ENDPROC rtNoCrtMathFma4f +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ldexp.asm b/src/VBox/Runtime/common/math/ldexp.asm index 49e2ed63dabc..ba1e7bbef953 100644 --- a/src/VBox/Runtime/common/math/ldexp.asm +++ b/src/VBox/Runtime/common/math/ldexp.asm @@ -1,4 +1,4 @@ -; $Id: ldexp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ldexp.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ldexp - AMD64 & X86. ; @@ -87,3 +87,4 @@ RT_NOCRT_BEGINPROC ldexp ret ENDPROC RT_NOCRT(ldexp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ldexpf.asm b/src/VBox/Runtime/common/math/ldexpf.asm index d7482eb3614a..4fc2a94ef764 100644 --- a/src/VBox/Runtime/common/math/ldexpf.asm +++ b/src/VBox/Runtime/common/math/ldexpf.asm @@ -1,4 +1,4 @@ -; $Id: ldexpf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ldexpf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ldexpf - AMD64 & X86. ; @@ -87,3 +87,4 @@ RT_NOCRT_BEGINPROC ldexpf ret ENDPROC RT_NOCRT(ldexpf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/ldexpl.asm b/src/VBox/Runtime/common/math/ldexpl.asm index 75f983a818cc..1a9a7c18d2e0 100644 --- a/src/VBox/Runtime/common/math/ldexpl.asm +++ b/src/VBox/Runtime/common/math/ldexpl.asm @@ -1,4 +1,4 @@ -; $Id: ldexpl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: ldexpl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT ldexpl - AMD64 & X86. ; @@ -78,3 +78,4 @@ RT_NOCRT_BEGINPROC ldexpl ret ENDPROC RT_NOCRT(ldexpl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/llrint.asm b/src/VBox/Runtime/common/math/llrint.asm index 2eae844df4f1..14dccc59294c 100644 --- a/src/VBox/Runtime/common/math/llrint.asm +++ b/src/VBox/Runtime/common/math/llrint.asm @@ -1,4 +1,4 @@ -; $Id: llrint.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: llrint.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT llrint - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC llrint ret ENDPROC RT_NOCRT(llrint) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/llrintf.asm b/src/VBox/Runtime/common/math/llrintf.asm index 2673b8c9da4c..f1f4ea13d3db 100644 --- a/src/VBox/Runtime/common/math/llrintf.asm +++ b/src/VBox/Runtime/common/math/llrintf.asm @@ -1,4 +1,4 @@ -; $Id: llrintf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: llrintf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT llrintf - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC llrintf ret ENDPROC RT_NOCRT(llrintf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/llrintl.asm b/src/VBox/Runtime/common/math/llrintl.asm index 1104ccb78b09..3b6dcd559b81 100644 --- a/src/VBox/Runtime/common/math/llrintl.asm +++ b/src/VBox/Runtime/common/math/llrintl.asm @@ -1,4 +1,4 @@ -; $Id: llrintl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: llrintl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT llrintl - AMD64 & X86. ; @@ -68,3 +68,4 @@ RT_NOCRT_BEGINPROC llrintl ret ENDPROC RT_NOCRT(llrintl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/log.asm b/src/VBox/Runtime/common/math/log.asm index 50f7d873a971..d06f93efdc7d 100644 --- a/src/VBox/Runtime/common/math/log.asm +++ b/src/VBox/Runtime/common/math/log.asm @@ -1,4 +1,4 @@ -; $Id: log.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: log.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT log - AMD64 & X86. ; @@ -77,6 +77,7 @@ RT_NOCRT_BEGINPROC log fstp st0 ; st1=log(2) st0=lrd fyl2x ; log(lrd) jmp .done + int3 .use_st1: fstp st1 ; st1=log(2) st0=lrd-1.0 @@ -89,9 +90,11 @@ RT_NOCRT_BEGINPROC log %endif leave ret + int3 ALIGNCODE(8) .one: dq 1.0 .limit: dq 0.29 ENDPROC RT_NOCRT(log) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/log2.asm b/src/VBox/Runtime/common/math/log2.asm index 42c845480ccc..2e9d5724b379 100644 --- a/src/VBox/Runtime/common/math/log2.asm +++ b/src/VBox/Runtime/common/math/log2.asm @@ -1,4 +1,4 @@ -; $Id: log2.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: log2.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT log2 - AMD64 & X86. ; @@ -83,6 +83,7 @@ RT_NOCRT_BEGINPROC log2 cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .inf jmp .nan + int3 .finite: ; Negative number? @@ -118,6 +119,7 @@ RT_NOCRT_BEGINPROC log2 fsub st0, st1 ; -> st0=input-1; st1=1.0 fyl2xp1 ; -> st0=1.0*log2(st0+1.0) jmp .return_val + int3 .cannot_use_fyl2xp1: fyl2x ; -> st0=1.0*log2(st0) @@ -133,6 +135,7 @@ RT_NOCRT_BEGINPROC log2 .return: leave ret + int3 ; @@ -142,6 +145,7 @@ RT_NOCRT_BEGINPROC log2 ffreep st0 fldz jmp .return_val + int3 ; ; Negative numbers: Return NaN and raise invalid operation. @@ -167,6 +171,7 @@ RT_NOCRT_BEGINPROC log2 fld qword [RT_WRT_RIP(.s_r64NaN)] %endif jmp .return + int3 ; ; +/-0.0: Return inf and raise divide by zero error. @@ -193,6 +198,7 @@ RT_NOCRT_BEGINPROC log2 fld qword [RT_WRT_RIP(.s_r64MinusInf)] %endif jmp .return + int3 ; ; -Inf: Same as other negative numbers @@ -210,6 +216,7 @@ RT_NOCRT_BEGINPROC log2 ffreep st0 %endif jmp .return + int3 ALIGNCODE(8) ;; The fyl2xp1 instruction only works between +/-1(1-sqrt(0.5)). @@ -227,3 +234,4 @@ ALIGNCODE(8) dq RTFLOAT64U_QNAN_MINUS ENDPROC RT_NOCRT(log2) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/log2f.asm b/src/VBox/Runtime/common/math/log2f.asm index 55088e1b7a55..c255705facb2 100644 --- a/src/VBox/Runtime/common/math/log2f.asm +++ b/src/VBox/Runtime/common/math/log2f.asm @@ -1,4 +1,4 @@ -; $Id: log2f.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: log2f.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT log2f - AMD64 & X86. ; @@ -83,6 +83,7 @@ RT_NOCRT_BEGINPROC log2f cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .inf jmp .nan + int3 .finite: ; Negative number? @@ -118,6 +119,7 @@ RT_NOCRT_BEGINPROC log2f fsub st0, st1 ; -> st0=input-1; st1=1.0 fyl2xp1 ; -> st0=1.0*log2(st0+1.0) jmp .return_val + int3 .cannot_use_fyl2xp1: fyl2x ; -> st0=1.0*log2(st0) @@ -133,6 +135,7 @@ RT_NOCRT_BEGINPROC log2f .return: leave ret + int3 ; @@ -142,6 +145,7 @@ RT_NOCRT_BEGINPROC log2f ffreep st0 fldz jmp .return_val + int3 ; ; Negative numbers: Return NaN and raise invalid operation. @@ -167,6 +171,7 @@ RT_NOCRT_BEGINPROC log2f fld dword [RT_WRT_RIP(.s_r32NaN)] %endif jmp .return + int3 ; ; +/-0.0: Return inf and raise divide by zero error. @@ -193,6 +198,7 @@ RT_NOCRT_BEGINPROC log2f fld dword [RT_WRT_RIP(.s_r32MinusInf)] %endif jmp .return + int3 ; ; -Inf: Same as other negative numbers @@ -210,6 +216,7 @@ RT_NOCRT_BEGINPROC log2f ffreep st0 %endif jmp .return + int3 ALIGNCODE(8) ;; The fyl2xp1 instruction only works between +/-1(1-sqrt(0.5)). @@ -225,3 +232,4 @@ ALIGNCODE(8) dd RTFLOAT32U_QNAN_MINUS ENDPROC RT_NOCRT(log2f) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/logf.asm b/src/VBox/Runtime/common/math/logf.asm index d185f27a289d..5f47ff7c6f39 100644 --- a/src/VBox/Runtime/common/math/logf.asm +++ b/src/VBox/Runtime/common/math/logf.asm @@ -1,4 +1,4 @@ -; $Id: logf.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: logf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT logf - AMD64 & X86. ; @@ -77,6 +77,7 @@ RT_NOCRT_BEGINPROC logf fstp st0 ; st1=log(2) st0=lrd fyl2x ; log(lrd) jmp .done + int3 .use_st1: fstp st1 ; st1=log(2) st0=lrd-1.0 @@ -95,3 +96,4 @@ ALIGNCODE(8) .limit: dq 0.29 ENDPROC RT_NOCRT(logf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/logl.asm b/src/VBox/Runtime/common/math/logl.asm index 2cd7f8ccb6b3..58a11a7179d1 100644 --- a/src/VBox/Runtime/common/math/logl.asm +++ b/src/VBox/Runtime/common/math/logl.asm @@ -1,4 +1,4 @@ -; $Id: logl.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: logl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT logl - AMD64 & X86. ; @@ -68,6 +68,7 @@ RT_NOCRT_BEGINPROC logl fstp st0 ; st1=log(2) st0=lrd fyl2x ; log(lrd) jmp .done + int3 .use_st1: fstp st1 ; st1=log(2) st0=lrd-1.0 @@ -82,3 +83,4 @@ ALIGNCODE(8) .limit: dq 0.29 ENDPROC RT_NOCRT(logl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/lrint.asm b/src/VBox/Runtime/common/math/lrint.asm index 5e4fbd1dfcc3..2bdd262889ba 100644 --- a/src/VBox/Runtime/common/math/lrint.asm +++ b/src/VBox/Runtime/common/math/lrint.asm @@ -1,4 +1,4 @@ -; $Id: lrint.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: lrint.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT lrint - AMD64 & X86. ; @@ -73,3 +73,4 @@ RT_NOCRT_BEGINPROC lrint ret ENDPROC RT_NOCRT(lrint) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/lrintf.asm b/src/VBox/Runtime/common/math/lrintf.asm index 0bf2bafd7be6..085c634d40cc 100644 --- a/src/VBox/Runtime/common/math/lrintf.asm +++ b/src/VBox/Runtime/common/math/lrintf.asm @@ -1,4 +1,4 @@ -; $Id: lrintf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: lrintf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT lrintf - AMD64 & X86. ; @@ -72,3 +72,4 @@ RT_NOCRT_BEGINPROC lrintf ret ENDPROC RT_NOCRT(lrintf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/lrintl.asm b/src/VBox/Runtime/common/math/lrintl.asm index 06a3bf055218..abfd5bd4232e 100644 --- a/src/VBox/Runtime/common/math/lrintl.asm +++ b/src/VBox/Runtime/common/math/lrintl.asm @@ -1,4 +1,4 @@ -; $Id: lrintl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: lrintl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT lrintl - AMD64 & X86. ; @@ -75,3 +75,4 @@ RT_NOCRT_BEGINPROC lrintl ret ENDPROC RT_NOCRT(lrintl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/pow.asm b/src/VBox/Runtime/common/math/pow.asm index aaca6627e63d..632c30124b00 100644 --- a/src/VBox/Runtime/common/math/pow.asm +++ b/src/VBox/Runtime/common/math/pow.asm @@ -1,4 +1,4 @@ -; $Id: pow.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: pow.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT pow - AMD64 & X86. ; @@ -103,6 +103,7 @@ RT_NOCRT_BEGINPROC pow pop xBX leave ret + int3 ; ; But sometimes, like if we have NaN or other special inputs, we should @@ -119,9 +120,11 @@ RT_NOCRT_BEGINPROC pow %endif .return_base: jmp .return + int3 .return_exp: movsd xmm0, xmm1 jmp .return ENDPROC RT_NOCRT(pow) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/powcore.asm b/src/VBox/Runtime/common/math/powcore.asm index 591df78155b1..699f68cba14e 100644 --- a/src/VBox/Runtime/common/math/powcore.asm +++ b/src/VBox/Runtime/common/math/powcore.asm @@ -1,4 +1,4 @@ -; $Id: powcore.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: powcore.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT common pow code - AMD64 & X86. ; @@ -102,6 +102,7 @@ BEGINPROC rtNoCrtMathPowCore cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .exp_inf jmp .exp_nan + int3 .exp_finite: ; @@ -118,6 +119,7 @@ BEGINPROC rtNoCrtMathPowCore cmp ax, X86_FSW_C0 | X86_FSW_C2 ; Infinity. je .base_inf jmp .base_nan + int3 .base_finite: ; @@ -209,10 +211,12 @@ BEGINPROC rtNoCrtMathPowCore ; Calculate the factor for the next bit. fmul st0, st0 jmp .integer_exp_loop + int3 .integer_exp_return: ffreep st0 ; drop the factor -> st0=result; no st1. jmp .return_val + int3 ; @@ -258,6 +262,7 @@ BEGINPROC rtNoCrtMathPowCore fsub st0, st1 ; -> st0=base-1; st1=1.0; st2=exponent fyl2xp1 ; -> st0=1.0*log2(base-1.0+1.0); st1=exponent jmp .done_log2 + int3 .cannot_use_fyl2xp1: fyl2x ; -> st0=1.0*log2(base); st1=exponent @@ -295,6 +300,7 @@ BEGINPROC rtNoCrtMathPowCore .return: leave ret + int3 ; @@ -323,6 +329,7 @@ BEGINPROC rtNoCrtMathPowCore .base_negative_non_integer_exp: CALL_feraiseexcept_WITH X86_FSW_IE jmp .return_nan + int3 ; ; 7. Exponent = +/-0.0, any base value including NaN: return +1.0 @@ -332,6 +339,7 @@ BEGINPROC rtNoCrtMathPowCore .return_plus_one: fld1 jmp .return_pop_pop_val + int3 ; ; 6. Exponent = whatever and base = 1: Return 1.0 @@ -363,11 +371,13 @@ BEGINPROC rtNoCrtMathPowCore test cx, X86_FSW_C1 ; cx=faxm(exponent); C1=sign jz .return_plus_inf ; Matches rule 14 (exponent is +Inf). jmp .return_plus_zero ; Matches rule 12 (exponent is -Inf). + int3 .exp_inf_base_smaller_than_one: test cx, X86_FSW_C1 ; cx=faxm(exponent); C1=sign jnz .return_plus_inf ; Matches rule 11 (exponent is -Inf). jmp .return_plus_zero ; Matches rule 13 (exponent is +Inf). + int3 ; ; 6. Exponent = whatever and base = 1: Return 1.0 @@ -381,6 +391,7 @@ BEGINPROC rtNoCrtMathPowCore fcomip st0, st2 jne .return_exp_nan jmp .return_plus_one + int3 ; ; 4a. base == +/-0.0 and exp < 0 and exp is odd integer: Return +/-Inf, raise div/0. @@ -407,14 +418,17 @@ BEGINPROC rtNoCrtMathPowCore .raise_de_and_return_minus_inf: CALL_feraiseexcept_WITH X86_FSW_DE jmp .return_minus_inf + int3 .raise_de_and_return_plus_inf: CALL_feraiseexcept_WITH X86_FSW_DE jmp .return_plus_inf + int3 ; Matching 4b. .base_zero_minus_exp_not_odd_int: CALL_feraiseexcept_WITH X86_FSW_DE jmp .return_plus_inf + int3 .base_zero_plus_exp: call .is_exp_odd_integer @@ -423,6 +437,7 @@ BEGINPROC rtNoCrtMathPowCore .return_plus_zero: ; Matching 9 fldz jmp .return_pop_pop_val + int3 ; ; 15. base == -Inf and exp < 0 and exp is odd integer: Return -0 @@ -449,6 +464,7 @@ BEGINPROC rtNoCrtMathPowCore fldz fchs jmp .return_pop_pop_val + int3 .base_inf_plus_exp: test dx, X86_FSW_C1 @@ -458,6 +474,7 @@ BEGINPROC rtNoCrtMathPowCore or eax, eax jnz .return_minus_inf ; Matches 17 (exp is odd and > 0, base == +Inf) jmp .return_plus_inf ; Matches 18 (exp not odd and > 0, base == +Inf) + int3 ; ; Return the exponent NaN (or whatever) value. @@ -466,6 +483,7 @@ BEGINPROC rtNoCrtMathPowCore fld st0 mov eax, 2 ; return param 2 jmp .return_pop_pop_val_with_eax + int3 ; ; Return the base NaN (or whatever) value. @@ -476,6 +494,7 @@ BEGINPROC rtNoCrtMathPowCore fld st1 mov eax, 1 ; return param 1 jmp .return_pop_pop_val_with_eax + int3 ; ; Pops the two values off the FPU stack and returns NaN. @@ -483,6 +502,7 @@ BEGINPROC rtNoCrtMathPowCore .return_nan: fld qword [RT_WRT_RIP(.s_r64QNan)] jmp .return_pop_pop_val + int3 ; ; Pops the two values off the FPU stack and returns +Inf. @@ -490,6 +510,7 @@ BEGINPROC rtNoCrtMathPowCore .return_plus_inf: fld qword [RT_WRT_RIP(.s_r64PlusInf)] jmp .return_pop_pop_val + int3 ; ; Pops the two values off the FPU stack and returns -Inf. @@ -497,6 +518,7 @@ BEGINPROC rtNoCrtMathPowCore .return_minus_inf: fld qword [RT_WRT_RIP(.s_r64MinusInf)] jmp .return_pop_pop_val + int3 ; ; Return st0, remove st1 and st2. @@ -507,6 +529,7 @@ BEGINPROC rtNoCrtMathPowCore fstp st2 ffreep st0 jmp .return + int3 ALIGNCODE(8) @@ -601,6 +624,7 @@ ALIGNCODE(8) jnz .is_exp_odd_integer__high_dword_is_zero lea eax, [edx + 20h] jmp .is_exp_odd_integer__first_bit_in_eax + int3 .is_exp_odd_integer__high_dword_is_zero: bsr eax, eax .is_exp_odd_integer__first_bit_in_eax: @@ -622,6 +646,7 @@ ALIGNCODE(8) ; Return. .is_exp_odd_integer__return_true: jmp .is_exp_odd_integer__return + int3 .is_exp_odd_integer__return_false: xor eax, eax .is_exp_odd_integer__return: @@ -631,3 +656,4 @@ ALIGNCODE(8) ENDPROC rtNoCrtMathPowCore +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/powf.asm b/src/VBox/Runtime/common/math/powf.asm index 38cb05015683..cf97e565e667 100644 --- a/src/VBox/Runtime/common/math/powf.asm +++ b/src/VBox/Runtime/common/math/powf.asm @@ -1,4 +1,4 @@ -; $Id: powf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: powf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT powf - AMD64 & X86. ; @@ -103,6 +103,7 @@ RT_NOCRT_BEGINPROC powf pop xBX leave ret + int3 ; ; But sometimes, like if we have NaN or other special inputs, we should @@ -119,9 +120,11 @@ RT_NOCRT_BEGINPROC powf %endif .return_base: jmp .return + int3 .return_exp: movss xmm0, xmm1 jmp .return ENDPROC RT_NOCRT(powf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/remainder.asm b/src/VBox/Runtime/common/math/remainder.asm index deb7b4e50aed..9500ab4d2b18 100644 --- a/src/VBox/Runtime/common/math/remainder.asm +++ b/src/VBox/Runtime/common/math/remainder.asm @@ -1,4 +1,4 @@ -; $Id: remainder.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: remainder.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT remainder - AMD64 & X86. ; @@ -102,3 +102,4 @@ RT_NOCRT_BEGINPROC remainder ret ENDPROC RT_NOCRT(remainder) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/remainderf.asm b/src/VBox/Runtime/common/math/remainderf.asm index 7c8991443c6c..519c657408c1 100644 --- a/src/VBox/Runtime/common/math/remainderf.asm +++ b/src/VBox/Runtime/common/math/remainderf.asm @@ -1,4 +1,4 @@ -; $Id: remainderf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: remainderf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT remainderf - AMD64 & X86. ; @@ -102,3 +102,4 @@ RT_NOCRT_BEGINPROC remainderf ret ENDPROC RT_NOCRT(remainderf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/remainderl.asm b/src/VBox/Runtime/common/math/remainderl.asm index 296e02f36164..0d5ed57b2f0e 100644 --- a/src/VBox/Runtime/common/math/remainderl.asm +++ b/src/VBox/Runtime/common/math/remainderl.asm @@ -1,4 +1,4 @@ -; $Id: remainderl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: remainderl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT remainderl - AMD64 & X86. ; @@ -86,3 +86,4 @@ RT_NOCRT_BEGINPROC remainderl ret ENDPROC RT_NOCRT(remainderl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/rint.asm b/src/VBox/Runtime/common/math/rint.asm index ecf90da3ec85..9b0728dcb4f1 100644 --- a/src/VBox/Runtime/common/math/rint.asm +++ b/src/VBox/Runtime/common/math/rint.asm @@ -1,4 +1,4 @@ -; $Id: rint.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: rint.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT rint - AMD64 & X86. ; @@ -81,6 +81,7 @@ RT_NOCRT_BEGINPROC rint ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN. %endif jmp .return + int3 .input_ok: ; @@ -97,3 +98,4 @@ RT_NOCRT_BEGINPROC rint ret ENDPROC RT_NOCRT(rint) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/rintf.asm b/src/VBox/Runtime/common/math/rintf.asm index 1fd1b7f8cac4..63e9eb3ac555 100644 --- a/src/VBox/Runtime/common/math/rintf.asm +++ b/src/VBox/Runtime/common/math/rintf.asm @@ -1,4 +1,4 @@ -; $Id: rintf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: rintf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT rintf - AMD64 & X86. ; @@ -81,6 +81,7 @@ RT_NOCRT_BEGINPROC rintf ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN. %endif jmp .return + int3 .input_ok: ; @@ -97,3 +98,4 @@ RT_NOCRT_BEGINPROC rintf ret ENDPROC RT_NOCRT(rintf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm b/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm index 7dc7710413c5..0fa044cd3b0b 100644 --- a/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm +++ b/src/VBox/Runtime/common/math/rtNoCrtHasSse.asm @@ -1,4 +1,4 @@ -; $Id: rtNoCrtHasSse.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: rtNoCrtHasSse.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT rtNoCrtHasSse - X86. ; @@ -55,6 +55,7 @@ BEGINPROC rtNoCrtHasSse test al, 0x80 jnz .detect_sse ret + int3 .detect_sse: push ebx @@ -76,3 +77,4 @@ BEGINPROC rtNoCrtHasSse ret ENDPROC rtNoCrtHasSse +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sin.asm b/src/VBox/Runtime/common/math/sin.asm index 2505f6a6ca7a..c0ef4187617a 100644 --- a/src/VBox/Runtime/common/math/sin.asm +++ b/src/VBox/Runtime/common/math/sin.asm @@ -1,4 +1,4 @@ -; $Id: sin.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: sin.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sin - AMD64 & X86. ; @@ -98,6 +98,7 @@ RT_NOCRT_BEGINPROC sin .do_sin: fsin jmp .return_val + int3 ; ; Finite number. @@ -142,6 +143,7 @@ RT_NOCRT_BEGINPROC sin .return: leave ret + int3 ; ; As explained already, we can return tiny numbers directly too as the @@ -167,6 +169,7 @@ RT_NOCRT_BEGINPROC sin ffreep st0 %endif jmp .return + int3 ALIGNCODE(8) ; Ca. 2**-17, absolute value. Inputs closer to zero than this can be @@ -183,3 +186,4 @@ ALIGNCODE(8) ENDPROC RT_NOCRT(sin) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sincore.asm b/src/VBox/Runtime/common/math/sincore.asm index 4848cf69f2b9..58914c828eb8 100644 --- a/src/VBox/Runtime/common/math/sincore.asm +++ b/src/VBox/Runtime/common/math/sincore.asm @@ -1,4 +1,4 @@ -; $Id: sincore.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: sincore.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT common sin & cos - AMD64 & X86. ; @@ -143,6 +143,7 @@ BEGINPROC rtNoCrtMathSinCore ; Ok, calculate sine. fsin jmp .return + int3 ; ; The value is in the range ]pi/2,pi[ @@ -176,6 +177,7 @@ BEGINPROC rtNoCrtMathSinCore fsin fchs jmp .return + int3 ; ; input in the range ]pi,2pi[ @@ -218,6 +220,7 @@ BEGINPROC rtNoCrtMathSinCore fsin fchs jmp .return + int3 ; ; The value is in the last pi/2 of the range: ]3pi/2,2pi[ @@ -249,6 +252,7 @@ BEGINPROC rtNoCrtMathSinCore fsubp st1, st0 fsin jmp .return + int3 ; ; sin(0) = 0 @@ -263,6 +267,7 @@ BEGINPROC rtNoCrtMathSinCore ffreep st0 fldz jmp .return + int3 ; ; sin(pi/2) = 1 @@ -272,6 +277,7 @@ BEGINPROC rtNoCrtMathSinCore ffreep st0 fld1 jmp .return + int3 ; ; sin(3*pi/2) = -1 @@ -282,6 +288,7 @@ BEGINPROC rtNoCrtMathSinCore fld1 fchs jmp .return + int3 ; ; Return. @@ -289,6 +296,7 @@ BEGINPROC rtNoCrtMathSinCore .return: leave ret + int3 ; ; Reduce st0 by reminder division by PI*2. The result should be positive here. @@ -325,6 +333,7 @@ BEGINPROC rtNoCrtMathSinCore .reduced_to_positive: fstp st1 ; Get rid of the 2pi value. jmp .in_range + int3 ALIGNCODE(8) .s_r64Max: @@ -350,3 +359,4 @@ ALIGNCODE(8) dq (-52 + 1023) << 52 ; long double / 80-bit / extended precision input ENDPROC rtNoCrtMathSinCore +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sinf.asm b/src/VBox/Runtime/common/math/sinf.asm index 897218a46db8..bf00fbd54130 100644 --- a/src/VBox/Runtime/common/math/sinf.asm +++ b/src/VBox/Runtime/common/math/sinf.asm @@ -1,4 +1,4 @@ -; $Id: sinf.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: sinf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sinf - AMD64 & X86. ; @@ -98,6 +98,7 @@ RT_NOCRT_BEGINPROC sinf .do_sin: fsin jmp .return_val + int3 ; ; Finite number. @@ -142,6 +143,7 @@ RT_NOCRT_BEGINPROC sinf .return: leave ret + int3 ; ; As explained already, we can return tiny numbers directly too as the @@ -167,6 +169,7 @@ RT_NOCRT_BEGINPROC sinf ffreep st0 %endif jmp .return + int3 ALIGNCODE(8) ; Ca. 2**-26, absolute value. Inputs closer to zero than this can be @@ -183,3 +186,4 @@ ALIGNCODE(8) ENDPROC RT_NOCRT(sinf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sinl.asm b/src/VBox/Runtime/common/math/sinl.asm index 2a958f741ed2..493d3ad84c81 100644 --- a/src/VBox/Runtime/common/math/sinl.asm +++ b/src/VBox/Runtime/common/math/sinl.asm @@ -1,4 +1,4 @@ -; $Id: sinl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: sinl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sinl - AMD64 & X86. ; @@ -69,3 +69,4 @@ RT_NOCRT_BEGINPROC sinl ret ENDPROC RT_NOCRT(sinl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sqrt.asm b/src/VBox/Runtime/common/math/sqrt.asm index 464e7de47899..fc22f4f247de 100644 --- a/src/VBox/Runtime/common/math/sqrt.asm +++ b/src/VBox/Runtime/common/math/sqrt.asm @@ -1,4 +1,4 @@ -; $Id: sqrt.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: sqrt.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sqrt - AMD64 & X86. ; @@ -63,3 +63,4 @@ RT_NOCRT_BEGINPROC sqrt ret ENDPROC RT_NOCRT(sqrt) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/sqrtf.asm b/src/VBox/Runtime/common/math/sqrtf.asm index aaabdfbdcf71..e4e6e3f4daae 100644 --- a/src/VBox/Runtime/common/math/sqrtf.asm +++ b/src/VBox/Runtime/common/math/sqrtf.asm @@ -1,4 +1,4 @@ -; $Id: sqrtf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: sqrtf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT sqrtf - AMD64 & X86. ; @@ -63,3 +63,4 @@ RT_NOCRT_BEGINPROC sqrtf ret ENDPROC RT_NOCRT(sqrtf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/tan.asm b/src/VBox/Runtime/common/math/tan.asm index 220f83ce8fbb..f8a21e023cc0 100644 --- a/src/VBox/Runtime/common/math/tan.asm +++ b/src/VBox/Runtime/common/math/tan.asm @@ -1,4 +1,4 @@ -; $Id: tan.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: tan.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT tan - AMD64 & X86. ; @@ -117,3 +117,4 @@ RT_NOCRT_BEGINPROC tan ret ENDPROC RT_NOCRT(tan) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/tanf.asm b/src/VBox/Runtime/common/math/tanf.asm index 4b75e9732c96..d4f0b299f001 100644 --- a/src/VBox/Runtime/common/math/tanf.asm +++ b/src/VBox/Runtime/common/math/tanf.asm @@ -1,4 +1,4 @@ -; $Id: tanf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: tanf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT tanf - AMD64 & X86. ; @@ -117,3 +117,4 @@ RT_NOCRT_BEGINPROC tanf ret ENDPROC RT_NOCRT(tanf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/tanl.asm b/src/VBox/Runtime/common/math/tanl.asm index 3ac0066d0c1e..f34fa9a70de1 100644 --- a/src/VBox/Runtime/common/math/tanl.asm +++ b/src/VBox/Runtime/common/math/tanl.asm @@ -1,4 +1,4 @@ -; $Id: tanl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: tanl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT tanl - AMD64 & X86. ; @@ -70,3 +70,4 @@ RT_NOCRT_BEGINPROC tanl ret ENDPROC RT_NOCRT(tanl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/trunc.asm b/src/VBox/Runtime/common/math/trunc.asm index 4b00e2a6dc17..7169d55c01db 100644 --- a/src/VBox/Runtime/common/math/trunc.asm +++ b/src/VBox/Runtime/common/math/trunc.asm @@ -1,4 +1,4 @@ -; $Id: trunc.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: trunc.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT trunc - AMD64 & X86. ; @@ -76,6 +76,7 @@ RT_NOCRT_BEGINPROC trunc ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN. %endif jmp .return_val + int3 .input_ok: ; @@ -106,3 +107,4 @@ RT_NOCRT_BEGINPROC trunc ret ENDPROC RT_NOCRT(trunc) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/truncf.asm b/src/VBox/Runtime/common/math/truncf.asm index 4f4b6403bc81..31f6770e7994 100644 --- a/src/VBox/Runtime/common/math/truncf.asm +++ b/src/VBox/Runtime/common/math/truncf.asm @@ -1,4 +1,4 @@ -; $Id: truncf.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: truncf.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT truncf - AMD64 & X86. ; @@ -76,6 +76,7 @@ RT_NOCRT_BEGINPROC truncf ffreep st0 ; return the xmm0 register value unchanged, as FLD changes SNaN to QNaN. %endif jmp .return_val + int3 .input_ok: ; @@ -106,3 +107,4 @@ RT_NOCRT_BEGINPROC truncf ret ENDPROC RT_NOCRT(truncf) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/math/truncl.asm b/src/VBox/Runtime/common/math/truncl.asm index 61aefdc8489e..15241beb6fbb 100644 --- a/src/VBox/Runtime/common/math/truncl.asm +++ b/src/VBox/Runtime/common/math/truncl.asm @@ -1,4 +1,4 @@ -; $Id: truncl.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: truncl.asm 115008 2026-08-12 23:35:24Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT truncl - AMD64 & X86. ; @@ -74,3 +74,4 @@ RT_NOCRT_BEGINPROC truncl ret ENDPROC RT_NOCRT(truncl) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/misc/setjmp.asm b/src/VBox/Runtime/common/misc/setjmp.asm index 7f21ba488355..fce7b9bc1a91 100644 --- a/src/VBox/Runtime/common/misc/setjmp.asm +++ b/src/VBox/Runtime/common/misc/setjmp.asm @@ -1,4 +1,4 @@ -; $Id: setjmp.asm 113142 2026-02-24 12:23:15Z knut.osmundsen@oracle.com $ +; $Id: setjmp.asm 115024 2026-08-13 02:02:28Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT setjmp & longjmp - AMD64 & X86. ; @@ -288,6 +288,7 @@ RT_NOCRT_BEGINPROC setjmp .strict_zero_args: %endif ret + int3 .have_xcpt_reg_rec: ; Get the parameter count. @@ -307,6 +308,7 @@ RT_NOCRT_BEGINPROC setjmp dec eax jnz .copy_unwind_data ret + int3 .set_try_level_from_xcpt_reg_rec_reload_ptr_first: mov edx, [ecx + RTJMPBUF.pXcptRegRec] @@ -314,6 +316,7 @@ RT_NOCRT_BEGINPROC setjmp mov edx, [edx + 12] ; Something following the EXCEPTION_REGISTRATION_RECORD... mov [ecx + RTJMPBUF.uTryLevel], edx ret + int3 ; Copy unwind data. .copy_unwind_data: @@ -475,7 +478,11 @@ RT_NOCRT_BEGINPROC longjmp cmp qword [rcx + RTJMPBUF.uFrame], byte 0 jnz .nt_restore - db 0xfe, 0x48, 0x0f, 0x1e, 0xca ; rdsspq rdx - a NOP unless CET is supported & enabled. + %ifdef __NASM__ + rdsspq rdx + %else + db 0xf3, 0x48, 0x0f, 0x1e, 0xca ; rdsspq rdx - a NOP unless CET is supported & enabled. + %endif test rdx, rdx jz .regular_restore %ifdef RT_STRICT @@ -519,6 +526,7 @@ RT_NOCRT_BEGINPROC longjmp int3 %endif jmp .nt_init_xcpt_rec + int3 %else ; RT_ARCH_X86 push 0 ; zero ('return value') lea eax, [ADDR_EXPR_XCPT_REC] @@ -651,3 +659,4 @@ ENDPROC longjmp %endif ; !RT_WITHOUT_NOCRT_WRAPPERS %endif ; RT_OS_WINDOWS +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: there are indirect calls here! diff --git a/src/VBox/Runtime/common/misc/zero.asm b/src/VBox/Runtime/common/misc/zero.asm index d36cc0d34f13..c8ac861b9e62 100644 --- a/src/VBox/Runtime/common/misc/zero.asm +++ b/src/VBox/Runtime/common/misc/zero.asm @@ -1,4 +1,4 @@ -; $Id: zero.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: zero.asm 115004 2026-08-12 23:33:28Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Zero Memory. ; @@ -73,3 +73,4 @@ EXPORTEDNAME_EX g_abRTZero64K, object, _64K times 0x10000/(16*4) dd 0,0,0,0,0,0,0,0, 0,0,0,0,0,0,0,0 %endif +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/RTStrEnd.asm b/src/VBox/Runtime/common/string/RTStrEnd.asm index 5645d00b7de4..a80a3dd55a06 100644 --- a/src/VBox/Runtime/common/string/RTStrEnd.asm +++ b/src/VBox/Runtime/common/string/RTStrEnd.asm @@ -1,4 +1,4 @@ -; $Id: RTStrEnd.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTStrEnd.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTStrEnd - AMD64 & X86. ; @@ -91,6 +91,7 @@ RT_BEGINPROC RTStrEnd %endif %endif ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -112,3 +113,4 @@ RT_BEGINPROC RTStrEnd ret ENDPROC RTStrEnd +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/RTStrMemFind32.asm b/src/VBox/Runtime/common/string/RTStrMemFind32.asm index 131d6aaf25f0..c2ead4339249 100644 --- a/src/VBox/Runtime/common/string/RTStrMemFind32.asm +++ b/src/VBox/Runtime/common/string/RTStrMemFind32.asm @@ -1,4 +1,4 @@ -; $Id: RTStrMemFind32.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: RTStrMemFind32.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - RTStrMemFind32 - AMD64 & X86. ; @@ -92,6 +92,7 @@ RT_BEGINPROC RTStrMemFind32 %endif %endif ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -109,3 +110,4 @@ RT_BEGINPROC RTStrMemFind32 ret ENDPROC RTStrMemFind32 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/bzero.asm b/src/VBox/Runtime/common/string/bzero.asm index 355c3c4fa5d4..67cba1e6b706 100644 --- a/src/VBox/Runtime/common/string/bzero.asm +++ b/src/VBox/Runtime/common/string/bzero.asm @@ -1,4 +1,4 @@ -; $Id: bzero.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: bzero.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT bzero - AMD64 & X86. ; @@ -143,3 +143,4 @@ GLOBALNAME __bzero ret ENDPROC RT_NOCRT(bzero) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memchr.asm b/src/VBox/Runtime/common/string/memchr.asm index a33d6b8530d2..b5bcc6e7f34b 100644 --- a/src/VBox/Runtime/common/string/memchr.asm +++ b/src/VBox/Runtime/common/string/memchr.asm @@ -1,4 +1,4 @@ -; $Id: memchr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memchr.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memchr - AMD64 & X86. ; @@ -95,6 +95,7 @@ RT_NOCRT_BEGINPROC memchr %endif %endif ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -116,3 +117,4 @@ RT_NOCRT_BEGINPROC memchr ret ENDPROC RT_NOCRT(memchr) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memcmp.asm b/src/VBox/Runtime/common/string/memcmp.asm index 167ee8f86b50..013fbf667955 100644 --- a/src/VBox/Runtime/common/string/memcmp.asm +++ b/src/VBox/Runtime/common/string/memcmp.asm @@ -1,4 +1,4 @@ -; $Id: memcmp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memcmp.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memcmp - AMD64 & X86. ; @@ -121,6 +121,7 @@ RT_NOCRT_BEGINPROC memcmp pop edi %endif ret + int3 ; ; Mismatches. @@ -136,6 +137,7 @@ RT_NOCRT_BEGINPROC memcmp movzx ecx, byte [xSI-1] sub eax, ecx jmp .done + int3 %endif .not_equal_dword: @@ -145,12 +147,14 @@ RT_NOCRT_BEGINPROC memcmp repe cmpsb %ifdef RT_ARCH_AMD64 jmp .not_equal_byte + int3 %else .not_equal_byte: mov al, [xDI-1] movzx ecx, byte [xSI-1] sub eax, ecx jmp .done + int3 %endif .not_equal_word: @@ -161,3 +165,4 @@ RT_NOCRT_BEGINPROC memcmp jmp .not_equal_byte ENDPROC RT_NOCRT(memcmp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memcpy.asm b/src/VBox/Runtime/common/string/memcpy.asm index 388c4080dff0..9302189bf3df 100644 --- a/src/VBox/Runtime/common/string/memcpy.asm +++ b/src/VBox/Runtime/common/string/memcpy.asm @@ -1,4 +1,4 @@ -; $Id: memcpy.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memcpy.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memcpy - AMD64 & X86. ; @@ -128,3 +128,4 @@ RT_NOCRT_BEGINPROC memcpy ret ENDPROC RT_NOCRT(memcpy) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memmove.asm b/src/VBox/Runtime/common/string/memmove.asm index 4b5276f4ddaa..a3afb1526b97 100644 --- a/src/VBox/Runtime/common/string/memmove.asm +++ b/src/VBox/Runtime/common/string/memmove.asm @@ -1,4 +1,4 @@ -; $Id: memmove.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memmove.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memmove - AMD64 & X86. ; @@ -96,6 +96,7 @@ RT_NOCRT_BEGINPROC memmove cld rep movsb jmp .epilog + int3 %else ; disabled - it seems to work, but play safe for now. ;sub xAX, xSI @@ -152,6 +153,7 @@ RT_NOCRT_BEGINPROC memmove pop edi %endif ret + int3 ; ; Slow/simple backward copy. @@ -167,3 +169,4 @@ ALIGNCODE(16) jmp .epilog ENDPROC RT_NOCRT(memmove) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/mempcpy.asm b/src/VBox/Runtime/common/string/mempcpy.asm index 3c029ca93540..73fe7429c75f 100644 --- a/src/VBox/Runtime/common/string/mempcpy.asm +++ b/src/VBox/Runtime/common/string/mempcpy.asm @@ -1,4 +1,4 @@ -; $Id: mempcpy.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: mempcpy.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT mempcpy - AMD64 & X86. ; @@ -116,3 +116,4 @@ RT_NOCRT_BEGINPROC mempcpy ret ENDPROC RT_NOCRT(mempcpy) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memrchr.asm b/src/VBox/Runtime/common/string/memrchr.asm index 22523cab5847..81a7b0a82588 100644 --- a/src/VBox/Runtime/common/string/memrchr.asm +++ b/src/VBox/Runtime/common/string/memrchr.asm @@ -1,4 +1,4 @@ -; $Id: memrchr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memrchr.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memrchr - AMD64 & X86. ; @@ -98,6 +98,7 @@ RT_NOCRT_BEGINPROC memrchr %endif cld ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -120,3 +121,4 @@ RT_NOCRT_BEGINPROC memrchr ret ENDPROC RT_NOCRT(memrchr) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/memset.asm b/src/VBox/Runtime/common/string/memset.asm index a622b50e42a2..f5e3eb256ee1 100644 --- a/src/VBox/Runtime/common/string/memset.asm +++ b/src/VBox/Runtime/common/string/memset.asm @@ -1,4 +1,4 @@ -; $Id: memset.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: memset.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT memset - AMD64 & X86. ; @@ -145,3 +145,4 @@ RT_NOCRT_BEGINPROC memset ret ENDPROC RT_NOCRT(memset) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strchr.asm b/src/VBox/Runtime/common/string/strchr.asm index 790c72ca72d5..d9ce339d879a 100644 --- a/src/VBox/Runtime/common/string/strchr.asm +++ b/src/VBox/Runtime/common/string/strchr.asm @@ -1,4 +1,4 @@ -; $Id: strchr.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strchr.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strchr - AMD64 & X86. ; @@ -98,6 +98,7 @@ RT_NOCRT_BEGINPROC strchr test al, al jz .not_found jmp .next + int3 .found: lea xAX, [xSI - 1] @@ -112,6 +113,7 @@ RT_NOCRT_BEGINPROC strchr %endif %endif ret + int3 .not_found: %ifdef ASM_CALL64_MSC @@ -126,6 +128,7 @@ RT_NOCRT_BEGINPROC strchr %endif xor eax, eax ret + int3 ; ; Special case: strchr(str, '\0'); @@ -166,3 +169,4 @@ align 16 ret ENDPROC RT_NOCRT(strchr) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strcmp.asm b/src/VBox/Runtime/common/string/strcmp.asm index 5ac9c516f7f3..9a82d4b1bed6 100644 --- a/src/VBox/Runtime/common/string/strcmp.asm +++ b/src/VBox/Runtime/common/string/strcmp.asm @@ -1,4 +1,4 @@ -; $Id: strcmp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strcmp.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strcmp - AMD64 & X86. ; @@ -99,6 +99,7 @@ RT_NOCRT_BEGINPROC strcmp add psz1, 4 add psz2, 4 jmp .next + int3 .equal: %ifdef RT_ARCH_X86 @@ -109,6 +110,7 @@ RT_NOCRT_BEGINPROC strcmp %endif xor eax, eax ret + int3 .not_equal: movzx ecx, ah @@ -123,3 +125,4 @@ RT_NOCRT_BEGINPROC strcmp ret ENDPROC RT_NOCRT(strcmp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strcpy.asm b/src/VBox/Runtime/common/string/strcpy.asm index 1a2abd50c98d..5b370e0ce8e9 100644 --- a/src/VBox/Runtime/common/string/strcpy.asm +++ b/src/VBox/Runtime/common/string/strcpy.asm @@ -1,4 +1,4 @@ -; $Id: strcpy.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strcpy.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strcpy - AMD64 & X86. ; @@ -91,6 +91,7 @@ RT_NOCRT_BEGINPROC strcpy add pszDst, 4 add pszSrc, 4 jmp .next + int3 .done: %ifdef RT_ARCH_AMD64 @@ -101,3 +102,4 @@ RT_NOCRT_BEGINPROC strcpy ret ENDPROC RT_NOCRT(strcpy) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strlen.asm b/src/VBox/Runtime/common/string/strlen.asm index 0b6f33fc49e0..cc77cb51adc4 100644 --- a/src/VBox/Runtime/common/string/strlen.asm +++ b/src/VBox/Runtime/common/string/strlen.asm @@ -1,4 +1,4 @@ -; $Id: strlen.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strlen.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strlen - AMD64 & X86. ; @@ -73,3 +73,4 @@ RT_NOCRT_BEGINPROC strlen ret ENDPROC RT_NOCRT(strlen) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strncmp.asm b/src/VBox/Runtime/common/string/strncmp.asm index d2c24bfd215e..57f7bc513bc9 100644 --- a/src/VBox/Runtime/common/string/strncmp.asm +++ b/src/VBox/Runtime/common/string/strncmp.asm @@ -1,4 +1,4 @@ -; $Id: strncmp.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strncmp.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strncmp - AMD64 & X86. ; @@ -117,6 +117,7 @@ RT_NOCRT_BEGINPROC strncmp add psz1, 4 add psz2, 4 jmp .next + int3 .equal: xor eax, eax @@ -126,6 +127,7 @@ RT_NOCRT_BEGINPROC strncmp %endif %endif ret + int3 .not_equal: movzx ecx, ah @@ -139,3 +141,4 @@ RT_NOCRT_BEGINPROC strncmp ret ENDPROC RT_NOCRT(strncmp) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/strncpy.asm b/src/VBox/Runtime/common/string/strncpy.asm index 3d77725e18fd..a91e345f4d94 100644 --- a/src/VBox/Runtime/common/string/strncpy.asm +++ b/src/VBox/Runtime/common/string/strncpy.asm @@ -1,4 +1,4 @@ -; $Id: strncpy.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: strncpy.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strncpy - AMD64 & X86. ; @@ -104,6 +104,7 @@ RT_NOCRT_BEGINPROC strncpy add pszSrc, 4 sub cbMax, 4 jmp .next + int3 ; ; Char by char. @@ -124,6 +125,7 @@ RT_NOCRT_BEGINPROC strncpy inc pszSrc inc pszDst jmp .simple_next + int3 .done: %ifdef RT_ARCH_AMD64 @@ -137,3 +139,4 @@ RT_NOCRT_BEGINPROC strncpy ret ENDPROC RT_NOCRT(strncpy) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/string/wcslen.asm b/src/VBox/Runtime/common/string/wcslen.asm index 4b81a8eb6a9b..9302862abc12 100644 --- a/src/VBox/Runtime/common/string/wcslen.asm +++ b/src/VBox/Runtime/common/string/wcslen.asm @@ -1,4 +1,4 @@ -; $Id: wcslen.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: wcslen.asm 115006 2026-08-12 23:34:22Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - No-CRT strlen - AMD64 & X86. ; @@ -80,3 +80,4 @@ RT_NOCRT_BEGINPROC wcslen ret ENDPROC RT_NOCRT(wcslen) +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/time/timesupA.asm b/src/VBox/Runtime/common/time/timesupA.asm index ccb9953a696a..05f630cea893 100644 --- a/src/VBox/Runtime/common/time/timesupA.asm +++ b/src/VBox/Runtime/common/time/timesupA.asm @@ -1,4 +1,4 @@ -; $Id: timesupA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: timesupA.asm 115004 2026-08-12 23:33:28Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Time using SUPLib, the Assembly Implementation. ; @@ -34,9 +34,8 @@ ; SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 ; -%ifndef IN_GUEST - %include "iprt/asmdefs.mac" +%ifndef IN_GUEST ; rest of the file %include "VBox/sup.mac" ; @@ -159,3 +158,5 @@ BEGINCODE %endif ; !IN_GUEST + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/common/time/timesupA.mac b/src/VBox/Runtime/common/time/timesupA.mac index 2807bb207da3..5b282336dfee 100644 --- a/src/VBox/Runtime/common/time/timesupA.mac +++ b/src/VBox/Runtime/common/time/timesupA.mac @@ -1,4 +1,4 @@ -; $Id: timesupA.mac 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: timesupA.mac 115004 2026-08-12 23:33:28Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Time using SUPLib, the Assembly Code Template. ; @@ -308,6 +308,7 @@ BEGINPROC rtTimeNanoTSInternalAsm inc dword [esi + RTTIMENANOTSDATA.cExpired] mov eax, u32UpdateIntervalTSC jmp .ContinueCalcs + int3 ;; @@ -352,6 +353,7 @@ BEGINPROC rtTimeNanoTSInternalAsm add eax, 1 adc edx, 0 jmp .Update + int3 .DeltaPrevNotInRecentPast: ; else if (!u64PrevNanoTS) /* We're resuming (see TMVirtualResume). */ @@ -361,6 +363,7 @@ BEGINPROC rtTimeNanoTSInternalAsm cmp dword u64PrevNanoTS_Hi, 0 jne .DeltaPrevNotZero jmp .Update + int3 .DeltaPrevNotZero: ; else @@ -389,6 +392,7 @@ BEGINPROC rtTimeNanoTSInternalAsm mov eax, u64RetNanoTS mov edx, u64RetNanoTS_Hi jmp .Update + int3 ;; @@ -422,6 +426,7 @@ BEGINPROC rtTimeNanoTSInternalAsm dec edi jnz .UpdateLoop jmp .Updated + int3 ;; @@ -433,6 +438,7 @@ BEGINPROC rtTimeNanoTSInternalAsm call [eax + RTTIMENANOTSDATA.pfnRediscover] add esp, 4h jmp .Done + int3 %ifdef WITH_TSC_DELTA @@ -443,6 +449,7 @@ BEGINPROC rtTimeNanoTSInternalAsm cmp dword [edi + SUPGIPCPU.i64TSCDelta + 4], 0x7fffffff jne .TscDeltaValid jmp .TscDeltaNotValid + int3 %endif ; @@ -758,7 +765,7 @@ ALIGNCODE(16) inc dword [pData + RTTIMENANOTSDATA.cExpired] mov eax, u32UpdateIntervalTSC jmp .ContinueCalcs - + int3 ;; ;; u64DeltaPrev >= 24h @@ -789,6 +796,7 @@ ALIGNCODE(16) inc dword [pData + RTTIMENANOTSDATA.c1nsSteps] lea rax, [u64PrevNanoTS + 1] jmp .Update + int3 ; else if (!u64PrevNanoTS) /* We're resuming (see TMVirtualResume) / first call. */ ; /* do nothing */; @@ -822,7 +830,7 @@ ALIGNCODE(16) mov rax, TmpVar mov pData, TmpVar2 jmp .Update - + int3 ;; ;; Attempt updating the previous value, provided we're still ahead of it. @@ -851,7 +859,7 @@ ALIGNCODE(16) dec edx jnz .UpdateLoop jmp .Updated - + int3 ;; ;; The GIP is seemingly invalid, redo the discovery. @@ -864,6 +872,7 @@ ALIGNCODE(16) %endif call [pData + RTTIMENANOTSDATA.pfnRediscover] jmp .Done + int3 ; diff --git a/src/VBox/Runtime/generic/semrw-generic.cpp b/src/VBox/Runtime/generic/semrw-generic.cpp index 703ac5c56bd3..95dba5ce9c27 100644 --- a/src/VBox/Runtime/generic/semrw-generic.cpp +++ b/src/VBox/Runtime/generic/semrw-generic.cpp @@ -1,4 +1,4 @@ -/* $Id: semrw-generic.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: semrw-generic.cpp 114984 2026-08-11 09:27:15Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Read-Write Semaphore, Generic. * @@ -746,9 +746,26 @@ DECL_FORCE_INLINE(int) rtSemRWRequestWrite(RTSEMRW hRWSem, RTMSINTERVAL cMillies */ if (pThis->u32Magic == RTSEMRW_MAGIC) { - RTCritSectEnter(&pThis->CritSect); - /* Adjust this counter, whether we got the critsect or not. */ + int rc2 = RTCritSectEnter(&pThis->CritSect); + AssertRCReturn(rc2, rc2); + + /* Adjust the waiting-writer count. */ + Assert(pThis->cWritesWaiting > 0); pThis->cWritesWaiting--; + + /* + * If this was the final waiting writer, we may have consumed the + * wakeup which kept readers blocked. So signal remaining readers to + * not run into waiting timeouts. + */ + if ( !pThis->cWritesWaiting + && !pThis->cWrites + && !pThis->cReads) + { + rc2 = RTSemEventMultiSignal(pThis->ReadEvent); + AssertMsgRC(rc2, ("Failed to signal readers on rwsem %p, rc=%Rrc\n", hRWSem, rc2)); + pThis->fNeedResetReadEvent = true; + } RTCritSectLeave(&pThis->CritSect); } return rc; diff --git a/src/VBox/Runtime/r0drv/nt/alloca-x86-r0drv-nt.asm b/src/VBox/Runtime/r0drv/nt/alloca-x86-r0drv-nt.asm index 41c523891890..1955fcda2b9e 100644 --- a/src/VBox/Runtime/r0drv/nt/alloca-x86-r0drv-nt.asm +++ b/src/VBox/Runtime/r0drv/nt/alloca-x86-r0drv-nt.asm @@ -1,4 +1,4 @@ -; $Id: alloca-x86-r0drv-nt.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: alloca-x86-r0drv-nt.asm 115003 2026-08-12 23:16:08Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Visual C++ __alloca__probe_16. ; @@ -67,3 +67,4 @@ BEGINPROC _alloca_probe_16 jmp [eax] ENDPROC _alloca_probe_16 +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: _alloc_probe_16 is doing an indirect call... diff --git a/src/VBox/Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm b/src/VBox/Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm index 9a0a6dcec204..bddfc3e861d0 100644 --- a/src/VBox/Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm +++ b/src/VBox/Runtime/r0drv/nt/nt3fakesA-r0drv-nt.asm @@ -1,4 +1,4 @@ -; $Id: nt3fakesA-r0drv-nt.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: nt3fakesA-r0drv-nt.asm 115003 2026-08-12 23:16:08Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Companion to nt3fakes-r0drv-nt.cpp that provides import stuff to satisfy the linker. ; @@ -54,11 +54,15 @@ BEGINPROC _rtNt3InitSymbolsAssembly ; @param 1 The fastcall name. ; @param 2 Byte size of arguments. %macro DefineImportDataAndInitCode 3 +%ifdef __NASM__ +extern %1 %+ Nt3Fb_ %+ %2 %+ @ %+ %3 +%else extern $%1 %+ Nt3Fb_ %+ %2 %+ @ %+ %3 +%endif BEGINDATA extern _g_pfnrt %+ %2 GLOBALNAME __imp_ %+ %1 %+ %2 %+ @ %+ %3 - dd $%1 %+ Nt3Fb_ %+ %2 %+ @ %+ %3 + dd %1 %+ Nt3Fb_ %+ %2 %+ @ %+ %3 BEGINCODE mov eax, [_g_pfnrt %+ %2] test eax, eax @@ -93,7 +97,11 @@ ENDPROC _rtNt3InitSymbolsAssembly BEGINCODE extern _g_pfnrt %+ %1 extern _g_pfnrt %+ %2 +%ifdef __NASM__ +BEGINPROC_EXPORTED @ %+ %1 %+ @ %+ %3 +%else BEGINPROC_EXPORTED $@ %+ %1 %+ @ %+ %3 +%endif mov eax, [_g_pfnrt %+ %1] cmp eax, 0 jnz .got_fast_call @@ -116,15 +124,20 @@ BEGINPROC_EXPORTED $@ %+ %1 %+ @ %+ %3 %endif leave ret + int3 .got_fast_call: mov [__imp_@ %+ %1 %+ @ %+ %3], eax jmp eax +%ifdef __NASM__ +ENDPROC @ %+ %1 %+ @ %+ %3 +%else ENDPROC $@ %+ %1 %+ @ %+ %3 +%endif BEGINDATA GLOBALNAME __imp_@ %+ %1 %+ @ %+ %3 - dd $@ %+ %1 %+ @ %+ %3 + dd @ %+ %1 %+ @ %+ %3 %endmacro FastOrStdCallWrapper IofCompleteRequest, IoCompleteRequest, 8, 0 @@ -140,14 +153,19 @@ FastOrStdCallWrapper KefReleaseSpinLockFromDpcLevel,KeReleaseSpinLockFromDpcLeve BEGINCODE ; LONG FASTCALL InterlockedExchange(LONG volatile *,LONG ); +%ifdef __NASM__ +BEGINPROC_EXPORTED @InterlockedExchange@8 +%else BEGINPROC_EXPORTED $@InterlockedExchange@8 +%endif mov eax, edx xchg [ecx], eax ret + int3 BEGINDATA GLOBALNAME __imp_@InterlockedExchange@8 - dd $@InterlockedExchange@8 + dd @InterlockedExchange@8 BEGINDATA @@ -155,3 +173,5 @@ GLOBALNAME __imp__KeTickCount GLOBALNAME _KeTickCount dd 0 + +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: we're doing indirect calls here of course. diff --git a/src/VBox/Runtime/r0drv/nt/security-cookie-vcc.asm b/src/VBox/Runtime/r0drv/nt/security-cookie-vcc.asm index 38f2709e97b3..c0a0efc48f2e 100644 --- a/src/VBox/Runtime/r0drv/nt/security-cookie-vcc.asm +++ b/src/VBox/Runtime/r0drv/nt/security-cookie-vcc.asm @@ -1,4 +1,4 @@ -; $Id: security-cookie-vcc.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: security-cookie-vcc.asm 115003 2026-08-12 23:16:08Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Stack related Visual C++ support routines, ring-0. ; @@ -138,3 +138,4 @@ BEGINPROC __security_init_cookie ret ENDPROC __security_init_cookie +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r0drv/nt/toxic-chkstk-r0drv-nt.asm b/src/VBox/Runtime/r0drv/nt/toxic-chkstk-r0drv-nt.asm index c283591823d0..b39f82df034c 100644 --- a/src/VBox/Runtime/r0drv/nt/toxic-chkstk-r0drv-nt.asm +++ b/src/VBox/Runtime/r0drv/nt/toxic-chkstk-r0drv-nt.asm @@ -1,4 +1,4 @@ -; $Id: toxic-chkstk-r0drv-nt.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: toxic-chkstk-r0drv-nt.asm 115003 2026-08-12 23:16:08Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Toxic _chkstk symbol. ; @@ -50,3 +50,4 @@ BEGINPROC _chkstk jmp MY_SYM ENDPROC _chkstk +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/posix/localipc-posix.cpp b/src/VBox/Runtime/r3/posix/localipc-posix.cpp index bcf840f95099..26312aa4b0b4 100644 --- a/src/VBox/Runtime/r3/posix/localipc-posix.cpp +++ b/src/VBox/Runtime/r3/posix/localipc-posix.cpp @@ -1,4 +1,4 @@ -/* $Id: localipc-posix.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: localipc-posix.cpp 114874 2026-08-06 21:28:04Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Local IPC Server & Client, Posix. */ @@ -39,6 +39,10 @@ * Header Files * *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_LOCALIPC +#include +#if defined(RT_OS_LINUX) && !defined(_GNU_SOURCE) +# define _GNU_SOURCE +#endif #include "internal/iprt.h" #include @@ -46,7 +50,10 @@ #include #include #include +#include +#include #include +#include #include #include #include @@ -58,6 +65,9 @@ #include #include #include +#ifdef RT_OS_SOLARIS +# include +#endif #ifndef RT_OS_OS2 # include #endif @@ -131,7 +141,109 @@ typedef RTLOCALIPCSESSIONINT *PRTLOCALIPCSESSIONINT; /** Local IPC name prefix for portable names. */ -#define RTLOCALIPC_POSIX_NAME_PREFIX "/tmp/.iprt-localipc-" +#define RTLOCALIPC_POSIX_NAME_PREFIX "/tmp/.iprt-localipc-" +/** Local IPC name prefix inside a protected user namespace. */ +#define RTLOCALIPC_POSIX_USER_NAME_PREFIX ".iprt-" + + +/** + * Validates a candidate protected user namespace directory. + * + * @returns IPRT status code. + * @param pszPath The directory to validate. + * @param fNamespace Whether this is the endpoint namespace itself, + * for which no group or other access is allowed. + */ +static int rtLocalIpcPosixValidateUserDir(const char *pszPath, bool fNamespace) +{ + AssertPtrReturn(pszPath, VERR_INVALID_POINTER); + AssertReturn(RTPathStartsWithRoot(pszPath), VERR_INVALID_NAME); + + RTFSOBJINFO ObjInfo; + int rc = RTPathQueryInfoEx(pszPath, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK); + if (RT_SUCCESS(rc)) + { + if (!RTFS_IS_DIRECTORY(ObjInfo.Attr.fMode)) + rc = VERR_NOT_A_DIRECTORY; + else if ( ObjInfo.Attr.u.Unix.uid == NIL_RTUID + || ObjInfo.Attr.u.Unix.uid != (RTUID)geteuid() + || (ObjInfo.Attr.fMode & (RTFS_UNIX_IWUSR | RTFS_UNIX_IXUSR)) + != (RTFS_UNIX_IWUSR | RTFS_UNIX_IXUSR) + || (fNamespace + ? RT_BOOL(ObjInfo.Attr.fMode & (RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO)) + : RT_BOOL(ObjInfo.Attr.fMode & (RTFS_UNIX_IWGRP | RTFS_UNIX_IWOTH)))) + rc = VERR_ACCESS_DENIED; + } + return rc; +} + + +/** Returns whether a restricted portable name fits in a Unix socket path. */ +static bool rtLocalIpcPosixUserNameFits(const char *pszPath, const char *pszName) +{ + size_t const cchPath = strlen(pszPath); + return cchPath > 0 + && cchPath + (RTPATH_IS_SLASH(pszPath[cchPath - 1]) ? 0 : 1) + + sizeof(RTLOCALIPC_POSIX_USER_NAME_PREFIX) - 1 + strlen(pszName) + 1 + <= sizeof(((struct sockaddr_un *)0)->sun_path); +} + + +/** + * Locates or creates the protected namespace used for restricted portable + * names. + * + * @returns IPRT status code. + * @param pszPath Where to return the directory. + * @param cbPath Size of the output buffer. + * @param pszName The restricted portable endpoint name. + */ +static int rtLocalIpcPosixGetUserDir(char *pszPath, size_t cbPath, const char *pszName) +{ + const char *pszCandidate = RTEnvGet("XDG_RUNTIME_DIR"); + if (pszCandidate && *pszCandidate && RTPathStartsWithRoot(pszCandidate)) + { + int rc = RTStrCopy(pszPath, cbPath, pszCandidate); + if (RT_SUCCESS(rc)) + rc = rtLocalIpcPosixValidateUserDir(pszPath, true /*fNamespace*/); + if (RT_SUCCESS(rc) && rtLocalIpcPosixUserNameFits(pszPath, pszName)) + return VINF_SUCCESS; + } + +#ifdef RT_OS_LINUX + ssize_t const cchRunUser = RTStrPrintf2(pszPath, cbPath, "/run/user/%RTuid", (RTUID)geteuid()); + if (cchRunUser > 0 && (size_t)cchRunUser < cbPath) + { + int const rc = rtLocalIpcPosixValidateUserDir(pszPath, true /*fNamespace*/); + if (RT_SUCCESS(rc) && rtLocalIpcPosixUserNameFits(pszPath, pszName)) + return VINF_SUCCESS; + } +#endif + + char szHome[RTPATH_MAX]; + int rc = RTPathUserHome(szHome, sizeof(szHome)); + if (RT_SUCCESS(rc) && !RTPathStartsWithRoot(szHome)) + rc = VERR_INVALID_NAME; + if (RT_SUCCESS(rc)) + rc = RTPathReal(szHome, pszPath, cbPath); + if (RT_SUCCESS(rc)) + rc = rtLocalIpcPosixValidateUserDir(pszPath, false /*fNamespace*/); + if (RT_SUCCESS(rc)) + rc = RTPathAppend(pszPath, cbPath, ".iprt-localipc"); + if (RT_SUCCESS(rc)) + { + rc = RTDirCreate(pszPath, RTFS_UNIX_IRWXU, 0 /*fCreate*/); + if (rc == VERR_ALREADY_EXISTS) + rc = VINF_SUCCESS; + else if (RT_SUCCESS(rc)) + rc = RTPathSetMode(pszPath, RTFS_UNIX_IRWXU); + } + if (RT_SUCCESS(rc)) + rc = rtLocalIpcPosixValidateUserDir(pszPath, true /*fNamespace*/); + if (RT_SUCCESS(rc) && !rtLocalIpcPosixUserNameFits(pszPath, pszName)) + rc = VERR_FILENAME_TOO_LONG; + return rc; +} /** @@ -177,9 +289,28 @@ static int rtLocalIpcPosixValidateName(const char *pszName, bool fNative) * @param pcbAddr Where to return the address size. * @param pszName The user specified name (valid). * @param fNative Whether it's a native name or a portable name. + * @param fRestrictToUser Whether portable names use the protected user + * namespace. */ -static int rtLocalIpcPosixConstructName(struct sockaddr_un *pAddr, uint8_t *pcbAddr, const char *pszName, bool fNative) +static int rtLocalIpcPosixConstructName(struct sockaddr_un *pAddr, uint8_t *pcbAddr, const char *pszName, + bool fNative, bool fRestrictToUser) { + char szUserName[RTPATH_MAX]; + if (!fNative && fRestrictToUser) + { + int rc = rtLocalIpcPosixGetUserDir(szUserName, sizeof(szUserName), pszName); + if (RT_FAILURE(rc)) + return rc; + rc = RTPathAppend(szUserName, sizeof(szUserName), RTLOCALIPC_POSIX_USER_NAME_PREFIX); + if (RT_FAILURE(rc)) + return rc; + rc = RTStrCat(szUserName, sizeof(szUserName), pszName); + if (RT_FAILURE(rc)) + return rc; + pszName = szUserName; + fNative = true; + } + const char *pszNativeName; int rc = rtPathToNative(&pszNativeName, pszName, NULL /*pszBasePath not support*/); if (RT_SUCCESS(rc)) @@ -251,7 +382,8 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa uint8_t cbAddr; rc = rtLocalIpcPosixConstructName(&pThis->Name, &cbAddr, pszName, - RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { rc = rtSocketBindRawAddr(pThis->hSocket, &pThis->Name, cbAddr); @@ -262,7 +394,11 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa } if (RT_SUCCESS(rc)) { - rc = rtSocketListen(pThis->hSocket, 16); + if ( !(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER) + || chmod(pThis->Name.sun_path, S_IRUSR | S_IWUSR) == 0) + rc = rtSocketListen(pThis->hSocket, 16); + else + rc = RTErrConvertFromErrno(errno); if (RT_SUCCESS(rc)) { LogFlow(("RTLocalIpcServerCreate: Created %p (%s)\n", pThis, pThis->Name.sun_path)); @@ -572,7 +708,9 @@ RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *p struct sockaddr_un Addr; uint8_t cbAddr; - rc = rtLocalIpcPosixConstructName(&Addr, &cbAddr, pszName, RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + rc = rtLocalIpcPosixConstructName(&Addr, &cbAddr, pszName, + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { rc = rtSocketConnectRaw(pThis->hSocket, &Addr, cbAddr); @@ -1118,27 +1256,77 @@ static int rtLocalIpcSessionQueryUcred(RTLOCALIPCSESSION hSession, PRTPROCESS pP AssertPtrReturn(pThis, VERR_INVALID_HANDLE); AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); -#if defined(RT_OS_LINUX) - struct ucred PeerCred = { (pid_t)NIL_RTPROCESS, (uid_t)NIL_RTUID, (gid_t)NIL_RTGID }; - socklen_t cbPeerCred = sizeof(PeerCred); - rtLocalIpcSessionRetain(pThis); - int rc = RTCritSectEnter(&pThis->CritSect);; + int rc = RTCritSectEnter(&pThis->CritSect); if (RT_SUCCESS(rc)) { - if (getsockopt(RTSocketToNative(pThis->hSocket), SOL_SOCKET, SO_PEERCRED, &PeerCred, &cbPeerCred) >= 0) + if (pThis->fCancelled) + rc = VERR_CANCELLED; + else { +#if defined(RT_OS_LINUX) + struct ucred PeerCred = { (pid_t)NIL_RTPROCESS, (uid_t)NIL_RTUID, (gid_t)NIL_RTGID }; + socklen_t cbPeerCred = sizeof(PeerCred); + if (getsockopt(RTSocketToNative(pThis->hSocket), SOL_SOCKET, SO_PEERCRED, &PeerCred, &cbPeerCred) >= 0) + { + if (pProcess) + *pProcess = PeerCred.pid; + if (pUid) + *pUid = PeerCred.uid; + if (pGid) + *pGid = PeerCred.gid; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromErrno(errno); +#elif defined(RT_OS_DARWIN) || defined(RT_OS_FREEBSD) \ + || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) + if (!pProcess) + { + uid_t uidPeer = (uid_t)NIL_RTUID; + gid_t gidPeer = (gid_t)NIL_RTGID; + if (getpeereid(RTSocketToNative(pThis->hSocket), &uidPeer, &gidPeer) == 0) + { + if (pUid) + *pUid = uidPeer; + if (pGid) + *pGid = gidPeer; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromErrno(errno); + } + else + { + *pProcess = NIL_RTPROCESS; + rc = VERR_NOT_SUPPORTED; + } +#elif defined(RT_OS_SOLARIS) + ucred_t *pCred = NULL; + if (getpeerucred(RTSocketToNative(pThis->hSocket), &pCred) == 0) + { + if (pProcess) + *pProcess = ucred_getpid(pCred); + if (pUid) + *pUid = ucred_geteuid(pCred); + if (pGid) + *pGid = ucred_getegid(pCred); + ucred_free(pCred); + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromErrno(errno); +#else if (pProcess) - *pProcess = PeerCred.pid; + *pProcess = NIL_RTPROCESS; if (pUid) - *pUid = PeerCred.uid; + *pUid = NIL_RTUID; if (pGid) - *pGid = PeerCred.gid; - rc = VINF_SUCCESS; + *pGid = NIL_RTGID; + rc = VERR_NOT_SUPPORTED; +#endif } - else - rc = RTErrConvertFromErrno(errno); int rc2 = RTCritSectLeave(&pThis->CritSect); AssertStmt(RT_SUCCESS(rc2), rc = RT_SUCCESS(rc) ? rc2 : rc); @@ -1147,13 +1335,6 @@ static int rtLocalIpcSessionQueryUcred(RTLOCALIPCSESSION hSession, PRTPROCESS pP rtLocalIpcSessionRelease(pThis); return rc; - -#else - /** @todo Implement on other platforms too (mostly platform specific this). - * Solaris: getpeerucred? Darwin: LOCALPEERCRED or getpeereid? */ - RT_NOREF(pProcess, pUid, pGid); - return VERR_NOT_SUPPORTED; -#endif } @@ -1163,6 +1344,16 @@ RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS } +RTDECL(int) RTLocalIpcSessionVerifySameUser(RTLOCALIPCSESSION hSession) +{ + RTUID uidPeer = NIL_RTUID; + int rc = rtLocalIpcSessionQueryUcred(hSession, NULL, &uidPeer, NULL); + if (RT_SUCCESS(rc) && uidPeer != (RTUID)geteuid()) + rc = VERR_ACCESS_DENIED; + return rc; +} + + RTDECL(int) RTLocalIpcSessionQueryUserId(RTLOCALIPCSESSION hSession, PRTUID pUid) { return rtLocalIpcSessionQueryUcred(hSession, NULL, pUid, NULL); @@ -1172,4 +1363,3 @@ RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGi { return rtLocalIpcSessionQueryUcred(hSession, NULL, NULL, pGid); } - diff --git a/src/VBox/Runtime/r3/posix/shmem-posix.cpp b/src/VBox/Runtime/r3/posix/shmem-posix.cpp index 485c96859fe2..41b04441318c 100644 --- a/src/VBox/Runtime/r3/posix/shmem-posix.cpp +++ b/src/VBox/Runtime/r3/posix/shmem-posix.cpp @@ -1,4 +1,4 @@ -/* $Id: shmem-posix.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: shmem-posix.cpp 114873 2026-08-06 20:59:25Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Named shared memory object, POSIX Implementation. */ @@ -177,7 +177,7 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, if (fFlags & RTSHMEM_O_F_TRUNCATE) fShmFlags |= O_TRUNC; pThis->iFdShm = shm_open(pThis->pszName, fShmFlags , 0600); - if (pThis->iFdShm > 0) + if (pThis->iFdShm >= 0) { if (cbMax) rc = RTShMemSetSize(pThis, cbMax); @@ -187,6 +187,8 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, return VINF_SUCCESS; } + if (fShmFlags & O_EXCL) + shm_unlink(pThis->pszName); close(pThis->iFdShm); } else @@ -398,7 +400,7 @@ RTDECL(int) RTShMemUnmapRegion(RTSHMEM hShMem, void *pv) AssertPtrReturn(pMappingDesc, VERR_INVALID_PARAMETER); int rc = VINF_SUCCESS; - size_t cbRegion = pMappingDesc->cMappings; + size_t const cbRegion = pMappingDesc->cbRegion; if (!ASMAtomicDecU32(&pMappingDesc->cMappings)) { /* Last mapping of this region was unmapped, so do the real unmapping now. */ @@ -416,4 +418,3 @@ RTDECL(int) RTShMemUnmapRegion(RTSHMEM hShMem, void *pv) return rc; } - diff --git a/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def b/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def index ec0a4e90a3ac..4707470ed9f2 100644 --- a/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def +++ b/src/VBox/Runtime/r3/win/VBoxRT-openssl-3.0.def @@ -1,4 +1,4 @@ -; $Id: VBoxRT-openssl-3.0.def 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VBoxRT-openssl-3.0.def 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ ;; @file ; IPRT - Windows OpenSSL exports we use outside VBoxRT (keep them few!). ; @@ -373,6 +373,8 @@ EVP_MD_CTX_free EVP_MD_CTX_new EVP_MD_CTX_reset + EVP_MD_fetch + EVP_MD_free EVP_md5 EVP_PKEY_CTX_new EVP_PKEY_CTX_new_id diff --git a/src/VBox/Runtime/r3/win/localipc-win.cpp b/src/VBox/Runtime/r3/win/localipc-win.cpp index 1ca26717109f..ddfbeed08f98 100644 --- a/src/VBox/Runtime/r3/win/localipc-win.cpp +++ b/src/VBox/Runtime/r3/win/localipc-win.cpp @@ -1,4 +1,4 @@ -/* $Id: localipc-win.cpp 113928 2026-04-16 23:39:20Z knut.osmundsen@oracle.com $ */ +/* $Id: localipc-win.cpp 114875 2026-08-06 21:35:39Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Local IPC, Windows Implementation Using Named Pipes. * @@ -43,8 +43,6 @@ *********************************************************************************************************************************/ #define LOG_GROUP RTLOGGROUP_LOCALIPC #include /* Need NtCancelIoFile and a few Rtl functions. */ -#include -#include #include "internal/iprt.h" #include @@ -54,9 +52,9 @@ #include #include #include -#include #include #include +#include #include #include #include @@ -74,6 +72,12 @@ *********************************************************************************************************************************/ /** Pipe prefix string. */ #define RTLOCALIPC_WIN_PREFIX L"\\\\.\\pipe\\IPRT-" +/** Pipe prefix string in the caller's protected login-session namespace. */ +#define RTLOCALIPC_WIN_USER_PREFIX L"\\\\.\\pipe\\LOCAL\\IPRT-" +/** Number of UTF-16 units in the hexadecimal session ID and trailing dash. */ +#define RTLOCALIPC_WIN_SESSION_ID_CWC 9 +/** Number of UTF-16 units in the hexadecimal logon LUID and trailing dash. */ +#define RTLOCALIPC_WIN_LOGON_ID_CWC 17 /********************************************************************************************************************************* @@ -129,6 +133,8 @@ typedef struct RTLOCALIPCSESSIONINT bool volatile fCancelled; /** Set if this is the server side, clear if the client. */ bool fServerSide; + /** Set if the session must remain in the current Windows logon session. */ + bool fRestricted; /** The named pipe handle. */ HANDLE hNmPipe; struct @@ -159,18 +165,361 @@ typedef struct RTLOCALIPCSESSIONINT typedef RTLOCALIPCSESSIONINT *PRTLOCALIPCSESSIONINT; +/** Pointer to a GetNamedPipeClientProcessId or GetNamedPipeServerProcessId function. */ +typedef BOOL (WINAPI *PFNRTLOCALIPCWINQUERYPIPEPROCESS)(HANDLE hPipe, PULONG pidProcess); +/** Pointer to a GetNamedPipeClientSessionId or GetNamedPipeServerSessionId function. */ +typedef BOOL (WINAPI *PFNRTLOCALIPCWINQUERYPIPESESSION)(HANDLE hPipe, PULONG pidSession); + + +/********************************************************************************************************************************* +* Global Variables * +*********************************************************************************************************************************/ +/** Init once structure for resolving the named pipe process query APIs. */ +static RTONCE g_rtLocalIpcWinQueryProcessResolveOnce = RTONCE_INITIALIZER; +/** GetNamedPipeClientProcessId, introduced with Windows Vista. */ +static PFNRTLOCALIPCWINQUERYPIPEPROCESS g_pfnGetNamedPipeClientProcessId = NULL; +/** GetNamedPipeServerProcessId, introduced with Windows Vista. */ +static PFNRTLOCALIPCWINQUERYPIPEPROCESS g_pfnGetNamedPipeServerProcessId = NULL; +/** Init once structure for resolving the named pipe session query APIs. */ +static RTONCE g_rtLocalIpcWinQuerySessionResolveOnce = RTONCE_INITIALIZER; +/** GetNamedPipeClientSessionId, introduced with Windows Vista. */ +static PFNRTLOCALIPCWINQUERYPIPESESSION g_pfnGetNamedPipeClientSessionId = NULL; +/** GetNamedPipeServerSessionId, introduced with Windows Vista. */ +static PFNRTLOCALIPCWINQUERYPIPESESSION g_pfnGetNamedPipeServerSessionId = NULL; + + /********************************************************************************************************************************* * Internal Functions * *********************************************************************************************************************************/ -static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession); +static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession, bool fRestricted); +static int rtLocalIpcWinVerifyUserSid(PSID pSid); + + +/** Queries the current process token's Windows session ID. */ +static int rtLocalIpcWinQuerySelfSessionId(uint32_t *pidSession) +{ + AssertPtrReturn(pidSession, VERR_INVALID_POINTER); + *pidSession = 0; + + HANDLE hToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + return RTErrConvertFromWin32(GetLastError()); + + DWORD idSession = 0; + DWORD cbSession = 0; + BOOL const fRc = GetTokenInformation(hToken, TokenSessionId, &idSession, sizeof(idSession), &cbSession); + DWORD const dwErr = fRc ? ERROR_SUCCESS : GetLastError(); + CloseHandle(hToken); + if (!fRc) + return RTErrConvertFromWin32(dwErr); + AssertReturn(cbSession == sizeof(idSession), VERR_INVALID_PARAMETER); + + *pidSession = idSession; + return VINF_SUCCESS; +} + + +/** Queries the current process token's Windows logon LUID. */ +static int rtLocalIpcWinQuerySelfLogonId(uint32_t *pidLogonHigh, uint32_t *pidLogonLow) +{ + AssertPtrReturn(pidLogonHigh, VERR_INVALID_POINTER); + AssertPtrReturn(pidLogonLow, VERR_INVALID_POINTER); + *pidLogonHigh = 0; + *pidLogonLow = 0; + + HANDLE hToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + return RTErrConvertFromWin32(GetLastError()); + + TOKEN_STATISTICS TokenStats; + DWORD cbTokenStats = 0; + BOOL const fRc = GetTokenInformation(hToken, TokenStatistics, &TokenStats, sizeof(TokenStats), &cbTokenStats); + DWORD const dwErr = fRc ? ERROR_SUCCESS : GetLastError(); + CloseHandle(hToken); + if (!fRc) + return RTErrConvertFromWin32(dwErr); + AssertReturn(cbTokenStats == sizeof(TokenStats), VERR_INVALID_PARAMETER); + + *pidLogonHigh = (uint32_t)TokenStats.AuthenticationId.HighPart; + *pidLogonLow = TokenStats.AuthenticationId.LowPart; + return VINF_SUCCESS; +} + + +/** + * Resolves the optional named pipe process query APIs. + * + * @returns IPRT status code. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int) rtLocalIpcWinQueryProcessResolveOnce(void *pvUser) +{ + RT_NOREF(pvUser); + + g_pfnGetNamedPipeClientProcessId = (PFNRTLOCALIPCWINQUERYPIPEPROCESS)GetProcAddress(g_hModKernel32, + "GetNamedPipeClientProcessId"); + g_pfnGetNamedPipeServerProcessId = (PFNRTLOCALIPCWINQUERYPIPEPROCESS)GetProcAddress(g_hModKernel32, + "GetNamedPipeServerProcessId"); + if ( g_pfnGetNamedPipeClientProcessId + && g_pfnGetNamedPipeServerProcessId) + return VINF_SUCCESS; + return VERR_NOT_SUPPORTED; +} + + +/** + * Resolves the optional named pipe session query APIs. + * + * @returns IPRT status code. + * @param pvUser Ignored. + */ +static DECLCALLBACK(int) rtLocalIpcWinQuerySessionResolveOnce(void *pvUser) +{ + RT_NOREF(pvUser); + + g_pfnGetNamedPipeClientSessionId = (PFNRTLOCALIPCWINQUERYPIPESESSION)GetProcAddress(g_hModKernel32, + "GetNamedPipeClientSessionId"); + g_pfnGetNamedPipeServerSessionId = (PFNRTLOCALIPCWINQUERYPIPESESSION)GetProcAddress(g_hModKernel32, + "GetNamedPipeServerSessionId"); + if ( g_pfnGetNamedPipeClientSessionId + && g_pfnGetNamedPipeServerSessionId) + return VINF_SUCCESS; + return VERR_NOT_SUPPORTED; +} + + +/** + * Queries the user information carried by a Windows access token. + * + * @returns IPRT status code. + * @param hToken The token to query. + * @param ppTokenUser Where to return the allocated token user + * information. Free with RTMemTmpFree(). + */ +static int rtLocalIpcWinQueryTokenUser(HANDLE hToken, PTOKEN_USER *ppTokenUser) +{ + AssertReturn(hToken != NULL && hToken != INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + AssertPtrReturn(ppTokenUser, VERR_INVALID_POINTER); + *ppTokenUser = NULL; + + DWORD cbTokenUser = 0; + if (GetTokenInformation(hToken, TokenUser, NULL, 0, &cbTokenUser)) + return VERR_INTERNAL_ERROR; + DWORD const dwErr = GetLastError(); + if (dwErr != ERROR_INSUFFICIENT_BUFFER) + return RTErrConvertFromWin32(dwErr); + AssertReturn(cbTokenUser >= sizeof(TOKEN_USER), VERR_INVALID_PARAMETER); + + PTOKEN_USER pTokenUser = (PTOKEN_USER)RTMemTmpAlloc(cbTokenUser); + if (!pTokenUser) + return VERR_NO_TMP_MEMORY; + if (!GetTokenInformation(hToken, TokenUser, pTokenUser, cbTokenUser, &cbTokenUser)) + { + int const rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pTokenUser); + return rc; + } + if (!IsValidSid(pTokenUser->User.Sid)) + { + RTMemTmpFree(pTokenUser); + return VERR_INVALID_PARAMETER; + } + + *ppTokenUser = pTokenUser; + return VINF_SUCCESS; +} + + +/** + * Queries the logon SID carried by a Windows access token. + * + * @returns IPRT status code. + * @param hToken The token to query. + * @param ppLogonSid Where to return the allocated logon SID. Free + * with RTMemTmpFree(). + */ +static int rtLocalIpcWinQueryTokenLogonSid(HANDLE hToken, PSID *ppLogonSid) +{ + AssertReturn(hToken != NULL && hToken != INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + AssertPtrReturn(ppLogonSid, VERR_INVALID_POINTER); + *ppLogonSid = NULL; + + DWORD cbTokenGroups = 0; + if (GetTokenInformation(hToken, TokenGroups, NULL, 0, &cbTokenGroups)) + return VERR_INTERNAL_ERROR; + DWORD const dwErr = GetLastError(); + if (dwErr != ERROR_INSUFFICIENT_BUFFER) + return RTErrConvertFromWin32(dwErr); + AssertReturn(cbTokenGroups >= sizeof(TOKEN_GROUPS), VERR_INVALID_PARAMETER); + + PTOKEN_GROUPS pTokenGroups = (PTOKEN_GROUPS)RTMemTmpAlloc(cbTokenGroups); + if (!pTokenGroups) + return VERR_NO_TMP_MEMORY; + if (!GetTokenInformation(hToken, TokenGroups, pTokenGroups, cbTokenGroups, &cbTokenGroups)) + { + int const rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pTokenGroups); + return rc; + } + + int rc = VERR_ACCESS_DENIED; + for (DWORD i = 0; i < pTokenGroups->GroupCount; i++) + if ((pTokenGroups->Groups[i].Attributes & SE_GROUP_LOGON_ID) == SE_GROUP_LOGON_ID) + { + PSID const pTokenLogonSid = pTokenGroups->Groups[i].Sid; + if (IsValidSid(pTokenLogonSid)) + { + DWORD const cbLogonSid = GetLengthSid(pTokenLogonSid); + PSID const pLogonSid = (PSID)RTMemTmpAlloc(cbLogonSid); + if (pLogonSid) + { + if (CopySid(cbLogonSid, pLogonSid, pTokenLogonSid)) + { + *ppLogonSid = pLogonSid; + rc = VINF_SUCCESS; + } + else + { + rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pLogonSid); + } + } + else + rc = VERR_NO_TMP_MEMORY; + } + else + rc = VERR_INVALID_PARAMETER; + break; + } + + RTMemTmpFree(pTokenGroups); + return rc; +} + + +/** Returns whether @a pSid is the LocalSystem SID. */ +static bool rtLocalIpcWinIsLocalSystemSid(PSID pSid) +{ + AssertReturn(pSid && IsValidSid(pSid), false); + + static SID_IDENTIFIER_AUTHORITY s_NtAuth = SECURITY_NT_AUTHORITY; + union + { + SID Sid; + uint8_t abPadding[SECURITY_MAX_SID_SIZE]; + } LocalSystem; + + NTSTATUS const rcNt = RtlInitializeSid(&LocalSystem.Sid, &s_NtAuth, 1); + AssertReturn(NT_SUCCESS(rcNt), false); + *RtlSubAuthoritySid(&LocalSystem.Sid, 0) = SECURITY_LOCAL_SYSTEM_RID; + return EqualSid(pSid, &LocalSystem.Sid) != FALSE; +} + + +/** Returns whether @a dwErr means that a named pipe peer disconnected. */ +static bool rtLocalIpcWinIsPeerGoneError(DWORD dwErr) +{ + return dwErr == ERROR_BROKEN_PIPE + || dwErr == ERROR_NO_DATA + || dwErr == ERROR_PIPE_NOT_CONNECTED; +} + + +/** Verifies that the named pipe peer belongs to the current Windows session. */ +static int rtLocalIpcWinVerifyPeerSession(HANDLE hPipe, bool fServerSide) +{ + AssertReturn(hPipe != NULL && hPipe != INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + + uint32_t idSelfSession = 0; + int rc = rtLocalIpcWinQuerySelfSessionId(&idSelfSession); + if (RT_SUCCESS(rc)) + { + rc = RTOnce(&g_rtLocalIpcWinQuerySessionResolveOnce, rtLocalIpcWinQuerySessionResolveOnce, NULL); + if (RT_SUCCESS(rc)) + { + ULONG idPeerSession = 0; + BOOL const fRc = fServerSide + ? g_pfnGetNamedPipeClientSessionId(hPipe, &idPeerSession) + : g_pfnGetNamedPipeServerSessionId(hPipe, &idPeerSession); + if (fRc) + rc = idPeerSession == idSelfSession ? VINF_SUCCESS : VERR_ACCESS_DENIED; + else + { + DWORD const dwErr = GetLastError(); + rc = fServerSide && rtLocalIpcWinIsPeerGoneError(dwErr) ? VERR_ACCESS_DENIED + : RTErrConvertFromWin32(dwErr); + } + } + } + return rc; +} + + +/** Verifies that the named pipe owner is the current process token's user. */ +static int rtLocalIpcWinVerifyPipeOwnerUser(HANDLE hPipe) +{ + AssertReturn(hPipe != NULL && hPipe != INVALID_HANDLE_VALUE, VERR_INVALID_HANDLE); + + /* GetSecurityInfo is unavailable on the NT 3.1 import baseline. */ + DWORD cbSecDesc = 0; + if (GetKernelObjectSecurity(hPipe, OWNER_SECURITY_INFORMATION, NULL, 0, &cbSecDesc)) + return VERR_INTERNAL_ERROR; + DWORD const dwErr = GetLastError(); + if (dwErr != ERROR_INSUFFICIENT_BUFFER) + return RTErrConvertFromWin32(dwErr); + + PSECURITY_DESCRIPTOR pSecDesc = (PSECURITY_DESCRIPTOR)RTMemTmpAlloc(cbSecDesc); + if (!pSecDesc) + return VERR_NO_TMP_MEMORY; + + int rc; + if (GetKernelObjectSecurity(hPipe, OWNER_SECURITY_INFORMATION, pSecDesc, cbSecDesc, &cbSecDesc)) + { + PSID pOwner = NULL; + BOOL fOwnerDefaulted = FALSE; + if (GetSecurityDescriptorOwner(pSecDesc, &pOwner, &fOwnerDefaulted)) + { + RT_NOREF(fOwnerDefaulted); + rc = rtLocalIpcWinVerifyUserSid(pOwner); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + else + rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pSecDesc); + return rc; +} + + +/** Verifies that @a pSid is the current process token's user SID. */ +static int rtLocalIpcWinVerifyUserSid(PSID pSid) +{ + if (!pSid || !IsValidSid(pSid)) + return VERR_INVALID_PARAMETER; + + HANDLE hSelfToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hSelfToken)) + return RTErrConvertFromWin32(GetLastError()); + + PTOKEN_USER pSelfTokenUser = NULL; + int rc = rtLocalIpcWinQueryTokenUser(hSelfToken, &pSelfTokenUser); + if (RT_SUCCESS(rc) && !EqualSid(pSid, pSelfTokenUser->User.Sid)) + rc = VERR_ACCESS_DENIED; + + RTMemTmpFree(pSelfTokenUser); + CloseHandle(hSelfToken); + return rc; +} /** - * DACL for block all network access and local users other than the creator/owner. + * DACL blocking network access while permitting the creating logon session + * and LocalSystem. * * ACE format: (ace_type;ace_flags;rights;object_guid;inherit_object_guid;account_sid) * - * Note! FILE_GENERIC_WRITE (SDDL_FILE_WRITE) is evil here because it includes + * Note! FILE_GENERIC_WRITE is evil here because it includes * the FILE_CREATE_PIPE_INSTANCE(=FILE_APPEND_DATA) flag. Thus the hardcoded * value 0x0012019b in the client ACE. The server-side still needs * setting FILE_CREATE_PIPE_INSTANCE although. @@ -188,36 +537,24 @@ static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE h * 0x00000004 - FILE_CREATE_PIPE_INSTANCE * = 0x0012019f * - * @todo Triple check this! - * @todo EVERYONE -> AUTHENTICATED USERS or something more appropriate? - * @todo Have trouble allowing the owner FILE_CREATE_PIPE_INSTANCE access, so for now I'm hacking - * it just to get progress - the service runs as local system. - * The CREATOR OWNER and PERSONAL SELF works (the former is only involved in inheriting - * it seems, which is why it won't work. The latter I've no idea about. Perhaps the solution - * is to go the annoying route of OpenProcessToken, QueryTokenInformation, - * ConvertSidToStringSid and then use the result... Suggestions are very welcome + * @returns NT status code. + * @param pDacl The initialized ACL to populate. + * @param pAccessSid The creating process token's logon SID. + * @param fServer Whether the ACL is for a server pipe end. */ -#define RTLOCALIPC_WIN_SDDL_BASE \ - SDDL_DACL SDDL_DELIMINATOR \ - SDDL_ACE_BEGIN SDDL_ACCESS_DENIED L";;" SDDL_GENERIC_ALL L";;;" SDDL_NETWORK SDDL_ACE_END \ - SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL L";;;" SDDL_LOCAL_SYSTEM SDDL_ACE_END -#define RTLOCALIPC_WIN_SDDL_SERVER \ - RTLOCALIPC_WIN_SDDL_BASE \ - SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019f" L";;;" SDDL_EVERYONE SDDL_ACE_END -#define RTLOCALIPC_WIN_SDDL_CLIENT \ - RTLOCALIPC_WIN_SDDL_BASE \ - SDDL_ACE_BEGIN SDDL_ACCESS_ALLOWED L";;" L"0x0012019b" L";;;" SDDL_EVERYONE SDDL_ACE_END -static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, bool fServer) +static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, PSID pAccessSid, bool fServer) { + AssertReturn(pAccessSid && IsValidSid(pAccessSid), STATUS_INVALID_PARAMETER); + static SID_IDENTIFIER_AUTHORITY s_NtAuth = SECURITY_NT_AUTHORITY; union { SID Sid; uint8_t abPadding[SECURITY_MAX_SID_SIZE]; - } Network, LocalSystem, Everyone; + } Network, LocalSystem, OwnerRights; - /* 1. SDDL_ACCESS_DENIED L";;" SDDL_GENERIC_ALL L";;;" SDDL_NETWORK */ + /* 1. Deny all access from network logons. */ NTSTATUS rcNt = RtlInitializeSid(&Network.Sid, &s_NtAuth, 1); AssertReturn(NT_SUCCESS(rcNt), rcNt); *RtlSubAuthoritySid(&Network.Sid, 0) = SECURITY_NETWORK_RID; @@ -225,21 +562,24 @@ static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, bool fServer) rcNt = RtlAddAccessDeniedAce(pDacl, ACL_REVISION, GENERIC_ALL, &Network.Sid); AssertReturn(NT_SUCCESS(rcNt), rcNt); - /* 2. SDDL_ACCESS_ALLOWED L";;" SDDL_FILE_ALL L";;;" SDDL_LOCAL_SYSTEM */ - rcNt = RtlInitializeSid(&LocalSystem.Sid, &s_NtAuth, 1); + /* 2. Suppress the account owner's implicit WRITE_DAC access. */ + static SID_IDENTIFIER_AUTHORITY s_CreatorAuth = SECURITY_CREATOR_SID_AUTHORITY; + rcNt = RtlInitializeSid(&OwnerRights.Sid, &s_CreatorAuth, 1); AssertReturn(NT_SUCCESS(rcNt), rcNt); - *RtlSubAuthoritySid(&LocalSystem.Sid, 0) = SECURITY_LOCAL_SYSTEM_RID; + *RtlSubAuthoritySid(&OwnerRights.Sid, 0) = SECURITY_CREATOR_OWNER_RIGHTS_RID; - rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, FILE_ALL_ACCESS, &Network.Sid); + rcNt = RtlAddAccessDeniedAce(pDacl, ACL_REVISION, WRITE_DAC | WRITE_OWNER, &OwnerRights.Sid); AssertReturn(NT_SUCCESS(rcNt), rcNt); + /* 3. Grant LocalSystem full access. */ + rcNt = RtlInitializeSid(&LocalSystem.Sid, &s_NtAuth, 1); + AssertReturn(NT_SUCCESS(rcNt), rcNt); + *RtlSubAuthoritySid(&LocalSystem.Sid, 0) = SECURITY_LOCAL_SYSTEM_RID; - /* 3. server: SDDL_ACCESS_ALLOWED L";;" L"0x0012019f" L";;;" SDDL_EVERYONE - client: SDDL_ACCESS_ALLOWED L";;" L"0x0012019b" L";;;" SDDL_EVERYONE */ - rcNt = RtlInitializeSid(&Everyone.Sid, &s_NtAuth, 1); + rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, FILE_ALL_ACCESS, &LocalSystem.Sid); AssertReturn(NT_SUCCESS(rcNt), rcNt); - *RtlSubAuthoritySid(&Everyone.Sid, 0) = SECURITY_WORLD_RID; + /* 4. Grant the creating logon session the access required by this pipe end. */ DWORD const fAccess = FILE_READ_DATA /* 0x00000001 */ | FILE_WRITE_DATA /* 0x00000002 */ | FILE_CREATE_PIPE_INSTANCE * fServer /* 0x00000004 */ @@ -251,10 +591,10 @@ static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, bool fServer) | SYNCHRONIZE; /* 0x00100000*/ Assert(fAccess == (fServer ? 0x0012019fU : 0x0012019bU)); - rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, fAccess, &Network.Sid); + rcNt = RtlAddAccessAllowedAce(pDacl, ACL_REVISION, fAccess, pAccessSid); AssertReturn(NT_SUCCESS(rcNt), rcNt); - return true; + return STATUS_SUCCESS; } @@ -265,78 +605,135 @@ static NTSTATUS rtLocalIpcBuildDacl(PACL pDacl, bool fServer) * @param ppDesc Where to store the allocated security descriptor on success. * Must be free'd using LocalFree(). * @param fServer Whether it's for a server or client instance. + * @param fRestrictToUser Whether to restrict access to the creating logon + * session and LocalSystem. */ -static int rtLocalIpcServerWinAllocSecurityDescriptor(PSECURITY_DESCRIPTOR *ppDesc, bool fServer) +static int rtLocalIpcServerWinAllocSecurityDescriptor(PSECURITY_DESCRIPTOR *ppDesc, bool fServer, + bool fRestrictToUser) { int rc; PSECURITY_DESCRIPTOR pSecDesc = NULL; -#if 0 - /* - * Resolve the API the first time around. - */ - static bool volatile s_fResolvedApis = false; - /** advapi32.dll API ConvertStringSecurityDescriptorToSecurityDescriptorW. */ - static decltype(ConvertStringSecurityDescriptorToSecurityDescriptorW) *s_pfnSSDLToSecDescW = NULL; - - if (!s_fResolvedApis) - { - s_pfnSSDLToSecDescW - = (decltype(s_pfnSSDLToSecDescW))RTLdrGetSystemSymbol("advapi32.dll", - "ConvertStringSecurityDescriptorToSecurityDescriptorW"); - ASMCompilerBarrier(); - s_fResolvedApis = true; - } - if (s_pfnSSDLToSecDescW) + if (!fRestrictToUser) { /* - * We'll create a security descriptor from a SDDL that denies - * access to network clients (this is local IPC after all), it - * makes some further restrictions to prevent non-authenticated - * users from screwing around. + * Preserve the legacy descriptor behavior. The initialized ACL was + * historically not attached to the descriptor, so Windows selected + * the creating token's default DACL. */ - PCRTUTF16 pwszSDDL = fServer ? RTLOCALIPC_WIN_SDDL_SERVER : RTLOCALIPC_WIN_SDDL_CLIENT; - ULONG cbSecDesc = 0; - SetLastError(0); - if (s_pfnSSDLToSecDescW(pwszSDDL, SDDL_REVISION_1, &pSecDesc, &cbSecDesc)) + uint32_t const cbAlloc = SECURITY_DESCRIPTOR_MIN_LENGTH * 2 + _8K; + pSecDesc = LocalAlloc(LMEM_FIXED, cbAlloc); + if (!pSecDesc) + return VERR_NO_MEMORY; + RT_BZERO(pSecDesc, cbAlloc); + + uint32_t const cbDacl = cbAlloc - SECURITY_DESCRIPTOR_MIN_LENGTH * 2; + PACL const pDacl = (PACL)((uint8_t *)pSecDesc + SECURITY_DESCRIPTOR_MIN_LENGTH * 2); + + if ( InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION) + && InitializeAcl(pDacl, cbDacl, ACL_REVISION)) { - DWORD dwErr = GetLastError(); RT_NOREF(dwErr); - AssertPtr(pSecDesc); *ppDesc = pSecDesc; return VINF_SUCCESS; } rc = RTErrConvertFromWin32(GetLastError()); + LocalFree(pSecDesc); + return rc; } - else -#endif + { /* * Manually construct the descriptor. * * This is a bit crude. The 8KB is probably 50+ times more than what we need. */ - uint32_t const cbAlloc = SECURITY_DESCRIPTOR_MIN_LENGTH * 2 + _8K; + HANDLE hSelfToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hSelfToken)) + return RTErrConvertFromWin32(GetLastError()); + + PTOKEN_USER pSelfTokenUser = NULL; + rc = rtLocalIpcWinQueryTokenUser(hSelfToken, &pSelfTokenUser); + PSID pSelfLogonSid = NULL; + if (RT_SUCCESS(rc)) + { + rc = rtLocalIpcWinQueryTokenLogonSid(hSelfToken, &pSelfLogonSid); + if ( rc == VERR_ACCESS_DENIED + && rtLocalIpcWinIsLocalSystemSid(pSelfTokenUser->User.Sid)) + { + DWORD const cbSystemSid = GetLengthSid(pSelfTokenUser->User.Sid); + pSelfLogonSid = (PSID)RTMemTmpAlloc(cbSystemSid); + if (!pSelfLogonSid) + rc = VERR_NO_TMP_MEMORY; + else if (CopySid(cbSystemSid, pSelfLogonSid, pSelfTokenUser->User.Sid)) + rc = VINF_SUCCESS; + else + { + rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pSelfLogonSid); + pSelfLogonSid = NULL; + } + } + } + CloseHandle(hSelfToken); + if (RT_FAILURE(rc)) + { + RTMemTmpFree(pSelfLogonSid); + RTMemTmpFree(pSelfTokenUser); + return rc; + } + + DWORD const cbOwnerSid = GetLengthSid(pSelfTokenUser->User.Sid); + AssertReturnStmt(cbOwnerSid > 0 && cbOwnerSid <= SECURITY_MAX_SID_SIZE, + RTMemTmpFree(pSelfLogonSid); RTMemTmpFree(pSelfTokenUser), VERR_INVALID_PARAMETER); + DWORD const cbLogonSid = GetLengthSid(pSelfLogonSid); + AssertReturnStmt(cbLogonSid > 0 && cbLogonSid <= SECURITY_MAX_SID_SIZE, + RTMemTmpFree(pSelfLogonSid); RTMemTmpFree(pSelfTokenUser), VERR_INVALID_PARAMETER); + + uint32_t const cbDacl = _8K; + uint32_t const cbAlloc = SECURITY_DESCRIPTOR_MIN_LENGTH * 2 + cbDacl + SECURITY_MAX_SID_SIZE * 2; pSecDesc = LocalAlloc(LMEM_FIXED, cbAlloc); if (!pSecDesc) + { + RTMemTmpFree(pSelfLogonSid); + RTMemTmpFree(pSelfTokenUser); return VERR_NO_MEMORY; + } RT_BZERO(pSecDesc, cbAlloc); - uint32_t const cbDacl = cbAlloc - SECURITY_DESCRIPTOR_MIN_LENGTH * 2; PACL const pDacl = (PACL)((uint8_t *)pSecDesc + SECURITY_DESCRIPTOR_MIN_LENGTH * 2); + PSID const pOwner = (PSID)((uint8_t *)pDacl + cbDacl); + PSID const pAccessSid = (PSID)((uint8_t *)pOwner + SECURITY_MAX_SID_SIZE); - if ( InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION) + if ( CopySid(SECURITY_MAX_SID_SIZE, pOwner, pSelfTokenUser->User.Sid) + && CopySid(SECURITY_MAX_SID_SIZE, pAccessSid, pSelfLogonSid) + && InitializeSecurityDescriptor(pSecDesc, SECURITY_DESCRIPTOR_REVISION) && InitializeAcl(pDacl, cbDacl, ACL_REVISION)) { - if (rtLocalIpcBuildDacl(pDacl, fServer)) + NTSTATUS rcNt = rtLocalIpcBuildDacl(pDacl, pAccessSid, fServer); + if (NT_SUCCESS(rcNt)) { - *ppDesc = pSecDesc; - return VINF_SUCCESS; + rcNt = RtlSetDaclSecurityDescriptor(pSecDesc, TRUE /*fDaclPresent*/, pDacl, FALSE /*fDaclDefaulted*/); + if ( NT_SUCCESS(rcNt) + && SetSecurityDescriptorOwner(pSecDesc, pOwner, FALSE /*bOwnerDefaulted*/)) + { + *ppDesc = pSecDesc; + RTMemTmpFree(pSelfLogonSid); + RTMemTmpFree(pSelfTokenUser); + return VINF_SUCCESS; + } + if (NT_SUCCESS(rcNt)) + rc = RTErrConvertFromWin32(GetLastError()); + else + rc = RTErrConvertFromNtStatus(rcNt); } - rc = VERR_GENERAL_FAILURE; + else + rc = RTErrConvertFromNtStatus(rcNt); } else rc = RTErrConvertFromWin32(GetLastError()); + RTMemTmpFree(pSelfLogonSid); + RTMemTmpFree(pSelfTokenUser); LocalFree(pSecDesc); } return rc; @@ -352,19 +749,22 @@ static int rtLocalIpcServerWinAllocSecurityDescriptor(PSECURITY_DESCRIPTOR *ppDe * @param phNmPipe Where to store the named pipe handle on success. * This will be set to INVALID_HANDLE_VALUE on failure. * @param pwszPipeName The named pipe name, full, UTF-16 encoded. + * @param fFlags The RTLOCALIPC_FLAGS_* used to create the server. * @param fFirst Set on the first call (from RTLocalIpcServerCreate), * otherwise clear. Governs the * FILE_FLAG_FIRST_PIPE_INSTANCE flag. */ -static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, PCRTUTF16 pwszPipeName, bool fFirst) +static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, PCRTUTF16 pwszPipeName, uint32_t fFlags, bool fFirst) { *phNmPipe = INVALID_HANDLE_VALUE; /* - * Create a security descriptor blocking access to the pipe via network. + * Create the legacy descriptor or, when requested, one blocking network + * and other-user access. */ PSECURITY_DESCRIPTOR pSecDesc; - int rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, fFirst /* Server? */); + int rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, fFirst /* Server? */, + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { #if 0 @@ -446,15 +846,20 @@ static int rtLocalIpcServerWinCreatePipeInstance(PHANDLE phNmPipe, PCRTUTF16 pws * @param pszName The name to validate. * @param pcwcFullName Where to return the UTF-16 length of the full name. * @param fNative Whether it's a native name or a portable name. + * @param fRestrictToUser Whether portable names use the login-session + * namespace. */ -static int rtLocalIpcWinValidateName(const char *pszName, size_t *pcwcFullName, bool fNative) +static int rtLocalIpcWinValidateName(const char *pszName, size_t *pcwcFullName, bool fNative, bool fRestrictToUser) { AssertPtrReturn(pszName, VERR_INVALID_POINTER); AssertReturn(*pszName, VERR_INVALID_NAME); if (!fNative) { - size_t cwcName = RT_ELEMENTS(RTLOCALIPC_WIN_PREFIX) - 1; + size_t cwcName = fRestrictToUser ? RT_ELEMENTS(RTLOCALIPC_WIN_USER_PREFIX) - 1 + : RT_ELEMENTS(RTLOCALIPC_WIN_PREFIX) - 1; + if (fRestrictToUser) + cwcName += RTLOCALIPC_WIN_SESSION_ID_CWC + RTLOCALIPC_WIN_LOGON_ID_CWC; for (;;) { char ch = *pszName++; @@ -488,16 +893,49 @@ static int rtLocalIpcWinValidateName(const char *pszName, size_t *pcwcFullName, * @param cwcFullName The output buffer size excluding the terminator. * @param fNative Whether the user supplied name is a native or * portable one. + * @param fRestrictToUser Whether portable names use the login-session + * namespace. */ -static int rtLocalIpcWinConstructName(const char *pszName, PRTUTF16 pwszFullName, size_t cwcFullName, bool fNative) +static int rtLocalIpcWinConstructName(const char *pszName, PRTUTF16 pwszFullName, size_t cwcFullName, + bool fNative, bool fRestrictToUser) { if (!fNative) { - static RTUTF16 const s_wszPrefix[] = RTLOCALIPC_WIN_PREFIX; - Assert(cwcFullName * sizeof(RTUTF16) > sizeof(s_wszPrefix)); - memcpy(pwszFullName, s_wszPrefix, sizeof(s_wszPrefix)); - cwcFullName -= RT_ELEMENTS(s_wszPrefix) - 1; - pwszFullName += RT_ELEMENTS(s_wszPrefix) - 1; + static RTUTF16 const s_wszPrefix[] = RTLOCALIPC_WIN_PREFIX; + static RTUTF16 const s_wszUserPrefix[] = RTLOCALIPC_WIN_USER_PREFIX; + PCRTUTF16 const pwszPrefix = fRestrictToUser ? s_wszUserPrefix : s_wszPrefix; + size_t const cwcPrefix = fRestrictToUser ? RT_ELEMENTS(s_wszUserPrefix) - 1 + : RT_ELEMENTS(s_wszPrefix) - 1; + Assert(cwcFullName > cwcPrefix); + memcpy(pwszFullName, pwszPrefix, cwcPrefix * sizeof(RTUTF16)); + cwcFullName -= cwcPrefix; + pwszFullName += cwcPrefix; + + if (fRestrictToUser) + { + uint32_t idSession = 0; + int rc = rtLocalIpcWinQuerySelfSessionId(&idSession); + if (RT_FAILURE(rc)) + return rc; + uint32_t idLogonHigh = 0; + uint32_t idLogonLow = 0; + rc = rtLocalIpcWinQuerySelfLogonId(&idLogonHigh, &idLogonLow); + if (RT_FAILURE(rc)) + return rc; + AssertReturn(cwcFullName >= RTLOCALIPC_WIN_SESSION_ID_CWC, VERR_BUFFER_OVERFLOW); + ssize_t const cwcSession = RTUtf16Printf(pwszFullName, RTLOCALIPC_WIN_SESSION_ID_CWC + 1, + "%08RX32-", idSession); + AssertReturn(cwcSession == RTLOCALIPC_WIN_SESSION_ID_CWC, VERR_INTERNAL_ERROR); + cwcFullName -= RTLOCALIPC_WIN_SESSION_ID_CWC; + pwszFullName += RTLOCALIPC_WIN_SESSION_ID_CWC; + + AssertReturn(cwcFullName >= RTLOCALIPC_WIN_LOGON_ID_CWC, VERR_BUFFER_OVERFLOW); + ssize_t const cwcLogon = RTUtf16Printf(pwszFullName, RTLOCALIPC_WIN_LOGON_ID_CWC + 1, + "%08RX32%08RX32-", idLogonHigh, idLogonLow); + AssertReturn(cwcLogon == RTLOCALIPC_WIN_LOGON_ID_CWC, VERR_INTERNAL_ERROR); + cwcFullName -= RTLOCALIPC_WIN_LOGON_ID_CWC; + pwszFullName += RTLOCALIPC_WIN_LOGON_ID_CWC; + } } return RTStrToUtf16Ex(pszName, RTSTR_MAX, &pwszFullName, cwcFullName + 1, NULL); } @@ -512,7 +950,9 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa *phServer = NIL_RTLOCALIPCSERVER; AssertReturn(!(fFlags & ~RTLOCALIPC_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); size_t cwcFullName; - int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { /* @@ -523,10 +963,13 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa AssertReturn(pThis, VERR_NO_MEMORY); pThis->u32Magic = RTLOCALIPCSERVER_MAGIC; + pThis->fFlags = fFlags; pThis->cRefs = 1; /* the one we return */ pThis->fCancelled = false; - rc = rtLocalIpcWinConstructName(pszName, pThis->wszName, cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME)); + rc = rtLocalIpcWinConstructName(pszName, pThis->wszName, cwcFullName, + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { rc = RTCritSectInit(&pThis->CritSect); @@ -540,7 +983,8 @@ RTDECL(int) RTLocalIpcServerCreate(PRTLOCALIPCSERVER phServer, const char *pszNa pThis->OverlappedIO.Internal = STATUS_PENDING; pThis->OverlappedIO.hEvent = pThis->hEvent; - rc = rtLocalIpcServerWinCreatePipeInstance(&pThis->hNmPipe, pThis->wszName, true /* fFirst */); + rc = rtLocalIpcServerWinCreatePipeInstance(&pThis->hNmPipe, pThis->wszName, pThis->fFlags, + true /* fFirst */); if (RT_SUCCESS(rc)) { *phServer = pThis; @@ -743,12 +1187,13 @@ RTDECL(int) RTLocalIpcServerListen(RTLOCALIPCSERVER hServer, PRTLOCALIPCSESSION || dwErr == ERROR_PIPE_CONNECTED) { HANDLE hNmPipe; - rc = rtLocalIpcServerWinCreatePipeInstance(&hNmPipe, pThis->wszName, false /* fFirst */); + rc = rtLocalIpcServerWinCreatePipeInstance(&hNmPipe, pThis->wszName, pThis->fFlags, false /* fFirst */); if (RT_SUCCESS(rc)) { HANDLE hNmPipeSession = pThis->hNmPipe; /* consumed */ pThis->hNmPipe = hNmPipe; - rc = rtLocalIpcWinCreateSession(phClientSession, hNmPipeSession); + rc = rtLocalIpcWinCreateSession(phClientSession, hNmPipeSession, + RT_BOOL(pThis->fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER)); } else { @@ -760,6 +1205,16 @@ RTDECL(int) RTLocalIpcServerListen(RTLOCALIPCSERVER hServer, PRTLOCALIPCSESSION AssertMsg(fRc, ("%d\n", GetLastError())); } } + else if ( (pThis->fFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER) + && rtLocalIpcWinIsPeerGoneError(dwErr)) + { + fRc = DisconnectNamedPipe(pThis->hNmPipe); + DWORD const dwDisconnectErr = fRc ? ERROR_SUCCESS : GetLastError(); + if (fRc || rtLocalIpcWinIsPeerGoneError(dwDisconnectErr)) + rc = VERR_TRY_AGAIN; + else + rc = RTErrConvertFromWin32(dwDisconnectErr); + } else rc = RTErrConvertFromWin32(dwErr); } @@ -844,15 +1299,29 @@ RTDECL(int) RTLocalIpcServerCancel(RTLOCALIPCSERVER hServer) * INVALID_HANDLE_VALUE if client connect. This will * be consumed by this session, meaning on failure to * create the session it will be closed. + * @param fRestricted Whether to enforce matching Windows session IDs. */ -static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession) +static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE hNmPipeSession, bool fRestricted) { AssertPtr(ppSession); + int rc; + if (fRestricted && hNmPipeSession != INVALID_HANDLE_VALUE) + { + rc = rtLocalIpcWinVerifyPeerSession(hNmPipeSession, true /*fServerSide*/); + if (RT_FAILURE(rc)) + { + BOOL const fRc = CloseHandle(hNmPipeSession); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + if (rc == VERR_ACCESS_DENIED) + rc = VERR_TRY_AGAIN; + return rc; + } + } + /* * Allocate and initialize the session instance data. */ - int rc; PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)RTMemAllocZ(sizeof(*pThis)); if (pThis) { @@ -861,6 +1330,7 @@ static int rtLocalIpcWinCreateSession(PRTLOCALIPCSESSIONINT *ppSession, HANDLE h pThis->fCancelled = false; pThis->fZeroByteRead = false; pThis->fServerSide = hNmPipeSession != INVALID_HANDLE_VALUE; + pThis->fRestricted = fRestricted; pThis->hNmPipe = hNmPipeSession; #if 0 /* Non-blocking writes are not yet supported. */ pThis->pbBounceBuf = NULL; @@ -920,27 +1390,31 @@ RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *p AssertReturn(!(fFlags & ~RTLOCALIPC_C_FLAGS_VALID_MASK), VERR_INVALID_FLAGS); size_t cwcFullName; - int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + int rc = rtLocalIpcWinValidateName(pszName, &cwcFullName, + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { /* * Create a session (shared with server client session creation). */ PRTLOCALIPCSESSIONINT pThis; - rc = rtLocalIpcWinCreateSession(&pThis, INVALID_HANDLE_VALUE); + rc = rtLocalIpcWinCreateSession(&pThis, INVALID_HANDLE_VALUE, + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)); if (RT_SUCCESS(rc)) { /* * Try open the pipe. */ PSECURITY_DESCRIPTOR pSecDesc; - rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, false /*fServer*/); + rc = rtLocalIpcServerWinAllocSecurityDescriptor(&pSecDesc, false /*fServer*/, false /*fRestrictToUser*/); if (RT_SUCCESS(rc)) { PRTUTF16 pwszFullName = RTUtf16Alloc((cwcFullName + 1) * sizeof(RTUTF16)); if (pwszFullName) rc = rtLocalIpcWinConstructName(pszName, pwszFullName, cwcFullName, - RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME)); + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_NATIVE_NAME), + RT_BOOL(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)); else rc = VERR_NO_UTF16_MEMORY; if (RT_SUCCESS(rc)) @@ -950,30 +1424,45 @@ RTDECL(int) RTLocalIpcSessionConnect(PRTLOCALIPCSESSION phSession, const char *p SecAttrs.lpSecurityDescriptor = pSecDesc; SecAttrs.bInheritHandle = FALSE; - /* The SECURITY_XXX flags are needed in order to prevent the server from impersonating with - this thread's security context (supported at least back to NT 3.51). See @bugref{9773}. */ + /* Default to an anonymous security context. Callers that explicitly need same-user + verification may permit the server to identify, but not impersonate, the client. */ + DWORD const fSecurityQos = fFlags & RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + ? SECURITY_IDENTIFICATION : SECURITY_ANONYMOUS; HANDLE hPipe = CreateFileW(pwszFullName, GENERIC_READ | GENERIC_WRITE, 0 /*no sharing*/, &SecAttrs, OPEN_EXISTING, - FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_ANONYMOUS, + FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | fSecurityQos, NULL /*no template handle*/); if (hPipe != INVALID_HANDLE_VALUE) { - pThis->hNmPipe = hPipe; + if (!(fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)) + rc = VINF_SUCCESS; + else + rc = rtLocalIpcWinVerifyPeerSession(hPipe, false /*fServerSide*/); + if ( RT_SUCCESS(rc) + && (fFlags & RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER)) + rc = rtLocalIpcWinVerifyPipeOwnerUser(hPipe); + if (RT_SUCCESS(rc)) + { + pThis->hNmPipe = hPipe; - LocalFree(pSecDesc); - RTUtf16Free(pwszFullName); + LocalFree(pSecDesc); + RTUtf16Free(pwszFullName); - /* - * We're done! - */ - *phSession = pThis; - return VINF_SUCCESS; - } + /* + * We're done! + */ + *phSession = pThis; + return VINF_SUCCESS; + } - rc = RTErrConvertFromWin32(GetLastError()); + BOOL const fRc = CloseHandle(hPipe); + AssertMsg(fRc, ("%d\n", GetLastError())); NOREF(fRc); + } + else + rc = RTErrConvertFromWin32(GetLastError()); } RTUtf16Free(pwszFullName); @@ -1765,8 +2254,105 @@ RTDECL(int) RTLocalIpcSessionCancel(RTLOCALIPCSESSION hSession) RTDECL(int) RTLocalIpcSessionQueryProcess(RTLOCALIPCSESSION hSession, PRTPROCESS pProcess) { - RT_NOREF_PV(hSession); RT_NOREF_PV(pProcess); - return VERR_NOT_SUPPORTED; + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + AssertPtrReturn(pProcess, VERR_INVALID_POINTER); + *pProcess = NIL_RTPROCESS; + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (!pThis->fCancelled) + { + rc = RTOnce(&g_rtLocalIpcWinQueryProcessResolveOnce, rtLocalIpcWinQueryProcessResolveOnce, NULL); + if (RT_SUCCESS(rc)) + { + ULONG idProcess = 0; + BOOL const fRc = pThis->fServerSide + ? g_pfnGetNamedPipeClientProcessId(pThis->hNmPipe, &idProcess) + : g_pfnGetNamedPipeServerProcessId(pThis->hNmPipe, &idProcess); + if (fRc) + { + *pProcess = (RTPROCESS)idProcess; + rc = VINF_SUCCESS; + } + else + rc = RTErrConvertFromWin32(GetLastError()); + } + } + else + rc = VERR_CANCELLED; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + + return rc; +} + + +RTDECL(int) RTLocalIpcSessionVerifySameUser(RTLOCALIPCSESSION hSession) +{ + PRTLOCALIPCSESSIONINT pThis = (PRTLOCALIPCSESSIONINT)hSession; + AssertPtrReturn(pThis, VERR_INVALID_HANDLE); + AssertReturn(pThis->u32Magic == RTLOCALIPCSESSION_MAGIC, VERR_INVALID_HANDLE); + + int rc = RTCritSectEnter(&pThis->CritSect); + if (RT_SUCCESS(rc)) + { + rtLocalIpcSessionRetain(pThis); + if (!pThis->fCancelled) + { + if (pThis->fRestricted) + { + rc = rtLocalIpcWinVerifyPeerSession(pThis->hNmPipe, pThis->fServerSide); + if (RT_FAILURE(rc)) + { + rtLocalIpcSessionReleaseAndUnlock(pThis); + return rc; + } + } + + if (pThis->fServerSide) + { + HANDLE hPeerToken = NULL; + if (ImpersonateNamedPipeClient(pThis->hNmPipe)) + { + if (!OpenThreadToken(GetCurrentThread(), TOKEN_QUERY, TRUE /*OpenAsSelf*/, &hPeerToken)) + rc = VERR_ACCESS_DENIED; + + if (!RevertToSelf()) + { + DWORD const dwErr = GetLastError(); + BOOL const fCleared = SetThreadToken(NULL, NULL); + AssertMsg(fCleared, ("SetThreadToken failed: %u (RevertToSelf: %u)\n", GetLastError(), dwErr)); + RT_NOREF(fCleared); + rc = RTErrConvertFromWin32(dwErr); + } + + if (RT_SUCCESS(rc)) + { + PTOKEN_USER pPeerTokenUser = NULL; + rc = rtLocalIpcWinQueryTokenUser(hPeerToken, &pPeerTokenUser); + if (RT_SUCCESS(rc)) + rc = rtLocalIpcWinVerifyUserSid(pPeerTokenUser->User.Sid); + RTMemTmpFree(pPeerTokenUser); + } + } + else + rc = VERR_ACCESS_DENIED; + + if (hPeerToken != NULL) + CloseHandle(hPeerToken); + } + else + rc = rtLocalIpcWinVerifyPipeOwnerUser(pThis->hNmPipe); + } + else + rc = VERR_CANCELLED; + rtLocalIpcSessionReleaseAndUnlock(pThis); + } + return rc; } @@ -1782,4 +2368,3 @@ RTDECL(int) RTLocalIpcSessionQueryGroupId(RTLOCALIPCSESSION hSession, PRTGID pGi RT_NOREF_PV(hSession); RT_NOREF_PV(pGid); return VERR_NOT_SUPPORTED; } - diff --git a/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm b/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm index e06321f5ba50..2703033c1487 100644 --- a/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm +++ b/src/VBox/Runtime/r3/win/nocrt-WinMainCRTStartup-win.asm @@ -1,4 +1,4 @@ -; $Id: nocrt-WinMainCRTStartup-win.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: nocrt-WinMainCRTStartup-win.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Alias WinMainCRTStartup to CustomMainEntrypoint in nocrt-startup-exe-win.cpp. ; @@ -42,3 +42,5 @@ BEGINPROC WinMainCRTStartup jmp NAME(CustomMainEntrypoint) ENDPROC WinMainCRTStartup + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm b/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm index b3a1dac56542..c3d13334c7be 100644 --- a/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm +++ b/src/VBox/Runtime/r3/win/nocrt-atexit-win.asm @@ -1,4 +1,4 @@ -; $Id: nocrt-atexit-win.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: nocrt-atexit-win.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Alias atexit to rtnocrt_atexit in nocrt-startup-exe-win.cpp. ; @@ -42,3 +42,4 @@ BEGINPROC atexit jmp NAME(nocrt_atexit) ENDPROC atexit +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm b/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm index bceb44133821..98c91fd3413c 100644 --- a/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm +++ b/src/VBox/Runtime/r3/win/nocrt-mainCRTStartup-win.asm @@ -1,4 +1,4 @@ -; $Id: nocrt-mainCRTStartup-win.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: nocrt-mainCRTStartup-win.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Alias mainCRTStartup to CustomMainEntrypoint in nocrt-startup-exe-win.cpp. ; @@ -42,3 +42,5 @@ BEGINPROC mainCRTStartup jmp NAME(CustomMainEntrypoint) ENDPROC mainCRTStartup + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/shmem-win.cpp b/src/VBox/Runtime/r3/win/shmem-win.cpp index 7f027fe76a38..f29163f280d6 100644 --- a/src/VBox/Runtime/r3/win/shmem-win.cpp +++ b/src/VBox/Runtime/r3/win/shmem-win.cpp @@ -1,4 +1,4 @@ -/* $Id: shmem-win.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: shmem-win.cpp 114873 2026-08-06 20:59:25Z andreas.loeffler@oracle.com $ */ /** @file * IPRT - Named shared memory object, Windows Implementation. */ @@ -189,6 +189,7 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, rc = RTStrToUtf16Ex(&szName[0], RTSTR_MAX, &pwszName, 0, NULL); if (RT_SUCCESS(rc)) { + DWORD dwErr; if (fFlags & RTSHMEM_O_F_CREATE) { #if HC_ARCH_BITS == 64 @@ -215,8 +216,17 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, else fProt |= PAGE_READWRITE; } + SetLastError(ERROR_SUCCESS); pThis->hShmObj = CreateFileMappingW(INVALID_HANDLE_VALUE, NULL, fProt, dwSzMaxHigh, dwSzMaxLow, pwszName); + dwErr = GetLastError(); + if ( pThis->hShmObj != NULL + && (fFlags & RTSHMEM_O_F_CREATE_EXCL) == RTSHMEM_O_F_CREATE_EXCL + && dwErr == ERROR_ALREADY_EXISTS) + { + CloseHandle(pThis->hShmObj); + pThis->hShmObj = NULL; + } } else { @@ -229,6 +239,7 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, fProt |= FILE_MAP_WRITE; pThis->hShmObj = OpenFileMappingW(fProt, FALSE, pwszName); + dwErr = GetLastError(); } RTUtf16Free(pwszName); if (pThis->hShmObj != NULL) @@ -237,7 +248,7 @@ RTDECL(int) RTShMemOpen(PRTSHMEM phShMem, const char *pszName, uint32_t fFlags, return VINF_SUCCESS; } else - rc = RTErrConvertFromWin32(GetLastError()); + rc = RTErrConvertFromWin32(dwErr); } } else @@ -470,4 +481,3 @@ RTDECL(int) RTShMemUnmapRegion(RTSHMEM hShMem, void *pv) return rc; } - diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm index db466a30ce86..c5ab2c7dc7b3 100644 --- a/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm +++ b/src/VBox/Runtime/r3/win/vcc-fakes-kernel32-A.asm @@ -1,4 +1,4 @@ -; $Id: vcc-fakes-kernel32-A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: vcc-fakes-kernel32-A.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Wrappers for kernel32 APIs missing in NT4 and earlier. ; @@ -56,3 +56,5 @@ GLOBALNAME vcc100_kernel32_fakes_asm %error "PORT ME!" %endif + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm index fb1c905f86d1..e2ab4b2b67b8 100644 --- a/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm +++ b/src/VBox/Runtime/r3/win/vcc-fakes-ntdll-A.asm @@ -1,4 +1,4 @@ -; $Id: vcc-fakes-ntdll-A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: vcc-fakes-ntdll-A.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Wrappers for ntdll APIs misisng NT4. ; @@ -55,3 +55,5 @@ GLOBALNAME vcc100_ntdll_fakes_asm MAKE_IMPORT_ENTRY RtlGetLastWin32Error, 0 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm index c37afbae53f9..ef79bb1c5ec4 100644 --- a/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm +++ b/src/VBox/Runtime/r3/win/vcc-fakes-shell32-A.asm @@ -1,4 +1,4 @@ -; $Id: vcc-fakes-shell32-A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: vcc-fakes-shell32-A.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Wrappers for shell32 APIs missing in NT 4 and earlier. ; @@ -57,3 +57,5 @@ GLOBALNAME vcc100_shell32_fakes_asm ; NT 3.1 MAKE_IMPORT_ENTRY CommandLineToArgvW, 8 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm b/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm index 28251acc95b5..e315a5abe5a9 100644 --- a/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm +++ b/src/VBox/Runtime/r3/win/vcc-fakes-ws2_32-A.asm @@ -1,4 +1,4 @@ -; $Id: vcc-fakes-ws2_32-A.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: vcc-fakes-ws2_32-A.asm 115005 2026-08-12 23:33:52Z knut.osmundsen@oracle.com $ ;; @file ; IPRT - Wrappers for ws2_32 APIs misisng NT4. ; @@ -56,3 +56,5 @@ GLOBALNAME vcc100_ws2_32_fakes_asm MAKE_IMPORT_ENTRY getaddrinfo, 16 MAKE_IMPORT_ENTRY freeaddrinfo, 4 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp index 66dbb8487d62..57914adfd917 100644 --- a/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp +++ b/src/VBox/Runtime/testcase/tstRTLocalIpc.cpp @@ -1,4 +1,4 @@ -/* $Id: tstRTLocalIpc.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: tstRTLocalIpc.cpp 114978 2026-08-11 08:33:42Z andreas.loeffler@oracle.com $ */ /** @file * IPRT Testcase - RTLocalIpc API. */ @@ -41,8 +41,10 @@ #include #include +#include #include #include +#include #include #include #include @@ -53,6 +55,13 @@ #include #include #include +#include + +#ifdef RT_OS_WINDOWS +# include +#else +# include +#endif /********************************************************************************************************************************* @@ -98,6 +107,9 @@ static void testBasics(void) RTTESTI_CHECK_RC(RTLocalIpcSessionCancel(NULL), VERR_INVALID_HANDLE); RTTESTI_CHECK_RC(RTLocalIpcSessionClose(NULL), VINF_SUCCESS); + RTPROCESS Process = NIL_RTPROCESS; + RTTESTI_CHECK_RC(RTLocalIpcSessionQueryProcess(NULL, &Process), VERR_INVALID_HANDLE); + RTTESTI_CHECK_RC(RTLocalIpcSessionVerifySameUser(NULL), VERR_INVALID_HANDLE); /* Basic client creation / destruction. */ RTTESTI_CHECK_RC_RETV(rc = RTLocalIpcSessionConnect(&hIpcSession, "BasicTest", 0), VERR_FILE_NOT_FOUND); @@ -107,6 +119,263 @@ static void testBasics(void) } +#ifndef RT_OS_WINDOWS +static void testRestrictedNamespaceProperties(void) +{ + RTTestISub("Restricted namespace properties"); + + char szUserDir[] = "/tmp/tstRTLocalIpc-XXXXXX"; + int rc = RTDirCreateTemp(szUserDir, 0700); + RTTESTI_CHECK_RC_RETV(rc, VINF_SUCCESS); + + char *pszSavedRuntimeDir = RTEnvDupEx(RTENV_DEFAULT, "XDG_RUNTIME_DIR"); + rc = RTEnvSet("XDG_RUNTIME_DIR", szUserDir); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + RTLOCALIPCSERVER hIpcServer = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcNamespace", + RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + char szSocket[RTPATH_MAX]; + int rcPath = RTStrCopy(szSocket, sizeof(szSocket), szUserDir); + if (RT_SUCCESS(rcPath)) + rcPath = RTPathAppend(szSocket, sizeof(szSocket), ".iprt-tstRTLocalIpcNamespace"); + RTTESTI_CHECK_RC(rcPath, VINF_SUCCESS); + if (RT_SUCCESS(rc) && RT_SUCCESS(rcPath)) + { + RTFSOBJINFO ObjInfo; + RTTESTI_CHECK_RC(rcPath = RTPathQueryInfoEx(szSocket, &ObjInfo, RTFSOBJATTRADD_UNIX, RTPATH_F_ON_LINK), + VINF_SUCCESS); + if (RT_SUCCESS(rcPath)) + { + RTTESTI_CHECK(RTFS_IS_SOCKET(ObjInfo.Attr.fMode)); + RTTESTI_CHECK((ObjInfo.Attr.fMode & RTFS_UNIX_ALL_ACCESS_PERMS) == (RTFS_UNIX_IRUSR | RTFS_UNIX_IWUSR)); + RTTESTI_CHECK(ObjInfo.Attr.u.Unix.uid == (RTUID)geteuid()); + } + } + + if (hIpcServer != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED); + if (RT_SUCCESS(rcPath)) + { + RTFSOBJINFO ObjInfo; + rcPath = RTPathQueryInfoEx(szSocket, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + RTTESTI_CHECK(rcPath == VERR_FILE_NOT_FOUND || rcPath == VERR_PATH_NOT_FOUND); + } + + /* An unsafe runtime directory must be ignored in favor of a protected + fallback namespace. */ + char szFallbackHome[] = "/tmp/tstRTLocalIpc-home-XXXXXX"; + RTTESTI_CHECK_RC(rc = RTDirCreateTemp(szFallbackHome, 0700), VINF_SUCCESS); + char *pszSavedHome = RTEnvDupEx(RTENV_DEFAULT, "HOME"); + if (RT_SUCCESS(rc)) + RTTESTI_CHECK_RC(rc = RTEnvSet("HOME", szFallbackHome), VINF_SUCCESS); + RTTESTI_CHECK_RC(rc = RTPathSetMode(szUserDir, RTFS_UNIX_IRWXU | RTFS_UNIX_IRWXG | RTFS_UNIX_IRWXO), + VINF_SUCCESS); + if (RT_SUCCESS(rc)) + RTTESTI_CHECK_RC(rc = RTEnvSet("XDG_RUNTIME_DIR", szUserDir), VINF_SUCCESS); + hIpcServer = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcUnsafeNamespace", + RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + char szUnsafeSocket[RTPATH_MAX]; + int rcUnsafe = RTStrCopy(szUnsafeSocket, sizeof(szUnsafeSocket), szUserDir); + if (RT_SUCCESS(rcUnsafe)) + rcUnsafe = RTPathAppend(szUnsafeSocket, sizeof(szUnsafeSocket), ".iprt-tstRTLocalIpcUnsafeNamespace"); + RTTESTI_CHECK_RC(rcUnsafe, VINF_SUCCESS); + if (RT_SUCCESS(rcUnsafe)) + { + RTFSOBJINFO ObjInfo; + rcUnsafe = RTPathQueryInfoEx(szUnsafeSocket, &ObjInfo, RTFSOBJATTRADD_NOTHING, RTPATH_F_ON_LINK); + RTTESTI_CHECK(rcUnsafe == VERR_FILE_NOT_FOUND || rcUnsafe == VERR_PATH_NOT_FOUND); + } + if (hIpcServer != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED); + RTTESTI_CHECK_RC(RTPathSetMode(szUserDir, RTFS_UNIX_IRWXU), VINF_SUCCESS); + if (pszSavedHome) + RTTESTI_CHECK_RC(RTEnvSet("HOME", pszSavedHome), VINF_SUCCESS); + else + RTTESTI_CHECK_RC(RTEnvUnset("HOME"), VINF_SUCCESS); + RTStrFree(pszSavedHome); + char szFallbackNamespace[RTPATH_MAX]; + RTTESTI_CHECK_RC(rcUnsafe = RTStrCopy(szFallbackNamespace, sizeof(szFallbackNamespace), szFallbackHome), VINF_SUCCESS); + if (RT_SUCCESS(rcUnsafe)) + RTTESTI_CHECK_RC(rcUnsafe = RTPathAppend(szFallbackNamespace, sizeof(szFallbackNamespace), ".iprt-localipc"), + VINF_SUCCESS); + /* Linux may select /run/user/ before reaching the home directory fallback. */ + if (RT_SUCCESS(rcUnsafe) && RTDirExists(szFallbackNamespace)) + RTTESTI_CHECK_RC(RTDirRemove(szFallbackNamespace), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRemove(szFallbackHome), VINF_SUCCESS); + + if (pszSavedRuntimeDir) + RTTESTI_CHECK_RC(RTEnvSet("XDG_RUNTIME_DIR", pszSavedRuntimeDir), VINF_SUCCESS); + else + RTTESTI_CHECK_RC(RTEnvUnset("XDG_RUNTIME_DIR"), VINF_SUCCESS); + RTStrFree(pszSavedRuntimeDir); + RTTESTI_CHECK_RC(RTDirRemove(szUserDir), VINF_SUCCESS); +} +#endif /* !RT_OS_WINDOWS */ + + +#ifdef RT_OS_WINDOWS +static void testRestrictedNamespaceProperties(void) +{ + RTTestISub("Restricted namespace properties"); + + HANDLE hToken = NULL; + if (!OpenProcessToken(GetCurrentProcess(), TOKEN_QUERY, &hToken)) + { + RTTestIFailed("OpenProcessToken failed: %u", GetLastError()); + return; + } + + DWORD idSession = 0; + DWORD cbTokenInfo = 0; + if (!GetTokenInformation(hToken, TokenSessionId, &idSession, sizeof(idSession), &cbTokenInfo)) + { + DWORD const dwErr = GetLastError(); + CloseHandle(hToken); + RTTestIFailed("Querying TokenSessionId failed: %u", dwErr); + return; + } + + TOKEN_STATISTICS TokenStats; + cbTokenInfo = 0; + if (!GetTokenInformation(hToken, TokenStatistics, &TokenStats, sizeof(TokenStats), &cbTokenInfo)) + { + DWORD const dwErr = GetLastError(); + CloseHandle(hToken); + RTTestIFailed("Querying TokenStatistics failed: %u", dwErr); + return; + } + CloseHandle(hToken); + uint32_t const idLogonHigh = (uint32_t)TokenStats.AuthenticationId.HighPart; + uint32_t const idLogonLow = TokenStats.AuthenticationId.LowPart; + + char szName[128]; + ssize_t cch = RTStrPrintf2(szName, sizeof(szName), "tstRTLocalIpcNamespace-%RU32", (uint32_t)RTProcSelf()); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szName)); + + char szOldName[RTPATH_MAX]; + cch = RTStrPrintf2(szOldName, sizeof(szOldName), "\\\\.\\pipe\\LOCAL\\IPRT-%s", szName); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szOldName)); + + char szOtherSessionName[RTPATH_MAX]; + cch = RTStrPrintf2(szOtherSessionName, sizeof(szOtherSessionName), + "\\\\.\\pipe\\LOCAL\\IPRT-%08RX32-%08RX32%08RX32-%s", + (uint32_t)idSession ^ RT_BIT_32(31), idLogonHigh, idLogonLow, szName); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szOtherSessionName)); + + char szOtherLogonName[RTPATH_MAX]; + cch = RTStrPrintf2(szOtherLogonName, sizeof(szOtherLogonName), + "\\\\.\\pipe\\LOCAL\\IPRT-%08RX32-%08RX32%08RX32-%s", + (uint32_t)idSession, idLogonHigh ^ RT_BIT_32(31), idLogonLow, szName); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szOtherLogonName)); + + char szExpectedName[RTPATH_MAX]; + cch = RTStrPrintf2(szExpectedName, sizeof(szExpectedName), + "\\\\.\\pipe\\LOCAL\\IPRT-%08RX32-%08RX32%08RX32-%s", + (uint32_t)idSession, idLogonHigh, idLogonLow, szName); + RTTESTI_CHECK_RETV(cch > 0 && (size_t)cch < sizeof(szExpectedName)); + + RTLOCALIPCSERVER hOldName = NIL_RTLOCALIPCSERVER; + int rc = RTLocalIpcServerCreate(&hOldName, szOldName, RTLOCALIPC_FLAGS_NATIVE_NAME); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + + RTLOCALIPCSERVER hOtherSession = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hOtherSession, szOtherSessionName, RTLOCALIPC_FLAGS_NATIVE_NAME); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + RTLOCALIPCSERVER hOtherLogon = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hOtherLogon, szOtherLogonName, RTLOCALIPC_FLAGS_NATIVE_NAME); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + RTLOCALIPCSERVER hRestricted = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + rc = RTLocalIpcServerCreate(&hRestricted, szName, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + } + + RTLOCALIPCSERVER hExpectedCollision = NIL_RTLOCALIPCSERVER; + if (RT_SUCCESS(rc)) + { + int const rcCollision = RTLocalIpcServerCreate(&hExpectedCollision, szExpectedName, + RTLOCALIPC_FLAGS_NATIVE_NAME); + RTTESTI_CHECK_RC(rcCollision, VERR_ACCESS_DENIED); + } + + if (hRestricted != NIL_RTLOCALIPCSERVER) + { + PRTUTF16 pwszExpectedName = NULL; + int rcRaw = RTStrToUtf16(szExpectedName, &pwszExpectedName); + RTTESTI_CHECK_RC(rcRaw, VINF_SUCCESS); + if (RT_SUCCESS(rcRaw)) + { + HANDLE hVanishedClient = CreateFileW((LPCWSTR)pwszExpectedName, + GENERIC_READ | GENERIC_WRITE, + 0 /*dwShareMode*/, NULL /*pSecurityAttributes*/, OPEN_EXISTING, + FILE_FLAG_OVERLAPPED | SECURITY_SQOS_PRESENT | SECURITY_IDENTIFICATION, + NULL /*hTemplateFile*/); + if (hVanishedClient == INVALID_HANDLE_VALUE) + RTTestIFailed("Opening the raw restricted pipe failed: %u", GetLastError()); + else + { + RTTESTI_CHECK(CloseHandle(hVanishedClient)); + + RTLOCALIPCSESSION hGoneSession = NIL_RTLOCALIPCSESSION; + int const rcGone = RTLocalIpcServerListen(hRestricted, &hGoneSession); + RTTESTI_CHECK(rcGone == VERR_TRY_AGAIN || rcGone == VINF_SUCCESS); + if (hGoneSession != NIL_RTLOCALIPCSESSION) + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hGoneSession), VINF_OBJECT_DESTROYED); + + RTLOCALIPCSESSION hClientSession = NIL_RTLOCALIPCSESSION; + int const rcClient = RTLocalIpcSessionConnect(&hClientSession, szName, + RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION + | RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER); + RTTESTI_CHECK_RC(rcClient, VINF_SUCCESS); + if (RT_SUCCESS(rcClient)) + { + RTLOCALIPCSESSION hServerSession = NIL_RTLOCALIPCSESSION; + int const rcServer = RTLocalIpcServerListen(hRestricted, &hServerSession); + RTTESTI_CHECK_RC(rcServer, VINF_SUCCESS); + if (hServerSession != NIL_RTLOCALIPCSESSION) + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hServerSession), VINF_OBJECT_DESTROYED); + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED); + } + } + } + RTUtf16Free(pwszExpectedName); + } + + if (hExpectedCollision != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hExpectedCollision), VINF_OBJECT_DESTROYED); + if (hRestricted != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hRestricted), VINF_OBJECT_DESTROYED); + if (hOtherLogon != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hOtherLogon), VINF_OBJECT_DESTROYED); + if (hOtherSession != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hOtherSession), VINF_OBJECT_DESTROYED); + if (hOldName != NIL_RTLOCALIPCSERVER) + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hOldName), VINF_OBJECT_DESTROYED); +} +#endif /* RT_OS_WINDOWS */ + + /********************************************************************************************************************************* * * @@ -114,9 +383,25 @@ static void testBasics(void) * * *********************************************************************************************************************************/ +typedef struct TESTSERVERLISTENARGS +{ + /** The server handle. */ + RTLOCALIPCSERVER hIpcServer; + /** Whether the client is expected to be a different process. */ + bool fExpectOtherProcess; +} TESTSERVERLISTENARGS; +/** Pointer to server listener thread arguments. */ +typedef TESTSERVERLISTENARGS *PTESTSERVERLISTENARGS; + +/** The connection worker runs in a different process. */ +#define TEST_CONNECTION_F_OTHER_PROCESS RT_BIT_32(0) +/** The connection worker must use the protected user namespace. */ +#define TEST_CONNECTION_F_RESTRICTED RT_BIT_32(1) + + static DECLCALLBACK(int) testServerListenThread(RTTHREAD hSelf, void *pvUser) { - RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser; + PTESTSERVERLISTENARGS pArgs = (PTESTSERVERLISTENARGS)pvUser; RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck); RTTESTI_CHECK_RC_OK(RTThreadUserSignal(hSelf)); @@ -125,11 +410,38 @@ static DECLCALLBACK(int) testServerListenThread(RTTHREAD hSelf, void *pvUser) for (;;) { RTLOCALIPCSESSION hIpcSession; - rc = RTLocalIpcServerListen(hIpcServer, &hIpcSession); + rc = RTLocalIpcServerListen(pArgs->hIpcServer, &hIpcSession); if (RT_SUCCESS(rc)) { RTThreadSleep(8); /* windows output fudge (purely esthetical) */ RTTestIPrintf(RTTESTLVL_INFO, "testServerListenThread: Got new client connection.\n"); + uint8_t bIdentity = 0; + RTTESTI_CHECK_RC(rc = RTLocalIpcSessionRead(hIpcSession, &bIdentity, sizeof(bIdentity), NULL), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK(bIdentity == UINT8_C(0x42)); +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) \ + || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) || defined(RT_OS_SOLARIS) + RTTESTI_CHECK_RC(RTLocalIpcSessionVerifySameUser(hIpcSession), VINF_SUCCESS); +#else + RTTESTI_CHECK_RC(RTLocalIpcSessionVerifySameUser(hIpcSession), VERR_NOT_SUPPORTED); +#endif + } +#ifdef RT_OS_WINDOWS + RTPROCESS Process = NIL_RTPROCESS; + RTTESTI_CHECK_RC(rc = RTLocalIpcSessionQueryProcess(hIpcSession, &Process), VINF_SUCCESS); + if (RT_SUCCESS(rc)) + { + if (pArgs->fExpectOtherProcess) + RTTESTI_CHECK(Process != NIL_RTPROCESS && Process != RTProcSelf()); + else + RTTESTI_CHECK(Process == RTProcSelf()); + + RTPROCESS ProcessAgain = NIL_RTPROCESS; + RTTESTI_CHECK_RC(RTLocalIpcSessionQueryProcess(hIpcSession, &ProcessAgain), VINF_SUCCESS); + RTTESTI_CHECK(ProcessAgain == Process); + } +#endif RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED); } else @@ -148,12 +460,46 @@ static DECLCALLBACK(int) testServerListenThread(RTTHREAD hSelf, void *pvUser) static DECLCALLBACK(int) tstRTLocalIpcSessionConnectionChild(RTTHREAD hSelf, void *pvUser) { RTLOCALIPCSESSION hClientSession; - RT_NOREF_PV(hSelf); RT_NOREF_PV(pvUser); + RT_NOREF_PV(hSelf); + + uintptr_t const fWorker = (uintptr_t)pvUser; + uint32_t fConnect = RTLOCALIPC_C_FLAGS_ALLOW_IDENTIFICATION; + if (fWorker & TEST_CONNECTION_F_RESTRICTED) + fConnect |= RTLOCALIPC_C_FLAGS_RESTRICT_TO_USER; RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck); - RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionConnection",0 /* Flags */), + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionConnection", + fConnect), VINF_SUCCESS, rcCheck); + uint8_t const bIdentity = UINT8_C(0x42); + RTTEST_CHECK_RC_OK_RET(g_hTest, RTLocalIpcSessionWrite(hClientSession, &bIdentity, sizeof(bIdentity)), rcCheck); + RTTEST_CHECK_RC_OK_RET(g_hTest, RTLocalIpcSessionFlush(hClientSession), rcCheck); +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) \ + || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) || defined(RT_OS_SOLARIS) + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionVerifySameUser(hClientSession), VINF_SUCCESS, rcCheck); +#else + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionVerifySameUser(hClientSession), VERR_NOT_SUPPORTED, rcCheck); +#endif +#ifdef RT_OS_WINDOWS + RTPROCESS ProcessExpected = RTProcSelf(); + if (fWorker & TEST_CONNECTION_F_OTHER_PROCESS) + RTTEST_CHECK_RC_OK_RET(g_hTest, RTProcQueryParent(RTProcSelf(), &ProcessExpected), rcCheck); + + RTPROCESS Process = NIL_RTPROCESS; + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionQueryProcess(hClientSession, &Process), VINF_SUCCESS, rcCheck); + RTTEST_CHECK_RET(g_hTest, Process == ProcessExpected, VERR_GENERAL_FAILURE); + RTPROCESS ProcessAgain = NIL_RTPROCESS; + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionQueryProcess(hClientSession, &ProcessAgain), VINF_SUCCESS, rcCheck); + RTTEST_CHECK_RET(g_hTest, ProcessAgain == Process, VERR_GENERAL_FAILURE); +#else + RT_NOREF_PV(pvUser); +#endif +#if defined(RT_OS_WINDOWS) || defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) \ + || defined(RT_OS_FREEBSD) || defined(RT_OS_NETBSD) || defined(RT_OS_OPENBSD) || defined(RT_OS_SOLARIS) + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionCancel(hClientSession), VINF_SUCCESS, rcCheck); + RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionVerifySameUser(hClientSession), VERR_CANCELLED, rcCheck); +#endif RTTEST_CHECK_RC_RET(g_hTest, RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED, rcCheck); @@ -161,23 +507,93 @@ static DECLCALLBACK(int) tstRTLocalIpcSessionConnectionChild(RTTHREAD hSelf, voi } -static void testSessionConnection(const char *pszExecPath) +static void testSessionConnection(const char *pszExecPath, uint32_t fServerFlags) { - RTTestISub(!pszExecPath ? "Connect from thread" : "Connect from child"); + if (fServerFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER) + RTTestISub(!pszExecPath ? "Restricted connect from thread" : "Restricted connect from child"); + else + RTTestISub(!pszExecPath ? "Connect from thread" : "Connect from child"); + + /* Occupy the legacy global endpoint while testing the protected namespace. */ + bool const fRestricted = RT_BOOL(fServerFlags & RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + int rc; +#ifdef RT_OS_WINDOWS + RTLOCALIPCSERVER hSquatter = NIL_RTLOCALIPCSERVER; + if (fRestricted) + RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hSquatter, "tstRTLocalIpcSessionConnection", 0 /*fFlags*/), + VINF_SUCCESS); +#else + char szSquatter[RTPATH_MAX]; + szSquatter[0] = '\0'; + if (fRestricted) + { + RTTESTI_CHECK(RTStrPrintf(szSquatter, sizeof(szSquatter), "/tmp/.iprt-localipc-%s", + "tstRTLocalIpcSessionConnection") > 0); + int const rcUnlink = RTPathUnlink(szSquatter, 0 /*fUnlink*/); + RTTESTI_CHECK(rcUnlink == VINF_SUCCESS || rcUnlink == VERR_FILE_NOT_FOUND || rcUnlink == VERR_PATH_NOT_FOUND); + RTTESTI_CHECK_RC_RETV(RTDirCreate(szSquatter, 0700, 0 /*fCreate*/), VINF_SUCCESS); + } +#endif + +#ifndef RT_OS_WINDOWS + char szRestrictedDir[] = "/tmp/tstRTLocalIpc-restricted-XXXXXX"; + char *pszSavedRuntimeDir = NULL; + if (fRestricted) + { + rc = RTDirCreateTemp(szRestrictedDir, 0700); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { + RTDirRemove(szSquatter); + return; + } + pszSavedRuntimeDir = RTEnvDupEx(RTENV_DEFAULT, "XDG_RUNTIME_DIR"); + rc = RTEnvSet("XDG_RUNTIME_DIR", szRestrictedDir); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { + RTStrFree(pszSavedRuntimeDir); + RTDirRemove(szRestrictedDir); + RTDirRemove(szSquatter); + return; + } + } +#endif /* * Create the test server. */ - RTLOCALIPCSERVER hIpcServer; - RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionConnection", 0), VINF_SUCCESS); + RTLOCALIPCSERVER hIpcServer = NIL_RTLOCALIPCSERVER; + rc = RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionConnection", fServerFlags); + RTTESTI_CHECK_RC(rc, VINF_SUCCESS); + if (RT_FAILURE(rc)) + { +#ifdef RT_OS_WINDOWS + RTLocalIpcServerDestroy(hSquatter); +#else + if (fRestricted) + { + if (pszSavedRuntimeDir) + RTEnvSet("XDG_RUNTIME_DIR", pszSavedRuntimeDir); + else + RTEnvUnset("XDG_RUNTIME_DIR"); + RTStrFree(pszSavedRuntimeDir); + RTDirRemove(szRestrictedDir); + RTDirRemove(szSquatter); + } +#endif + return; + } /* * Create worker thread that listens and closes incoming connections until * cancelled. */ - int rc; RTTHREAD hListenThread; - RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testServerListenThread, hIpcServer, 0 /* Stack */, + TESTSERVERLISTENARGS Args; + Args.hIpcServer = hIpcServer; + Args.fExpectOtherProcess = pszExecPath != NULL; + RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testServerListenThread, &Args, 0 /* Stack */, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-1")); if (RT_SUCCESS(rc)) { @@ -189,7 +605,14 @@ static void testSessionConnection(const char *pszExecPath) if (pszExecPath) { RTPROCESS hClientProc; - const char *apszArgs[4] = { pszExecPath, "child", "tstRTLocalIpcSessionConnectionChild", NULL }; + const char *apszArgs[5] = + { + pszExecPath, + "child", + "tstRTLocalIpcSessionConnectionChild", + fRestricted ? "restricted" : NULL, + NULL + }; RTTESTI_CHECK_RC_OK(rc = RTProcCreate(pszExecPath, apszArgs, RTENV_DEFAULT, 0 /* fFlags*/, &hClientProc)); if (RT_SUCCESS(rc)) { @@ -202,7 +625,8 @@ static void testSessionConnection(const char *pszExecPath) else { RTTHREAD hClientThread; - RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionConnectionChild, NULL, + void *pvWorker = (void *)(uintptr_t)(fRestricted ? TEST_CONNECTION_F_RESTRICTED : 0); + RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hClientThread, tstRTLocalIpcSessionConnectionChild, pvWorker, 0 /* Stack */, RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "client-1")); if (RT_SUCCESS(rc)) { @@ -226,9 +650,89 @@ static void testSessionConnection(const char *pszExecPath) } RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED); +#ifdef RT_OS_WINDOWS + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hSquatter), fRestricted ? VINF_OBJECT_DESTROYED : VINF_SUCCESS); +#else + if (fRestricted) + { + if (pszSavedRuntimeDir) + RTTESTI_CHECK_RC(RTEnvSet("XDG_RUNTIME_DIR", pszSavedRuntimeDir), VINF_SUCCESS); + else + RTTESTI_CHECK_RC(RTEnvUnset("XDG_RUNTIME_DIR"), VINF_SUCCESS); + RTStrFree(pszSavedRuntimeDir); + RTTESTI_CHECK_RC(RTDirRemove(szRestrictedDir), VINF_SUCCESS); + RTTESTI_CHECK_RC(RTDirRemove(szSquatter), VINF_SUCCESS); + } +#endif } +#ifdef RT_OS_WINDOWS +static DECLCALLBACK(int) testServerListenAnonymousThread(RTTHREAD hSelf, void *pvUser) +{ + RTLOCALIPCSERVER hIpcServer = (RTLOCALIPCSERVER)pvUser; + RTTEST_CHECK_RC_OK_RET(g_hTest, RTTestSetDefault(g_hTest, NULL), rcCheck); + + RTTEST_CHECK_RC_OK_RET(g_hTest, RTThreadUserSignal(hSelf), rcCheck); + + RTLOCALIPCSESSION hIpcSession; + RTTEST_CHECK_RC_OK_RET(g_hTest, RTLocalIpcServerListen(hIpcServer, &hIpcSession), rcCheck); + uint8_t bIdentity = 0; + RTTEST_CHECK_RC_OK_RET(g_hTest, RTLocalIpcSessionRead(hIpcSession, &bIdentity, sizeof(bIdentity), NULL), rcCheck); + RTTEST_CHECK_RET(g_hTest, bIdentity == UINT8_C(0x24), VERR_GENERAL_FAILURE); + RTTESTI_CHECK_RC(RTLocalIpcSessionVerifySameUser(hIpcSession), VERR_ACCESS_DENIED); + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hIpcSession), VINF_OBJECT_DESTROYED); + return VINF_SUCCESS; +} + + +static void testSessionAnonymousClient(void) +{ + RTTestISub("Anonymous client identification"); + + RTLOCALIPCSERVER hIpcServer; + RTTESTI_CHECK_RC_RETV(RTLocalIpcServerCreate(&hIpcServer, "tstRTLocalIpcSessionAnonymous", 0), VINF_SUCCESS); + + RTTHREAD hListenThread; + int rc; + RTTESTI_CHECK_RC_OK(rc = RTThreadCreate(&hListenThread, testServerListenAnonymousThread, hIpcServer, 0 /*cbStack*/, + RTTHREADTYPE_DEFAULT, RTTHREADFLAGS_WAITABLE, "listen-anon")); + if (RT_SUCCESS(rc)) + { + RTTESTI_CHECK_RC_OK(RTThreadUserWait(hListenThread, RT_MS_1MIN / 2)); + + RTLOCALIPCSESSION hClientSession = NIL_RTLOCALIPCSESSION; + RTTESTI_CHECK_RC_OK(rc = RTLocalIpcSessionConnect(&hClientSession, "tstRTLocalIpcSessionAnonymous", 0 /*fFlags*/)); + if (RT_SUCCESS(rc)) + { + uint8_t const bIdentity = UINT8_C(0x24); + RTTESTI_CHECK_RC_OK(rc = RTLocalIpcSessionWrite(hClientSession, &bIdentity, sizeof(bIdentity))); + if (RT_SUCCESS(rc)) + RTTESTI_CHECK_RC_OK(rc = RTLocalIpcSessionFlush(hClientSession)); + if (RT_FAILURE(rc)) + { + RTLocalIpcSessionClose(hClientSession); + hClientSession = NIL_RTLOCALIPCSESSION; + } + int rcThread; + RTTESTI_CHECK_RC_OK(rc = RTThreadWait(hListenThread, RT_MS_1MIN / 2, &rcThread)); + if (RT_SUCCESS(rc)) + RTTESTI_CHECK_RC(rcThread, VINF_SUCCESS); + if (hClientSession != NIL_RTLOCALIPCSESSION) + RTTESTI_CHECK_RC(RTLocalIpcSessionClose(hClientSession), VINF_OBJECT_DESTROYED); + } + else + { + RTTESTI_CHECK_RC(RTLocalIpcServerCancel(hIpcServer), VINF_SUCCESS); + RTTESTI_CHECK_RC_OK(RTThreadWait(hListenThread, RT_MS_1MIN / 2, NULL)); + } + } + + RTTESTI_CHECK_RC(RTLocalIpcServerDestroy(hIpcServer), VINF_OBJECT_DESTROYED); +} +#endif /* RT_OS_WINDOWS */ + + /********************************************************************************************************************************* * * @@ -909,13 +1413,24 @@ int main(int argc, char **argv) RTAssertSetQuiet(fQuiet); /* Do real tests if the basics are fine. */ + if (RTTestErrorCount(g_hTest) == 0) + testRestrictedNamespaceProperties(); char szExecPath[RTPATH_MAX]; if (RTProcGetExecutablePath(szExecPath, sizeof(szExecPath))) { if (RTTestErrorCount(g_hTest) == 0) - testSessionConnection(NULL); + testSessionConnection(NULL, 0 /*fServerFlags*/); + if (RTTestErrorCount(g_hTest) == 0) + testSessionConnection(szExecPath, 0 /*fServerFlags*/); if (RTTestErrorCount(g_hTest) == 0) - testSessionConnection(szExecPath); + testSessionConnection(NULL, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + if (RTTestErrorCount(g_hTest) == 0) + testSessionConnection(szExecPath, RTLOCALIPC_FLAGS_RESTRICT_TO_USER); + +#ifdef RT_OS_WINDOWS + if (RTTestErrorCount(g_hTest) == 0) + testSessionAnonymousClient(); +#endif if (RTTestErrorCount(g_hTest) == 0) testSessionWait(NULL); @@ -938,7 +1453,7 @@ int main(int argc, char **argv) /* * Child process. */ - else if ( argc == 3 + else if ( (argc == 3 || argc == 4) && !strcmp(argv[1], "child")) { rc = RTTestCreateChild(argv[2], &g_hTest); @@ -946,7 +1461,12 @@ int main(int argc, char **argv) return RTEXITCODE_FAILURE; if (!strcmp(argv[2], "tstRTLocalIpcSessionConnectionChild")) - tstRTLocalIpcSessionConnectionChild(RTThreadSelf(), g_hTest); + { + uintptr_t fWorker = TEST_CONNECTION_F_OTHER_PROCESS; + if (argc == 4 && !strcmp(argv[3], "restricted")) + fWorker |= TEST_CONNECTION_F_RESTRICTED; + tstRTLocalIpcSessionConnectionChild(RTThreadSelf(), (void *)fWorker); + } else if (!strcmp(argv[2], "tstRTLocalIpcSessionWaitChild")) tstRTLocalIpcSessionWaitChild(RTThreadSelf(), g_hTest); else if (!strcmp(argv[2], "tstRTLocalIpcSessionDataChild")) @@ -967,4 +1487,3 @@ int main(int argc, char **argv) */ return RTTestSummaryAndDestroy(g_hTest); } - diff --git a/src/VBox/Runtime/testcase/tstRTNoCrt-2.cpp b/src/VBox/Runtime/testcase/tstRTNoCrt-2.cpp index 71c2ab292a8b..2c0ef4f6c591 100644 --- a/src/VBox/Runtime/testcase/tstRTNoCrt-2.cpp +++ b/src/VBox/Runtime/testcase/tstRTNoCrt-2.cpp @@ -1,4 +1,4 @@ -/* $Id: tstRTNoCrt-2.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: tstRTNoCrt-2.cpp 114996 2026-08-12 10:42:45Z andreas.loeffler@oracle.com $ */ /** @file * IPRT Testcase - Testcase for the No-CRT math bits. */ @@ -2931,33 +2931,35 @@ void testFma() { RTTestSub(g_hTest, "fma[f]"); - CHECK_DBL(RT_NOCRT(fma)(1.0, 1.0, 1.0), 2.0); - CHECK_DBL(RT_NOCRT(fma)(4.0, 2.0, 1.0), 9.0); - CHECK_DBL(RT_NOCRT(fma)(4.0, 2.0, -1.0), 7.0); - CHECK_DBL_SAME(fma, (0.0, 0.0, 0.0)); - CHECK_DBL_SAME(fma, (999999.0, 33334.0, 29345.0)); - CHECK_DBL_SAME(fma, (39560.32334, 9605.5546, -59079.345069)); - CHECK_DBL_SAME(fma, (39560.32334, -59079.345069, 9605.5546)); - CHECK_DBL_SAME(fma, (-59079.345069, 39560.32334, 9605.5546)); - CHECK_DBL_SAME(fma, (+INFINITY, +INFINITY, -INFINITY)); - CHECK_DBL_SAME(fma, (4.0, +INFINITY, 2.0)); - CHECK_DBL_SAME(fma, (4.0, 4.0, +INFINITY)); - CHECK_DBL_SAME(fma, (-INFINITY, 4.0, 4.0)); - CHECK_DBL_SAME(fma, (2.34960584706e100, 7.6050698459e-13, 9.99996777e77)); - - CHECK_FLT(RT_NOCRT(fmaf)(1.0f, 1.0f, 1.0), 2.0); - CHECK_FLT(RT_NOCRT(fmaf)(4.0f, 2.0f, 1.0), 9.0); - CHECK_FLT(RT_NOCRT(fmaf)(4.0f, 2.0f, -1.0), 7.0); - CHECK_FLT_SAME(fmaf, (0.0f, 0.0f, 0.0f)); - CHECK_FLT_SAME(fmaf, (999999.0f, 33334.0f, 29345.0f)); - CHECK_FLT_SAME(fmaf, (39560.32334f, 9605.5546f, -59079.345069f)); - CHECK_FLT_SAME(fmaf, (39560.32334f, -59079.345069f, 9605.5546f)); - CHECK_FLT_SAME(fmaf, (-59079.345069f, 39560.32334f, 9605.5546f)); - CHECK_FLT_SAME(fmaf, (+INFINITY, +INFINITY, -INFINITY)); - CHECK_FLT_SAME(fmaf, (4.0f, +INFINITY, 2.0f)); - CHECK_FLT_SAME(fmaf, (4.0f, 4.0f, +INFINITY)); - CHECK_FLT_SAME(fmaf, (-INFINITY, 4.0f, 4.0f)); - CHECK_FLT_SAME(fmaf, (2.34960584706e22f, 7.6050698459e-13f, 9.99996777e27f)); + CHECK_DBL( RT_NOCRT(fma)(1.0, 1.0, 1.0), 2.0); + CHECK_DBL( RT_NOCRT(fma)(4.0, 2.0, 1.0), 9.0); + CHECK_DBL( RT_NOCRT(fma)(4.0, 2.0, -1.0), 7.0); + CHECK_DBL_SAME( fma, (0.0, 0.0, 0.0)); + CHECK_DBL_SAME( fma, (999999.0, 33334.0, 29345.0)); + CHECK_DBL_SAME( fma, (39560.32334, 9605.5546, -59079.345069)); + CHECK_DBL_SAME( fma, (39560.32334, -59079.345069, 9605.5546)); + CHECK_DBL_SAME( fma, (-59079.345069, 39560.32334, 9605.5546)); + /* The NaN sign resulting from this invalid operation is not specified. */ + CHECK_DBL_SAME_RELAXED_NAN(fma, (+INFINITY, +INFINITY, -INFINITY)); + CHECK_DBL_SAME( fma, (4.0, +INFINITY, 2.0)); + CHECK_DBL_SAME( fma, (4.0, 4.0, +INFINITY)); + CHECK_DBL_SAME( fma, (-INFINITY, 4.0, 4.0)); + CHECK_DBL_SAME( fma, (2.34960584706e100, 7.6050698459e-13, 9.99996777e77)); + + CHECK_FLT( RT_NOCRT(fmaf)(1.0f, 1.0f, 1.0), 2.0); + CHECK_FLT( RT_NOCRT(fmaf)(4.0f, 2.0f, 1.0), 9.0); + CHECK_FLT( RT_NOCRT(fmaf)(4.0f, 2.0f, -1.0), 7.0); + CHECK_FLT_SAME( fmaf, (0.0f, 0.0f, 0.0f)); + CHECK_FLT_SAME( fmaf, (999999.0f, 33334.0f, 29345.0f)); + CHECK_FLT_SAME( fmaf, (39560.32334f, 9605.5546f, -59079.345069f)); + CHECK_FLT_SAME( fmaf, (39560.32334f, -59079.345069f, 9605.5546f)); + CHECK_FLT_SAME( fmaf, (-59079.345069f, 39560.32334f, 9605.5546f)); + /* The NaN sign resulting from this invalid operation is not specified. */ + CHECK_FLT_SAME_RELAXED_NAN(fmaf, (+INFINITY, +INFINITY, -INFINITY)); + CHECK_FLT_SAME( fmaf, (4.0f, +INFINITY, 2.0f)); + CHECK_FLT_SAME( fmaf, (4.0f, 4.0f, +INFINITY)); + CHECK_FLT_SAME( fmaf, (-INFINITY, 4.0f, 4.0f)); + CHECK_FLT_SAME( fmaf, (2.34960584706e22f, 7.6050698459e-13f, 9.99996777e27f)); } @@ -3543,24 +3545,25 @@ void testCos() CHECK_DBL( RT_NOCRT(cos)( RTStrNanDouble("123s", false)), RTStrNanDouble("123s", false)); CHECK_DBL( RT_NOCRT(cos)( RTStrNanDouble("9991s", true)), RTStrNanDouble("9991s", true)); - CHECK_DBL_SAME( cos,( 1.0)); - CHECK_DBL_SAME( cos,( 1.5)); - CHECK_DBL_SAME( cos,( +0.0)); - CHECK_DBL_SAME( cos,( +0.0)); - CHECK_DBL_SAME( cos,( -0.0)); - CHECK_DBL_SAME( cos,( -0.0)); - CHECK_DBL_SAME( cos,( 238.6634566)); - CHECK_DBL_SAME( cos,( -49.4578999)); - CHECK_DBL_SAME( cos,( +M_PI)); - CHECK_DBL_SAME( cos,( -M_PI)); + /* FCOS and the CRT may differ slightly in the last few bits. */ + CHECK_DBL_APPROX_SAME(cos,( 1.0), 1); + CHECK_DBL_SAME( cos,( 1.5)); + CHECK_DBL_SAME( cos,( +0.0)); + CHECK_DBL_SAME( cos,( +0.0)); + CHECK_DBL_SAME( cos,( -0.0)); + CHECK_DBL_SAME( cos,( -0.0)); + CHECK_DBL_SAME( cos,( 238.6634566)); + CHECK_DBL_SAME( cos,( -49.4578999)); + CHECK_DBL_SAME( cos,( +M_PI)); + CHECK_DBL_SAME( cos,( -M_PI)); #if 0 /* UCRT does not produce 0.0 here, but some 2**-54 value */ - CHECK_DBL_SAME( cos,( +M_PI_2)); - CHECK_DBL_SAME( cos,( -M_PI_2)); + CHECK_DBL_SAME( cos,( +M_PI_2)); + CHECK_DBL_SAME( cos,( -M_PI_2)); #endif - CHECK_DBL_SAME( cos,( +INFINITY)); - CHECK_DBL_SAME( cos,( -INFINITY)); - CHECK_DBL_SAME( cos,(RTStrNanDouble(NULL, false))); - CHECK_DBL_SAME( cos,(RTStrNanDouble(NULL, true))); + CHECK_DBL_SAME( cos,( +INFINITY)); + CHECK_DBL_SAME( cos,( -INFINITY)); + CHECK_DBL_SAME( cos,(RTStrNanDouble(NULL, false))); + CHECK_DBL_SAME( cos,(RTStrNanDouble(NULL, true))); CHECK_FLT( RT_NOCRT(cosf)( +0.0f), +1.0f); @@ -3845,4 +3848,3 @@ int main() return RTTestSummaryAndDestroy(g_hTest); } - diff --git a/src/VBox/Runtime/testcase/tstRTShMem.cpp b/src/VBox/Runtime/testcase/tstRTShMem.cpp index 27c2314df0b3..43de46d515e1 100644 --- a/src/VBox/Runtime/testcase/tstRTShMem.cpp +++ b/src/VBox/Runtime/testcase/tstRTShMem.cpp @@ -1,4 +1,4 @@ -/* $Id: tstRTShMem.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: tstRTShMem.cpp 114873 2026-08-06 20:59:25Z andreas.loeffler@oracle.com $ */ /** @file * IPRT Testcase - RTShMem. */ @@ -84,6 +84,14 @@ static void tstRTShMem1(void) RTTESTI_CHECK_RETV(g_hShMem != NIL_RTSHMEM); + /* Creating the same named object exclusively must fail. */ + RTSHMEM hShMemDuplicate = NIL_RTSHMEM; + RTTESTI_CHECK_RC(RTShMemOpen(&hShMemDuplicate, "tstRTShMem-Share", + RTSHMEM_O_F_CREATE_EXCL | RTSHMEM_O_F_READWRITE | RTSHMEM_O_F_MAYBE_EXEC, _512K, 0), + VERR_ALREADY_EXISTS); + if (hShMemDuplicate != NIL_RTSHMEM) + RTTESTI_CHECK_RC(RTShMemClose(hShMemDuplicate), VINF_SUCCESS); + /* Query the size. */ size_t cbShMem = 0; RTTESTI_CHECK_RC(RTShMemQuerySize(g_hShMem, &cbShMem), VINF_SUCCESS); @@ -155,4 +163,3 @@ int main() */ return RTTestSummaryAndDestroy(hTest); } - diff --git a/src/VBox/Runtime/tools/RTSignTool.cpp b/src/VBox/Runtime/tools/RTSignTool.cpp index f2085c2b7673..3920cfcbc09d 100644 --- a/src/VBox/Runtime/tools/RTSignTool.cpp +++ b/src/VBox/Runtime/tools/RTSignTool.cpp @@ -1,4 +1,4 @@ -/* $Id: RTSignTool.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: RTSignTool.cpp 114861 2026-08-05 16:23:07Z klaus.espenlaub@oracle.com $ */ /** @file * IPRT - Signing Tool. */ @@ -6440,7 +6440,7 @@ static RTEXITCODE HelpCreateSelfSignedRsaCert(PRTSTREAM pStrm, RTSIGNTOOLHELP en { RT_NOREF_PV(enmLevel); RTStrmWrappedPrintf(pStrm, RTSTRMWRAPPED_F_HANGING_INDENT, - "create-self-signed-rsa-cert [--verbose|--quiet] [--key-bits ] [--digest ] [--out-cert=] [--out-pkey=]\n"); + "create-self-signed-rsa-cert --subject= [--key-bits=] [--days= | --secs=] [--digest=] [--out-cert=] [--out-pkey=]\n"); return RTEXITCODE_SUCCESS; } @@ -6482,15 +6482,16 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) */ static const RTGETOPTDEF s_aOptions[] = { + { "--subject", 'S', RTGETOPT_REQ_STRING }, { "--digest", 'd', RTGETOPT_REQ_STRING }, { "--bits", 'b', RTGETOPT_REQ_UINT32 }, { "--key-bits", 'b', RTGETOPT_REQ_UINT32 }, { "--days", 'D', RTGETOPT_REQ_UINT32 }, { "--days", 'D', RTGETOPT_REQ_UINT32 }, - { "--out-cert", 'c', RTGETOPT_REQ_UINT32 }, - { "--out-certificate", 'c', RTGETOPT_REQ_UINT32 }, - { "--out-pkey", 'p', RTGETOPT_REQ_UINT32 }, - { "--out-private-key", 'p', RTGETOPT_REQ_UINT32 }, + { "--out-cert", 'c', RTGETOPT_REQ_STRING }, + { "--out-certificate", 'c', RTGETOPT_REQ_STRING }, + { "--out-pkey", 'p', RTGETOPT_REQ_STRING }, + { "--out-private-key", 'p', RTGETOPT_REQ_STRING }, { "--secs", 's', RTGETOPT_REQ_UINT32 }, { "--seconds", 's', RTGETOPT_REQ_UINT32 }, }; @@ -6500,6 +6501,7 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) uint32_t cSecsValidFor = 365 * RT_SEC_1DAY; uint32_t fKeyUsage = 0; uint32_t fExtKeyUsage = 0; + const char *pszSubject = NULL; const char *pszOutCert = NULL; const char *pszOutPrivKey = NULL; @@ -6544,6 +6546,14 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) cSecsValidFor = ValueUnion.u32; break; + case 'S': + if (pszSubject) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "The --subject option can only be used once."); + if (!ValueUnion.psz || !*ValueUnion.psz) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "The --subject option must be non-empty."); + pszSubject = ValueUnion.psz; + break; + case VINF_GETOPT_NOT_OPTION: if (!pszOutCert) pszOutCert = ValueUnion.psz; @@ -6558,6 +6568,8 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) default: return RTGetOptPrintError(ch, &ValueUnion); } } + if (!pszSubject) + return RTMsgErrorExit(RTEXITCODE_FAILURE, "No subject name specified."); if (!pszOutCert) return RTMsgErrorExit(RTEXITCODE_FAILURE, "No output certificate file name specified."); if (!pszOutPrivKey) @@ -6568,7 +6580,7 @@ static RTEXITCODE HandleCreateSelfSignedRsaCert(int cArgs, char **papszArgs) */ RTERRINFOSTATIC StaticErrInfo; rc = RTCrX509Certificate_GenerateSelfSignedRsa(enmDigestType, cKeyBits, cSecsValidFor, - fKeyUsage, fExtKeyUsage, NULL /*pvSubjectTodo*/, + fKeyUsage, fExtKeyUsage, pszSubject, pszOutCert, pszOutPrivKey, RTErrInfoInitStatic(&StaticErrInfo)); if (RT_SUCCESS(rc)) { diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicBitClear.asm b/src/VBox/Runtime/win/amd64/ASMAtomicBitClear.asm index d440aaac29b0..23ed7d4756cf 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicBitClear.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicBitClear.asm @@ -52,3 +52,4 @@ RT_BEGINPROC ASMAtomicBitClear ret ENDPROC ASMAtomicBitClear +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicBitTestAndToggle.asm b/src/VBox/Runtime/win/amd64/ASMAtomicBitTestAndToggle.asm index f716cd479ef8..82d28a83ee8f 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicBitTestAndToggle.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicBitTestAndToggle.asm @@ -54,3 +54,4 @@ RT_BEGINPROC ASMAtomicBitTestAndToggle ret ENDPROC ASMAtomicBitTestAndToggle +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicBitToggle.asm b/src/VBox/Runtime/win/amd64/ASMAtomicBitToggle.asm index 288c850d822d..b77ba37ba377 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicBitToggle.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicBitToggle.asm @@ -51,3 +51,4 @@ RT_BEGINPROC ASMAtomicBitToggle ret ENDPROC ASMAtomicBitToggle +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicReadU64.asm b/src/VBox/Runtime/win/amd64/ASMAtomicReadU64.asm index df43ba2d92b1..289dd814d531 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicReadU64.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicReadU64.asm @@ -53,3 +53,4 @@ RT_BEGINPROC ASMAtomicReadU64 ret ENDPROC ASMAtomicReadU64 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMAtomicXchgU8.asm b/src/VBox/Runtime/win/amd64/ASMAtomicXchgU8.asm index 3c495f418864..721db58ddd02 100644 --- a/src/VBox/Runtime/win/amd64/ASMAtomicXchgU8.asm +++ b/src/VBox/Runtime/win/amd64/ASMAtomicXchgU8.asm @@ -50,3 +50,4 @@ RT_BEGINPROC ASMAtomicXchgU8 ret ENDPROC ASMAtomicXchgU8 +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetCS.asm b/src/VBox/Runtime/win/amd64/ASMGetCS.asm index f79f9b6c785e..720af298d4fb 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetCS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetCS.asm @@ -50,3 +50,4 @@ RT_BEGINPROC ASMGetCS ret ENDPROC ASMGetCS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR0.asm b/src/VBox/Runtime/win/amd64/ASMGetDR0.asm index 45a1e54b1584..285f046ee7b5 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR0.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR0.asm @@ -55,3 +55,5 @@ RT_BEGINPROC ASMSetDR0 mov dr0, rcx ret ENDPROC ASMSetDR0 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR1.asm b/src/VBox/Runtime/win/amd64/ASMGetDR1.asm index 0b3277772617..ce6616face87 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR1.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR1.asm @@ -54,3 +54,5 @@ RT_BEGINPROC ASMSetDR1 mov dr1, rcx ret ENDPROC ASMSetDR1 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR2.asm b/src/VBox/Runtime/win/amd64/ASMGetDR2.asm index fb846ba7e13c..b04e953761ce 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR2.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR2.asm @@ -55,3 +55,5 @@ RT_BEGINPROC ASMSetDR2 mov dr2, rcx ret ENDPROC ASMSetDR2 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR3.asm b/src/VBox/Runtime/win/amd64/ASMGetDR3.asm index 5630cb90c288..bf1c0f65025a 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR3.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR3.asm @@ -55,3 +55,5 @@ RT_BEGINPROC ASMSetDR3 mov dr3, rcx ret ENDPROC ASMSetDR3 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR6.asm b/src/VBox/Runtime/win/amd64/ASMGetDR6.asm index b677b912ef02..170f3fb6ee49 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR6.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR6.asm @@ -54,3 +54,5 @@ RT_BEGINPROC ASMSetDR6 mov dr6, rcx ret ENDPROC ASMSetDR6 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDR7.asm b/src/VBox/Runtime/win/amd64/ASMGetDR7.asm index ab7a17b0f6da..46d9bd91873a 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDR7.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDR7.asm @@ -53,3 +53,5 @@ RT_BEGINPROC ASMSetDR7 mov dr7, rcx ret ENDPROC ASMSetDR7 + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetDS.asm b/src/VBox/Runtime/win/amd64/ASMGetDS.asm index 23fbf4c19ebc..850d6e7d6a9c 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetDS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetDS.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetDS ret ENDPROC ASMGetDS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetES.asm b/src/VBox/Runtime/win/amd64/ASMGetES.asm index 5350c360bf68..56b56af08aae 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetES.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetES.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetES ret ENDPROC ASMGetES +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetFS.asm b/src/VBox/Runtime/win/amd64/ASMGetFS.asm index 0d6a5e1bb751..000cb2820fe0 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetFS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetFS.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetFS ret ENDPROC ASMGetFS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetGS.asm b/src/VBox/Runtime/win/amd64/ASMGetGS.asm index 86f8e9508c3c..224a416bdd33 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetGS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetGS.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetGS ret ENDPROC ASMGetGS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMGetSS.asm b/src/VBox/Runtime/win/amd64/ASMGetSS.asm index 766193211295..9f6d1a8364b2 100644 --- a/src/VBox/Runtime/win/amd64/ASMGetSS.asm +++ b/src/VBox/Runtime/win/amd64/ASMGetSS.asm @@ -49,3 +49,4 @@ RT_BEGINPROC ASMGetSS ret ENDPROC ASMGetSS +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Runtime/win/amd64/ASMProbeReadByte.asm b/src/VBox/Runtime/win/amd64/ASMProbeReadByte.asm index 6875db9098fd..0445bf4952dc 100644 --- a/src/VBox/Runtime/win/amd64/ASMProbeReadByte.asm +++ b/src/VBox/Runtime/win/amd64/ASMProbeReadByte.asm @@ -56,3 +56,4 @@ RT_BEGINPROC ASMProbeReadByte ret ENDPROC ASMProbeReadByte +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/Storage/CUE.cpp b/src/VBox/Storage/CUE.cpp index 0628d3c90728..11dcc9389f40 100644 --- a/src/VBox/Storage/CUE.cpp +++ b/src/VBox/Storage/CUE.cpp @@ -1,4 +1,4 @@ -/* $Id: CUE.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: CUE.cpp 114910 2026-08-10 11:42:35Z alexander.eichner@oracle.com $ */ /** @file * CUE - CUE/BIN Disk image, Core Code. */ @@ -954,7 +954,8 @@ static int cueParseTrack(PCUEIMAGE pThis, PCUETOKENIZER pTokenizer) if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_INTEGER_UNSIGNED) { uint64_t u64Track = cueTokenizerConsumeInteger(pTokenizer); - if (u64Track <= 99) + if ( u64Track >= 1 + && u64Track <= 99) { /* Parse the data mode. */ if (cueTokenizerGetTokenType(pTokenizer) == CUETOKENTYPE_KEYWORD) diff --git a/src/VBox/VMM/VMMAll/CPUMAllCpuId.cpp b/src/VBox/VMM/VMMAll/CPUMAllCpuId.cpp index 8a2b54e6a443..b91fbe814997 100644 --- a/src/VBox/VMM/VMMAll/CPUMAllCpuId.cpp +++ b/src/VBox/VMM/VMMAll/CPUMAllCpuId.cpp @@ -1,4 +1,4 @@ -/* $Id: CPUMAllCpuId.cpp 112779 2026-02-01 19:19:54Z knut.osmundsen@oracle.com $ */ +/* $Id: CPUMAllCpuId.cpp 115026 2026-08-13 02:15:03Z knut.osmundsen@oracle.com $ */ /** @file * CPUM - CPU ID part, common bits. */ @@ -1796,6 +1796,10 @@ VMMDECL(int) CPUMCpuIdExplodeFeaturesX86(PCCPUMCPUIDLEAF paLeaves, uint32_t cLea pFeatures->fArchCap = RT_BOOL(pSxfLeaf0->uEdx & X86_CPUID_STEXT_FEATURE_EDX_ARCHCAP); pFeatures->fCoreCap = RT_BOOL(pSxfLeaf0->uEdx & X86_CPUID_STEXT_FEATURE_EDX_CORECAP); pFeatures->fMdsClear = RT_BOOL(pSxfLeaf0->uEdx & X86_CPUID_STEXT_FEATURE_EDX_MD_CLEAR); + + pFeatures->fIbpbNoRet = pFeatures->fIbpb + && ( pFeatures->enmCpuVendor == CPUMCPUVENDOR_AMD + || pFeatures->enmCpuVendor == CPUMCPUVENDOR_HYGON); } PCCPUMCPUIDLEAF const pSxfLeaf2 = cpumCpuIdFindLeafEx(paLeaves, cLeaves, 7, 2); if (pSxfLeaf2) @@ -1863,6 +1867,7 @@ VMMDECL(int) CPUMCpuIdExplodeFeaturesX86(PCCPUMCPUIDLEAF paLeaves, uint32_t cLea pFeatures->fStibp |= RT_BOOL(pExtLeaf8->uEbx & X86_CPUID_AMD_EFEID_EBX_STIBP); pFeatures->fSsbd |= RT_BOOL(pExtLeaf8->uEbx & X86_CPUID_AMD_EFEID_EBX_SPEC_CTRL_SSBD); pFeatures->fPsfd |= RT_BOOL(pExtLeaf8->uEbx & X86_CPUID_AMD_EFEID_EBX_PSFD); + pFeatures->fIbpbNoRet = pFeatures->fIbpb && !RT_BOOL(pExtLeaf8->uEbx & X86_CPUID_AMD_EFEID_EBX_IBPB_RET); } PCCPUMCPUIDLEAF pExtLeaf21 = cpumCpuIdFindLeaf(paLeaves, cLeaves, 0x80000021); diff --git a/src/VBox/VMM/VMMAll/IEMAllN8veHlpA.asm b/src/VBox/VMM/VMMAll/IEMAllN8veHlpA.asm index b84f298ef25a..5ed638f030c6 100644 --- a/src/VBox/VMM/VMMAll/IEMAllN8veHlpA.asm +++ b/src/VBox/VMM/VMMAll/IEMAllN8veHlpA.asm @@ -1,4 +1,4 @@ -; $Id: IEMAllN8veHlpA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: IEMAllN8veHlpA.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; IEM - Native Recompiler Assembly Helpers. ; @@ -303,3 +303,5 @@ SEH64_END_PROLOGUE ret ENDPROC iemNativeFpCtrlRegRestore + +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: iemNativeTbEntry does an indirect jmp (probably not something we want to touch). diff --git a/src/VBox/VMM/VMMAll/PDMAll.cpp b/src/VBox/VMM/VMMAll/PDMAll.cpp index a4dcd6bcf04f..744e35c1b747 100644 --- a/src/VBox/VMM/VMMAll/PDMAll.cpp +++ b/src/VBox/VMM/VMMAll/PDMAll.cpp @@ -1,4 +1,4 @@ -/* $Id: PDMAll.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: PDMAll.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * PDM Critical Sections */ @@ -72,18 +72,23 @@ VMMDECL(int) PDMGetInterrupt(PVMCPUCC pVCpu, uint8_t *pu8Interrupt) int rc = VERR_NO_DATA; if (VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC)) { - VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_INTERRUPT_APIC); - uint32_t uTagSrc; rc = PDMApicGetInterrupt(pVCpu, pu8Interrupt, &uTagSrc); if (RT_SUCCESS(rc)) { VBOXVMM_PDM_IRQ_GET(pVCpu, RT_LOWORD(uTagSrc), RT_HIWORD(uTagSrc), *pu8Interrupt); Log8(("PDMGetInterrupt: irq=%#x tag=%#x (apic)\n", *pu8Interrupt, uTagSrc)); + VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_INTERRUPT_APIC); return VINF_SUCCESS; } - /* else if it's masked by TPR/PPR/whatever, go ahead checking the PIC. Such masked - interrupts shouldn't prevent ExtINT from being delivered. */ + + /* + * If it's masked by TPR/PPR/whatever, go ahead checking the PIC. Such masked + * interrupts shouldn't prevent ExtINT from being delivered. If the interrupt is + * deferred for delivery by the hardware, do -not- clear the force-flag here. + */ + if (rc != VERR_APIC_INTR_DEFER) + VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_INTERRUPT_APIC); } PVMCC pVM = pVCpu->CTX_SUFF(pVM); diff --git a/src/VBox/VMM/VMMAll/VMMAllA.asm b/src/VBox/VMM/VMMAll/VMMAllA.asm index 7745c91389b6..4b325a82d43a 100644 --- a/src/VBox/VMM/VMMAll/VMMAllA.asm +++ b/src/VBox/VMM/VMMAll/VMMAllA.asm @@ -1,4 +1,4 @@ -; $Id: VMMAllA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VMMAllA.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; VMM - All Contexts Assembly Routines. ; @@ -91,3 +91,4 @@ BEGINPROC VMMTrashVolatileXMMRegs ret ENDPROC VMMTrashVolatileXMMRegs +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h b/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h index 131e0c26602e..13f7ce5e46d1 100644 --- a/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h +++ b/src/VBox/VMM/VMMAll/VMXAllTemplate.cpp.h @@ -1,4 +1,4 @@ -/* $Id: VMXAllTemplate.cpp.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: VMXAllTemplate.cpp.h 115087 2026-08-19 13:09:55Z alexander.eichner@oracle.com $ */ /** @file * HM VMX (Intel VT-x) - Code template for our own hypervisor and the NEM darwin backend using Apple's Hypervisor.framework. */ @@ -263,6 +263,7 @@ static FNVMXEXITHANDLER vmxHCExitMonitor; static FNVMXEXITHANDLER vmxHCExitPause; static FNVMXEXITHANDLERNSRC vmxHCExitTprBelowThreshold; static FNVMXEXITHANDLER vmxHCExitApicAccess; +static FNVMXEXITHANDLER vmxHCExitApicWrite; static FNVMXEXITHANDLER vmxHCExitEptViolation; static FNVMXEXITHANDLER vmxHCExitEptMisconfig; static FNVMXEXITHANDLER vmxHCExitRdtscp; @@ -671,7 +672,7 @@ static const struct CLANG11NOTHROWWEIRDNESS { PFNVMXEXITHANDLER pfn; } g_aVMExit #endif /* 54 VMX_EXIT_WBINVD */ { vmxHCExitWbinvd }, /* 55 VMX_EXIT_XSETBV */ { vmxHCExitXsetbv }, - /* 56 VMX_EXIT_APIC_WRITE */ { vmxHCExitErrUnexpected }, + /* 56 VMX_EXIT_APIC_WRITE */ { vmxHCExitApicWrite }, /* 57 VMX_EXIT_RDRAND */ { vmxHCExitErrUnexpected }, /* 58 VMX_EXIT_INVPCID */ { vmxHCExitInvpcid }, /* 59 VMX_EXIT_VMFUNC */ { vmxHCExitErrUnexpected }, @@ -5955,6 +5956,7 @@ DECLINLINE(VBOXSTRICTRC) vmxHCHandleExit(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTrans case VMX_EXIT_RDTSC: VMEXIT_CALL_RET(0, vmxHCExitRdtsc(pVCpu, pVmxTransient)); case VMX_EXIT_RDTSCP: VMEXIT_CALL_RET(0, vmxHCExitRdtscp(pVCpu, pVmxTransient)); case VMX_EXIT_APIC_ACCESS: VMEXIT_CALL_RET(0, vmxHCExitApicAccess(pVCpu, pVmxTransient)); + case VMX_EXIT_APIC_WRITE: VMEXIT_CALL_RET(0, vmxHCExitApicWrite(pVCpu, pVmxTransient)); case VMX_EXIT_XCPT_OR_NMI: VMEXIT_CALL_RET(0, vmxHCExitXcptOrNmi(pVCpu, pVmxTransient)); case VMX_EXIT_MOV_CRX: VMEXIT_CALL_RET(0, vmxHCExitMovCRx(pVCpu, pVmxTransient)); case VMX_EXIT_EXT_INT: VMEXIT_CALL_RET(0, vmxHCExitExtInt(pVCpu, pVmxTransient)); @@ -6022,7 +6024,6 @@ DECLINLINE(VBOXSTRICTRC) vmxHCHandleExit(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTrans case VMX_EXIT_VIRTUALIZED_EOI: case VMX_EXIT_GDTR_IDTR_ACCESS: case VMX_EXIT_LDTR_TR_ACCESS: - case VMX_EXIT_APIC_WRITE: case VMX_EXIT_RDRAND: case VMX_EXIT_RSM: case VMX_EXIT_VMFUNC: @@ -9339,6 +9340,33 @@ HMVMX_EXIT_DECL vmxHCExitApicAccess(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient) } +/** + * VM-exit handler for APIC access (VMX_EXIT_APIC_WRITE). Conditional VM-exit. + */ +HMVMX_EXIT_DECL vmxHCExitApicWrite(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransient) +{ + HMVMX_VALIDATE_EXIT_HANDLER_PARAMS(pVCpu, pVmxTransient); + STAM_COUNTER_INC(&VCPU_2_VMXSTATS(pVCpu).StatExitApicWrite); + +#ifndef IN_NEM_DARWIN + vmxHCReadToTransient(pVCpu, pVmxTransient); + + /* + * APIC write exits are all trap like, so the instruction has already completed and + * the APIC page got updated. + */ + pVCpu->hm.s.offApicReg = pVmxTransient->uExitQual; + VBOXSTRICTRC rcStrict = PDMApicUpdateStateAfterWrite(pVCpu, pVmxTransient->uExitQual); + if (rcStrict != VINF_SUCCESS) + STAM_COUNTER_INC(&VCPU_2_VMXSTATS(pVCpu).StatSwitchApicWriteToR3); + return rcStrict; +#else + AssertFailed(); /** @todo */ + return VERR_NOT_IMPLEMENTED; +#endif +} + + /** * VM-exit handler for debug-register accesses (VMX_EXIT_MOV_DRX). Conditional * VM-exit. diff --git a/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp b/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp index 28140537f65e..792cb15e54ff 100644 --- a/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp +++ b/src/VBox/VMM/VMMAll/target-x86/APICAll-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: APICAll-x86.cpp 114733 2026-07-21 06:02:36Z alexander.eichner@oracle.com $ */ +/* $Id: APICAll-x86.cpp 115078 2026-08-19 10:21:30Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller - All Contexts. */ @@ -608,14 +608,19 @@ static VBOXSTRICTRC apicSendIntr(PVMCC pVM, PVMCPUCC pVCpu, uint8_t uVector, XAP && pVCpu) { /* - * Flag only errors when the delivery mode is fixed and not others. + * Flag only errors when the delivery mode is fixed or lowest-priority and not + * others. This applies to ICR and self-IPI in both xAPIC and x2APIC modes. + * - Intel: Documented under "Error Status Register (ESR)" in the + * Intel spec. "13.5.3 Error Handling". + * - AMD: Documented under "APICx280 [Error Status] (ErrorStatus)" in the CPU + * specific manual (e.g. "Processor Programming Reference (PPR) for + * AMD Family 17h Model 01h, Revision B1 Processors". * * Ubuntu 10.04-3 amd64 live CD with 2 VCPUs gets upset as it sends an SIPI to the * 2nd VCPU with vector 6 and checks the ESR for no errors, see @bugref{8245#c86}. */ - /** @todo The spec says this for LVT, but not explcitly for ICR-lo - * but it probably is true. */ - if (enmDeliveryMode == XAPICDELIVERYMODE_FIXED) + if ( enmDeliveryMode == XAPICDELIVERYMODE_FIXED + || enmDeliveryMode == XAPICDELIVERYMODE_LOWEST_PRIO) { if (RT_UNLIKELY(uVector <= XAPIC_ILLEGAL_VECTOR_END)) apicSetError(pVCpu, XAPIC_ESR_SEND_ILLEGAL_VECTOR); @@ -765,6 +770,7 @@ static VBOXSTRICTRC apicSetIcrLo(PVMCPUCC pVCpu, uint32_t uIcrLo, int rcRZ, bool STAM_COUNTER_INC(&pVCpu->apic.s.StatIcrLoWrite); RT_NOREF(fUpdateStat); + Assert(!(pXApicPage->icr_lo.all.u32IcrLo & XAPIC_LVT_DELIVERY_STATUS)); return apicSendIpi(pVCpu, rcRZ); } @@ -905,6 +911,65 @@ static int apicSetTprEx(PVMCPUCC pVCpu, uint32_t uTpr, bool fForceX2ApicBehaviou } +/** + * Helper for processing an EOI when the vector corresponding to the EOI is + * given. + * + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector for attempted EOI. This is the highest + * in-service vector found in the ISR. + */ +static void apicProcessEoi(PVMCPUCC pVCpu, uint8_t uVector) +{ + /* + * Broadcast the EOI to the I/O APIC(s). + * + * We'll handle the EOI broadcast first as there is tiny chance we get rescheduled to + * ring-3 due to contention on the I/O APIC lock. This way we don't mess with the rest + * of the APIC state and simply restart the EOI write operation from ring-3. + */ + PXAPICPAGE pXApicPage = VMCPU_TO_XAPICPAGE(pVCpu); + bool const fLevelTriggered = apicTestVectorInReg(&pXApicPage->tmr, uVector); + if (fLevelTriggered) + { + PDMIoApicBroadcastEoi(pVCpu->CTX_SUFF(pVM), uVector); + + /* + * Clear the vector from the TMR. + * + * The broadcast to I/O APIC can re-trigger new interrupts to arrive via the bus. However, + * apicUpdatePendingInterrupts() which updates TMR can only be done from EMT which we + * currently are on, so no possibility of concurrent updates. + */ + apicClearVectorInReg(&pXApicPage->tmr, uVector); + + /* + * Clear the remote IRR bit for level-triggered, fixed mode LINT0 interrupt. + * The LINT1 pin does not support level-triggered interrupts. + * See Intel spec. 10.5.1 "Local Vector Table". + */ + uint32_t const uLvtLint0 = pXApicPage->lvt_lint0.all.u32LvtLint0; + if ( XAPIC_LVT_GET_REMOTE_IRR(uLvtLint0) + && XAPIC_LVT_GET_VECTOR(uLvtLint0) == uVector + && XAPIC_LVT_GET_DELIVERY_MODE(uLvtLint0) == XAPICDELIVERYMODE_FIXED) + { + ASMAtomicAndU32((volatile uint32_t *)&pXApicPage->lvt_lint0.all.u32LvtLint0, ~XAPIC_LVT_REMOTE_IRR); + Log2(("APIC%u: apicSetEoi: Cleared remote-IRR for LINT0. uVector=%#x\n", pVCpu->idCpu, uVector)); + } + + Log2(("APIC%u: apicSetEoi: Cleared level triggered interrupt from TMR. uVector=%#x\n", pVCpu->idCpu, uVector)); + } + + /* + * Mark interrupt as serviced, update the PPR and signal pending interrupts. + */ + Log2(("APIC%u: apicSetEoi: Clearing interrupt from ISR. uVector=%#x\n", pVCpu->idCpu, uVector)); + apicClearVectorInReg(&pXApicPage->isr, uVector); + apicUpdatePpr(pVCpu); + apicSignalNextPendingIntr(pVCpu); +} + + /** * Sets the End-Of-Interrupt (EOI) register. * @@ -930,53 +995,9 @@ static DECLCALLBACK(VBOXSTRICTRC) apicSetEoi(PVMCPUCC pVCpu, uint32_t uEoi, bool int isrv = apicGetHighestSetBitInReg(&pXApicPage->isr, -1 /* rcNotFound */); if (isrv >= 0) { - /* - * Broadcast the EOI to the I/O APIC(s). - * - * We'll handle the EOI broadcast first as there is tiny chance we get rescheduled to - * ring-3 due to contention on the I/O APIC lock. This way we don't mess with the rest - * of the APIC state and simply restart the EOI write operation from ring-3. - */ Assert(isrv <= (int)UINT8_MAX); - uint8_t const uVector = isrv; - bool const fLevelTriggered = apicTestVectorInReg(&pXApicPage->tmr, uVector); - if (fLevelTriggered) - { - PDMIoApicBroadcastEoi(pVCpu->CTX_SUFF(pVM), uVector); - - /* - * Clear the vector from the TMR. - * - * The broadcast to I/O APIC can re-trigger new interrupts to arrive via the bus. However, - * apicUpdatePendingInterrupts() which updates TMR can only be done from EMT which we - * currently are on, so no possibility of concurrent updates. - */ - apicClearVectorInReg(&pXApicPage->tmr, uVector); - - /* - * Clear the remote IRR bit for level-triggered, fixed mode LINT0 interrupt. - * The LINT1 pin does not support level-triggered interrupts. - * See Intel spec. 10.5.1 "Local Vector Table". - */ - uint32_t const uLvtLint0 = pXApicPage->lvt_lint0.all.u32LvtLint0; - if ( XAPIC_LVT_GET_REMOTE_IRR(uLvtLint0) - && XAPIC_LVT_GET_VECTOR(uLvtLint0) == uVector - && XAPIC_LVT_GET_DELIVERY_MODE(uLvtLint0) == XAPICDELIVERYMODE_FIXED) - { - ASMAtomicAndU32((volatile uint32_t *)&pXApicPage->lvt_lint0.all.u32LvtLint0, ~XAPIC_LVT_REMOTE_IRR); - Log2(("APIC%u: apicSetEoi: Cleared remote-IRR for LINT0. uVector=%#x\n", pVCpu->idCpu, uVector)); - } - - Log2(("APIC%u: apicSetEoi: Cleared level triggered interrupt from TMR. uVector=%#x\n", pVCpu->idCpu, uVector)); - } - - /* - * Mark interrupt as serviced, update the PPR and signal pending interrupts. - */ - Log2(("APIC%u: apicSetEoi: Clearing interrupt from ISR. uVector=%#x\n", pVCpu->idCpu, uVector)); - apicClearVectorInReg(&pXApicPage->isr, uVector); - apicUpdatePpr(pVCpu); - apicSignalNextPendingIntr(pVCpu); + uint8_t const uVector = isrv; + apicProcessEoi(pVCpu, uVector); } else { @@ -992,6 +1013,30 @@ static DECLCALLBACK(VBOXSTRICTRC) apicSetEoi(PVMCPUCC pVCpu, uint32_t uEoi, bool } +/** + * Sets the End-Of-Interrupt (EOI) register when the ISR is already known. + * + * This is an optimization that lets us avoid scanning the 256-bit sparse ISR + * register figuring out the highest pending in-service vector. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector for attempted EOI. This is the vector for the + * highest in-service vector. + * + * @note It it assumed the caller (hardware in the case of SVM AVIC) has + * already validated the value written to the EOI register. + */ +static DECLCALLBACK(VBOXSTRICTRC) apicSetEoiFast(PVMCPUCC pVCpu, uint8_t uVector) +{ + VMCPU_ASSERT_EMT(pVCpu); + Log2(("APIC%u: apicSetEoiFast: uVector=%#x\n", pVCpu->idCpu, uVector)); + STAM_COUNTER_INC(&pVCpu->apic.s.StatEoiWriteFast); + apicProcessEoi(pVCpu, uVector); + return VINF_SUCCESS; +} + + /** * Sets the Logical Destination Register (LDR). * @@ -1554,7 +1599,7 @@ static DECLCALLBACK(VBOXSTRICTRC) apicReadMsr(PVMCPUCC pVCpu, uint32_t u32Reg, u * Validate. */ VMCPU_ASSERT_EMT(pVCpu); - Assert(u32Reg >= MSR_IA32_X2APIC_ID && u32Reg <= MSR_IA32_X2APIC_SELF_IPI); + AssertMsg(u32Reg >= MSR_IA32_X2APIC_ID && u32Reg <= MSR_IA32_X2APIC_SELF_IPI, ("u32Reg=%#x\n", u32Reg)); Assert(pu64Value); /* @@ -2388,6 +2433,20 @@ static DECLCALLBACK(int) apicGetInterrupt(PVMCPUCC pVCpu, uint8_t *pu8Vector, ui LogFlow(("APIC%u: apicGetInterrupt:\n", pVCpu->idCpu)); + /* + * When SVM AVIC is in use, we only deliver PIC-style interrupts here. + * Other interrupts are updated in the APIC page by the usual mechanism + * and picked up by the hardware without explicit event injection. + */ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + PCAPIC pApic = VM_TO_APIC(pVM); + if (pApic->fAvicEnabled) + { + *pu8Vector = 0; + *puSrcTag = 0; + return VERR_APIC_INTR_DEFER; + } + PXAPICPAGE pXApicPage = VMCPU_TO_XAPICPAGE(pVCpu); bool const fApicHwEnabled = apicIsEnabled(pVCpu); if ( fApicHwEnabled @@ -2616,7 +2675,8 @@ DECLCALLBACK(bool) apicPostInterrupt(PVMCPUCC pVCpu, uint8_t uVector, XAPICTRIGG uint32_t uSrcTag) { Assert(pVCpu); - Assert(uVector > XAPIC_ILLEGAL_VECTOR_END); + AssertMsg(uVector > XAPIC_ILLEGAL_VECTOR_END, ("uVector=%#x, IcrLo=%#RX32 IcrHi=%#RX32\n", uVector, + VMCPU_TO_CX2APICPAGE(pVCpu)->icr_lo.all.u32IcrLo, VMCPU_TO_CX2APICPAGE(pVCpu)->icr_hi.u32IcrHi)); RT_NOREF(fAutoEoi); PVMCC pVM = pVCpu->CTX_SUFF(pVM); @@ -2835,7 +2895,7 @@ static DECLCALLBACK(void) apicUpdatePendingInterrupts(PVMCPUCC pVCpu) PXAPICPAGE pXApicPage = VMCPU_TO_XAPICPAGE(pVCpu); bool fHasPendingIntrs = false; - Log3(("APIC%u: apicUpdatePendingInterrupts:\n", pVCpu->idCpu)); + Log2(("APIC%u: apicUpdatePendingInterrupts:\n", pVCpu->idCpu)); STAM_PROFILE_START(&pApicCpu->StatUpdatePendingIntrs, a); /* Update edge-triggered pending interrupts. */ @@ -2949,6 +3009,56 @@ static DECLCALLBACK(VBOXSTRICTRC) apicExportState(PVMCPUCC pVCpu) } +/** + * @interface_method_impl{PDMAPICBACKENDR0,pfnUpdateStateAfterWrite} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicVBoxUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg) +{ + AssertReturn(pVCpu, VERR_INVALID_PARAMETER); + + Assert(PDMHasApic(pVCpu->CTX_SUFF(pVM))); + + /* + * In SVM, vAPIC registers are 32-bits wide and currently the two 64-bit accesses + * (Self-IPI, and ICR) are both trap-like accesses meaning the the higher 32 bits + * are already updated. + */ + PPDMDEVINS pDevIns = VMCPU_TO_DEVINS(pVCpu); + VBOXSTRICTRC rcStrict; + if (XAPIC_IN_X2APIC_MODE(pVCpu->apic.s.uApicBaseMsr)) + { + /* This is the conversion documented by AMD in 16.11.1 "x2APIC Register Address Space". */ + uint32_t const idMsr = MSR_IA32_X2APIC_START + (offApicReg >> 4); + + /* + * We shouldn't be getting called with reading just the ICR_HI bits in x2APIC mode. + * If we do, we just forward the error to the guest, like normal x2APIC operation. + * The assert is thus debug only. + */ + Assert(offApicReg != XAPIC_OFF_ICR_HI); + + uint64_t u64Value = 0; + rcStrict = apicReadMsr(pVCpu, idMsr, &u64Value); + if (rcStrict == VINF_SUCCESS) + rcStrict = apicWriteMsr(pVCpu, idMsr, u64Value); + if ( rcStrict == VINF_CPUM_R3_MSR_READ + || rcStrict == VINF_CPUM_R3_MSR_WRITE) + rcStrict = VINF_APIC_R3_UPDATE_STATE; + } + else + { + uint32_t u32Value = 0; + rcStrict = apicReadRegister(pDevIns, pVCpu, offApicReg, &u32Value); + if (rcStrict == VINF_SUCCESS) + rcStrict = apicWriteRegister(pDevIns, pVCpu, offApicReg, u32Value); + if ( rcStrict == VINF_IOM_R3_MMIO_READ + || rcStrict == VINF_IOM_R3_MMIO_WRITE) + rcStrict = VINF_APIC_R3_UPDATE_STATE; + } + return rcStrict; +} + + #ifndef IN_RING3 /** @@ -3083,5 +3193,7 @@ const PDMAPICBACKEND g_ApicBackend = #endif /* .pfnImportState = */ apicImportState, /* .pfnExportState = */ apicExportState, + /* .pfnUpdateStateAfterWrite = */ apicVBoxUpdateStateAfterWrite, + /* .pfnSetEoiFast = */ apicSetEoiFast, }; diff --git a/src/VBox/VMM/VMMAll/target-x86/IEMAllAImpl-x86-amd64.asm b/src/VBox/VMM/VMMAll/target-x86/IEMAllAImpl-x86-amd64.asm index a8355093a4a2..ee25dff2194a 100644 --- a/src/VBox/VMM/VMMAll/target-x86/IEMAllAImpl-x86-amd64.asm +++ b/src/VBox/VMM/VMMAll/target-x86/IEMAllAImpl-x86-amd64.asm @@ -1,4 +1,4 @@ -; $Id: IEMAllAImpl-x86-amd64.asm 114135 2026-05-14 18:43:29Z knut.osmundsen@oracle.com $ +; $Id: IEMAllAImpl-x86-amd64.asm 115018 2026-08-12 23:41:47Z knut.osmundsen@oracle.com $ ;; @file ; IEM - Instruction Implementation in Assembly, x86 target, amd64 host. ; @@ -93,36 +93,44 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME %endmacro %macro EPILOGUE_1_ARGS 0 ret + int3 %endmacro %macro EPILOGUE_1_ARGS_EX 0 ret + int3 %endmacro %macro PROLOGUE_2_ARGS 0 %endmacro %macro EPILOGUE_2_ARGS 0 ret + int3 %endmacro %macro EPILOGUE_2_ARGS_EX 1 ret + int3 %endmacro %macro PROLOGUE_3_ARGS 0 %endmacro %macro EPILOGUE_3_ARGS 0 ret + int3 %endmacro %macro EPILOGUE_3_ARGS_EX 1 ret + int3 %endmacro %macro PROLOGUE_4_ARGS 0 %endmacro %macro EPILOGUE_4_ARGS 0 ret + int3 %endmacro %macro EPILOGUE_4_ARGS_EX 1 ret + int3 %endmacro %ifdef ASM_CALL64_GCC @@ -201,10 +209,12 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME %macro EPILOGUE_1_ARGS 0 pop edi ret 0 + int3 %endmacro %macro EPILOGUE_1_ARGS_EX 1 pop edi ret %1 + int3 %endmacro %macro PROLOGUE_2_ARGS 0 @@ -213,10 +223,12 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME %macro EPILOGUE_2_ARGS 0 pop edi ret 0 + int3 %endmacro %macro EPILOGUE_2_ARGS_EX 1 pop edi ret %1 + int3 %endmacro %macro PROLOGUE_3_ARGS 0 @@ -231,6 +243,7 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME pop edi pop ebx ret %1 + int3 %endmacro %macro EPILOGUE_3_ARGS 0 EPILOGUE_3_ARGS_EX 4 @@ -251,6 +264,7 @@ GLOBALNAME_RAW NAME_FASTCALL(%1,%2,@), function, hidden, CALC_PROC_SIZE_RAW(NAME pop edi pop ebx ret %1 + int3 %endmacro %macro EPILOGUE_4_ARGS 0 EPILOGUE_4_ARGS_EX 8 @@ -2198,6 +2212,7 @@ BEGINPROC_FASTCALL iemAImpl_cmpxchg_u64 %+ %2, 16 pop edi pop esi ret 8 + int3 .cmpxchg8b_not_equal: cmp [esi + 4], edx ;; @todo FIXME - verify 64-bit compare implementation @@ -2736,6 +2751,7 @@ BEGINPROC_FASTCALL iemAImpl_ %+ %1 %+ _u8 %+ %5, 12 cmp T0_8, A1_8 jae .div_overflow jmp .div_no_overflow + int3 .divisor_negative: neg A1_8 @@ -2801,6 +2817,7 @@ BEGINPROC_FASTCALL iemAImpl_ %+ %1 %+ _u16 %+ %5, 16 cmp T0_16, T1_16 jae .div_overflow jmp .div_no_overflow + int3 .divisor_negative: neg T1_16 @@ -2881,6 +2898,7 @@ BEGINPROC_FASTCALL iemAImpl_ %+ %1 %+ _u32 %+ %5, 16 cmp T0_32, A2_32 jae .div_overflow jmp .div_no_overflow + int3 .divisor_negative: neg A2_32 @@ -2968,6 +2986,7 @@ BEGINPROC_FASTCALL iemAImpl_ %+ %1 %+ _u64 %+ %5, 20 cmp T0, A2 jae .div_overflow jmp .div_no_overflow + int3 .divisor_negative: neg A2 @@ -7517,3 +7536,5 @@ IEMIMPL_ADX_64 adcx, X86_EFL_CF IEMIMPL_ADX_32 adox, X86_EFL_OF IEMIMPL_ADX_64 adox, X86_EFL_OF + +MARK_OBJECT_RETPOLINE_SAFE ;; @todo retpoline: lots of indirect jmps/calls here for instructions with behavrioual immediates. Buch of missing int3 after ret too. diff --git a/src/VBox/VMM/VMMAll/target-x86/IEMAllCImplSvmInstr-x86.cpp b/src/VBox/VMM/VMMAll/target-x86/IEMAllCImplSvmInstr-x86.cpp index f23bfcc4808c..4c7c1b9ed832 100644 --- a/src/VBox/VMM/VMMAll/target-x86/IEMAllCImplSvmInstr-x86.cpp +++ b/src/VBox/VMM/VMMAll/target-x86/IEMAllCImplSvmInstr-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: IEMAllCImplSvmInstr-x86.cpp 113745 2026-04-07 10:06:17Z alexander.eichner@oracle.com $ */ +/* $Id: IEMAllCImplSvmInstr-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * IEM - AMD-V (Secure Virtual Machine) instruction implementation (x86 target). */ @@ -482,7 +482,7 @@ static VBOXSTRICTRC iemSvmVmrun(PVMCPUCC pVCpu, uint8_t cbInstr, RTGCPHYS GCPhys pVmcbCtrl->TLBCtrl.n.u24Reserved = 0; pVmcbCtrl->IntCtrl.n.u6Reserved = 0; pVmcbCtrl->IntCtrl.n.u3Reserved = 0; - pVmcbCtrl->IntCtrl.n.u5Reserved = 0; + pVmcbCtrl->IntCtrl.n.u4Reserved = 0; pVmcbCtrl->IntCtrl.n.u24Reserved = 0; pVmcbCtrl->IntShadow.n.u30Reserved = 0; pVmcbCtrl->ExitIntInfo.n.u19Reserved = 0; @@ -516,6 +516,16 @@ static VBOXSTRICTRC iemSvmVmrun(PVMCPUCC pVCpu, uint8_t cbInstr, RTGCPHYS GCPhys pVmcbCtrl->IntCtrl.n.u1AvicEnable = 0; } + /* X2AVIC. */ + if ( pVmcbCtrl->IntCtrl.n.u1X2AvicEnable + && ( !pVmcbCtrl->IntCtrl.n.u1AvicEnable + || !pVM->cpum.ro.GuestFeatures.fSvmX2Avic + || !pVM->cpum.ro.GuestFeatures.fSvmAvic)) + { + Log(("iemSvmVmrun: X2AVIC not supported or trying to be enabled without XAVIC -> #VMEXIT\n")); + return iemSvmVmexit(pVCpu, SVM_EXIT_INVALID, 0 /* uExitInfo1 */, 0 /* uExitInfo2 */); + } + /* Last branch record (LBR) virtualization. */ if ( pVmcbCtrl->LbrVirt.n.u1LbrVirt && !pVM->cpum.ro.GuestFeatures.fSvmLbrVirt) diff --git a/src/VBox/VMM/VMMAll/target-x86/PDMAllApic-x86.cpp b/src/VBox/VMM/VMMAll/target-x86/PDMAllApic-x86.cpp index 9a03588e365b..a9865ceac039 100644 --- a/src/VBox/VMM/VMMAll/target-x86/PDMAllApic-x86.cpp +++ b/src/VBox/VMM/VMMAll/target-x86/PDMAllApic-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: PDMAllApic-x86.cpp 112682 2026-01-25 17:10:52Z alexander.eichner@oracle.com $ */ +/* $Id: PDMAllApic-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * PDM - APIC (Advanced Programmable Interrupt Controller) Interface. */ @@ -464,6 +464,39 @@ VMM_INT_DECL(VBOXSTRICTRC) PDMApicExportState(PVMCPUCC pVCpu) } +/** + * Updates the APIC state after a write to the APIC page by hardware. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param offApicReg The APIC register offset which was updated. + * + * @note This is a helper for AVIC/APICv when used on AMD or Intel. + */ +VMM_INT_DECL(VBOXSTRICTRC) PDMApicUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg) +{ + AssertReturn(PDMCPU_TO_APICBACKEND(pVCpu)->pfnUpdateStateAfterWrite, VERR_INVALID_POINTER); + return PDMCPU_TO_APICBACKEND(pVCpu)->pfnUpdateStateAfterWrite(pVCpu, offApicReg); +} + + +/** + * Sets the End-Of-Interrupt (EOI) register when the ISR is already known. + * + * This is an optimization that lets us avoid scanning the 256-bit sparse ISR + * register figuring out the highest pending in-service vector. + * + * @returns Strict VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param uVector The vector (highest ISR) for the attempted EOI. + */ +VMM_INT_DECL(VBOXSTRICTRC) PDMApicSetEoiFast(PVMCPUCC pVCpu, uint8_t uVector) +{ + AssertReturn(PDMCPU_TO_APICBACKEND(pVCpu)->pfnSetEoiFast, VERR_INVALID_POINTER); + return PDMCPU_TO_APICBACKEND(pVCpu)->pfnSetEoiFast(pVCpu, uVector); +} + + /** * Registers a PDM APIC backend. * @@ -506,6 +539,8 @@ VMM_INT_DECL(int) PDMApicRegisterBackend(PVMCC pVM, PDMAPICBACKENDTYPE enmBacken #elif defined(IN_RING0) AssertPtrReturn(pBackend->pfnGetApicPageForCpu, VERR_INVALID_POINTER); #endif + AssertPtrReturn(pBackend->pfnUpdateStateAfterWrite, VERR_INVALID_POINTER); + AssertPtrReturn(pBackend->pfnSetEoiFast, VERR_INVALID_POINTER); /* * Register the backend. diff --git a/src/VBox/VMM/VMMR0/CPUMR0A.asm b/src/VBox/VMM/VMMR0/CPUMR0A.asm index 16c6d3f8af55..cb273fe2339c 100644 --- a/src/VBox/VMM/VMMR0/CPUMR0A.asm +++ b/src/VBox/VMM/VMMR0/CPUMR0A.asm @@ -1,4 +1,4 @@ - ; $Id: CPUMR0A.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ + ; $Id: CPUMR0A.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; CPUM - Ring-0 Assembly Routines (supporting HM and IEM). ; @@ -149,6 +149,7 @@ SEH64_END_PROLOGUE %ifdef VBOX_WITH_KERNEL_USING_XMM jmp .load_guest + int3 %endif .already_saved_host: %ifdef VBOX_WITH_KERNEL_USING_XMM @@ -310,3 +311,4 @@ SEH64_END_PROLOGUE %undef pXState ENDPROC cpumR0SaveGuestRestoreHostFPUState +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR0/PDMR0Driver.cpp b/src/VBox/VMM/VMMR0/PDMR0Driver.cpp index deaca4a77ce0..a671075ad342 100644 --- a/src/VBox/VMM/VMMR0/PDMR0Driver.cpp +++ b/src/VBox/VMM/VMMR0/PDMR0Driver.cpp @@ -1,4 +1,4 @@ -/* $Id: PDMR0Driver.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: PDMR0Driver.cpp 114981 2026-08-11 09:05:54Z knut.osmundsen@oracle.com $ */ /** @file * PDM - Pluggable Device and Driver Manager, R0 Driver parts. */ @@ -227,6 +227,7 @@ VMMR0_INT_DECL(int) PDMR0DriverCallReqHandler(PGVM pGVM, PPDMDRIVERCALLREQHANDLE AssertPtrReturn(pReq, VERR_INVALID_POINTER); AssertMsgReturn(pReq->Hdr.cbReq == sizeof(*pReq), ("%#x != %#x\n", pReq->Hdr.cbReq, sizeof(*pReq)), VERR_INVALID_PARAMETER); +#if 0 /** @todo ring-3/0 separation */ PPDMDRVINS pDrvIns = pReq->pDrvInsR0; AssertPtrReturn(pDrvIns, VERR_INVALID_POINTER); AssertReturn(pDrvIns->Internal.s.pVMR0 == pGVM, VERR_INVALID_PARAMETER); @@ -235,6 +236,9 @@ VMMR0_INT_DECL(int) PDMR0DriverCallReqHandler(PGVM pGVM, PPDMDRIVERCALLREQHANDLE AssertPtrReturn(pfnReqHandlerR0, VERR_INVALID_POINTER); rc = pfnReqHandlerR0(pDrvIns, pReq->uOperation, pReq->u64Arg); +#else + rc = VERR_NOT_SUPPORTED; +#endif } return rc; } diff --git a/src/VBox/VMM/VMMR0/VMMR0JmpA-amd64.asm b/src/VBox/VMM/VMMR0/VMMR0JmpA-amd64.asm index 17f88d2345c8..f1c37fb7bbb5 100644 --- a/src/VBox/VMM/VMMR0/VMMR0JmpA-amd64.asm +++ b/src/VBox/VMM/VMMR0/VMMR0JmpA-amd64.asm @@ -1,4 +1,4 @@ -; $Id: VMMR0JmpA-amd64.asm 114133 2026-05-14 13:05:57Z knut.osmundsen@oracle.com $ +; $Id: VMMR0JmpA-amd64.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; VMM - R0 SetJmp / LongJmp routines for AMD64. ; @@ -204,6 +204,7 @@ SEH64_END_PROLOGUE jbe .do_stack_buffer_big_enough mov ecx, ebx ; too much to copy, limit to ebx jmp .do_stack_copying + int3 .do_stack_buffer_big_enough: mov ebx, ecx ; ecx is smaller, update ebx for cbStackValid @@ -250,6 +251,7 @@ SEH64_END_PROLOGUE .unexpected_return_loop: int3 jmp .unexpected_return_loop + int3 ; ; Failure @@ -279,3 +281,4 @@ SEH64_END_PROLOGUE ret ENDPROC vmmR0CallRing3LongJmp +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR0/VMMR0TripleFaultHackA.asm b/src/VBox/VMM/VMMR0/VMMR0TripleFaultHackA.asm index 925ea045f9c9..13229c651d64 100644 --- a/src/VBox/VMM/VMMR0/VMMR0TripleFaultHackA.asm +++ b/src/VBox/VMM/VMMR0/VMMR0TripleFaultHackA.asm @@ -1,4 +1,4 @@ -; $Id: VMMR0TripleFaultHackA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: VMMR0TripleFaultHackA.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; VMM - Host Context Ring 0, Assembly Code for The Triple Fault Debugging Hack. ; @@ -57,6 +57,7 @@ BEGINPROC vmmR0TripleFaultHack .forever: hlt jmp .forever + int3 .s_szHello: db 'Hello post-reset world', 0ah, 0dh, 0 @@ -188,6 +189,7 @@ BEGINPROC vmmR0TripleFaultHackKbdWait pop xAX ret + int3 .read_data_and_status: in al, 60h @@ -272,3 +274,4 @@ BEGINPROC vmmR0TripleFaultHackTripleFault ret ENDPROC vmmR0TripleFaultHackTripleFault +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp index 7ab41223acf1..62009cef0f31 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0-x86.cpp 114490 2026-06-22 17:29:00Z knut.osmundsen@oracle.com $ */ +/* $Id: HMR0-x86.cpp 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * Hardware Assisted Virtualization Manager (HM) - Host Context Ring-0. */ @@ -861,6 +861,7 @@ static int hmR0EnableCpu(PVMCC pVM, RTCPUID idCpu) Assert(!RTThreadPreemptIsEnabled(NIL_RTTHREAD)); pHostCpu->idCpu = idCpu; + pHostCpu->idApic = ASMGetApicId(); /* Do NOT reset cTlbFlushes here, see @bugref{6255}. */ int rc; @@ -1297,19 +1298,45 @@ VMMR0_INT_DECL(int) HMR0InitVM(PVMCC pVM) /* Use the VMCS controls for swapping the EFER MSR if supported. */ pVM->hm.s.ForR3.vmx.fSupportsVmcsEfer = g_fHmVmxSupportsVmcsEfer; -#if 0 - /* Enable APIC register virtualization and virtual-interrupt delivery if supported. */ - if ( (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_APIC_REG_VIRT) - && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_INTR_DELIVERY)) - pVM->hm.s.fVirtApicRegs = true; - - /* Enable posted-interrupt processing if supported. */ - /** @todo Add and query IPRT API for host OS support for posted-interrupt IPI - * here. */ - if ( (g_HmMsrs.u.vmx.PinCtls.n.allowed1 & VMX_PIN_CTLS_POSTED_INT) - && (g_HmMsrs.u.vmx.ExitCtls.n.allowed1 & VMX_EXIT_CTLS_ACK_EXT_INT)) - pVM->hm.s.fPostedIntrs = true; -#endif + /* Configure the APICv level. */ + HMVMXAPICVLVL enmApicvLvl = pVM->hm.s.vmx.enmApicvLvl; + if ( enmApicvLvl >= kVmxApicvLvl_ApicAccessVirt + && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_APIC_ACCESS)) + { + if ( enmApicvLvl >= kVmxApicvLvl_ApicRegVirt + && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_APIC_REG_VIRT)) + { + pVM->hm.s.fVirtApicRegs = true; + + if ( enmApicvLvl >= kVmxApicvLvl_IntrDelivery + && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_INT_DELIVERY)) + { + + /* Enable posted-interrupt processing if supported. */ + /** @todo Add and query IPRT API for host OS support for posted-interrupt IPI + * here. */ + if ( enmApicvLvl >= kVmxApicvLvl_PostedIntrs + && (g_HmMsrs.u.vmx.PinCtls.n.allowed1 & VMX_PIN_CTLS_POSTED_INT) + && (g_HmMsrs.u.vmx.ExitCtls.n.allowed1 & VMX_EXIT_CTLS_ACK_EXT_INT)) + enmApicvLvl = kVmxApicvLvl_PostedIntrs; + else + enmApicvLvl = kVmxApicvLvl_IntrDelivery; + } + else + enmApicvLvl = kVmxApicvLvl_ApicRegVirt; + } + else + enmApicvLvl = kVmxApicvLvl_ApicAccessVirt; + } + else + enmApicvLvl = kVmxApicvLvl_None; + + /** @todo Update/remove as the implementation of the higher level features progresses. */ + if (enmApicvLvl > kVmxApicvLvl_ApicRegVirt) + enmApicvLvl = kVmxApicvLvl_ApicRegVirt; + + pVM->hmr0.s.vmx.enmApicvLvl = enmApicvLvl; + pVM->hm.s.ForR3.vmx.enmApicvLvl = enmApicvLvl; } else if (pVM->hm.s.svm.fSupported) { @@ -1363,6 +1390,15 @@ VMMR0_INT_DECL(int) HMR0InitVM(PVMCC pVM) fWorldSwitcher |= HM_WSF_IBPB_EXIT; if (pVM->hm.s.fIbpbOnVmEntry) fWorldSwitcher |= HM_WSF_IBPB_ENTRY; + + /* If IBPB doesn't clear the RSBs, do so manually unless shadow stack is enabled. */ + if (fWorldSwitcher & (HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT)) + { + if (g_CpumHostFeatures.s.fIbpbNoRet && !hmR0IsShadowStackEnabled()) + fWorldSwitcher |= HM_WSF_IBPB_MAN_RET; + if (!g_CpumHostFeatures.s.fArchPbrsbNo && !hmR0IsShadowStackEnabled()) + fWorldSwitcher |= HM_WSF_IBPB_PBRSB; + } } if (g_CpumHostFeatures.s.fFlushCmd) { diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm index 1094c028c627..1fb8fd545313 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0A-x86.asm @@ -1,4 +1,4 @@ -; $Id: HMR0A-x86.asm 114828 2026-07-31 09:19:37Z alexander.eichner@oracle.com $ +; $Id: HMR0A-x86.asm 115030 2026-08-13 02:46:39Z knut.osmundsen@oracle.com $ ;; @file ; HM - Ring-0 VMX, SVM world-switch and helper routines. ; @@ -308,6 +308,7 @@ BEGINPROC hmR0VmxExportHostSegmentRegsAsmHlp mov [pRestoreHost + VMXRESTOREHOST.uHostSelDS], ax ret + int3 ALIGNCODE(16) .use_rdmsr_for_fs_and_gs_base: @@ -435,6 +436,7 @@ BEGINPROC VMXRestoreHostState mov rsi, r11 %endif ret + int3 ALIGNCODE(8) .gdt_readonly_or_need_writable: @@ -450,6 +452,7 @@ ALIGNCODE(8) ltr dx mov cr0, r9 jmp .restore_fs + int3 ALIGNCODE(8) .gdt_readonly_need_writable: @@ -459,6 +462,7 @@ ALIGNCODE(8) ltr dx lgdt [rsi + VMXRESTOREHOST.HostGdtr] ; load the original GDT jmp .restore_fs + int3 ALIGNCODE(8) .restore_fs_using_wrmsr: @@ -497,6 +501,80 @@ BEGINPROC hmR0MdsClear ENDPROC hmR0MdsClear +;; +; Helper for checking whether shadow stacks are enabled. +; +BEGINPROC hmR0IsShadowStackEnabled + SEH64_END_PROLOGUE + + ; + ; The rdsspq instruction is a NOP unless CET is supported & enabled. + ; So, we do 'rdsspq rax' on a zero rax register and check if the result is non-zero. + ; + xor eax, eax +%ifdef __NASM__ + rdsspq rax +%else + db 0xf3, 0x48, 0x0f, 0x1e, 0xc8 ; rdsspq rdx - a NOP unless CET is supported & enabled. +%endif + test rax, rax + setnz al + movzx eax, al + ret +ENDPROC hmR0IsShadowStackEnabled + + +; +; Stuffing the return stack buffers (RSB), aka. return target buffers (RTB). +; +; The assumption here is that the RSB operates as a stack and has 32 or +; fewer entries. So, to stuff all of them, we do 32 calls without any returns. +; + +;; Do one (changes RSP). +%macro STUFF_ONE_RET_BUFFER 0 + call %%call_target + int3 +%%call_target: +%endmacro + +;; Do eight and adjust RSP afterwards. +%macro STUFF_EIGHT_RET_BUFFERS 0 + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + STUFF_ONE_RET_BUFFER + add xSP, xCB * 8 +%endmacro + +;; Do all 32. +%macro STUFF_ALL_RET_BUFFERS 0 + STUFF_EIGHT_RET_BUFFERS + STUFF_EIGHT_RET_BUFFERS + STUFF_EIGHT_RET_BUFFERS + STUFF_EIGHT_RET_BUFFERS +%endmacro + + +;; +; Stuffs the return target prediction buffers. +; +; @clobbers Nothing (only tramples the stack). +; @note This is not safe to call with shadow stack enabled! +; +ALIGNCODE(64) +BEGINPROC hmR0StuffReturnTargetPredictionBuffers + SEH64_END_PROLOGUE + STUFF_ALL_RET_BUFFERS + ret +ENDPROC hmR0StuffReturnTargetPredictionBuffers + + + ;; ; Common restore logic for success and error paths. We duplicate this because we ; don't want to waste writing the VINF_SUCCESS return value to the stack in the @@ -504,7 +582,7 @@ ENDPROC hmR0MdsClear ; ; @param 1 Zero if regular return, non-zero if error return. Controls label emission. ; @param 2 fLoadSaveGuestXcr0 value -; @param 3 The (HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY) + HM_WSF_IBPB_EXIT value. +; @param 3 The (HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY) + HM_WSF_IBPB_EXIT + HM_WSF_IBPB_PBRSB value. ; The entry values are either all set or not at all, as we're too lazy to flesh out all the variants. ; @param 4 The SSE saving/restoring: 0 to do nothing, 1 to do it manually, 2 to use xsave/xrstor. ; @@ -581,6 +659,10 @@ ENDPROC hmR0MdsClear mov eax, MSR_IA32_PRED_CMD_F_IBPB xor edx, edx wrmsr + %if (%3) & HM_WSF_IBPB_PBRSB + STUFF_ONE_RET_BUFFER + add rsp, xCB + %endif %endif %endif @@ -624,7 +706,7 @@ ENDPROC hmR0MdsClear ; ; @param 1 The suffix of the variation. ; @param 2 fLoadSaveGuestXcr0 value -; @param 3 The HM_WSF_IBPB_ENTRY + HM_WSF_IBPB_EXIT value. +; @param 3 The HM_WSF_IBPB_ENTRY + HM_WSF_IBPB_EXIT + HM_WSF_IBPB_PBRSB value. ; @param 4 The SSE saving/restoring: 0 to do nothing, 1 to do it manually, 2 to use xsave/xrstor. ; Drivers shouldn't use AVX registers without saving+loading: ; https://msdn.microsoft.com/en-us/library/windows/hardware/ff545910%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 @@ -664,6 +746,7 @@ BEGINPROC RT_CONCAT(hmR0VmxStartVm,%1) %endif je RT_CONCAT3(hmR0VmxStartVm,%1,_SseManual) jmp RT_CONCAT3(hmR0VmxStartVm,%1,_SseXSave) + int3 .save_xmm_no_need: %endif %endif @@ -732,7 +815,7 @@ BEGINPROC RT_CONCAT(hmR0VmxStartVm,%1) jne NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).precond_failure_return) mov eax, [rsi + GVMCPU.hmr0 + HMR0PERVCPU.fWorldSwitcher] - and eax, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT ; | HM_WSF_SPEC_CTRL + and eax, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB ; | HM_WSF_SPEC_CTRL cmp eax, (%3) mov eax, VERR_VMX_STARTVM_PRECOND_1 jne NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).precond_failure_return) @@ -862,6 +945,10 @@ BEGINPROC RT_CONCAT(hmR0VmxStartVm,%1) %if (%3) & HM_WSF_IBPB_ENTRY ; Indirect branch barrier. mov ecx, MSR_IA32_PRED_CMD wrmsr + %if (%3) & HM_WSF_IBPB_PBRSB + STUFF_ONE_RET_BUFFER + add rsp, xCB + %endif %endif %if (%3) & HM_WSF_L1D_ENTRY ; Level 1 data cache flush. mov ecx, MSR_IA32_FLUSH_CMD @@ -897,12 +984,14 @@ BEGINPROC RT_CONCAT(hmR0VmxStartVm,%1) jc NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmxstart64_invalid_vmcs_ptr) jz NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmxstart64_start_failed) jmp NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1)) ; here if vmresume detected a failure + int3 .vmlaunch64_launch: vmlaunch jc NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmxstart64_invalid_vmcs_ptr) jz NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmxstart64_start_failed) jmp NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1)) ; here if vmlaunch detected a failure + int3 ; Put these two outside the normal code path as they should rarely change. @@ -918,6 +1007,7 @@ ALIGNCODE(8) jna NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmwrite_failed) %endif jmp .wrote_host_rip + int3 ALIGNCODE(8) .write_host_rsp: @@ -931,6 +1021,7 @@ ALIGNCODE(8) jna NAME(RT_CONCAT(hmR0VmxStartVmHostRIP,%1).vmwrite_failed) %endif jmp .wrote_host_rsp + int3 ALIGNCODE(64) GLOBALNAME_EX RT_CONCAT(hmR0VmxStartVmHostRIP,%1), notype, hidden, (NAME(hmR0VmxStartVmHostRIP %+ %1) - NAME(hmR0VmxStartVm %+ %1 %+ _EndProc)) @@ -994,6 +1085,7 @@ GLOBALNAME_EX RT_CONCAT(hmR0VmxStartVmHostRIP,%1), notype, hidden, (NAME(hmR0Vmx popf leave ret + int3 ; ; Error returns. @@ -1004,16 +1096,19 @@ GLOBALNAME_EX RT_CONCAT(hmR0VmxStartVmHostRIP,%1), notype, hidden, (NAME(hmR0Vmx jz .return_after_vmwrite_error mov dword [rsp + cbFrame + frm_rcError], VERR_VMX_INVALID_VMCS_PTR jmp .return_after_vmwrite_error + int3 %endif .vmxstart64_invalid_vmcs_ptr: mov dword [rsp + cbFrame + frm_rcError], VERR_VMX_INVALID_VMCS_PTR_TO_START_VM jmp .vmstart64_error_return + int3 .vmxstart64_start_failed: mov dword [rsp + cbFrame + frm_rcError], VERR_VMX_UNABLE_TO_START_VM .vmstart64_error_return: RESTORE_STATE_VMX 1, %2, %3, %4 mov eax, [rbp + frm_rcError] jmp .vmstart64_end + int3 %ifdef VBOX_STRICT ; Precondition checks failed. @@ -1023,6 +1118,7 @@ GLOBALNAME_EX RT_CONCAT(hmR0VmxStartVmHostRIP,%1), notype, hidden, (NAME(hmR0Vmx %error Bad frame size value: cbFrame, expected cbBaseFrame %endif jmp .return_with_restored_preserved_registers + int3 %endif %undef frm_fRFlags @@ -1042,38 +1138,71 @@ ENDPROC RT_CONCAT(hmR0VmxStartVm,%1) %endmacro ; hmR0VmxStartVmTemplate %macro hmR0VmxStartVmSseTemplate 1-2 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 0, 0 | 0 | 0 | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 1, 0 | 0 | 0 | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 , %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 0, 0 | 0 | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 1, 0 | 0 | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 -hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, 0 | 0 | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, 0 | 0 | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, 0 | 0 | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, 0 | 0 | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | 0 , %1 + +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, 0 | 0 | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, 0 | 0 | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | 0 | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, 0 | 0 | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, 0 | 0 | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | 0 | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, 0 | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | 0 | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, 0 | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 0, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 +hmR0VmxStartVmTemplate _WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb %+ %2, 1, HM_WSF_IBPB_ENTRY | HM_WSF_L1D_ENTRY | HM_WSF_MDS_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_IBPB_PBRSB, %1 %endmacro hmR0VmxStartVmSseTemplate 0 @@ -1088,7 +1217,7 @@ hmR0VmxStartVmSseTemplate 2,_SseXSave ; ; @param 1 The suffix of the variation. ; @param 2 fLoadSaveGuestXcr0 value -; @param 3 The HM_WSF_IBPB_ENTRY + HM_WSF_IBPB_EXIT + HM_WSF_SPEC_CTRL value. +; @param 3 The HM_WSF_IBPB_ENTRY + HM_WSF_IBPB_EXIT + HM_WSF_SPEC_CTRL + HM_WSF_IBPB_MAN_RET value. ; @param 4 The SSE saving/restoring: 0 to do nothing, 1 to do it manually, 2 to use xsave/xrstor. ; Drivers shouldn't use AVX registers without saving+loading: ; https://msdn.microsoft.com/en-us/library/windows/hardware/ff545910%28v=vs.85%29.aspx?f=255&MSPPError=-2147217396 @@ -1129,6 +1258,7 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) %endif je RT_CONCAT3(hmR0SvmVmRun,%1,_SseManual) jmp RT_CONCAT3(hmR0SvmVmRun,%1,_SseXSave) + int3 .save_xmm_no_need: %endif %endif @@ -1198,7 +1328,7 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) jne .failure_return mov eax, [rsi + GVMCPU.hmr0 + HMR0PERVCPU.fWorldSwitcher] - and eax, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_SPEC_CTRL + and eax, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_MAN_RET cmp eax, (%3) mov eax, VERR_SVM_VMRUN_PRECOND_1 jne .failure_return @@ -1308,6 +1438,9 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) %if (%3) & HM_WSF_IBPB_ENTRY ; Fight spectre (trashes rax, rdx and rcx). + %if (%3) & HM_WSF_IBPB_MAN_RET + call NAME(hmR0StuffReturnTargetPredictionBuffers) + %endif mov ecx, MSR_IA32_PRED_CMD mov eax, MSR_IA32_PRED_CMD_F_IBPB xor edx, edx @@ -1408,6 +1541,13 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) mov eax, MSR_IA32_PRED_CMD_F_IBPB xor edx, edx wrmsr + %if (%3) & HM_WSF_IBPB_MAN_RET + %if 0 + call NAME(hmR0StuffReturnTargetPredictionBuffers) + %else + STUFF_EIGHT_RET_BUFFERS + %endif + %endif %endif %if %2 != 0 @@ -1486,6 +1626,7 @@ BEGINPROC RT_CONCAT(hmR0SvmVmRun,%1) popf leave ret + int3 %ifdef VBOX_STRICT ; Precondition checks failed. @@ -1512,55 +1653,108 @@ ENDPROC RT_CONCAT(hmR0SvmVmRun,%1) ; ; Instantiate the hmR0SvmVmRun various variations. ; -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl, 0, 0, 0 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl, 1, 0, 0 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl, 0, HM_WSF_IBPB_ENTRY, 0 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl, 1, HM_WSF_IBPB_ENTRY, 0 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl, 0, HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl, 1, HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl, 0, HM_WSF_SPEC_CTRL, 0 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl, 1, HM_WSF_SPEC_CTRL, 0 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet, 0, 0, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet, 1, 0, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet, 0, HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet, 1, HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet, 0, HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet, 1, HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet, 0, HM_WSF_SPEC_CTRL, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet, 1, HM_WSF_SPEC_CTRL, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 + +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 0 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 0 %ifdef VBOX_WITH_KERNEL_USING_XMM -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SseManual, 0, 0, 1 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SseManual, 1, 0, 1 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SseManual, 0, HM_WSF_IBPB_ENTRY, 1 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SseManual, 1, HM_WSF_IBPB_ENTRY, 1 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SseManual, 0, HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SseManual, 1, HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SseManual, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SseManual, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SseManual, 0, HM_WSF_SPEC_CTRL, 1 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SseManual, 1, HM_WSF_SPEC_CTRL, 1 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 - -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SseXSave, 0, 0, 2 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SseXSave, 1, 0, 2 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SseXSave, 0, HM_WSF_IBPB_ENTRY, 2 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SseXSave, 1, HM_WSF_IBPB_ENTRY, 2 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SseXSave, 0, HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SseXSave, 1, HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SseXSave, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SseXSave, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SseXSave, 0, HM_WSF_SPEC_CTRL, 2 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SseXSave, 1, HM_WSF_SPEC_CTRL, 2 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 -hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 -hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseManual, 0, 0, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseManual, 1, 0, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseManual, 0, HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseManual, 1, HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseManual, 0, HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseManual, 1, HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseManual, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseManual, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseManual, 0, HM_WSF_SPEC_CTRL, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseManual, 1, HM_WSF_SPEC_CTRL, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseManual, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseManual, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 + +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 1 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseManual, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseManual, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 1 + +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 0, 0, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 1, 0, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_SPEC_CTRL, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_SPEC_CTRL, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 0, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet_SseXSave, 1, HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 + +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY, 2 +hmR0SvmVmRunTemplate _SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 0, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 +hmR0SvmVmRunTemplate _WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet_SseXSave, 1, HM_WSF_IBPB_MAN_RET | HM_WSF_SPEC_CTRL | HM_WSF_IBPB_ENTRY | HM_WSF_IBPB_EXIT, 2 %endif + +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp index 8b546b6bb965..3d1e069f0308 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0SVM-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0SVM-x86.cpp 114522 2026-06-25 09:32:47Z alexander.eichner@oracle.com $ */ +/* $Id: HMR0SVM-x86.cpp 115076 2026-08-19 10:08:30Z alexander.eichner@oracle.com $ */ /** @file * HM SVM (AMD-V) - Host Context Ring-0. */ @@ -33,6 +33,7 @@ #define VMCPU_INCL_CPUM_GST_CTX #include #include +#include #include #include @@ -380,6 +381,8 @@ static FNSVMEXITHANDLER hmR0SvmExitSwInt; static FNSVMEXITHANDLER hmR0SvmExitTrRead; static FNSVMEXITHANDLER hmR0SvmExitTrWrite; static FNSVMEXITHANDLER hmR0SvmExitBusLock; +static FNSVMEXITHANDLER hmR0SvmExitAvicIncompleteIpi; +static FNSVMEXITHANDLER hmR0SvmExitAvicNoAccel; #ifdef VBOX_WITH_NESTED_HWVIRT_SVM static FNSVMEXITHANDLER hmR0SvmExitClgi; static FNSVMEXITHANDLER hmR0SvmExitStgi; @@ -409,6 +412,57 @@ static RTHCPHYS g_HCPhysIOBitmap; /** Pointer to the IO bitmap. */ static R0PTRTYPE(void *) g_pvIOBitmap; + +/** A list of x2APIC MSRs we don't want to intercept when using the AVIC. */ +static const uint32_t g_aX2AvicMsrs[] = +{ + MSR_IA32_X2APIC_ID, + MSR_IA32_X2APIC_VERSION, + MSR_IA32_X2APIC_TPR, + MSR_IA32_X2APIC_PPR, + MSR_IA32_X2APIC_EOI, + MSR_IA32_X2APIC_LDR, + MSR_IA32_X2APIC_SVR, + MSR_IA32_X2APIC_ISR0, + MSR_IA32_X2APIC_ISR1, + MSR_IA32_X2APIC_ISR2, + MSR_IA32_X2APIC_ISR3, + MSR_IA32_X2APIC_ISR4, + MSR_IA32_X2APIC_ISR5, + MSR_IA32_X2APIC_ISR6, + MSR_IA32_X2APIC_ISR7, + MSR_IA32_X2APIC_TMR0, + MSR_IA32_X2APIC_TMR1, + MSR_IA32_X2APIC_TMR2, + MSR_IA32_X2APIC_TMR3, + MSR_IA32_X2APIC_TMR4, + MSR_IA32_X2APIC_TMR5, + MSR_IA32_X2APIC_TMR6, + MSR_IA32_X2APIC_TMR7, + MSR_IA32_X2APIC_IRR0, + MSR_IA32_X2APIC_IRR1, + MSR_IA32_X2APIC_IRR2, + MSR_IA32_X2APIC_IRR3, + MSR_IA32_X2APIC_IRR4, + MSR_IA32_X2APIC_IRR5, + MSR_IA32_X2APIC_IRR6, + MSR_IA32_X2APIC_IRR7, + MSR_IA32_X2APIC_ESR, + MSR_IA32_X2APIC_LVT_CMCI, + MSR_IA32_X2APIC_ICR, + MSR_IA32_X2APIC_LVT_TIMER, + MSR_IA32_X2APIC_LVT_THERMAL, + MSR_IA32_X2APIC_LVT_PERF, + MSR_IA32_X2APIC_LVT_LINT0, + MSR_IA32_X2APIC_LVT_LINT1, + MSR_IA32_X2APIC_LVT_ERROR, + MSR_IA32_X2APIC_TIMER_ICR, + MSR_IA32_X2APIC_TIMER_CCR, + MSR_IA32_X2APIC_TIMER_DCR, + MSR_IA32_X2APIC_SELF_IPI +}; + + #ifdef VBOX_STRICT # define HMSVM_LOG_RBP_RSP RT_BIT_32(0) # define HMSVM_LOG_CR_REGS RT_BIT_32(1) @@ -425,6 +479,7 @@ static R0PTRTYPE(void *) g_pvIOBitmap; | HMSVM_LOG_GS \ | HMSVM_LOG_LBR) + /** * Dumps virtual CPU state and additional info. to the logger for diagnostics. * @@ -665,6 +720,14 @@ VMMR0DECL(void) SVMR0GlobalTerm(void) */ DECLINLINE(void) hmR0SvmFreeStructs(PVMCC pVM) { + if (pVM->hmr0.s.svm.hMemObjAvicHost != NIL_RTR0MEMOBJ) + { + RTR0MemObjFree(pVM->hmr0.s.svm.hMemObjAvicHost, false); + pVM->hmr0.s.svm.HCPhysAvicPhysIdTbl = 0; + pVM->hmr0.s.svm.HCPhysAvicLogicalIdTbl = 0; + pVM->hmr0.s.svm.hMemObjAvicHost = NIL_RTR0MEMOBJ; + } + for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++) { PVMCPUCC pVCpu = VMCC_GET_CPU(pVM, idCpu); @@ -714,27 +777,45 @@ static void hmR0SvmUpdateVmRunFunction(PVMCPUCC pVCpu) { static const struct CLANGWORKAROUND { PFNHMSVMVMRUN pfn; } s_aHmR0SvmVmRunFunctions[] = { - { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl }, - { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet }, + + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet }, + { hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet }, }; - uintptr_t const idx = (pVCpu->hmr0.s.fLoadSaveGuestXcr0 ? 1 : 0) - | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_ENTRY ? 2 : 0) - | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_EXIT ? 4 : 0) - | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_SPEC_CTRL ? 8 : 0); + uintptr_t const idx = (pVCpu->hmr0.s.fLoadSaveGuestXcr0 ? 1 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_ENTRY ? 2 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_EXIT ? 4 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_SPEC_CTRL ? 8 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_MAN_RET ? 16 : 0); PFNHMSVMVMRUN const pfnVMRun = s_aHmR0SvmVmRunFunctions[idx].pfn; if (pVCpu->hmr0.s.svm.pfnVMRun != pfnVMRun) pVCpu->hmr0.s.svm.pfnVMRun = pfnVMRun; @@ -776,6 +857,8 @@ VMMR0DECL(int) SVMR0InitVM(PVMCC pVM) /* * Initialize the R0 memory objects up-front so we can properly cleanup on allocation failures. */ + pVM->hmr0.s.svm.hMemObjAvicHost = NIL_RTR0MEMOBJ; + for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++) { PVMCPUCC pVCpu = VMCC_GET_CPU(pVM, idCpu); @@ -784,6 +867,23 @@ VMMR0DECL(int) SVMR0InitVM(PVMCC pVM) pVCpu->hmr0.s.svm.hMemObjMsrBitmap = NIL_RTR0MEMOBJ; } + /* + * Create the physical and logical APIC ID tables if AVIC is going to be used. + */ + void *pvAvicHost = NULL; + size_t const cbAvicPages = SVM_AVIC_PAGES << HOST_PAGE_SHIFT; + rc = RTR0MemObjAllocCont(&pVM->hmr0.s.svm.hMemObjAvicHost, cbAvicPages, + NIL_RTHCPHYS /*PhysHighest*/, false /* fExecutable */); + if (RT_FAILURE(rc)) + goto failure_cleanup; + + pvAvicHost = RTR0MemObjAddress(pVM->hmr0.s.svm.hMemObjAvicHost); + RT_BZERO(pvAvicHost, cbAvicPages); + pVM->hmr0.s.svm.HCPhysAvicPhysIdTbl = RTR0MemObjGetPagePhysAddr(pVM->hmr0.s.svm.hMemObjAvicHost, 0 /* iPage */); + pVM->hmr0.s.svm.HCPhysAvicLogicalIdTbl = RTR0MemObjGetPagePhysAddr(pVM->hmr0.s.svm.hMemObjAvicHost, 1 /* iPage */); + pVM->hmr0.s.svm.HCPhysApicAccess = RTR0MemObjGetPagePhysAddr(pVM->hmr0.s.svm.hMemObjAvicHost, 2 /* iPage */); + pVM->hmr0.s.svm.paAvicPhysIdTbl = (volatile uint64_t *)pvAvicHost; + for (VMCPUID idCpu = 0; idCpu < pVM->cCpus; idCpu++) { PVMCPUCC pVCpu = VMCC_GET_CPU(pVM, idCpu); @@ -1024,6 +1124,12 @@ VMMR0DECL(int) SVMR0SetupVM(PVMCC pVM) bool const fLbrVirt = RT_BOOL(g_fHmSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_LBR_VIRT); bool const fUseLbrVirt = fLbrVirt && pVM->hm.s.svm.fLbrVirt; /** @todo IEM implementation etc. */ + bool const fAvic = RT_BOOL(g_fHmSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_AVIC); + bool const fUseAvic = fAvic && pVM->hm.s.svm.fAvic && PDMHasApic(pVM); + + //bool const fX2Avic = RT_BOOL(g_fHmSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_X2AVIC); + //bool const fUseX2Avic = fX2Avic && pVM->hm.s.svm.fAvic; + #ifdef VBOX_WITH_NESTED_HWVIRT_SVM bool const fVirtVmsaveVmload = RT_BOOL(g_fHmSvmFeatures & X86_CPUID_SVM_FEATURE_EDX_VIRT_VMSAVE_VMLOAD); bool const fUseVirtVmsaveVmload = fVirtVmsaveVmload && pVM->hm.s.svm.fVirtVmsaveVmload && fNestedPaging; @@ -1210,6 +1316,24 @@ VMMR0DECL(int) SVMR0SetupVM(PVMCC pVM) /* Initially all VMCB clean bits MBZ indicating that everything should be loaded from the VMCB in memory. */ Assert(pVmcbCtrl0->u32VmcbCleanBits == 0); + if (fUseAvic) + { + void *pvVirtApic = NULL; + RTHCPHYS HCPhysVirtApic = 0; + int rc = PDMR0ApicGetApicPageForCpu(pVCpu0, &HCPhysVirtApic, (PRTR0PTR)&pvVirtApic, NULL /*pR3Ptr*/); + AssertRCReturn(rc, rc); + + pVmcbCtrl0->AvicBackingPagePtr.u = HCPhysVirtApic; + pVmcbCtrl0->AvicLogicalTablePtr.u = pVM->hmr0.s.svm.HCPhysAvicLogicalIdTbl; + pVmcbCtrl0->AvicPhysicalTablePtr.u = pVM->hmr0.s.svm.HCPhysAvicPhysIdTbl | (pVM->cCpus - 1); + pVmcbCtrl0->IntCtrl.n.u1AvicEnable = 1; + + pVCpu0->hmr0.s.svm.u64PhysIdEntry = RT_BIT_64(63) | HCPhysVirtApic; + pVCpu0->hm.s.svm.fUseAvic = fUseAvic; + } + else + Assert(!pVCpu0->hm.s.svm.fUseAvic); + for (VMCPUID idCpu = 1; idCpu < pVM->cCpus; idCpu++) { PVMCPUCC pVCpuCur = VMCC_GET_CPU(pVM, idCpu); @@ -1232,6 +1356,26 @@ VMMR0DECL(int) SVMR0SetupVM(PVMCC pVM) Assert(pVCpuCur->hm.s.fGIMTrapXcptUD == pVCpu0->hm.s.fGIMTrapXcptUD); /* Same for GCM, #DE trapping should be uniform across VCPUs. */ Assert(pVCpuCur->hm.s.fGCMTrapXcptDE == pVCpu0->hm.s.fGCMTrapXcptDE); + + /* Update the per-VCPU/VMCB specific fields for the AVIC. */ + if (pVCpu0->hm.s.svm.fUseAvic) + { + void *pvVirtApic = NULL; + RTHCPHYS HCPhysVirtApic = 0; + int rc = PDMR0ApicGetApicPageForCpu(pVCpuCur, &HCPhysVirtApic, (PRTR0PTR)&pvVirtApic, NULL /*pR3Ptr*/); + AssertRCReturn(rc, rc); + + pVmcbCtrlCur->AvicBackingPagePtr.u = HCPhysVirtApic; + pVCpuCur->hmr0.s.svm.u64PhysIdEntry = RT_BIT_64(63) | HCPhysVirtApic; + pVCpuCur->hm.s.svm.fUseAvic = true; + + Assert(pVmcbCtrlCur->AvicLogicalTablePtr.u == pVmcb0->ctrl.AvicLogicalTablePtr.u); + Assert(pVmcbCtrlCur->AvicPhysicalTablePtr.u == pVmcb0->ctrl.AvicPhysicalTablePtr.u); + Assert(pVmcbCtrlCur->IntCtrl.n.u1AvicEnable == pVmcb0->ctrl.IntCtrl.n.u1AvicEnable); + Assert(pVCpuCur->hm.s.svm.fUseAvic == pVCpu0->hm.s.svm.fUseAvic); + } + else + Assert(!pVCpuCur->hm.s.svm.fUseAvic); } #ifdef VBOX_WITH_NESTED_HWVIRT_SVM @@ -2163,7 +2307,8 @@ static int hmR0SvmExportGuestApicTpr(PVMCPUCC pVCpu, PSVMVMCB pVmcb) { PVMCC pVM = pVCpu->CTX_SUFF(pVM); if ( PDMHasApic(pVM) - && PDMApicIsEnabled(pVCpu)) + && PDMApicIsEnabled(pVCpu) + && !pVCpu->hm.s.svm.fUseAvic) { bool fPendingIntr; uint8_t u8Tpr; @@ -3164,8 +3309,8 @@ static VBOXSTRICTRC hmR0SvmExitToRing3(PVMCPUCC pVCpu, VBOXSTRICTRC rcExit) /* Please, no longjumps here (any logging shouldn't flush jump back to ring-3). NO LOGGING BEFORE THIS POINT! */ VMMRZCallRing3Disable(pVCpu); - Log4Func(("rcExit=%d LocalFF=%#RX64 GlobalFF=%#RX32\n", VBOXSTRICTRC_VAL(rcExit), (uint64_t)pVCpu->fLocalForcedActions, - pVCpu->CTX_SUFF(pVM)->fGlobalForcedActions)); + Log4Func(("rcExit=%d LocalFF=%#RX64 GlobalFF=%#RX32 PicInterrupt=%RTbool\n", VBOXSTRICTRC_VAL(rcExit), (uint64_t)pVCpu->fLocalForcedActions, + pVCpu->CTX_SUFF(pVM)->fGlobalForcedActions, VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_PIC))); /* We need to do this only while truly exiting the "inner loop" back to ring-3 and -not- for any longjmp to ring3. */ if (pVCpu->hm.s.Event.fPending) @@ -3677,11 +3822,11 @@ static VBOXSTRICTRC hmR0SvmEvaluatePendingEvent(PVMCPUCC pVCpu, PCSVMTRANSIENT p * * See AMD spec. 15.21.4 "Injecting Virtual (INTR) Interrupts". */ - else if ( VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC) - && !pVCpu->hm.s.fSingleInstruction) + if ( VMCPU_FF_IS_ANY_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC | VMCPU_FF_INTERRUPT_PIC) + && !pVCpu->hm.s.fSingleInstruction) { bool const fBlockInt = !pSvmTransient->fIsNestedGuest ? !(pCtx->eflags.u & X86_EFL_IF) - : CPUMIsGuestSvmPhysIntrEnabled(pVCpu, pCtx); + : CPUMIsGuestSvmPhysIntrEnabled(pVCpu, pCtx); if ( fGif && !fBlockInt && !fIntShadow) @@ -3694,28 +3839,49 @@ static VBOXSTRICTRC hmR0SvmEvaluatePendingEvent(PVMCPUCC pVCpu, PCSVMTRANSIENT p return IEMExecSvmVmexit(pVCpu, SVM_EXIT_INTR, 0, 0); } #endif - uint8_t u8Interrupt; - int rc = PDMGetInterrupt(pVCpu, &u8Interrupt); - if (RT_SUCCESS(rc)) - { - Log4(("Setting external interrupt %#x pending for injection\n", u8Interrupt)); - SVMEVENT Event; - Event.u = 0; - Event.n.u1Valid = 1; - Event.n.u8Vector = u8Interrupt; - Event.n.u3Type = SVM_EVENT_EXTERNAL_IRQ; - hmR0SvmSetPendingEvent(pVCpu, &Event, 0 /* GCPtrFaultAddress */); - } - else if (rc == VERR_APIC_INTR_MASKED_BY_TPR) + /* With the AVIC, we still need to deliver PIC style interrupts ourselves. */ + bool fGetInterrupt = true; + if (pVCpu->hm.s.svm.fUseAvic) { + if (!VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_PIC)) + fGetInterrupt = false; /* - * AMD-V has no TPR thresholding feature. TPR and the force-flag will be - * updated eventually when the TPR is written by the guest. + * We clear the interrupt flag here because we are certain that all + * conditions necessary for the AVIC hardware to deliver the interrupt + * are met. */ - STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchTprMaskedIrq); + if (VMCPU_FF_IS_SET(pVCpu, VMCPU_FF_INTERRUPT_APIC)) + VMCPU_FF_CLEAR(pVCpu, VMCPU_FF_INTERRUPT_APIC); + } + if (fGetInterrupt) + { + uint8_t u8Interrupt; + int rc = PDMGetInterrupt(pVCpu, &u8Interrupt); + if (RT_SUCCESS(rc)) + { + Log4(("Setting external interrupt %#x pending for injection\n", u8Interrupt)); + SVMEVENT Event; + Event.u = 0; + Event.n.u1Valid = 1; + Event.n.u8Vector = u8Interrupt; + Event.n.u3Type = SVM_EVENT_EXTERNAL_IRQ; + hmR0SvmSetPendingEvent(pVCpu, &Event, 0 /* GCPtrFaultAddress */); + } + else if (rc == VERR_APIC_INTR_MASKED_BY_TPR) + { + /* + * AMD-V has no TPR thresholding feature. TPR and the force-flag will be + * updated eventually when the TPR is written by the guest. + */ + STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchTprMaskedIrq); + Log4(("External interrupt %#x masked by TPR\n", u8Interrupt)); + } + else + { + Log4(("PDMGetInterrupt failed. rc=%Rrc\n", rc)); + STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchGuestIrq); + } } - else - STAM_COUNTER_INC(&pVCpu->hm.s.StatSwitchGuestIrq); } else if (!fGif) hmR0SvmSetCtrlIntercept(pVmcb, SVM_CTRL_INTERCEPT_STGI); @@ -3871,7 +4037,9 @@ static void hmR0SvmReportWorldSwitchError(PVMCPUCC pVCpu, int rcVMRun) Log4(("ctrl.IntCtrl.u3Reserved %#x\n", pVmcb->ctrl.IntCtrl.n.u3Reserved)); Log4(("ctrl.IntCtrl.u1VIntrMasking %#x\n", pVmcb->ctrl.IntCtrl.n.u1VIntrMasking)); Log4(("ctrl.IntCtrl.u1VGifEnable %#x\n", pVmcb->ctrl.IntCtrl.n.u1VGifEnable)); - Log4(("ctrl.IntCtrl.u5Reserved1 %#x\n", pVmcb->ctrl.IntCtrl.n.u5Reserved)); + Log4(("ctrl.IntCtrl.u4Reserved4 %#x\n", pVmcb->ctrl.IntCtrl.n.u4Reserved)); + Log4(("ctrl.IntCtrl.u1X2AvicEnable %#x\n", pVmcb->ctrl.IntCtrl.n.u1X2AvicEnable)); + Log4(("ctrl.IntCtrl.u1AvicEnable %#x\n", pVmcb->ctrl.IntCtrl.n.u1AvicEnable)); Log4(("ctrl.IntCtrl.u8VIntrVector %#x\n", pVmcb->ctrl.IntCtrl.n.u8VIntrVector)); Log4(("ctrl.IntCtrl.u24Reserved %#x\n", pVmcb->ctrl.IntCtrl.n.u24Reserved)); @@ -4065,6 +4233,47 @@ static VBOXSTRICTRC hmR0SvmCheckForceFlags(PVMCPUCC pVCpu) } +/** + * Unmaps the APIC-access page for virtualizing APIC accesses. + * + * @returns VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param GCPhysApicBase The guest-physical address of the APIC access page. + */ +static int hmR0SvmUnmapHCApicAccessPage(PVMCPUCC pVCpu, RTGCPHYS GCPhysApicBase) +{ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + Assert(GCPhysApicBase); + + Log4Func(("Unaliasing any existing mapping to the HC APIC-access page at %#RGp\n", GCPhysApicBase)); + return PGMHandlerPhysicalReset(pVM, GCPhysApicBase); +} + + +/** + * Map the APIC-access page for virtualizing APIC accesses. + * + * This can cause a longjumps to R3 due to the acquisition of the PGM lock. Hence, + * this not done as part of exporting guest state, see @bugref{8721}. + * + * @returns VBox status code. + * @param pVCpu The cross context virtual CPU structure. + * @param GCPhysApicBase The guest-physical address of the APIC access page. + */ +static int hmR0SvmMapHCApicAccessPage(PVMCPUCC pVCpu, RTGCPHYS GCPhysApicBase) +{ + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + Assert(GCPhysApicBase); + + Log4Func(("Mapping HC APIC-access page at %#RGp\n", GCPhysApicBase)); + + int const rc = IOMR0MmioMapMmioHCPage(pVM, pVCpu, GCPhysApicBase, pVM->hmr0.s.svm.HCPhysApicAccess, + X86_PTE_RW | X86_PTE_P); + AssertRCReturn(rc, rc); + return VINF_SUCCESS; +} + + /** * Does the preparations before executing guest code in AMD-V. * @@ -4132,6 +4341,57 @@ static VBOXSTRICTRC hmR0SvmPreRunGuest(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransie ASMAtomicUoOrU64(&pVCpu->hm.s.fCtxChanged, HM_CHANGED_ALL_GUEST); #endif + /* + * Setup the AVIC state if enabled. + */ + if (pVCpu->hm.s.svm.fUseAvic) + { + Assert(PDMHasApic(pVM)); + + /* Get the APIC base MSR from the virtual APIC device. */ + uint64_t const uApicBaseMsr = PDMApicGetBaseMsrNoCheck(pVCpu); + Assert( MSR_IA32_APICBASE_GET_ADDR(uApicBaseMsr) == MSR_IA32_APICBASE_ADDR + || !(uApicBaseMsr & MSR_IA32_APICBASE_EN)); + if (uApicBaseMsr != pVCpu->hm.s.svm.u64GstMsrApicBase) + { + PSVMVMCB pVmcb = pVCpu->hmr0.s.svm.pVmcb; + + /* Unalias any existing mapping. */ + RTGCPHYS const GCPhysApic = uApicBaseMsr & ~(RTGCPHYS)GUEST_PAGE_OFFSET_MASK; + hmR0SvmUnmapHCApicAccessPage(pVCpu, GCPhysApic); + Log4(("Unaliasing any previous AVIC backing page mappings at %#RGp\n", GCPhysApic)); + + /* XAPIC. */ + if (uApicBaseMsr & MSR_IA32_APICBASE_EN) + { + rc = hmR0SvmMapHCApicAccessPage(pVCpu, GCPhysApic); + AssertRCReturn(rc, rc); + Log4(("Mapped AVIC backing page at %#RGp\n", GCPhysApic)); + } + + /* X2APIC. */ + { + /* If enabled don't intercept X2APIC MSRs as the intercept has higher priority than the AVIC hardware. */ + bool const fX2AvicEnable = (uApicBaseMsr & (MSR_IA32_APICBASE_EN | MSR_IA32_APICBASE_EXTD)) + == (MSR_IA32_APICBASE_EN | MSR_IA32_APICBASE_EXTD); + SVMMSREXITREAD const fRdPerm = fX2AvicEnable ? SVMMSREXIT_PASSTHRU_READ : SVMMSREXIT_INTERCEPT_READ; + SVMMSREXITWRITE const fWrPerm = fX2AvicEnable ? SVMMSREXIT_PASSTHRU_WRITE : SVMMSREXIT_INTERCEPT_WRITE; + + uint8_t *pbMsrBitmap = (uint8_t *)pVCpu->hmr0.s.svm.pvMsrBitmap; + for (uint32_t i = 0; i < RT_ELEMENTS(g_aX2AvicMsrs); i++) + hmR0SvmSetMsrPermission(pVCpu, pbMsrBitmap, g_aX2AvicMsrs[i], fRdPerm, fWrPerm); + pVmcb->ctrl.IntCtrl.n.u1X2AvicEnable = fX2AvicEnable; + } + + pVmcb->ctrl.AvicBar.u = MSR_IA32_APICBASE_GET_ADDR(uApicBaseMsr); + pVmcb->ctrl.u32VmcbCleanBits &= ~HMSVM_VMCB_CLEAN_AVIC; + /** @todo Do we need to flush the TLB with SVM_TLB_FLUSH_SINGLE_CONTEXT here? */ + + /* Update the per-VCPU cache of the APIC base MSR corresponding to the mapped APIC access page. */ + pVCpu->hm.s.svm.u64GstMsrApicBase = uApicBaseMsr; + } + } + #ifdef VBOX_WITH_NESTED_HWVIRT_SVM /* * Set up the nested-guest VMCB for execution using hardware-assisted SVM. @@ -4269,6 +4529,15 @@ static void hmR0SvmPreRunGuestCommitted(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransi pSvmTransient->fWasGuestDebugStateActive = CPUMIsGuestDebugStateActive(pVCpu); pSvmTransient->fWasHyperDebugStateActive = CPUMIsHyperDebugStateActive(pVCpu); + /* Set AVIC state. */ + if ( pVCpu->hm.s.svm.fUseAvic + && PDMHasApic(pVM)) /** @todo Check where we can merge the PDMHasApic() call into fUseAvic. */ + { + ASMAtomicUoWriteU64(&pVM->hmr0.s.svm.paAvicPhysIdTbl[pVCpu->idCpu], pVCpu->hmr0.s.svm.u64PhysIdEntry /*| RT_BIT_64(62) | pHostCpu->idApic*/); + if (fMigratedHostCpu) + pVCpu->hmr0.s.fForceTLBFlush = true; /* Need to flush the TLB with SVM_TLB_FLUSH_SINGLE_CONTEXT */ + } + #ifdef VBOX_WITH_NESTED_HWVIRT_SVM uint8_t *pbMsrBitmap; if (!pSvmTransient->fIsNestedGuest) @@ -4403,6 +4672,19 @@ static void hmR0SvmPostRunGuest(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransient, VBO ASMSetFlags(pSvmTransient->fEFlags); /* Enable interrupts. */ VMMRZCallRing3Enable(pVCpu); /* It is now safe to do longjmps to ring-3!!! */ + /* Set AVIC state. */ + if ( pVCpu->hm.s.svm.fUseAvic + && PDMHasApic(pVM)) /** @todo Check where we can merge the PDMHasApic() call into fUseAvic. */ + { + ASMAtomicUoWriteU64(&pVM->hmr0.s.svm.paAvicPhysIdTbl[pVCpu->idCpu], pVCpu->hmr0.s.svm.u64PhysIdEntry); /* Clear IsRunning bit. */ + /* + * It's possible VMCPU_FF_INTERRUPT_APIC might be pending here if interrupts were seen as disabled + * (or interrupt shadow was active) in hmR0SvmEvaluatePendingEvent. There is no easy way to detect + * if the interrupt was delivered by the AVIC hardware. Leaving the force-flag pending here should + * be fine (an extra check the next time around, mostly harmless). + */ + } + /* If VMRUN failed, we can bail out early. This does -not- cover SVM_EXIT_INVALID. */ if (RT_UNLIKELY(rcVMRun != VINF_SUCCESS)) { @@ -4496,6 +4778,7 @@ static VBOXSTRICTRC hmR0SvmRunGuestCodeNormal(PVMCPUCC pVCpu, uint32_t *pcLoops) SvmTransient.fUpdateTscOffsetting = true; SvmTransient.pVmcb = pVCpu->hmr0.s.svm.pVmcb; + Log2Func(("\n")); VBOXSTRICTRC rc = VERR_INTERNAL_ERROR_5; for (;;) { @@ -5403,6 +5686,8 @@ static VBOXSTRICTRC hmR0SvmHandleExit(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransien case SVM_EXIT_XSETBV: VMEXIT_CALL_RET(0, hmR0SvmExitXsetbv(pVCpu, pSvmTransient)); case SVM_EXIT_FERR_FREEZE: VMEXIT_CALL_RET(0, hmR0SvmExitFerrFreeze(pVCpu, pSvmTransient)); case SVM_EXIT_BUSLOCK: VMEXIT_CALL_RET(0, hmR0SvmExitBusLock(pVCpu, pSvmTransient)); + case SVM_EXIT_AVIC_INCOMPLETE_IPI: VMEXIT_CALL_RET(0, hmR0SvmExitAvicIncompleteIpi(pVCpu, pSvmTransient)); + case SVM_EXIT_AVIC_NOACCEL: VMEXIT_CALL_RET(0, hmR0SvmExitAvicNoAccel(pVCpu, pSvmTransient)); default: { @@ -9025,6 +9310,124 @@ HMSVM_EXIT_DECL hmR0SvmExitBusLock(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransient) } +/** + * \#VMEXIT handler for AVIC incomplete IPI delivery operations due to a halting vCPU. + * Conditional \#VMEXIT. + */ +HMSVM_EXIT_DECL hmR0SvmExitAvicIncompleteIpi(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransient) +{ + HMSVM_VALIDATE_EXIT_HANDLER_PARAMS(pVCpu, pSvmTransient); + STAM_REL_COUNTER_INC(&pVCpu->hm.s.StatSvmExitAvicIncompleteIpi); + + uint64_t const u64ExitInfo1 = pSvmTransient->pVmcb->ctrl.u64ExitInfo1; + uint32_t const u32ApicIcrLo = RT_LO_U32(u64ExitInfo1); RT_NOREF(u32ApicIcrLo); + uint32_t const u32ApicIcrHi = RT_HI_U32(u64ExitInfo1); RT_NOREF(u32ApicIcrHi); + + uint64_t const u64ExitInfo2 = pSvmTransient->pVmcb->ctrl.u64ExitInfo2; + uint32_t const idApic = (uint32_t)(u64ExitInfo2 & SVM_EXIT2_INC_IPI_INDEX_MASK); + uint32_t const idFailure = (uint32_t)(u64ExitInfo2 >> SVM_EXIT2_INC_IPI_ID_SHIFT); + + Log2Func(("\n")); + Log4Func(("AVICExitIncompleteIpi/%u: ICRL=%#x ICRH=%#x idApic=%#x idFailure=%#x\n", + pVCpu->idCpu, u32ApicIcrLo, u32ApicIcrHi, idApic, idFailure)); + if ( idFailure == SVM_EXIT2_INC_IPI_INDEX_INVALID_INTR_TYPE + || idFailure == SVM_EXIT2_INC_IPI_INDEX_INVALID_TARGET) + { + pVCpu->hm.s.offApicReg = XAPIC_OFF_ICR_LO; + return PDMApicUpdateStateAfterWrite(pVCpu, XAPIC_OFF_ICR_LO); + } + + if (idFailure == SVM_EXIT2_INC_IPI_INDEX_TARGET_NOT_RUNNING) + { + PVMCC pVM = pVCpu->CTX_SUFF(pVM); + PVMCPUCC pVCpuDst = pVM->CTX_SUFF(apCpus)[idApic]; + VMCPUID idCpu = pVCpuDst->idCpu; + if (VMMGetCpuId(pVM) != idCpu) + { + switch (VMCPU_GET_STATE(pVCpuDst)) + { + case VMCPUSTATE_STARTED_EXEC: + Log7Func(("idCpu=%u VMCPUSTATE_STARTED_EXEC\n", idCpu)); + GVMMR0SchedPokeNoGVMNoLock(pVM, idCpu); + break; + + case VMCPUSTATE_STARTED_HALTED: + Log7Func(("idCpu=%u VMCPUSTATE_STARTED_HALTED\n", idCpu)); + VMCPU_FF_SET(pVCpuDst, VMCPU_FF_UNHALT); + GVMMR0SchedWakeUpNoGVMNoLock(pVM, idCpu); + break; + + default: + Log7Func(("idCpu=%u enmState=%d\n", idCpu, pVCpu->enmState)); + break; /* nothing to do in other states. */ + } + } + return VINF_SUCCESS; + } + + AssertMsgFailed(("hmR0SvmExitAvicIncompleteIpi: Unexpected failure type %#x\n", idFailure)); + return VERR_SVM_IPE_4; +} + + +/** + * \#VMEXIT handler for AVIC no acceleration operations. + * Conditional \#VMEXIT. + */ +HMSVM_EXIT_DECL hmR0SvmExitAvicNoAccel(PVMCPUCC pVCpu, PSVMTRANSIENT pSvmTransient) +{ + HMSVM_VALIDATE_EXIT_HANDLER_PARAMS(pVCpu, pSvmTransient); + STAM_REL_COUNTER_INC(&pVCpu->hm.s.StatSvmExitAvicNoAccel); + + uint16_t const offApicReg = pSvmTransient->pVmcb->ctrl.u64ExitInfo1 & 0xfff; + bool const fWr = RT_BOOL(pSvmTransient->pVmcb->ctrl.u64ExitInfo1 & RT_BIT_64(32)); RT_NOREF(fWr); + + /* + * Determine whether the access is fault or trap like. + * Trap like exits have the value updated in the APIC page already + * while fault like exits need emulating the instruction. + * + * Table 15-22 in chapter 15.29.3.1 (40332_4.09_APM_PUB.pdf) gives an overview. + */ + switch (offApicReg) + { + case XAPIC_OFF_ID: + case XAPIC_OFF_RRD: + case XAPIC_OFF_LDR: + case XAPIC_OFF_DFR: + case XAPIC_OFF_SVR: + case XAPIC_OFF_ESR: + case XAPIC_OFF_ICR_LO: + case XAPIC_OFF_LVT_TIMER: + case XAPIC_OFF_LVT_THERMAL: + case XAPIC_OFF_LVT_PERF: + case XAPIC_OFF_LVT_LINT0: + case XAPIC_OFF_LVT_LINT1: + case XAPIC_OFF_LVT_ERROR: + case XAPIC_OFF_TIMER_ICR: + case XAPIC_OFF_TIMER_DCR: + /** @todo Extended Interrupt Local Vector Table Registers when we start supporting it. */ + Assert(fWr); + break; + + case XAPIC_OFF_EOI: + { + uint8_t const uVector = pSvmTransient->pVmcb->ctrl.u64ExitInfo2 & 0xff; + return PDMApicSetEoiFast(pVCpu, uVector); + } + + default: + /* These accesses fault -> emulate completely and be done with it. */ + /** @todo Check whether we can speed things up with EMHistoryExec... */ + return hmR0SvmExitInterpretInstruction(pVCpu, pSvmTransient, HMSVM_CPUMCTX_EXTRN_ALL, HM_CHANGED_ALL_GUEST); + } + + Log2(("AVICNoAccelExit/%u: Trapping offApicReg=%#x\n", pVCpu->idCpu, offApicReg)); + pVCpu->hm.s.offApicReg = offApicReg; + return PDMApicUpdateStateAfterWrite(pVCpu, offApicReg); +} + + #ifdef VBOX_WITH_NESTED_HWVIRT_SVM /** * \#VMEXIT handler for CLGI (SVM_EXIT_CLGI). Conditional \#VMEXIT. diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0UtilA-x86.asm b/src/VBox/VMM/VMMR0/target-x86/HMR0UtilA-x86.asm index 26f8bb43b9bb..9af6a15cecd3 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0UtilA-x86.asm +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0UtilA-x86.asm @@ -1,4 +1,4 @@ -; $Id: HMR0UtilA-x86.asm 114485 2026-06-22 14:00:21Z klaus.espenlaub@oracle.com $ +; $Id: HMR0UtilA-x86.asm 115016 2026-08-12 23:40:45Z knut.osmundsen@oracle.com $ ;; @file ; HM - Ring-0 VMX & SVM Helpers. ; @@ -59,6 +59,7 @@ BEGINPROC VMXWriteVmcs64 jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_VMX_INVALID_VMCS_FIELD @@ -90,6 +91,7 @@ BEGINPROC VMXReadVmcs64 jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_VMX_INVALID_VMCS_FIELD @@ -123,6 +125,7 @@ BEGINPROC VMXReadVmcs32 jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_VMX_INVALID_VMCS_FIELD @@ -156,6 +159,7 @@ BEGINPROC VMXWriteVmcs32 jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_VMX_INVALID_VMCS_FIELD @@ -183,6 +187,7 @@ BEGINPROC VMXEnable jnc .good mov eax, VERR_VMX_INVALID_VMXON_PTR jmp .the_end + int3 .good: jnz .the_end @@ -301,6 +306,7 @@ BEGINPROC VMXR0InvEPT jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_INVALID_PARAMETER @@ -332,6 +338,7 @@ BEGINPROC VMXR0InvVPID jnc .valid_vmcs mov eax, VERR_VMX_INVALID_VMCS_PTR ret + int3 .valid_vmcs: jnz .the_end mov eax, VERR_INVALID_PARAMETER @@ -364,3 +371,4 @@ BEGINPROC SVMR0InvlpgA ret ENDPROC SVMR0InvlpgA +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp b/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp index a56faa6e6b63..393460b84e32 100644 --- a/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp +++ b/src/VBox/VMM/VMMR0/target-x86/HMR0VMX-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR0VMX-x86.cpp 114828 2026-07-31 09:19:37Z alexander.eichner@oracle.com $ */ +/* $Id: HMR0VMX-x86.cpp 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * HM VMX (Intel VT-x) - Host Context Ring-0. */ @@ -499,44 +499,77 @@ static void hmR0VmxUpdateStartVmFunction(PVMCPUCC pVCpu) { static const struct CLANGWORKAROUND { PFNHMVMXSTARTVM pfn; } s_aHmR0VmxStartVmFunctions[] = { - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit }, - { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, + { hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb }, }; uintptr_t const idx = (pVCpu->hmr0.s.fLoadSaveGuestXcr0 ? 1 : 0) | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_ENTRY ? 2 : 0) | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_L1D_ENTRY ? 4 : 0) | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_MDS_ENTRY ? 8 : 0) - | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_EXIT ? 16 : 0); + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_EXIT ? 16 : 0) + | (pVCpu->hmr0.s.fWorldSwitcher & HM_WSF_IBPB_PBRSB ? 32 : 0); PFNHMVMXSTARTVM const pfnStartVm = s_aHmR0VmxStartVmFunctions[idx].pfn; if (pVCpu->hmr0.s.vmx.pfnStartVm != pfnStartVm) pVCpu->hmr0.s.vmx.pfnStartVm = pfnStartVm; @@ -1028,7 +1061,7 @@ static int hmR0VmxStructsAlloc(PVMCC pVM) /* * Allocate per-VM VT-x structures. */ - bool const fVirtApicAccess = RT_BOOL(g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_APIC_ACCESS); + bool const fVirtApicAccess = pVM->hmr0.s.vmx.enmApicvLvl >= kVmxApicvLvl_ApicAccessVirt; bool const fUseVmcsShadowing = pVM->hmr0.s.vmx.fUseVmcsShadowing; VMXPAGEALLOCINFO aAllocInfo[] = { @@ -2676,28 +2709,22 @@ static int hmR0VmxSetupVmcsProcCtls2(PVMCPUCC pVCpu, PVMXVMCSINFO pVmcsInfo) if (pVM->hmr0.s.vmx.fUnrestrictedGuest) fVal |= VMX_PROC_CTLS2_UNRESTRICTED_GUEST; -#if 0 - if (pVM->hm.s.fVirtApicRegs) - { - /* Enable APIC-register virtualization. */ - Assert(g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_APIC_REG_VIRT); - fVal |= VMX_PROC_CTLS2_APIC_REG_VIRT; - - /* Enable virtual-interrupt delivery. */ - Assert(g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_INTR_DELIVERY); - fVal |= VMX_PROC_CTLS2_VIRT_INTR_DELIVERY; - } -#endif - /* Virtualize-APIC accesses if supported by the CPU. The virtual-APIC page is where the TPR shadow resides. */ /** @todo VIRT_X2APIC support, it's mutually exclusive with this. So must be * done dynamically. */ - if (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_APIC_ACCESS) + if (pVM->hmr0.s.vmx.enmApicvLvl >= kVmxApicvLvl_ApicAccessVirt) { fVal |= VMX_PROC_CTLS2_VIRT_APIC_ACCESS; hmR0VmxSetupVmcsApicAccessAddr(pVCpu); - } + } + + if (pVM->hmr0.s.vmx.enmApicvLvl >= kVmxApicvLvl_ApicRegVirt) + { + /* Enable APIC-register virtualization. */ + Assert(g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_APIC_REG_VIRT); + fVal |= VMX_PROC_CTLS2_APIC_REG_VIRT; + } /* Enable the RDTSCP instruction if we expose it to the guest and is supported by the hardware. Without this, guest executing RDTSCP would cause a #UD. */ @@ -3350,6 +3377,7 @@ VMMR0DECL(int) VMXR0SetupVM(PVMCC pVM) Log4Func(("pVCpu=%p idCpu=%RU32\n", pVCpu, pVCpu->idCpu)); pVCpu->hmr0.s.vmx.pfnStartVm = hmR0VmxStartVmSelector; + pVCpu->hmr0.s.vmx.enmApicvLvl = pVM->hmr0.s.vmx.enmApicvLvl; rc = hmR0VmxSetupVmcs(pVCpu, &pVCpu->hmr0.s.vmx.VmcsInfo, false /* fIsNstGstVmcs */); if (RT_SUCCESS(rc)) @@ -5917,7 +5945,7 @@ static VBOXSTRICTRC hmR0VmxPreRunGuest(PVMCPUCC pVCpu, PVMXTRANSIENT pVmxTransie */ PVMCC pVM = pVCpu->CTX_SUFF(pVM); if ( !pVCpu->hm.s.vmx.u64GstMsrApicBase - && (g_HmMsrs.u.vmx.ProcCtls2.n.allowed1 & VMX_PROC_CTLS2_VIRT_APIC_ACCESS) + && pVM->hmr0.s.vmx.enmApicvLvl >= kVmxApicvLvl_ApicAccessVirt && PDMHasApic(pVM)) { /* Get the APIC base MSR from the virtual APIC device. */ diff --git a/src/VBox/VMM/VMMR3/EMR3.cpp b/src/VBox/VMM/VMMR3/EMR3.cpp index f36adaa26bc7..d5301417a05a 100644 --- a/src/VBox/VMM/VMMR3/EMR3.cpp +++ b/src/VBox/VMM/VMMR3/EMR3.cpp @@ -1,4 +1,4 @@ -/* $Id: EMR3.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: EMR3.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * EM - Execution Monitor / Manager. */ @@ -1970,9 +1970,9 @@ int emR3ForcedActions(PVM pVM, PVMCPU pVCpu, int rc) rc2 = emR3SvmNstGstIntrIntercept(pVCpu); else rc2 = VINF_NO_CHANGE; - if (rc2 == VINF_NO_CHANGE) { +#if 1 bool fInjected = false; CPUM_IMPORT_EXTRN_RET(pVCpu, IEM_CPUMCTX_EXTRN_XCPT_MASK); /** @todo this really isn't nice, should properly handle this */ @@ -1989,6 +1989,9 @@ int emR3ForcedActions(PVM pVM, PVMCPU pVCpu, int rc) if (fInjected) rcIrq = rc2; # endif +#else + rc2 = VINF_EM_RESCHEDULE; +#endif } UPDATE_RC(); } diff --git a/src/VBox/VMM/VMMR3/EMR3HM.cpp b/src/VBox/VMM/VMMR3/EMR3HM.cpp index dc5631a720cf..b7b5aee37481 100644 --- a/src/VBox/VMM/VMMR3/EMR3HM.cpp +++ b/src/VBox/VMM/VMMR3/EMR3HM.cpp @@ -1,4 +1,4 @@ -/* $Id: EMR3HM.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: EMR3HM.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * EM - Execution Monitor / Manager - hardware virtualization */ @@ -43,10 +43,12 @@ #include #include #include +#include #include #include #include #include "EMInternal.h" +#include "HMInternal.h" #include #include #include diff --git a/src/VBox/VMM/VMMR3/PGMR3DbgA.asm b/src/VBox/VMM/VMMR3/PGMR3DbgA.asm index 26a7fc7a32e2..b1de56b2df6f 100644 --- a/src/VBox/VMM/VMMR3/PGMR3DbgA.asm +++ b/src/VBox/VMM/VMMR3/PGMR3DbgA.asm @@ -1,4 +1,4 @@ -; $Id: PGMR3DbgA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +; $Id: PGMR3DbgA.asm 115019 2026-08-12 23:42:04Z knut.osmundsen@oracle.com $ ;; @file ; PGM - Page Manager and Monitor - Debugger & Debugging API Optimizations. ; @@ -95,6 +95,7 @@ SEH64_END_PROLOGUE jne .continue inc r11d jmp .needle_check + int3 .return_edi: lea xAX, [xDI - 8] @@ -104,6 +105,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -154,6 +156,7 @@ SEH64_END_PROLOGUE jne .continue inc r11d jmp .needle_check + int3 .return_edi: lea xAX, [xDI - 4] @@ -162,6 +165,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -212,6 +216,7 @@ SEH64_END_PROLOGUE jne .continue inc r11d jmp .needle_check + int3 .return_edi: lea xAX, [xDI - 2] @@ -220,6 +225,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -267,6 +273,7 @@ SEH64_END_PROLOGUE jne .continue inc r11d jmp .needle_check + int3 .return_edi: lea xAX, [xDI - 1] @@ -275,6 +282,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -324,6 +332,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -367,6 +376,7 @@ SEH64_END_PROLOGUE cmp rax, [xDI - 1] jne .continue jmp .return_edi + int3 .check_smaller: cmp ecx, 3 jb .return_null @@ -380,6 +390,7 @@ SEH64_END_PROLOGUE mov rdi, r10 %endif ret + int3 .return_null: xor eax, eax @@ -389,3 +400,4 @@ SEH64_END_PROLOGUE ret ENDPROC pgmR3DbgFixedMemScan8Wide1Step +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/VMMR3/TRPMR3.cpp b/src/VBox/VMM/VMMR3/TRPMR3.cpp index ec482f610241..099c43a2348d 100644 --- a/src/VBox/VMM/VMMR3/TRPMR3.cpp +++ b/src/VBox/VMM/VMMR3/TRPMR3.cpp @@ -1,4 +1,4 @@ -/* $Id: TRPMR3.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: TRPMR3.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * TRPM - The Trap Monitor. */ @@ -439,7 +439,8 @@ VMMR3DECL(int) TRPMR3InjectEvent(PVM pVM, PVMCPU pVCpu, TRPMEVENT enmEvent, bool else { /* Can happen if the interrupt is masked by TPR or APIC is disabled. */ - AssertMsg(rc == VERR_APIC_INTR_MASKED_BY_TPR || rc == VERR_NO_DATA, ("PDMGetInterrupt failed. rc=%Rrc\n", rc)); + AssertMsg(rc == VERR_APIC_INTR_MASKED_BY_TPR || rc == VERR_NO_DATA || rc == VERR_APIC_INTR_DEFER, + ("PDMGetInterrupt failed. rc=%Rrc\n", rc)); } # if 0 /* HMR3IsActive is not reliable (esp. after restore), just return VINF_EM_RESCHEDULE. */ return HMR3IsActive(pVCpu) ? VINF_EM_RESCHEDULE_HM diff --git a/src/VBox/VMM/VMMR3/target-x86/APICR3-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/APICR3-x86.cpp index f0c29d3aaddf..5f29ef2b44f5 100644 --- a/src/VBox/VMM/VMMR3/target-x86/APICR3-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/APICR3-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: APICR3-x86.cpp 112727 2026-01-28 17:02:49Z alexander.eichner@oracle.com $ */ +/* $Id: APICR3-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller. */ @@ -912,9 +912,10 @@ DECLCALLBACK(int) apicR3InitComplete(PPDMDEVINS pDevIns) pApic->fSupportsTscDeadline = RT_BOOL(CpuLeaf.uEcx & X86_CPUID_FEATURE_ECX_TSCDEADL); pApic->fPostedIntrsEnabled = HMR3IsPostedIntrsEnabled(pVM->pUVM); pApic->fVirtApicRegsEnabled = HMR3AreVirtApicRegsEnabled(pVM->pUVM); + pApic->fAvicEnabled = HMR3IsAvicEnabled(pVM->pUVM); - LogRel(("APIC: fPostedIntrsEnabled=%RTbool fVirtApicRegsEnabled=%RTbool fSupportsTscDeadline=%RTbool\n", - pApic->fPostedIntrsEnabled, pApic->fVirtApicRegsEnabled, pApic->fSupportsTscDeadline)); + LogRel(("APIC: fPostedIntrsEnabled=%RTbool fVirtApicRegsEnabled=%RTbool fSupportsTscDeadline=%RTbool fAvicEnabled=%RTbool\n", + pApic->fPostedIntrsEnabled, pApic->fVirtApicRegsEnabled, pApic->fSupportsTscDeadline, pApic->fAvicEnabled)); return VINF_SUCCESS; } @@ -1094,6 +1095,7 @@ DECLCALLBACK(int) apicR3Construct(PPDMDEVINS pDevIns, int iInstance, PCFGMNODE p APIC_REG_COUNTER(&pApicCpu->StatTprWrite, "%u/TprWrite", "Number of TPR writes."); APIC_REG_COUNTER(&pApicCpu->StatTprRead, "%u/TprRead", "Number of TPR reads."); APIC_REG_COUNTER(&pApicCpu->StatEoiWrite, "%u/EoiWrite", "Number of EOI writes."); + APIC_REG_COUNTER(&pApicCpu->StatEoiWriteFast, "%u/EoiWriteFast", "Number of EOI writes using the fast path."); APIC_REG_COUNTER(&pApicCpu->StatMaskedByTpr, "%u/MaskedByTpr", "Number of times TPR masks an interrupt in apicGetInterrupt."); APIC_REG_COUNTER(&pApicCpu->StatMaskedByPpr, "%u/MaskedByPpr", "Number of times PPR masks an interrupt in apicGetInterrupt."); APIC_REG_COUNTER(&pApicCpu->StatTimerIcrWrite, "%u/TimerIcrWrite", "Number of times the timer ICR is written."); diff --git a/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-linux-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-linux-x86.cpp index f04c3ab558f9..0a4fe1b16217 100644 --- a/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-linux-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-linux-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: APICR3Nem-linux-x86.cpp 113859 2026-04-14 11:43:54Z alexander.eichner@oracle.com $ */ +/* $Id: APICR3Nem-linux-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller - NEM KVM backend. */ @@ -506,6 +506,28 @@ static DECLCALLBACK(VBOXSTRICTRC) apicR3KvmExportState(PVMCPUCC pVCpu) } +/** + * @interface_method_impl{PDMAPICBACKEND,pfnUpdateStateAfterWrite} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicR3KvmUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg) +{ + RT_NOREF(pVCpu, offApicReg); + AssertReleaseMsgFailed(("Unexpected interface call\n")); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{PDMAPICBACKEND,pfnSetEoiFast} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicR3KvmSetEoiFast(PVMCPUCC pVCpu, uint8_t uVector) +{ + RT_NOREF(pVCpu, uVector); + AssertReleaseMsgFailed(("Unexpected interface call\n")); + return VERR_NOT_SUPPORTED; +} + + /** * Dumps basic APIC state. * @@ -1356,6 +1378,8 @@ const PDMAPICBACKEND g_ApicNemBackend = /* .pfnSetHvCompatMode = */ apicR3KvmSetHvCompatMode, /* .pfnImportState = */ apicR3KvmImportState, /* .pfnExportState = */ apicR3KvmExportState, + /* .pfnUpdateStateAfterWrite = */ apicR3KvmUpdateStateAfterWrite, + /* .pfnSetEoiFast = */ apicR3KvmSetEoiFast, }; #endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-win-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-win-x86.cpp index c4e911a4831d..aa8d84599cc7 100644 --- a/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-win-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/APICR3Nem-win-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: APICR3Nem-win-x86.cpp 114734 2026-07-21 06:14:23Z alexander.eichner@oracle.com $ */ +/* $Id: APICR3Nem-win-x86.cpp 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller - NEM Hyper-V backend. */ @@ -1064,6 +1064,28 @@ static DECLCALLBACK(VBOXSTRICTRC) apicR3HvExportState(PVMCPUCC pVCpu) } +/** + * @interface_method_impl{PDMAPICBACKEND,pfnUpdateStateAfterWrite} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicR3HvUpdateStateAfterWrite(PVMCPUCC pVCpu, uint16_t offApicReg) +{ + RT_NOREF(pVCpu, offApicReg); + AssertReleaseMsgFailed(("Unexpected interface call\n")); + return VERR_NOT_SUPPORTED; +} + + +/** + * @interface_method_impl{PDMAPICBACKEND,pfnSetEoiFast} + */ +static DECLCALLBACK(VBOXSTRICTRC) apicR3HvSetEoiFast(PVMCPUCC pVCpu, uint8_t uVector) +{ + RT_NOREF(pVCpu, uVector); + AssertReleaseMsgFailed(("Unexpected interface call\n")); + return VERR_NOT_SUPPORTED; +} + + /** * Dumps basic APIC state. * @@ -1753,6 +1775,8 @@ const PDMAPICBACKEND g_ApicNemBackend = /* .pfnSetHvCompatMode = */ apicR3HvSetHvCompatMode, /* .pfnImportState = */ apicR3HvImportState, /* .pfnExportState = */ apicR3HvExportState, + /* .pfnUpdateStateAfterWrite = */ apicR3HvUpdateStateAfterWrite, + /* .pfnSetEoiFast = */ apicR3HvSetEoiFast, }; #endif /* !VBOX_DEVICE_STRUCT_TESTCASE */ diff --git a/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp b/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp index ead02c27cd9e..2447e6f30379 100644 --- a/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp +++ b/src/VBox/VMM/VMMR3/target-x86/HMR3-x86.cpp @@ -1,4 +1,4 @@ -/* $Id: HMR3-x86.cpp 113496 2026-03-22 22:23:27Z knut.osmundsen@oracle.com $ */ +/* $Id: HMR3-x86.cpp 115085 2026-08-19 13:04:31Z alexander.eichner@oracle.com $ */ /** @file * HM - Intel/AMD VM Hardware Support Manager. */ @@ -202,6 +202,61 @@ static const char *hmR3GetXcptName(uint8_t uVector) #endif /* VBOX_WITH_STATISTICS */ +/** + * Converts the given config string for the VMX APICv level to the corresponding enum. + * + * @returns VBox status code. + * @param pVM The cross context VM structure. + * @param pszVmxApicvLvl The desired VMX APICv level. + */ +static int hmR3CfgVmxApicvLvlStrToEnum(PVM pVM, const char *pszVmxApicvLvl) +{ + if (!strcmp(pszVmxApicvLvl, "None")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_None; + else if (!strcmp(pszVmxApicvLvl, "ApicAccessVirt")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_ApicAccessVirt; + else if (!strcmp(pszVmxApicvLvl, "ApicRegVirt")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_ApicRegVirt; + else if (!strcmp(pszVmxApicvLvl, "IntrDelivery")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_IntrDelivery; + else if (!strcmp(pszVmxApicvLvl, "PostedIntrs")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_PostedIntrs; + else if (!strcmp(pszVmxApicvLvl, "IpiVirt")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_IpiVirt; + else if (!strcmp(pszVmxApicvLvl, "Max")) + pVM->hm.s.vmx.enmApicvLvl = kVmxApicvLvl_Max; + else + return VMSetError(pVM, VERR_INVALID_PARAMETER, RT_SRC_POS, "VmxApicLvl contains invalid value '%s'", pszVmxApicvLvl); + + return VINF_SUCCESS; +} + + +/** + * Returns the string variant of the given APICv level enum. + * + * @returns VBox status code. + * @param enmApicvLvl The APICv level to convert. + */ +static const char *hmR3CfgVmxApicvLvlEnumToStr(HMVMXAPICVLVL enmApicvLvl) +{ + switch (enmApicvLvl) + { + case kVmxApicvLvl_None: return "None"; + case kVmxApicvLvl_ApicAccessVirt: return "ApicAccessVirt"; + case kVmxApicvLvl_ApicRegVirt: return "ApicRegVirt"; + case kVmxApicvLvl_IntrDelivery: return "Virtual Interrupt Delivery"; + case kVmxApicvLvl_PostedIntrs: return "Posted Interrupts"; + case kVmxApicvLvl_IpiVirt: return "IPI Virtualization"; + case kVmxApicvLvl_Max: return "Maximum"; + default: break; + } + + AssertFailed(); + return ""; +} + + /** * Initializes the HM. * @@ -277,10 +332,12 @@ VMMR3_INT_DECL(int) HMR3Init(PVM pVM) "|VmxPleWindow" "|VmxLbr" "|UseVmxPreemptTimer" + "|VmxApicvLvl" "|SvmPauseFilter" "|SvmPauseFilterThreshold" "|SvmVirtVmsaveVmload" "|SvmVGif" + "|SvmAvic" "|LovelyMesaDrvWorkaround" "|MissingOS2TlbFlushWorkaround" "|AlwaysInterceptVmxMovDRx" @@ -384,6 +441,16 @@ VMMR3_INT_DECL(int) HMR3Init(PVM pVM) rc = CFGMR3QueryBoolDef(pCfgHm, "VmxLbr", &pVM->hm.s.vmx.fLbrCfg, false); AssertRCReturn(rc, rc); + /** @cfgm{/HM/VmxApicvLvl, string, "Max"} + * The desired APIC virtualization level. Currently allows None, ApicAccessVirt, ApicRegVirt, + * IntrDelivery, PostedIntrs, IpiVirt, Max where Max is the default. + * The actually configured level depends on supported hardware. */ + char szVmxApicvLvl[16]; RT_ZERO(szVmxApicvLvl); + rc = CFGMR3QueryStringDef(pCfgHm, "VmxApicvLvl", &szVmxApicvLvl[0], sizeof(szVmxApicvLvl), "Max"); + AssertRCReturn(rc, rc); + rc = hmR3CfgVmxApicvLvlStrToEnum(pVM, &szVmxApicvLvl[0]); + AssertRCReturn(rc, rc); + /** @cfgm{/HM/SvmPauseFilterCount, uint16_t, 0} * A counter that is decrement each time a PAUSE instruction is executed by the * guest. When the counter is 0, a \#VMEXIT is triggered. @@ -425,6 +492,12 @@ VMMR3_INT_DECL(int) HMR3Init(PVM pVM) rc = CFGMR3QueryBoolDef(pCfgHm, "SvmLbrVirt", &pVM->hm.s.svm.fLbrVirt, false); AssertRCReturn(rc, rc); + /** @cfgm{/HM/SvmAvic, bool, false} + * Whether to make use of the AVIC virtualization feature of the CPU if it's + * available. */ + rc = CFGMR3QueryBoolDef(pCfgHm, "SvmAvic", &pVM->hm.s.svm.fAvic, false); + AssertRCReturn(rc, rc); + /** @cfgm{/HM/Exclusive, bool} * Determines the init method for AMD-V and VT-x. If set to true, HM will do a * global init for each host CPU. If false, we do local init each time we wish @@ -904,6 +977,7 @@ static int hmR3InitFinalizeR3(PVM pVM) HM_REG_COUNTER(&pHmCpu->StatExitTprBelowThreshold, "/HM/CPU%u/Exit/TprBelowThreshold", "TPR lowered below threshold by the guest."); HM_REG_COUNTER(&pHmCpu->StatExitTaskSwitch, "/HM/CPU%u/Exit/TaskSwitch", "Task switch caused through task gate in IDT."); HM_REG_COUNTER(&pHmCpu->StatExitApicAccess, "/HM/CPU%u/Exit/ApicAccess", "APIC access. Guest attempted to access memory at a physical address on the APIC-access page."); + HM_REG_COUNTER(&pHmCpu->StatExitApicWrite, "/HM/CPU%u/Exit/ApicWrite", "APIC write emulation. Guest wrote APIC register which the hardware couldn't emulate."); HM_REG_COUNTER(&pHmCpu->StatSwitchTprMaskedIrq, "/HM/CPU%u/Switch/TprMaskedIrq", "PDMGetInterrupt() signals TPR masks pending Irq."); HM_REG_COUNTER(&pHmCpu->StatSwitchGuestIrq, "/HM/CPU%u/Switch/IrqPending", "PDMGetInterrupt() cleared behind our back!?!."); @@ -917,6 +991,7 @@ static int hmR3InitFinalizeR3(PVM pVM) HM_REG_COUNTER(&pHmCpu->StatSwitchMaxResumeLoops, "/HM/CPU%u/Switch/MaxResumeLoops", "Maximum VMRESUME inner-loop counter reached."); HM_REG_COUNTER(&pHmCpu->StatSwitchHltToR3, "/HM/CPU%u/Switch/HltToR3", "HLT causing us to go to ring-3."); HM_REG_COUNTER(&pHmCpu->StatSwitchApicAccessToR3, "/HM/CPU%u/Switch/ApicAccessToR3", "APIC access causing us to go to ring-3."); + HM_REG_COUNTER(&pHmCpu->StatSwitchApicWriteToR3, "/HM/CPU%u/Switch/ApicWriteToR3", "APIC write emulation causing us to go to ring-3."); #endif HM_REG_COUNTER(&pHmCpu->StatSwitchPreempt, "/HM/CPU%u/Switch/Preempting", "EMT has been preempted while in HM context."); #ifdef VBOX_WITH_STATISTICS @@ -991,6 +1066,11 @@ static int hmR3InitFinalizeR3(PVM pVM) HM_REG_COUNTER(&pHmCpu->StatVmxPreemptionRecalcingDeadline, "/HM/CPU%u/PreemptTimer/RecalcingDeadline", "VMX-preemption timer arming logic recalculating the deadline (slightly expensive)"); HM_REG_COUNTER(&pHmCpu->StatVmxPreemptionRecalcingDeadlineExpired, "/HM/CPU%u/PreemptTimer/RecalcingDeadlineExpired", "VMX-preemption timer arming logic found recalculated deadline expired (ignored)"); } + else + { + HM_REG_COUNTER(&pHmCpu->StatSvmExitAvicIncompleteIpi, "/HM/CPU%u/AVIC/IncompleteIpi", "VM exit due to an incomplete IPI."); + HM_REG_COUNTER(&pHmCpu->StatSvmExitAvicNoAccel, "/HM/CPU%u/AVIC/UnAcceleratedAccess", "VM exit due to an unaccelerated APIC access"); + } #ifdef VBOX_WITH_STATISTICS /* * Guest Exit reason stats. @@ -1890,6 +1970,12 @@ static int hmR3InitFinalizeR0Intel(PVM pVM) else LogRel(("HM: Disabled VMX-preemption timer\n")); + if (pVM->hm.s.ForR3.vmx.enmApicvLvl != kVmxApicvLvl_None) + LogRel(("HM: Enabled APICv level: %s (configured %s)\n", hmR3CfgVmxApicvLvlEnumToStr(pVM->hm.s.ForR3.vmx.enmApicvLvl), + hmR3CfgVmxApicvLvlEnumToStr(pVM->hm.s.vmx.enmApicvLvl))); + else + LogRel(("HM: Disabled APICv support (configured %s)\n", hmR3CfgVmxApicvLvlEnumToStr(pVM->hm.s.vmx.enmApicvLvl))); + if (pVM->hm.s.fVirtApicRegs) LogRel(("HM: Enabled APIC-register virtualization support\n")); @@ -2061,6 +2147,9 @@ static int hmR3InitFinalizeR0Amd(PVM pVM) if (pVM->hm.s.fPostedIntrs) LogRel(("HM: Enabled posted-interrupt processing support\n")); + if (pVM->apCpusR3[0]->hm.s.svm.fUseAvic) + LogRel(("HM: Enabled AVIC support\n")); + hmR3DisableRawMode(pVM); LogRel((pVM->hm.s.fTprPatchingAllowed ? "HM: Enabled TPR patching\n" @@ -2946,6 +3035,26 @@ VMMR3DECL(bool) HMR3IsPostedIntrsEnabled(PUVM pUVM) } +/** + * Checks if the SVM AVIC feature is enabled. + * + * This returns whether the APIC should only deliver PIC-style interrupts + * to the guest while all other interrupts are updated to the APIC page + * using the posted-interrupt bitmap. + * + * @returns @c true if SVM AVIC feature is enabled, @c false otherwise. + * @param pUVM The user mode VM handle. + */ +VMMR3DECL(bool) HMR3IsAvicEnabled(PUVM pUVM) +{ + UVM_ASSERT_VALID_EXT_RETURN(pUVM, false); + PVM pVM = pUVM->pVM; + VM_ASSERT_VALID_EXT_RETURN(pVM, false); + PCVMCPU pVCpu0 = pVM->apCpusR3[0]; + return pVCpu0->hm.s.svm.fUseAvic; +} + + /** * Checks if we are currently using VPID in VT-x mode. * diff --git a/src/VBox/VMM/VMMRZ/CPUMRZA.asm b/src/VBox/VMM/VMMRZ/CPUMRZA.asm index d7a0f304f51d..106e650acf68 100644 --- a/src/VBox/VMM/VMMRZ/CPUMRZA.asm +++ b/src/VBox/VMM/VMMRZ/CPUMRZA.asm @@ -1,4 +1,4 @@ - ; $Id: CPUMRZA.asm 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ + ; $Id: CPUMRZA.asm 115015 2026-08-12 23:40:14Z knut.osmundsen@oracle.com $ ;; @file ; CPUM - Raw-mode and Ring-0 Context Assembly Routines. ; @@ -348,3 +348,4 @@ SEH64_END_PROLOGUE ret ENDPROC cpumRZSaveGuestAvxRegisters +MARK_OBJECT_RETPOLINE_SAFE diff --git a/src/VBox/VMM/include/APICInternal.h b/src/VBox/VMM/include/APICInternal.h index b92029da3dcf..48825382b501 100644 --- a/src/VBox/VMM/include/APICInternal.h +++ b/src/VBox/VMM/include/APICInternal.h @@ -1,4 +1,4 @@ -/* $Id: APICInternal.h 112683 2026-01-25 17:23:05Z alexander.eichner@oracle.com $ */ +/* $Id: APICInternal.h 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * APIC - Advanced Programmable Interrupt Controller, Internal header. */ @@ -195,6 +195,8 @@ typedef struct APIC bool fVirtApicRegsEnabled; /** Whether posted-interrupt processing is enabled. */ bool fPostedIntrsEnabled; + /** Whether the SVM AVIC feature is enabled. */ + bool fAvicEnabled; /** Whether TSC-deadline timer mode is supported for the guest. */ bool fSupportsTscDeadline; /** Whether this VM has an IO-APIC. */ @@ -211,6 +213,8 @@ typedef struct APIC * kernel load area and macOS kernel selector value (8), as we must not ever * apply this to the EFI code. */ bool fMacOSWorkaround; + /** Alignment padding. */ + bool afPadding[3]; /** The max supported APIC mode from CFGM. */ PDMAPICMODE enmMaxMode; /** @} */ @@ -336,6 +340,8 @@ typedef struct APICCPU STAMCOUNTER StatTprRead; /** Number of times the EOI is written. */ STAMCOUNTER StatEoiWrite; + /** Number of times the EOI is written in the fast path. */ + STAMCOUNTER StatEoiWriteFast; /** Number of times TPR masks an interrupt in apicGetInterrupt(). */ STAMCOUNTER StatMaskedByTpr; /** Number of times PPR masks an interrupt in apicGetInterrupt(). */ diff --git a/src/VBox/VMM/include/EMHandleRCTmpl.h b/src/VBox/VMM/include/EMHandleRCTmpl.h index 4bd10840ff95..41bb96a061bc 100644 --- a/src/VBox/VMM/include/EMHandleRCTmpl.h +++ b/src/VBox/VMM/include/EMHandleRCTmpl.h @@ -1,4 +1,4 @@ -/* $Id: EMHandleRCTmpl.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: EMHandleRCTmpl.h 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ */ /** @file * EM - emR3[Raw|Hm|Nem]HandleRC template. */ @@ -151,6 +151,10 @@ int emR3NemHandleRC(PVM pVM, PVMCPU pVCpu, int rc) case VINF_EM_HM_PATCH_TPR_INSTR: rc = HMR3PatchTprInstr(pVM, pVCpu); break; + + case VINF_APIC_R3_UPDATE_STATE: + rc = VBOXSTRICTRC_TODO(PDMApicUpdateStateAfterWrite(pVCpu, pVCpu->hm.s.offApicReg)); + break; #endif case VINF_EM_RAW_GUEST_TRAP: diff --git a/src/VBox/VMM/include/HMInternal.h b/src/VBox/VMM/include/HMInternal.h index f5d40a6aab74..f1271fc4c8f9 100644 --- a/src/VBox/VMM/include/HMInternal.h +++ b/src/VBox/VMM/include/HMInternal.h @@ -1,4 +1,4 @@ -/* $Id: HMInternal.h 114490 2026-06-22 17:29:00Z knut.osmundsen@oracle.com $ */ +/* $Id: HMInternal.h 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * HM - Internal header file. */ @@ -77,6 +77,30 @@ RT_C_DECLS_BEGIN #define HM_VTX_TOTAL_DEVHEAP_MEM (HM_EPT_IDENTITY_PG_TABLE_SIZE + HM_VTX_TSS_SIZE) +/** + * The VT-x APICv level wanted/configured. + */ +typedef enum HMVMXAPICVLVL +{ + /** No APICv. */ + kVmxApicvLvl_None = 0, + /** APIC access virtualization. */ + kVmxApicvLvl_ApicAccessVirt, + /** APIC register virtualization. */ + kVmxApicvLvl_ApicRegVirt, + /** Virtual interrupt delivery. */ + kVmxApicvLvl_IntrDelivery, + /** Posted interrupt processing. */ + kVmxApicvLvl_PostedIntrs, + /** IPI virtualization. */ + kVmxApicvLvl_IpiVirt, + /** Max level. */ + kVmxApicvLvl_Max, + /** 32-bit hack. */ + kVmxApicvLvl_32Bit_Hack = 0x7fffffff +} HMVMXAPICVLVL; + + /** @name Macros for enabling and disabling preemption. * These are really just for hiding the RTTHREADPREEMPTSTATE and asserting that * preemption has already been disabled when there is no context hook. @@ -134,6 +158,8 @@ typedef struct HMPHYSCPU bool fVmxeAlreadyEnabled; /** In use by our code. (for power suspend) */ bool volatile fInUse; + /** The APIC ID of the physical CPU associated with this entry. */ + uint8_t idApic; #ifdef VBOX_WITH_NESTED_HWVIRT_SVM /** Nested-guest union (put data common to SVM/VMX outside the union). */ union @@ -292,6 +318,8 @@ typedef struct HM * In the default case it is only always intercepted when setting DR6 to 0 on * the host results in a value different from X86_DR6_RA1_MASK. */ int8_t fAlwaysInterceptMovDRxCfg; + /** Desired APICv virtualization level. */ + HMVMXAPICVLVL enmApicvLvl; /** @} */ /** Pause-loop exiting (PLE) gap in ticks. */ @@ -321,7 +349,9 @@ typedef struct HM bool fVGif; /** Whether to use LBR virtualization feature. */ bool fLbrVirt; - bool afAlignment1[2]; + /** Whether to use the AVIC feature if available. */ + bool fAvic; + bool fAlignment1; /** Pause filter counter. */ uint16_t cPauseFilter; @@ -415,6 +445,9 @@ typedef struct HM VMXTLBFLUSHEPT enmTlbFlushEpt; /** Flush type to use for INVVPID (only for ring-3 consumption). */ VMXTLBFLUSHVPID enmTlbFlushVpid; + + /** Configured APICv virtualization level (only for ring-3 consumption). */ + HMVMXAPICVLVL enmApicvLvl; } vmx; struct @@ -520,6 +553,16 @@ typedef struct HMR0PERVM { /** Set if erratum 170 affects the AMD cpu. */ bool fAlwaysFlushTLB; + /** Ring-0 memory object for per-VM SVM AVIC structures. */ + RTR0MEMOBJ hMemObjAvicHost; + /** Host physical address of the physical APIC ID table. */ + RTHCPHYS HCPhysAvicPhysIdTbl; + /** Host physical address of the logical APIC ID table. */ + RTHCPHYS HCPhysAvicLogicalIdTbl; + /** Host-physical address of the APIC-access page. */ + RTHCPHYS HCPhysApicAccess; + /** R0 pointer to the physical APIC ID table. */ + volatile uint64_t *paAvicPhysIdTbl; } svm; /** VT-x specific data. */ @@ -546,6 +589,8 @@ typedef struct HMR0PERVM VMXTLBFLUSHEPT enmTlbFlushEpt; /** Flush type to use for INVVPID. */ VMXTLBFLUSHVPID enmTlbFlushVpid; + /** The set APICv virtualization level. */ + HMVMXAPICVLVL enmApicvLvl; /** The host LBR TOS (top-of-stack) MSR id. */ uint32_t idLbrTosMsr; @@ -844,11 +889,16 @@ typedef struct HMCPU * long-mode and to intercept reads and writes to the SYSENTER MSRs in order to * preserve the upper 32 bits written to them (AMD will ignore and discard). */ bool fEmulateLongModeSysEnterExit; - uint8_t au8Alignment0[7]; + /** Flag whether to utilize AVIC hardware. */ + bool fUseAvic; + uint8_t au8Alignment0[6]; /** Cache of the nested-guest's VMCB fields that we modify in order to run the * nested-guest using AMD-V. This will be restored on \#VMEXIT. */ SVMNESTEDVMCBCACHE NstGstVmcbCache; + + /** Cached guest APIC-base MSR for identifying when to enable the AVIC if supported. */ + uint64_t u64GstMsrApicBase; } svm; /** Event injection state. */ @@ -857,7 +907,9 @@ typedef struct HMCPU /** Current shadow paging mode for updating CR4. * @todo move later (@bugref{9217}). */ PGMMODE enmShadowMode; - uint32_t u32TemporaryPadding; + uint16_t u16TemporaryPadding; + /** The APIC register offset from an unaccelerated write causing the return to R3. */ + uint16_t offApicReg; /** The PAE PDPEs used with Nested Paging (only valid when * VMCPU_FF_HM_UPDATE_PAE_PDPES is set). */ @@ -947,6 +999,7 @@ typedef struct HMCPU STAMCOUNTER StatExitTprBelowThreshold; STAMCOUNTER StatExitTaskSwitch; STAMCOUNTER StatExitApicAccess; + STAMCOUNTER StatExitApicWrite; STAMCOUNTER StatExitReasonNpf; STAMCOUNTER StatNestedExitReasonNpf; @@ -980,6 +1033,7 @@ typedef struct HMCPU STAMCOUNTER StatSwitchMaxResumeLoops; STAMCOUNTER StatSwitchHltToR3; STAMCOUNTER StatSwitchApicAccessToR3; + STAMCOUNTER StatSwitchApicWriteToR3; STAMCOUNTER StatSwitchPreempt; STAMCOUNTER StatSwitchNstGstVmexit; @@ -1016,6 +1070,9 @@ typedef struct HMCPU STAMCOUNTER StatVmxPreemptionReusingDeadline; STAMCOUNTER StatVmxPreemptionReusingDeadlineExpired; + STAMCOUNTER StatSvmExitAvicIncompleteIpi; + STAMCOUNTER StatSvmExitAvicNoAccel; + #ifdef VBOX_WITH_STATISTICS STAMCOUNTER aStatExitReason[MAX_EXITREASON_STAT]; STAMCOUNTER aStatNestedExitReason[MAX_EXITREASON_STAT]; @@ -1096,7 +1153,9 @@ typedef struct HMR0PERVCPU /* Whether the nested-guest VMCS was the last current VMCS (authoritative copy). * @see HMCPU::vmx.fSwitchedToNstGstVmcsCopyForRing3 */ bool fSwitchedToNstGstVmcs; - bool afAlignment0[7]; + bool afAlignment0[3]; + /** Active APICv virtualization level (to avoid going through pVM in hot paths). */ + HMVMXAPICVLVL enmApicvLvl; /** Pointer to the VMX transient info during VM-exit. */ PVMXTRANSIENT pVmxTransient; /** @} */ @@ -1162,6 +1221,9 @@ typedef struct HMR0PERVCPU /** Host's TSC_AUX MSR (used when RDTSCP doesn't cause VM-exits). */ uint64_t u64HostTscAux; + /* The AVIC physical ID entry for this vCPU. */ + uint64_t u64PhysIdEntry; + /** For saving stack space, the disassembler state is allocated here * instead of on the stack. */ DISSTATE Dis; @@ -1173,7 +1235,6 @@ AssertCompileMemberAlignment(HMR0PERVCPU, cWorldSwitchExits, 4); AssertCompileMemberAlignment(HMR0PERVCPU, fForceTLBFlush, 4); AssertCompileMemberAlignment(HMR0PERVCPU, vmx.RestoreHost, 8); - /** @name HM_WSF_XXX - @bugref{9453}, @bugref{9087} * @note If you change these values don't forget to update the assembly * defines as well! @@ -1182,15 +1243,19 @@ AssertCompileMemberAlignment(HMR0PERVCPU, vmx.RestoreHost, 8); #define HM_WSF_IBPB_EXIT RT_BIT_32(0) /** Touch IA32_PRED_CMD.IBPB on VM entry. */ #define HM_WSF_IBPB_ENTRY RT_BIT_32(1) +/** AMD: IA32_PRED_CMD.IBPB require manual return target prediction clearing. */ +#define HM_WSF_IBPB_MAN_RET RT_BIT_32(2) +/** CPU suffers from the PBRSB problem. */ +#define HM_WSF_IBPB_PBRSB RT_BIT_32(3) /** Touch IA32_FLUSH_CMD.L1D on VM entry. */ -#define HM_WSF_L1D_ENTRY RT_BIT_32(2) +#define HM_WSF_L1D_ENTRY RT_BIT_32(4) /** Flush MDS buffers on VM entry. */ -#define HM_WSF_MDS_ENTRY RT_BIT_32(3) +#define HM_WSF_MDS_ENTRY RT_BIT_32(5) /** MSR_IA32_SPEC_CTRL needs to be replaced upon entry and exit. * Save host value on entry, load guest value, run guest, save guest value on * exit and restore the host value. * @todo may not reliable for VT-x/Intel. */ -#define HM_WSF_SPEC_CTRL RT_BIT_32(4) +#define HM_WSF_SPEC_CTRL RT_BIT_32(6) /** Touch IA32_FLUSH_CMD.L1D on VM scheduling. */ #define HM_WSF_L1D_SCHED RT_BIT_32(16) @@ -1223,6 +1288,7 @@ VMMR0_INT_DECL(void) hmR0DumpDescriptor(PCX86DESCHC pDesc, RTSEL Sel, con # endif DECLASM(void) hmR0MdsClear(void); +DECLASM(bool) hmR0IsShadowStackEnabled(void); #endif /* IN_RING0 */ @@ -1246,22 +1312,39 @@ VMM_INT_DECL(int) hmEmulateSvmMovTpr(PVMCC pVM, PVMCPUCC pVCpu); * * @{ */ -DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); -DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_SansManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); + +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_SansSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_SansIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_SansIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_SansXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); +DECLASM(int) hmR0SvmVmRun_WithXcr0_WithIbpbEntry_WithIbpbExit_WithSpecCtrl_WithManRet(PVMCC pVM, PVMCPUCC pVCpu, RTHCPHYS HCPhyspVMCB); /** @} */ /** @} */ @@ -1304,38 +1387,70 @@ DECLASM(int) VMXRestoreHostState(uint32_t fRestoreHostFlags, PVMX * * @{ */ -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); -DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_SansPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_SansIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_SansMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_SansL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_SansIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_SansXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); +DECLASM(int) hmR0VmxStartVm_WithXcr0_WithIbpbEntry_WithL1dEntry_WithMdsEntry_WithIbpbExit_WithPbrsb(PVMXVMCSINFO pVmcsInfo, PVMCPUCC pVCpu, bool fResume); /** @} */ /** @} */ diff --git a/src/VBox/VMM/include/HMInternal.mac b/src/VBox/VMM/include/HMInternal.mac index a651083a5ac0..8933ba48ff96 100644 --- a/src/VBox/VMM/include/HMInternal.mac +++ b/src/VBox/VMM/include/HMInternal.mac @@ -1,4 +1,4 @@ -;$Id: HMInternal.mac 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +;$Id: HMInternal.mac 115073 2026-08-19 10:00:47Z alexander.eichner@oracle.com $ ;; @file ; HM - Internal header file. ; @@ -151,9 +151,11 @@ endstruc struc HMCPUSVM .fEmulateLongModeSysEnterExit resb 1 + .fUseAvic resb 1 alignb 8 .NstGstVmcbCache resb 40 + .u64GstMsrApicBase resq 1 endstruc struc HMCPU @@ -241,6 +243,7 @@ struc HMR0CPUSVM alignb 8 .pSvmTransient RTR0PTR_RES 1 .u64HostTscAux resq 1 + .u64PhysIdEntry resq 1 alignb 8 .Dis resb 0d8h @@ -273,7 +276,9 @@ endstruc %define HM_WSF_IBPB_EXIT RT_BIT_32(0) %define HM_WSF_IBPB_ENTRY RT_BIT_32(1) -%define HM_WSF_L1D_ENTRY RT_BIT_32(2) -%define HM_WSF_MDS_ENTRY RT_BIT_32(3) -%define HM_WSF_SPEC_CTRL RT_BIT_32(4) +%define HM_WSF_IBPB_MAN_RET RT_BIT_32(2) +%define HM_WSF_IBPB_PBRSB RT_BIT_32(3) +%define HM_WSF_L1D_ENTRY RT_BIT_32(4) +%define HM_WSF_MDS_ENTRY RT_BIT_32(5) +%define HM_WSF_SPEC_CTRL RT_BIT_32(6) diff --git a/src/VBox/VMM/include/VMXInternal.h b/src/VBox/VMM/include/VMXInternal.h index ed45a18a72cf..769ca418dc35 100644 --- a/src/VBox/VMM/include/VMXInternal.h +++ b/src/VBox/VMM/include/VMXInternal.h @@ -1,4 +1,4 @@ -/* $Id: VMXInternal.h 113496 2026-03-22 22:23:27Z knut.osmundsen@oracle.com $ */ +/* $Id: VMXInternal.h 115084 2026-08-19 12:52:58Z alexander.eichner@oracle.com $ */ /** @file * VMX - Internal header file for the VMX code template. */ @@ -245,6 +245,7 @@ typedef struct VMXSTATISTICS STAMCOUNTER StatExitTprBelowThreshold; STAMCOUNTER StatExitTaskSwitch; STAMCOUNTER StatExitApicAccess; + STAMCOUNTER StatExitApicWrite; STAMCOUNTER StatExitReasonNpf; STAMCOUNTER StatNestedExitReasonNpf; @@ -278,6 +279,7 @@ typedef struct VMXSTATISTICS STAMCOUNTER StatSwitchMaxResumeLoops; STAMCOUNTER StatSwitchHltToR3; STAMCOUNTER StatSwitchApicAccessToR3; + STAMCOUNTER StatSwitchApicWriteToR3; STAMCOUNTER StatSwitchPreempt; STAMCOUNTER StatSwitchNstGstVmexit; diff --git a/src/VBox/VMM/testcase/tstVMREQ.cpp b/src/VBox/VMM/testcase/tstVMREQ.cpp index 30ad15ce5a14..e08d9be100af 100644 --- a/src/VBox/VMM/testcase/tstVMREQ.cpp +++ b/src/VBox/VMM/testcase/tstVMREQ.cpp @@ -1,4 +1,4 @@ -/* $Id: tstVMREQ.cpp 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: tstVMREQ.cpp 114980 2026-08-11 09:04:39Z andreas.loeffler@oracle.com $ */ /** @file * VMM Testcase. */ @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -643,15 +644,27 @@ tstVMREQConfigConstructor(PUVM pUVM, PVM pVM, PCVMMR3VTABLE pVMM, void *pvUser) extern "C" DECLEXPORT(int) TrustedMain(int argc, char **argv, char **envp) { RT_NOREF1(envp); - RTR3InitExe(argc, &argv, RTR3INIT_FLAGS_TRY_SUPLIB); - RTTestCreate(TESTCASE, &g_hTest); +#if defined(RT_OS_LINUX) || defined(RT_OS_DARWIN) || (defined(RT_OS_WINDOWS) && !defined(VBOX_WITH_HARDENING)) + /* VMR3Create initializes SUPLib in driverless mode, so don't initialize it here first. */ + uint32_t const fRtInit = 0; + uint64_t const fVmCreate = VMCREATE_F_DRIVERLESS; +#else + uint32_t const fRtInit = RTR3INIT_FLAGS_TRY_SUPLIB; + uint64_t const fVmCreate = 0; +#endif + int rc = RTR3InitExe(argc, &argv, fRtInit); + if (RT_FAILURE(rc)) + return RTMsgInitFailure(rc); + rc = RTTestCreate(TESTCASE, &g_hTest); + if (RT_FAILURE(rc)) + return RTMsgErrorExitFailure("RTTestCreate failed: %Rrc", rc); RTTestSub(g_hTest, "Setup..."); /* * Create empty VM. */ PUVM pUVM; - int rc = VMR3Create(1 /*cCpus*/, NULL, 0 /*fFlags*/, NULL, NULL, tstVMREQConfigConstructor, NULL, NULL, &pUVM); + rc = VMR3Create(1 /*cCpus*/, NULL, fVmCreate, NULL, NULL, tstVMREQConfigConstructor, NULL, NULL, &pUVM); if (RT_SUCCESS(rc)) { /* diff --git a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 index c135b461ac0a..50e4cc026191 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 +++ b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-32.c32 @@ -1,4 +1,4 @@ -/* $Id: bs3-apic-1-32.c32 114765 2026-07-24 08:34:04Z alexander.eichner@oracle.com $ */ +/* $Id: bs3-apic-1-32.c32 115065 2026-08-18 12:57:16Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - bs3-apic-1, 32-bit C code. */ @@ -48,6 +48,7 @@ #define CPUS_MAX 32 #define CPU_STACK_SIZE _4K +#define APIC_TIMER_VECTOR 0x6f #define APIC_VECTOR 0x70 #define APIC_X2APIC_VECTOR 0x71 @@ -80,6 +81,8 @@ typedef enum APICCPUREQ kApicCpuReq_Pong, /** A simple pong to a ping but don't clear the request. */ kApicCpuReq_PongNoClear, + /** Starts the APIC timer and waits for it to complete. */ + kApicCpuReq_ApicTimerStartAndWaitToComplete, /** 32-bit hack. */ kApicCpuReq_32Bit_Hack = 0x7fffffff } APICCPUREQ; @@ -90,6 +93,8 @@ typedef enum APICCPUREQ */ typedef struct APICCPU { + /** Pointer to this CPU. */ + struct APICCPU *pThis; /** The stack for this CPU. */ uint8_t abStack[CPU_STACK_SIZE]; /** The MMIO base for the APIC when in xAPIC mode. */ @@ -105,13 +110,15 @@ typedef struct APICCPU /** Flag whether the APIC is in x2Apic mode. */ bool fX2Apic; /** Padding. */ - bool afAlignment[11]; + bool afAlignment[7]; } APICCPU; typedef APICCPU *PAPICCPU; AssertCompileSizeAlignment(APICCPU, 16); BS3_DECL_CALLBACK(void) bs3ApicApTrampoline(void); BS3_DECL_CALLBACK(void) bs3ApicApTrampoline_EndProc(void); +BS3_DECL(void) bs3ApicApSetGs(uint16_t u16Gs); +BS3_DECL(PAPICCPU) bs3ApicApGetCpu(void); /** The AP lock protecting against concurrent access to some APIs and the startup code (lives in the DATA16 segment). */ #define g_fApLock BS3_DATA_NM(g_fApLock) @@ -125,7 +132,8 @@ static volatile uint32_t g_cCpus = 1; static volatile uint32_t g_cCpusResponded = 0; static volatile uint64_t g_bmCpusValid = 0; static volatile uint64_t g_bmCpusResponded = 0; - +static volatile bool g_fTimerDone = false; +static volatile bool g_fTimerSetIpi = false; static void bs3ApicBusyWait(uint64_t u64Usec) { @@ -223,6 +231,7 @@ static void bs3ApicTestFailedF(const char BS3_FAR *pszFormat, ...) static PAPICCPU bs3ApicGetCpu(void) { +#if 0 uint64_t uApicBase; uApicBase = ASMRdMsr(MSR_IA32_APICBASE); @@ -242,6 +251,43 @@ static PAPICCPU bs3ApicGetCpu(void) } return NULL; +#else + return bs3ApicApGetCpu(); +#endif +} + + +static void bs3ApicTimerStart(PAPICCPU pApicCpu, uint8_t bVector, uint32_t u32Icr) +{ + /* Program the timer. */ + if (pApicCpu->fX2Apic) + { + ASMWrMsr(MSR_IA32_X2APIC_TIMER_DCR, 0); + ASMWrMsr(MSR_IA32_X2APIC_LVT_TIMER, bVector); + ASMIntEnable(); + ASMWrMsr(MSR_IA32_X2APIC_TIMER_ICR, u32Icr); + } + else + { + uint32_t BS3_FAR volatile * const pau32Apic = pApicCpu->pau32Apic; + pau32Apic[XAPIC_OFF_TIMER_DCR / sizeof(uint32_t)] = 0; + pau32Apic[XAPIC_OFF_LVT_TIMER / sizeof(uint32_t)] = (uint32_t)bVector; + + ASMIntEnable(); + pau32Apic[XAPIC_OFF_TIMER_ICR / sizeof(uint32_t)] = u32Icr; + } +} + + +static uint32_t bs3ApicTimerGetCurrent(PAPICCPU pApicCpu) +{ + if (pApicCpu->fX2Apic) + return (uint32_t)ASMRdMsr(MSR_IA32_X2APIC_TIMER_CCR); + else + { + uint32_t BS3_FAR volatile * const pau32Apic = pApicCpu->pau32Apic; + return pau32Apic[XAPIC_OFF_TIMER_CCR / sizeof(uint32_t)]; + } } @@ -267,6 +313,13 @@ BS3_DECL_NEAR_CALLBACK(void) BS3_CMN_NM(bs3ApicIpiHandler)(PBS3TRAPFRAME pTrapFr case kApicCpuReq_Pong: bs3ApicTestFinish(pApicCpu->idCpu); break; + case kApicCpuReq_ApicTimerStartAndWaitToComplete: + { + bs3ApicTimerStart(pApicCpu, APIC_TIMER_VECTOR, 0x1); + while (bs3ApicTimerGetCurrent(pApicCpu) > 0) + ASMNopPause(); + break; + } default: /* Invalid but can't print anything here. */ break; @@ -354,6 +407,10 @@ BS3_DECL(uint32_t) bs3ApicApStartup_pe32(void) uint32_t const idApic = pau32Apic[XAPIC_OFF_ID / sizeof(uint32_t)] >> 24; PAPICCPU pApicCpu = &g_paCpus[idApic]; + /* Set the GS selector. */ + bs3ApicApSetGs(BS3_SEL_FREE_PART4 + idApic * 8); + + pApicCpu->pThis = pApicCpu; pApicCpu->idCpu = idApic; pApicCpu->pau32Apic = pau32Apic; pApicCpu->enmWaitMethod = kApicCpuWaitMethod_StiHltCli; @@ -420,11 +477,20 @@ static bool bs3ApicStartAllAps(uint32_t BS3_FAR volatile * const pau32Apic) PAPICCPU pApicCpu = &g_paCpus[0]; void BS3_FAR *pv = Bs3MemAllocZ(BS3MEMKIND_REAL, _4K); uintptr_t const uPtr = (uintptr_t)pv; + uint8_t i; + pApicCpu->pThis = pApicCpu; pApicCpu->idCpu = 0; pApicCpu->fX2Apic = false; pApicCpu->pau32Apic = pau32Apic; + /* Setup the GDT entries for the per vCPU data. */ + for (i = 0; i < CPUS_MAX; i++) + Bs3SelSetup32BitData(&Bs3GdteFreePart4[i], (uint32_t)(uintptr_t)&g_paCpus[i], sizeof(g_paCpus[0]), 0 /*bDpl*/); + + /* Set the GS selector for CPU0. */ + bs3ApicApSetGs(BS3_SEL_FREE_PART4); + if ( !(uPtr & X86_PAGE_OFFSET_MASK) && (uPtr >> X86_PAGE_SHIFT) < UINT8_MAX) { @@ -495,6 +561,8 @@ static void bs3ApicTestWaitForResponses(uint32_t cCpusToRespond, uint64_t bmCpus static void bs3ApicTestSelfIpi(PAPICCPU pApicCpu) { + /* Set the GS selector for CPU0. */ + bs3ApicApSetGs(BS3_SEL_FREE_PART4); bs3ApicTestSetIntrHandlerReq(pApicCpu, kApicCpuReq_Pong); ASMIntEnable(); @@ -504,6 +572,64 @@ static void bs3ApicTestSelfIpi(PAPICCPU pApicCpu) } +BS3_DECL_NEAR_CALLBACK(void) BS3_CMN_NM(bs3ApicTimerHandler)(PBS3TRAPFRAME pTrapFrame) +{ + PAPICCPU pApicCpu = bs3ApicGetCpu(); + if (pApicCpu) + { + if (g_fTimerSetIpi) + { + /* Cause an IPI with a higher priority to interrupt this handler. */ + ASMIntEnable(); + bs3ApicTestReset(); + bs3ApicTestSetIntrHandlerReq(pApicCpu, kApicCpuReq_Pong); + apicSelfIpi(pApicCpu, APIC_VECTOR); + bs3ApicTestWaitForResponses(1, RT_BIT_64(pApicCpu->idCpu)); + } + + g_fTimerDone = true; + + /* Finish servicing this interrupt. */ + if (pApicCpu->fX2Apic) + ASMWrMsr(MSR_IA32_X2APIC_EOI, 0); + else + pApicCpu->pau32Apic[XAPIC_OFF_EOI / sizeof(uint32_t)] = 0; + } + else + bs3ApicTestFailedF("APIC not enabled!"); + + RT_NOREF(pTrapFrame); +} + + +static void bs3ApicTestNestedIrqs(PAPICCPU pApicCpu) +{ + /* Set the GS selector for CPU0. */ + bs3ApicApSetGs(BS3_SEL_FREE_PART4); + Bs3TrapSetHandler(APIC_TIMER_VECTOR, bs3ApicTimerHandler_c32); + g_fTimerDone = false; + g_fTimerSetIpi = true; + + bs3ApicTimerStart(pApicCpu, APIC_TIMER_VECTOR, 0xffff); + + while (!g_fTimerDone) + ASMNopPause(); + + /* + * Test that a lower priority interrupt is serviced when it arrives while a + * higher priiority interrupt is serviced when it finished. + */ + g_fTimerDone = false; + g_fTimerSetIpi = false; + bs3ApicTestSetIntrHandlerReq(pApicCpu, kApicCpuReq_ApicTimerStartAndWaitToComplete); + apicSelfIpi(pApicCpu, APIC_VECTOR); + + while (!g_fTimerDone) + ASMNopPause(); + ASMIntDisable(); +} + + static void bs3ApicTestIpiStress(PAPICCPU pApicCpu) { /* Hammer the APIC with pings to all CPUs. */ @@ -570,6 +696,9 @@ static void bs3ApicRunTests(PAPICCPU pApicCpu) Bs3TestSubSub("Self IPI"); bs3ApicTestSelfIpi(pApicCpu); + Bs3TestSubSub("Nested Interrupts"); + bs3ApicTestNestedIrqs(pApicCpu); + if (g_cCpus > 1) { Bs3TestSubSub("Pinging all APs"); diff --git a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-asm.asm b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-asm.asm index e26f40923053..a31c86cd11a8 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3-apic-1-asm.asm +++ b/src/VBox/ValidationKit/bootsectors/bs3-apic-1-asm.asm @@ -1,4 +1,4 @@ -; $Id: bs3-apic-1-asm.asm 114752 2026-07-22 16:52:58Z alexander.eichner@oracle.com $ +; $Id: bs3-apic-1-asm.asm 115063 2026-08-18 07:54:16Z alexander.eichner@oracle.com $ ;; @file ; BS3Kit - bs3-apic-1 ; @@ -121,3 +121,26 @@ BS3_GLOBAL_LOCAL_LABEL .ap_lck_busy int 3 BS3_PROC_END NAME(bs3ApicApTrampoline) +BS3_BEGIN_TEXT32 + +;; +; @cproto BS3_DECL(void) bs3ApicApSetGs(uint16_t u16Gs); +; +BS3_PROC_BEGIN NAME(bs3ApicApSetGs) + push edi + + mov di, [esp + 8] ; u16Gs + mov gs, di + pop edi + ret +BS3_PROC_END NAME(bs3ApicApSetGs) + + +;; +; @cproto BS3_DECL(PAPICCPU) bs3ApicApGetCpu(void); +; +BS3_PROC_BEGIN NAME(bs3ApicApGetCpu) + mov eax, [gs:0] + ret +BS3_PROC_END NAME(bs3ApicApGetCpu) + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk b/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk index b5c1078703ec..9d1f52db6e4a 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: Makefile.kmk 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ ## @file # VirtualBox Validation Kit - Bootsector Kit v3 # @@ -204,6 +204,7 @@ VBOX_BS3KIT_COMMON_SOURCES = \ bs3-cmn-SelLnkPtrToFlat.c \ bs3-cmn-SelSetup16BitData.c \ bs3-cmn-SelSetup16BitCode.c \ + bs3-cmn-SelSetup32BitData.c \ bs3-cmn-SelSetup32BitCode.c \ bs3-cmn-SelSetupGate.c \ bs3-cmn-SelSetupGate64.c \ diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitData.c b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitData.c new file mode 100644 index 000000000000..14833c8beed2 --- /dev/null +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3-cmn-SelSetup32BitData.c @@ -0,0 +1,62 @@ +/* $Id: bs3-cmn-SelSetup32BitData.c 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ */ +/** @file + * BS3Kit - Bs3SelSetup32BitData + */ + +/* + * Copyright (C) 2026 Oracle and/or its affiliates. + * + * This file is part of VirtualBox base platform packages, as + * available from https://www.virtualbox.org. + * + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU General Public License + * as published by the Free Software Foundation, in version 3 of the + * License. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, see . + * + * The contents of this file may alternatively be used under the terms + * of the Common Development and Distribution License Version 1.0 + * (CDDL), a copy of it is provided in the "COPYING.CDDL" file included + * in the VirtualBox distribution, in which case the provisions of the + * CDDL are applicable instead of those of the GPL. + * + * You may elect to license modified versions of this file under the + * terms and conditions of either the GPL or the CDDL or both. + * + * SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 + */ + + +/********************************************************************************************************************************* +* Header Files * +*********************************************************************************************************************************/ +#include + + +#undef Bs3SelSetup32BitData +BS3_CMN_DEF(void, Bs3SelSetup32BitData,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint32_t uLimit, uint8_t bDpl)) +{ + uint8_t const cLimitShift = uLimit <= UINT32_C(0xfffff) ? 0 : 12; + pDesc->Gen.u16LimitLow = (uint16_t)(uLimit >> cLimitShift); + pDesc->Gen.u16BaseLow = (uint16_t)uBaseAddr; + pDesc->Gen.u8BaseHigh1 = (uint8_t)(uBaseAddr >> 16); + pDesc->Gen.u4Type = X86_SEL_TYPE_RW_ACC; + pDesc->Gen.u1DescType = 1; /* data/code */ + pDesc->Gen.u2Dpl = bDpl & 3; + pDesc->Gen.u1Present = 1; + pDesc->Gen.u4LimitHigh = (unsigned)(uLimit >> (16 + cLimitShift)); + pDesc->Gen.u1Available = 0; + pDesc->Gen.u1Long = 0; + pDesc->Gen.u1DefBig = 1; + pDesc->Gen.u1Granularity = cLimitShift != 0; + pDesc->Gen.u8BaseHigh2 = (uint8_t)(uBaseAddr >> 24); +} + diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h index c0b3e1757670..3bdbdc390859 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-define.h @@ -1,4 +1,4 @@ -/* $Id: bs3kit-mangling-code-define.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: bs3kit-mangling-code-define.h 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - Function needing mangling - generated by the bs3kit-mangling-code-define.h makefile rule. */ @@ -175,6 +175,7 @@ #define Bs3SelRealModeDataToProtFar16 BS3_CMN_MANGLER(Bs3SelRealModeDataToProtFar16) #define Bs3SelSetup16BitCode BS3_CMN_MANGLER(Bs3SelSetup16BitCode) #define Bs3SelSetup16BitData BS3_CMN_MANGLER(Bs3SelSetup16BitData) +#define Bs3SelSetup32BitData BS3_CMN_MANGLER(Bs3SelSetup32BitData) #define Bs3SelSetup32BitCode BS3_CMN_MANGLER(Bs3SelSetup32BitCode) #define Bs3SelSetupGate BS3_CMN_MANGLER(Bs3SelSetupGate) #define Bs3SelSetupGate64 BS3_CMN_MANGLER(Bs3SelSetupGate64) diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h index 2ef988101fcd..a61619b3be03 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit-mangling-code-undef.h @@ -1,4 +1,4 @@ -/* $Id: bs3kit-mangling-code-undef.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: bs3kit-mangling-code-undef.h 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - Undefining function mangling - automatically generated by the bs3kit-mangling-code-undef.h makefile rule. */ @@ -175,6 +175,7 @@ #undef Bs3SelRealModeDataToProtFar16 #undef Bs3SelSetup16BitCode #undef Bs3SelSetup16BitData +#undef Bs3SelSetup32BitData #undef Bs3SelSetup32BitCode #undef Bs3SelSetupGate #undef Bs3SelSetupGate64 diff --git a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h index 1290843e2a62..22b8c3e235cf 100644 --- a/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h +++ b/src/VBox/ValidationKit/bootsectors/bs3kit/bs3kit.h @@ -1,4 +1,4 @@ -/* $Id: bs3kit.h 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ */ +/* $Id: bs3kit.h 115062 2026-08-18 07:52:42Z alexander.eichner@oracle.com $ */ /** @file * BS3Kit - structures, symbols, macros and stuff. */ @@ -2099,6 +2099,18 @@ BS3_CMN_PROTO_STUB(void, Bs3SelSetup16BitData,(X86DESC BS3_FAR *pDesc, uint32_t */ BS3_CMN_PROTO_STUB(void, Bs3SelSetup16BitCode,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint8_t bDpl)); +/** + * Sets up a 32-bit read-write selector with a user specified limit. + * + * @param pDesc Pointer to the descriptor table entry. + * @param uBaseAddr The base address of the descriptor. + * @param uLimit The limit. (This is included here and not in the 16-bit + * functions because we're more likely to want to set it + * than for 16-bit selectors.) + * @param bDpl The descriptor privilege level. + */ +BS3_CMN_PROTO_STUB(void, Bs3SelSetup32BitData,(X86DESC BS3_FAR *pDesc, uint32_t uBaseAddr, uint32_t uLimit, uint8_t bDpl)); + /** * Sets up a 32-bit execute-read selector with a user specified limit. * diff --git a/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py b/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py index c23f8d70a965..1e2f7c8c47e6 100755 --- a/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py +++ b/src/VBox/ValidationKit/testmanager/batch/virtual_test_sheriff.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# $Id: virtual_test_sheriff.py 113949 2026-04-17 23:48:48Z knut.osmundsen@oracle.com $ +# $Id: virtual_test_sheriff.py 114985 2026-08-11 09:55:45Z andreas.loeffler@oracle.com $ # pylint: disable=line-too-long """ @@ -45,7 +45,7 @@ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 """ -__version__ = "$Revision: 113949 $" +__version__ = "$Revision: 114985 $" # Standard python imports @@ -353,7 +353,7 @@ def __init__(self): if self.oConfig.sLogFile: self.oLogFile = open(self.oConfig.sLogFile, "a"); # pylint: disable=consider-using-with,unspecified-encoding - self.oLogFile.write('VirtualTestSheriff: $Revision: 113949 $ \n'); + self.oLogFile.write('VirtualTestSheriff: $Revision: 114985 $ \n'); def eprint(self, sText): @@ -768,7 +768,7 @@ def caseClosed(self, oCaseFile): for idTestResult, tReason in dReasonForResultId.items(): oFailureReason = self.getFailureReason(tReason); if oFailureReason is not None: - sComment = 'Set by $Revision: 113949 $' # Handy for reverting later. + sComment = 'Set by $Revision: 114985 $' # Handy for reverting later. if idTestResult in dCommentForResultId: sComment += ': ' + dCommentForResultId[idTestResult]; @@ -900,6 +900,7 @@ def findInAnyAndReturnRestOfLine(asHaystacks, sNeedle): ], 'win': [ # ( Whether to stop on hit, reason tuple, needle text. ) + ( True, ktReason_Host_HostMemoryLow, 'Error = 0x000005AA' ), ( True, ktReason_Host_InstallationWantReboot, 'ERROR_SUCCESS_REBOOT_REQUIRED' ), ( False, ktReason_Host_InstallationFailed, 'Installation error.' ), ( True, ktReason_Host_InvalidPackage, 'Uninstaller failed, exit code: 1620' ), diff --git a/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py b/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py index 2c36629ee7a7..e71e70a3978d 100644 --- a/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py +++ b/src/VBox/ValidationKit/testmanager/core/webservergluecgi.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# $Id: webservergluecgi.py 112403 2026-01-11 19:29:08Z knut.osmundsen@oracle.com $ +# $Id: webservergluecgi.py 115041 2026-08-14 21:54:05Z knut.osmundsen@oracle.com $ """ Test Manager Core - Web Server Abstraction Base Class. @@ -36,12 +36,12 @@ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 """ -__version__ = "$Revision: 112403 $" +__version__ = "$Revision: 115041 $" # Standard python imports. -import cgi; # pylint: disable=deprecated-module ## @todo these will be retired in python 3.13! -import cgitb; # pylint: disable=deprecated-module ## @todo these will be retired in python 3.13! +import cgi; # pylint: disable=deprecated-module # Retired in python 3.13. Install (python3-)legacy-cgi. +import cgitb; # pylint: disable=deprecated-module # Ditto. import os; import sys; diff --git a/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py b/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py index fc2b09bf5ef4..77573135bdfa 100644 --- a/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py +++ b/src/VBox/ValidationKit/testmanager/webui/wuicontentbase.py @@ -1,5 +1,5 @@ # -*- coding: utf-8 -*- -# $Id: wuicontentbase.py 113947 2026-04-17 23:13:52Z knut.osmundsen@oracle.com $ +# $Id: wuicontentbase.py 115032 2026-08-13 07:41:47Z knut.osmundsen@oracle.com $ """ Test Manager Web-UI - Content Base Classes. @@ -36,7 +36,7 @@ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 """ -__version__ = "$Revision: 113947 $" +__version__ = "$Revision: 115032 $" # Standard python imports. @@ -85,7 +85,7 @@ class WuiLinkBase(WuiHtmlBase): # pylint: disable=too-few-public-methods For passing links from WuiListContentBase._formatListEntry. """ - def __init__(self, sName, sUrlBase, dParams = None, # pylint: disable=too-many-arguments + def __init__(self, sName, sUrlBase, dParams = None, # pylint: disable=too-many-arguments,too-many-positional-arguments sConfirm = None, sTitle = None, sFragmentId = None, fBracketed = True, sExtraAttrs = '', sImgFile = None, sImgClass = 'icon'): WuiHtmlBase.__init__(self); @@ -211,7 +211,7 @@ def __init__(self, sUrlBase, dParams = None, sName = None, sTitle = None, class WuiAdminLink(WuiTmLink): # pylint: disable=too-few-public-methods """ Local link to the test manager's admin portion. """ - def __init__(self, sName, sAction, tsEffectiveDate = None, # pylint: disable=too-many-arguments + def __init__(self, sName, sAction, tsEffectiveDate = None, # pylint: disable=too-many-arguments,too-many-positional-arguments dParams = None, sConfirm = None, sTitle = None, sFragmentId = None, fBracketed = True, sImgFile = None): from testmanager.webui.wuiadmin import WuiAdmin; if not dParams: diff --git a/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py b/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py index 35b75737c18b..6741ce30ece3 100755 --- a/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py +++ b/src/VBox/ValidationKit/tests/unittests/tdUnitTest1.py @@ -1,6 +1,6 @@ #!/usr/bin/env python # -*- coding: utf-8 -*- -# $Id: tdUnitTest1.py 113466 2026-03-19 12:10:45Z brent.paulson@oracle.com $ +# $Id: tdUnitTest1.py 115050 2026-08-17 15:20:35Z andreas.loeffler@oracle.com $ """ VirtualBox Validation Kit - Unit Tests. @@ -37,7 +37,7 @@ SPDX-License-Identifier: GPL-3.0-only OR CDDL-1.0 """ -__version__ = "$Revision: 113466 $" +__version__ = "$Revision: 115050 $" # Standard Python imports. @@ -160,7 +160,6 @@ class tdUnitTest1(vbox.TestDriver): 'testcase/tstClipboardX11Smoke': '', # (Old naming, deprecated) Needs X, not available on all test boxes. 'testcase/tstClipboardGH-X11Smoke': '', # (New name) Ditto. 'testcase/tstClipboardHttpServerX11': '', # Ditto. - 'testcase/tstClipboardMockHGCM': '', # Ditto. 'tstClipboardQt': '', # Is interactive and needs Qt, needed for Qt clipboard bugfixing. 'testcase/tstClipboardQt': '', # In case it moves here. 'tstDragAndDropQt': '', # Is interactive and needs Qt, needed for Qt drag'n drop bugfixing. @@ -302,7 +301,6 @@ class tdUnitTest1(vbox.TestDriver): kdTestCasesWhiteList = { 'testcase/tstFile': '', 'testcase/tstFileLock': '', - 'testcase/tstClipboardMockHGCM': '', # Requires X on Linux OSes. Execute on remote targets only (guests). 'testcase/tstRTFsQueries': '', 'testcase/tstRTLocalIpc': '', 'testcase/tstRTPathQueryInfo': '', diff --git a/src/bldprogs/VBoxTpG.cpp b/src/bldprogs/VBoxTpG.cpp index 39f045a2e9f5..854164af37aa 100644 --- a/src/bldprogs/VBoxTpG.cpp +++ b/src/bldprogs/VBoxTpG.cpp @@ -1,4 +1,4 @@ -/* $Id: VBoxTpG.cpp 114139 2026-05-14 22:38:18Z knut.osmundsen@oracle.com $ */ +/* $Id: VBoxTpG.cpp 115020 2026-08-12 23:43:12Z knut.osmundsen@oracle.com $ */ /** @file * VBox Build Tool - VBox Tracepoint Generator. */ @@ -454,7 +454,7 @@ static RTEXITCODE generateAssembly(PSCMSTREAM pStrm) * Write the file header. */ ScmStreamPrintf(pStrm, - "; $Id: VBoxTpG.cpp 114139 2026-05-14 22:38:18Z knut.osmundsen@oracle.com $ \n" + "; $Id: VBoxTpG.cpp 115020 2026-08-12 23:43:12Z knut.osmundsen@oracle.com $ \n" ";; @file\n" "; Automatically generated from %s. Do NOT edit!\n" ";\n" @@ -909,13 +909,52 @@ static RTEXITCODE generateAssembly(PSCMSTREAM pStrm) , g_pszProbeFnName); ScmStreamPrintf(pStrm, + " int3\n" ".return:\n" " ret ; The probe was disabled, return\n" ".end_proc:\n" + " int3\n" "\n"); } } + /* + * Emit unwind info for the stubs. + */ + if (fWin64) + { + ScmStreamPrintf(pStrm, + "\n" + "section .pdata rdata align=4\n"); + RTListForEach(&g_ProviderHead, pProvider, VTGPROVIDER, ListEntry) + { + RTListForEach(&pProvider->ProbeHead, pProbe, VTGPROBE, ListEntry) + { + ScmStreamPrintf(pStrm, + "dd NAME(VTGProbeStub_%s_%s) wrt ..imagebase\n" + "dd NAME(VTGProbeStub_%s_%s.end_proc) wrt ..imagebase\n" + "dd vtg_unwind_info wrt ..imagebase\n" + , pProvider->pszName, pProbe->pszMangledName, pProvider->pszName, pProbe->pszMangledName); + } + } + + ScmStreamPrintf(pStrm, + "\n" + "section .xdata rdata align=4\n" + "align 4, db 0\n" + "vtg_unwind_info:\n" + " db 1 ; version 1 (3 bit), no flags (5 bits)\n" + " db 0 ; prolog size (0)\n" + " db 0 ; info array length (0)\n" + " db 0 ; frame register and offset.\n" + "\n" + "@feat.00 equ 1\n"); + } + + ScmStreamPrintf(pStrm, + "\n" + "MARK_OBJECT_RETPOLINE_SAFE"); /** @todo retpoline: there are plenty indirect jmps above... */ + return RTEXITCODE_SUCCESS; } @@ -1009,7 +1048,7 @@ static RTEXITCODE generateHeader(PSCMSTREAM pStrm) } ScmStreamPrintf(pStrm, - "/* $Id: VBoxTpG.cpp 114139 2026-05-14 22:38:18Z knut.osmundsen@oracle.com $ */\n" + "/* $Id: VBoxTpG.cpp 115020 2026-08-12 23:43:12Z knut.osmundsen@oracle.com $ */\n" "/** @file\n" " * Automatically generated from %s. Do NOT edit!\n" " */\n" @@ -1215,7 +1254,7 @@ static RTEXITCODE generateWrapperHeader(PSCMSTREAM pStrm) } ScmStreamPrintf(pStrm, - "/* $Id: VBoxTpG.cpp 114139 2026-05-14 22:38:18Z knut.osmundsen@oracle.com $ */\n" + "/* $Id: VBoxTpG.cpp 115020 2026-08-12 23:43:12Z knut.osmundsen@oracle.com $ */\n" "/** @file\n" " * Automatically generated from %s. Do NOT edit!\n" " */\n" @@ -2543,7 +2582,7 @@ static RTEXITCODE parseArguments(int argc, char **argv) case 'V': { /* The following is assuming that svn does it's job here. */ - static const char s_szRev[] = "$Revision: 114139 $"; + static const char s_szRev[] = "$Revision: 115020 $"; const char *psz = RTStrStripL(strchr(s_szRev, ' ')); RTPrintf("r%.*s\n", strchr(psz, ' ') - psz, psz); return RTEXITCODE_SUCCESS; diff --git a/src/libs/Makefile.kmk b/src/libs/Makefile.kmk index ca06ee0516fa..490af4fde1c5 100644 --- a/src/libs/Makefile.kmk +++ b/src/libs/Makefile.kmk @@ -1,4 +1,4 @@ -# $Id: Makefile.kmk 114504 2026-06-23 16:56:50Z aleksey.ilyushin@oracle.com $ +# $Id: Makefile.kmk 114896 2026-08-07 16:50:21Z klaus.espenlaub@oracle.com $ ## @file # Top-level makefile for the external libraries. # @@ -69,7 +69,8 @@ endif if (defined(VBOX_WITH_VRDP) || defined(VBOX_WITH_USB_VIDEO_IMPL)) \ && !defined(VBOX_ONLY_ADDITIONS) \ && !defined(VBOX_ONLY_SDK) \ - && !defined(VBOX_ONLY_VALIDATIONKIT) + && !defined(VBOX_ONLY_VALIDATIONKIT) \ + && "$(SDK_VBoxLibJpeg_INCS)" == "$(SDK_VBoxLibJpeg_DEFAULT_INCS)" include $(PATH_SUB_CURRENT)/libjpeg-turbo-3.1.0/Makefile.kmk endif @@ -90,7 +91,7 @@ endif if defined(VBOX_WITH_LIBSSH) \ && !defined(VBOX_ONLY_BUILD) \ && "$(intersects $(KBUILD_TARGET_ARCH),$(VBOX_SUPPORTED_HOST_ARCHS))" != "" - include $(PATH_SUB_CURRENT)/libssh-0.11.4/Makefile.kmk + include $(PATH_SUB_CURRENT)/libssh-0.12.2/Makefile.kmk endif diff --git a/src/libs/libssh-0.12.2/.arcconfig b/src/libs/libssh-0.12.2/.arcconfig new file mode 100644 index 000000000000..30b1a60cf8c8 --- /dev/null +++ b/src/libs/libssh-0.12.2/.arcconfig @@ -0,0 +1,4 @@ +{ + "phabricator.uri" : "https://bugs.libssh.org/", + "history.immutable": true +} diff --git a/src/libs/libssh-0.12.2/.clang-format b/src/libs/libssh-0.12.2/.clang-format new file mode 100644 index 000000000000..d6f60e37bc8d --- /dev/null +++ b/src/libs/libssh-0.12.2/.clang-format @@ -0,0 +1,29 @@ +--- +# https://clang.llvm.org/docs/ClangFormatStyleOptions.html +BasedOnStyle: LLVM +IndentWidth: 4 +UseTab: Never +AllowShortIfStatementsOnASingleLine: false +BreakBeforeBraces: Custom +BraceWrapping: + AfterEnum: false + AfterFunction: true + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeElse: false + BeforeWhile: false +IndentCaseLabels: false +IndentCaseBlocks: false +ColumnLimit: 80 +AlignAfterOpenBracket: Align +AllowAllParametersOfDeclarationOnNextLine: false +BinPackArguments: false +BinPackParameters: false +AllowAllArgumentsOnNextLine: false +AllowShortFunctionsOnASingleLine: Empty +BreakAfterReturnType: ExceptShortType +AlwaysBreakAfterReturnType: AllDefinitions +AlignEscapedNewlines: Left +ForEachMacros: ['ssh_callbacks_iterate'] +AlignConsecutiveMacros: 'Consecutive' diff --git a/src/libs/libssh-0.12.2/.clang-format-ignore b/src/libs/libssh-0.12.2/.clang-format-ignore new file mode 100644 index 000000000000..459f5916d7fd --- /dev/null +++ b/src/libs/libssh-0.12.2/.clang-format-ignore @@ -0,0 +1 @@ +src/external/* diff --git a/src/libs/libssh-0.12.2/.cmake-format.yaml b/src/libs/libssh-0.12.2/.cmake-format.yaml new file mode 100644 index 000000000000..f411792840f3 --- /dev/null +++ b/src/libs/libssh-0.12.2/.cmake-format.yaml @@ -0,0 +1,6 @@ +--- +line_width: 80 +tab_size: 4 +use_tabchars: false +separate_ctrl_name_with_space: true +separate_fn_name_with_space: false diff --git a/src/libs/libssh-0.12.2/.editorconfig b/src/libs/libssh-0.12.2/.editorconfig new file mode 100644 index 000000000000..bb3dfbb054de --- /dev/null +++ b/src/libs/libssh-0.12.2/.editorconfig @@ -0,0 +1,23 @@ +root = true + +[*] +charset = utf-8 +max_line_length = 80 +end_of_line = lf +trim_trailing_whitespace = true +insert_final_newline = true + +[*.{c,h}] +indent_style = space +indent_size = 4 +tab_width = 4 + +[CMakeLists.txt] +indent_style = space +indent_size = 4 +tab_width = 4 + +[*.cmake] +indent_style = space +indent_size = 4 +tab_width = 4 diff --git a/src/libs/libssh-0.12.2/AUTHORS b/src/libs/libssh-0.12.2/AUTHORS new file mode 100644 index 000000000000..51b3e46f4b9b --- /dev/null +++ b/src/libs/libssh-0.12.2/AUTHORS @@ -0,0 +1,15 @@ +Author(s): +Aris Adamantiadis (project initiator) + +Andreas Schneider (developer) + +Nick Zitzmann (mostly client SFTP stuff) + +Norbert Kiesel (getaddrinfo and other patches) + +Jean-Philippe Garcia Ballester (Port to libgcrypt and configure.in voodoo, debian packaging) + +Contributor(s): + +Laurent Bigonville (debian packaging) + diff --git a/src/libs/libssh-0.12.2/BSD b/src/libs/libssh-0.12.2/BSD new file mode 100644 index 000000000000..b8dba0d24074 --- /dev/null +++ b/src/libs/libssh-0.12.2/BSD @@ -0,0 +1,24 @@ +Some parts are under the BSDv2 License : + + +Copyright (c) 2000 Markus Friedl. All rights reserved. +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + 1. Redistributions of source code must retain the above copyright + notice, this list of conditions and the following disclaimer. + 2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + diff --git a/src/libs/libssh-0.12.2/CHANGELOG b/src/libs/libssh-0.12.2/CHANGELOG new file mode 100644 index 000000000000..109f7747ce57 --- /dev/null +++ b/src/libs/libssh-0.12.2/CHANGELOG @@ -0,0 +1,826 @@ +CHANGELOG +========= + +version 0.12.2 (released 2026-07-28) + * Security: + * CVE-2026-59843: Denial of service via zero advertised channel packet size + (accidentally missed from previous release backports) + * Bugfixes: + * Fix fallback to default provider if FIPS provider does not have ML-KEM + implementation + * Do not override explicitly set options through a configuration file to + match OpenSSH behavior. + * Return SSH_KNOWN_HOSTS_UNKNOWN when GSSAPI key exchange is used without any + hostkey + * New API: + * API to check the GSSAPI key exchange was used for hostkey handling + * API to initiate GSSAPI keyex authentication (missing from last release) + +version 0.12.1 (released 2026-07-21) + * Security: + * CVE-2026-15370: Stack buffer overflow in SFTP server longname construction + * CVE-2026-59842: Information disclosure via short GSSAPI Curve25519 public key + * CVE-2026-59844: Denial of service via oversized SFTP read length + * CVE-2026-59845: Denial of service via unchecked ProxyCommand fork() failure + * CVE-2026-59846: Information disclosure via ProxyCommand %r username expansion + * CVE-2026-59847: Integrity downgrade via OpenSSL AES-GCM tag verification + * CVE-2026-59848: Denial of service via SFTP responses with unknown request IDs + * CVE-2026-59849: Denial of service via automatic certificate authentication loop + * CVE-2026-59850: Use-after-free via data callbacks on closed channels + * CVE-2026-59851: Authentication bypass via missing GSSAPI principal check + * Zero-initialize every ssh_string + * Compatibility: + * Fix compatibility with C23 / gcc16 + * Allow hybrid ML-KEM key exchange in FIPS mode + * Bugfixes: + * Fix multiple memory leaks, null checks, and error checks + * Fix parameter size mismatch in mlkem768x25519-sha256 + * Fix client SFTP messages being ignored if sent at high rate + * Validate peer public key in DH key exchange + * Fix ambiguous error reporting of sftp_init + * Fix hidden integer underflow in socket packet callback + * Avoid remote window overflow + * Fix socket data callback return value on rekey failure + * Avoid off-by-one overflow during kbdint authentication + * Avoid logging uninitialized sequence numbers + * Avoid double conversion of SFTP version number + * Send correct SFTP server version number + * Avoid handling repeated SFTP INIT messages + * Harmonize return values from SFTP server callbacks + +version 0.12.0 (released 2026-02-10) + * Deprecations and removals: + * Bumped minimal RSA key size to 1024 bits + * New functionality: + * Add support for hybrid key exchange mechanisms using Quantum Resistant + cryptography for all backends. These are now preferred: + * sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com + * mlkem768nistp256-sha256 + * mlkem768x25519-sha256 + * mlkem1024nistp384-sha384 (only OpenSSL 3.5+ and libgcrypt) + * New cmake option WITH_HERMETIC_USR + * Added support for Ed25519 keys through PKCS#11 + * Support for host-bound public key authentication + (publickey-hostbound-v00@openssh.com) + * Use curve25519 implementation from mbedTLS and libgcrypt + * New functions for signing arbitrary data (commits) with SSH keys + * sshsig_sign() + * sshsig_verify() + * Support for FIDO/U2F keys (internal implementation using libfido2) + * Compatible with OpenSSH: should work out of the box + * Extensible with callbacks + * Add support for GSSAPI Key Exchange (RFC 4462, RFC 8732) + * Add support for new configuration options (client and server): + * RequiredRsaSize + * AddressFamily (client) + * GSSAPIKeyExchange + * GSSAPIKexAlgorithms + * New option to get list of configured identities (SSH_OPTIONS_NEXT_IDENTITY) + * More OpenSSH compatible percent expansion characters + * Add new server auth_kbdint_function() callback + * New PKI Context structure for key operations + * Stability and compatibility improvements of ProxyJump + * SFTP + * Prevent failures when SFTP status message does not contain error message + * Fix possible timeouts while waiting for SFTP messages + * Support for users-groups-by-id@openssh.com extension in client + * Support for SSH_FXF_TRUNC in server + +version 0.11.4 (released 2026-02-10) + * Security: + * CVE-2025-14821: libssh loads configuration files from the C:\etc directory + on Windows + * CVE-2026-0964: SCP Protocol Path Traversal in ssh_scp_pull_request() + * CVE-2026-0965: Possible Denial of Service when parsing unexpected + configuration files + * CVE-2026-0966: Buffer underflow in ssh_get_hexa() on invalid input + * CVE-2026-0967: Specially crafted patterns could cause DoS + * CVE-2026-0968: OOB Read in sftp_parse_longname() + * libssh-2026-sftp-extensions: Read buffer overrun when handling SFTP + extensions + * Stability and compatibility improvements of ProxyJump + +version 0.11.3 (released 2025-09-09) + * Security: + * CVE-2025-8114: Fix NULL pointer dereference after allocation failure + * CVE-2025-8277: Fix memory leak of ephemeral key pair during repeated wrong KEX + * Potential UAF when send() fails during key exchange + * Fix possible timeout during KEX if client sends authentication too early (#311) + * Cleanup OpenSSL PKCS#11 provider when loaded + * Zeroize buffers containing private key blobs during export + +version 0.11.2 (released 2025-06-24) + * Security: + * CVE-2025-4877 - Write beyond bounds in binary to base64 conversion + * CVE-2025-4878 - Use of uninitialized variable in privatekey_from_file() + * CVE-2025-5318 - Likely read beyond bounds in sftp server handle management + * CVE-2025-5351 - Double free in functions exporting keys + * CVE-2025-5372 - ssh_kdf() returns a success code on certain failures + * CVE-2025-5449 - Likely read beyond bounds in sftp server message decoding + * CVE-2025-5987 - Invalid return code for chacha20 poly1305 with OpenSSL + * Compatibility + * Fixed compatibility with CPM.cmake + * Compatibility with OpenSSH 10.0 + * Tests compatibility with new Dropbear releases + * Removed p11-kit remoting from the pkcs11 testsuite + * Bugfixes + * Implement missing packet filter for DH GEX + * Properly process the SSH2_MSG_DEBUG message + * Allow escaping quotes in quoted arguments to ssh configuration + * Do not fail with unknown match keywords in ssh configuration + * Process packets before selecting signature algorithm during authentication + * Do not fail hard when the SFTP status message is not sent by noncompliant + servers + +version 0.11.1 (released 2024-08-30) + * Fixed default TTY modes that are set when stdin is not connected to tty (#270) + * Fixed zlib cleanup procedure, which could crash on i386 + * Various test fixes improving their stability + * Fixed cygwin build + +version 0.11.0 (released 2024-07-31) + * Deprecations and Removals: + * Dropped support for DSA + * Deprecated Blowfish cipher (will be removed in next release) + * Deprecated SSH_BIND_OPTIONS_{RSA,ECDSA}KEY in favor of generic HOSTKEY + * Removed the usage of deprecated OpenSSL APIs (Note: Minimum supported + OpenSSL version is 1.1.1) + * Disabled preauth compression (zlib) by default + * Support for pkcs#11 engines are deprecated, pkcs11-provider is used instead + * Deprecation of old async SFTP API + * libgcrypt cryptographic backend is deprecated + * Deprecation of knownhosts hashing + * SFTP Improvements: + * Added support for async SFTP IO + * Added support for sftp_limits() and applied capping to SFTP read/write + operations accordingly + * Added sftp_home_directory() API support for sftp extension "home-directory" + * Added sftp_lsetstat() API for lsetstat extensions + * Added sftp_expand_path() to canonicalize path using expand-path@openssh.com + extension + * Implemented stat and realpath in sftpserver + * Added sftp_readlink() API to support hardlink@openssh.com + * New extensible callback based SFTP server + * Introduced the posix-rename@openssh.com extension + * New functions and features: + * Added support for PKCS #11 provider for OpenSSL 3.0 + * Added testing for GSSAPI Authentication + * Implemented proxy jump using libssh + * Recategorized loglevels to show fatal errors and alignment with OpenSSH + log levels + * Added ssh_channel_request_pty_size_modes() API to set terminal modes for + PTYs + * Added function to check username syntax + * Added support to check all keys in authorized_keys instead of one in + example server implementation + * Handled hostkey similar to OpenSSH + * Added ssh_session_socket_close() API in order to not close socket passed + through options on error conditions + * Added option SSH_BIND_OPTIONS_IMPORT_KEY_STR to read user-supplied key + string in ssh_bind_options_set() + * Improved log handling around ssh_set_callbacks + * Added ssh_set_error_invalid in ssh_options_set() + * Prevented signature blob to start with 1 bit in libgcrypt + * Added support to unbreak key comparison of Ed25519 keys imported from PEM + or OpenSSH container + * Added support to calculate missing CRT parameters when building RSA key + * Added ssh_pki_export_privkey_base64_format() and + ssh_pki_export_privkey_file_format() to support exporting keys in different + formats (PEM, OpenSSH) + * Added support to compare certificates and handle automatic certificate + authentication + * Added support to make compile-commands generation conditional + * Built fuzzers for normal testing + * Avoided passing other events to callbacks when called recursively + * Added control master and path options + * Refactored channel_rcv_data, check for errors and report more useful errors + * Added support to connect to other host addresses than just the first one + * Terminated the server properly when the MaxAuthTries is reached + * Added support for no-more-sessions@openssh.com request in both client and + server + * Added callback to support forwarded-tcpip requests + * Bumped minimal CMake version to 3.12 + * Added support for MBedTLS 3.6.x + * Added support for +,-,^ modifiers in front of algorithm lists in options + * Added callbacks for channel open response, and channel request response + * Replaced chroot() from chroot_wrapper internal library with chroot() + from priv_wrapper package + * Added a placeholder for non-expanded identities + * Improved handling of channel transfer window sizes + +version 0.10.6 (released 2023-12-18) + * Fix CVE-2023-6004: Command injection using proxycommand + * Fix CVE-2023-48795: Potential downgrade attack using strict kex + * Fix CVE-2023-6918: Missing checks for return values of MD functions + * Fix ssh_send_issue_banner() for CMD(PowerShell) + * Avoid passing other events to callbacks when poll is called recursively (#202) + * Allow @ in usernames when parsing from URI composes + +version 0.10.5 (released 2023-05-04) + * Fix CVE-2023-1667: a NULL dereference during rekeying with algorithm guessing + * Fix CVE-2023-2283: a possible authorization bypass in + pki_verify_data_signature under low-memory conditions. + * Fix several memory leaks in GSSAPI handling code + * Escape braces in ProxyCommand created from ProxyJump options for zsh + compatibility. + * Fix pkg-config path relocation for MinGW + * Improve doxygen documentation + * Fix build with cygwin due to the glob support + * Do not enqueue outgoing packets after sending SSH2_MSG_NEWKEYS + * Add support for SSH_SUPPRESS_DEPRECATED + * Avoid functions declarations without prototype to build with clang 15 + * Fix spelling issues + * Avoid expanding KnownHosts, ProxyCommands and IdentityFiles repetitively + * Add support sk-* keys through configuration + * Improve checking for Argp library + * Log information about received extensions + * Correctly handle rekey with delayed compression + * Move the EC keys handling to OpenSSL 3.0 API + * Record peer disconnect message + * Avoid deadlock when write buffering occurs and we call poll recursively to + flush the output buffer + * Disable preauthentication compression by default + * Add CentOS 8 Stream / OpenSSL 1.1.1 to CI + * Add accidentally removed default compile flags + * Solve incorrect parsing of ProxyCommand option + +version 0.10.4 (released 2022-09-07) + * Fixed issues with KDF on big endian + +version 0.10.3 (released 2022-09-05) + * Fixed possible infinite loop in known hosts checking + +version 0.10.2 (released 2022-09-02) + * Fixed tilde expansion when handling include directives + * Fixed building the shared torture library + * Made rekey test more robust (fixes running on i586 build systems e.g koji) + +version 0.10.1 (released 2022-08-30) + * Fixed proxycommand support + * Fixed musl libc support + +version 0.10.0 (released 2022-08-26) + * Added support for OpenSSL 3.0 + * Added support for mbedTLS 3 + * Added support for Smart Cards (through openssl pkcs11 engine) + * Added support for chacha20-poly1305@openssh.com with libgcrypt + * Added support ed25519 keys in PEM files + * Added support for sk-ecdsa and sk-ed25519 (server side) + * Added support for limiting RSA key sizes and not accepting small one by + default + * Added support for ssh-agent on Windows + * Added ssh_userauth_publickey_auto_get_current_identity() API + * Added ssh_vlog() API + * Added ssh_send_issue_banner() API + * Added ssh_session_set_disconnect_message() API + * Added new configuration options: + + IdentityAgent + + ModuliFile + * Provided X11 client example + * Disabled DSA support at build time by default (will be removed in the next + release) + * Deprecated the SCP API! + * Deprecated old pubkey, privatekey API + * Avoided some needless large stack buffers to minimize memory footprint + * Removed support for OpenSSL < 1.0.1 + +version 0.9.6 (released 2021-08-26) + * CVE-2021-3634: Fix possible heap-buffer overflow when rekeying with + different key exchange mechanism + * Fix several memory leaks on error paths + * Reset pending_call_state on disconnect + * Fix handshake bug with AEAD ciphers and no HMAC overlap + * Use OPENSSL_CRYPTO_LIBRARIES in CMake + * Ignore request success and failure message if they are not expected + * Support more identity files in configuration + * Avoid setting compiler flags directly in CMake + * Support build directories with special characters + * Include stdlib.h to avoid crash in Windows + * Fix sftp_new_channel constructs an invalid object + * Fix Ninja multiple rules error + * Several tests fixes + +version 0.9.5 (released 2020-09-10) + * CVE-2020-16135: Avoid null pointer dereference in sftpserver (T232) + * Improve handling of library initialization (T222) + * Fix parsing of subsecond times in SFTP (T219) + * Make the documentation reproducible + * Remove deprecated API usage in OpenSSL + * Fix regression of ssh_channel_poll_timeout() returning SSH_AGAIN + * Define version in one place (T226) + * Prevent invalid free when using different C runtimes than OpenSSL (T229) + * Compatibility improvements to testsuite + +version 0.9.4 (released 2020-04-09) + * Fixed CVE-2020-1730 - Possible DoS in client and server when handling + AES-CTR keys with OpenSSL + * Added diffie-hellman-group14-sha256 + * Fixed several possible memory leaks + +version 0.9.3 (released 2019-12-10) + * Fixed CVE-2019-14889 - SCP: Unsanitized location leads to command execution + * SSH-01-003 Client: Missing NULL check leads to crash in erroneous state + * SSH-01-006 General: Various unchecked Null-derefs cause DOS + * SSH-01-007 PKI Gcrypt: Potential UAF/double free with RSA pubkeys + * SSH-01-010 SSH: Deprecated hash function in fingerprinting + * SSH-01-013 Conf-Parsing: Recursive wildcards in hostnames lead to DOS + * SSH-01-014 Conf-Parsing: Integer underflow leads to OOB array access + * SSH-01-001 State Machine: Initial machine states should be set explicitly + * SSH-01-002 Kex: Differently bound macros used to iterate same array + * SSH-01-005 Code-Quality: Integer sign confusion during assignments + * SSH-01-008 SCP: Protocol Injection via unescaped File Names + * SSH-01-009 SSH: Update documentation which RFCs are implemented + * SSH-01-012 PKI: Information leak via uninitialized stack buffer + +version 0.9.2 (released 2019-11-07) + * Fixed libssh-config.cmake + * Fixed issues with rsa algorithm negotiation (T191) + * Fixed detection of OpenSSL ed25519 support (T197) + +version 0.9.1 (released 2019-10-25) + * Added support for Ed25519 via OpenSSL + * Added support for X25519 via OpenSSL + * Added support for localuser in Match keyword + * Fixed Match keyword to be case sensitive + * Fixed compilation with LibreSSL + * Fixed error report of channel open (T75) + * Fixed sftp documentation (T137) + * Fixed known_hosts parsing (T156) + * Fixed build issue with MinGW (T157) + * Fixed build with gcc 9 (T164) + * Fixed deprecation issues (T165) + * Fixed known_hosts directory creation (T166) + +version 0.9.0 (released 2019-02-xx) + * Added support for AES-GCM + * Added improved rekeying support + * Added performance improvements + * Disabled blowfish support by default + * Fixed several ssh config parsing issues + * Added support for DH Group Exchange KEX + * Added support for Encrypt-then-MAC mode + * Added support for parsing server side configuration file + * Added support for ECDSA/Ed25519 certificates + * Added FIPS 140-2 compatibility + * Improved known_hosts parsing + * Improved documentation + * Improved OpenSSL API usage for KEX, DH, and signatures + +version 0.8.0 (released 2018-08-10) + * Removed support for deprecated SSHv1 protocol + * Added new connector API for clients + * Added new known_hosts parsing API + * Added support for OpenSSL 1.1 + * Added support for chacha20-poly1305 cipher + * Added crypto backend for mbedtls crypto library + * Added ECDSA support with gcrypt backend + * Added advanced client and server testing using cwrap.org + * Added support for curve25519-sha256 alias + * Added support for global known_hosts file + * Added support for symbol versioning + * Improved ssh_config parsing + * Improved threading support + +version 0.7.5 (released 2017-04-13) + * Fixed a memory allocation issue with buffers + * Fixed PKI on Windows + * Fixed some SSHv1 functions + * Fixed config hostname expansion + +version 0.7.4 (released 2017-02-03) + * Added id_ed25519 to the default identity list + * Fixed sftp EOF packet handling + * Fixed ssh_send_banner() to confirm with RFC 4253 + * Fixed some memory leaks + +version 0.7.3 (released 2016-01-23) + * Fixed CVE-2016-0739 + * Fixed ssh-agent on big endian + * Fixed some documentation issues + +version 0.7.2 (released 2015-09-15) + * Fixed OpenSSL detection on Windows + * Fixed return status for ssh_userauth_agent() + * Fixed KEX to prefer hmac-sha2-256 + * Fixed sftp packet handling + * Fixed return values of ssh_key_is_(public|private) + * Fixed bug in global success reply + +version 0.7.1 (released 2015-06-30) + * Fixed SSH_AUTH_PARTIAL auth with auto public key + * Fixed memory leak in session options + * Fixed allocation of ed25519 public keys + * Fixed channel exit-status and exit-signal + * Reintroduce ssh_forward_listen() + +version 0.7.0 (released 2015-05-11) + * Added support for ed25519 keys + * Added SHA2 algorithms for HMAC + * Added improved and more secure buffer handling code + * Added callback for auth_none_function + * Added support for ECDSA private key signing + * Added more tests + * Fixed a lot of bugs + * Improved API documentation + +version 0.6.5 (released 2015-04-29) + * Fixed CVE-2015-3146 + * Fixed port handling in config file + * Fixed the build with libgcrypt + * Fixed SFTP endian issues (rlo #179) + * Fixed uninitilized sig variable (rlo #167) + * Fixed polling issues which could result in a hang + * Fixed handling of EINTR in ssh_poll() (rlo #186) + * Fixed C99 issues with __func__ + * Fixed some memory leaks + * Improved macro detection on Windows + +version 0.6.4 (released 2014-12-19) + * Fixed CVE-2014-8132. + * Added SHA-2 for session ID signing with ECDSA keys. + * Added support for ECDSA host keys. + * Added support for more ECDSA hostkey algorithms. + * Added ssh_pki_key_ecdsa_name() API. + * Fixed setting the bindfd only after successful listen. + * Fixed issues with user created sockets. + * Fixed several issues in libssh C++ wrapper. + * Fixed several documentation issues. + * Fixed channel exit-signal request. + * Fixed X11 request screen number in messages. + * Fixed several memory leaks. + +version 0.6.3 (released 2014-03-04) + * Fixed CVE-2014-0017. + * Fixed memory leak with ecdsa signatures. + +version 0.6.2 (released 2014-03-04) + * security: fix for vulnerability CVE-2014-0017 + +version 0.6.1 (released 2014-02-08) + * Added support for libgcrypt 1.6. + * Added ssh_channel_accept_forward(). + * Added known_hosts heuristic during connection (#138). + * Added getters for session cipher names. + * Fixed decrypt of zero length buffer. + * Fixed padding in RSA signature blobs. + * Fixed DSA signature extraction. + * Fixed some memory leaks. + * Fixed read of non-connected socket. + * Fixed thread detection. + +version 0.6.0 (released 2014-01-08) + * Added new publicy key API. + * Added new userauth API. + * Added ssh_get_publickey_hash() function. + * Added ssh_get_poll_flags() function. + * Added gssapi-mic userauth. + * Added GSSAPIServerIdentity option. + * Added GSSAPIClientIdentity option. + * Added GSSAPIDelegateCredentials option. + * Added new callback based server API. + * Added Elliptic Curve DSA (ECDSA) support (with OpenSSL). + * Added Elliptic Curve Diffie Hellman (ECDH) support. + * Added Curve25519 for ECDH key exchange. + * Added improved logging system. + * Added SSH-agent forwarding. + * Added key-reexchange. + * Added more unit tests. + * Improved documentation. + * Fixed timeout handling. + +version 0.5.5 (released 2013-07-26) + * BUG 103: Fix ProxyCommand parsing. + * Fix setting -D_FORTIFY_SOURCE=2. + * Fix pollset error return if empty. + * Fix NULL pointer checks in channel functions. + * Several bugfixes. + +version 0.5.4 (released 2013-01-22) + * CVE-2013-0176 - NULL dereference leads to denial of service + * Fixed several NULL pointer dereferences in SSHv1. + * Fixed a free crash bug in options parsing. + +version 0.5.3 (released 2012-11-20) + * CVE-2012-4559 Fixed multiple double free() flaws. + * CVE-2012-4560 Fixed multiple buffer overflow flaws. + * CVE-2012-4561 Fixed multiple invalid free() flaws. + * BUG #84 - Fix bug in sftp_mkdir not returning on error. + * BUG #85 - Fixed a possible channel infinite loop if the connection dropped. + * BUG #88 - Added missing channel request_state and set it to accepted. + * BUG #89 - Reset error state to no error on successful SSHv1 authentication. + * Fixed a possible use after free in ssh_free(). + * Fixed multiple possible NULL pointer dereferences. + * Fixed multiple memory leaks in error paths. + * Fixed timeout handling. + * Fixed regression in pre-connected socket setting. + * Handle all unknown global messages. + +version 0.5.2 (released 2011-09-17) + * Increased window size x10. + * Fixed SSHv1. + * Fixed bugged lists. + * Fixed use-after-free + inconsistent callbacks call in poll. + * Fixed scp documentation. + * Fixed possible infinite loop in channel_read(). + * Fixed handling of short reads of sftp_async_read(). + * Fixed handling request service timeout in blocking mode. + * Fixed ssh_auth_list() documentation. + * Fixed incorrect return values in ssh_channel_write(). + * Fixed an infinite loop in the termination callback. + * Fixed handling of SSH_AGAIN in channel_open(). + * Fixed "status -5 inflating zlib packet" + +version 0.5.1 (released 2011-08-09) + * Added checks for NULL pointers in string.c. + * Set the channel max packet size to 32768. + * Don't (de)compress empty buffers. + * Fixed ssh_scp_write so it works when doing recursive copy. + * Fixed another source of endless wait. + * Fixed an endless loop in case of a channel_open error. + * Fixed session timeout handling. + * Fixed ssh_channel_from_local() loop. + * Fixed permissions of scp example when we copy a file. + * Workaround ssh_get_user_home_dir on LDAP users. + * Added pkg-config support for libssh_threads. + * Fixed compilation without server and sftp modes. + * Fix static .lib overwriting on Windows. + +version 0.5.0 (released 2011-06-01) + * Added ssh_ prefix to all functions. + * Added complete Windows support. + * Added improved server support. + * Added unit tests for a lot of functions. + * Added asynchronous service request. + * Added a multiplatform ssh_getpass() function. + * Added a tutorial. + * Added a lot of documentation. + * Fixed a lot of bugs. + * Fixed several memory leaks. + +version 0.4.8 (released 2011-01-15) + * Fixed memory leaks in session signing. + * Fixed memory leak in ssh_print_hexa. + * Fixed problem with ssh_connect w/ timeout and fd > 1024. + * Fixed some warnings on OS/2. + * Fixed installation path for OS/2. + +version 0.4.7 (released 2010-12-28) + * Fixed a possible memory leak in ssh_get_user_home(). + * Fixed a memory leak in sftp_xstat. + * Fixed uninitialized fd->revents member. + * Fixed timeout value in ssh_channel_accept(). + * Fixed length checks in ssh_analyze_banner(). + * Fixed a possible data overread and crash bug. + * Fixed setting max_fd which breaks ssh_select(). + * Fixed some pedantic build warnings. + * Fixed a memory leak with session->bindaddr. + +version 0.4.6 (released 2010-09-03) + * Added a cleanup function to free the ws2_32 library. + * Fixed build with gcc 3.4. + * Fixed the Windows build on Vista and newer. + * Fixed the usage of WSAPoll() on Windows. + * Fixed "@deprecated" in doxygen + * Fixed some mingw warnings. + * Fixed handling of opened channels. + * Fixed keepalive problem on older openssh servers. + * Fixed testing for big endian on Windows. + * Fixed the Windows preprocessor macros and defines. + +version 0.4.5 (released 2010-07-13) + * Added option to bind a client to an ip address. + * Fixed the ssh socket polling function. + * Fixed Windows related bugs in bsd_poll(). + * Fixed several build warnings. + +version 0.4.4 (released 2010-06-01) + * Fixed a bug in the expand function for escape sequences. + * Fixed a bug in the tilde expand function. + * Fixed a bug in setting the options. + +version 0.4.3 (released 2010-05-18) + * Added global/keepalive responses. + * Added runtime detection of WSAPoll(). + * Added a select(2) based poll-emulation if poll(2) is not available. + * Added a function to expand an escaped string. + * Added a function to expand the tilde from a path. + * Added a proxycommand support. + * Added ssh_privatekey_type public function + * Added the possibility to define _OPENSSL_DIR and _ZLIB_DIR. + * Fixed sftp_chown. + * Fixed sftp_rename on protocol version 3. + * Fixed a blocking bug in channel_poll. + * Fixed config parsing which has overwritten user specified values. + * Fixed hashed [host]:port format in knownhosts + * Fixed Windows build. + * Fixed doublefree happening after a negotiation error. + * Fixed aes*-ctr with <= OpenSSL 0.9.7b. + * Fixed some documentation. + * Fixed exec example which has broken read usage. + * Fixed broken algorithm choice for server. + * Fixed a typo that we don't export all symbols. + * Removed the unneeded dependency to doxygen. + * Build examples only on the Linux platform. + +version 0.4.2 (released 2010-03-15) + * Added owner and group information in sftp attributes. + * Added missing SSH_OPTIONS_FD option. + * Added printout of owner and group in the sftp example. + * Added a prepend function for ssh_list. + * Added send back replies to openssh's keepalives. + * Fixed documentation in scp code + * Fixed longname parsing, this only workings with readdir. + * Fixed and added support for several identity files. + * Fixed sftp_parse_longname() on Windows. + * Fixed a race condition bug in ssh_scp_close() + * Remove config support for SSHv1 Cipher variable. + * Rename ssh_list_add to ssh_list_append. + * Rename ssh_list_get_head to ssh_list_pop_head + +version 0.4.1 (released 2010-02-13) + * Added support for aes128-ctr, aes192-ctr and aes256-ctr encryption. + * Added an example for exec. + * Added private key type detection feature in privatekey_from_file(). + * Fixed zlib compression fallback. + * Fixed kex bug that client preference should be priority + * Fixed known_hosts file set by the user. + * Fixed a memleak in channel_accept(). + * Fixed underflow when leave_function() are unbalanced + * Fixed memory corruption in handle_channel_request_open(). + * Fixed closing of a file handle case of errors in privatekey_from_file(). + * Fixed ssh_get_user_home_dir() to be thread safe. + * Fixed the doxygen documentation. + +version 0.4.0 (released 2009-12-10) + * Added scp support. + * Added support for sending signals (RFC 4254, section 6.9). + * Added MSVC support. + * Added support for ~/.ssh/config. + * Added sftp extension support. + * Added X11 forwarding support for client. + * Added forward listening. + * Added support for openssh extensions (statvfs, fstatvfs). + * Added a cleaned up interface for setting options. + * Added a generic way to handle sockets asynchronously. + * Added logging of the sftp flags used to open a file. + * Added full poll() support and poll-emulation for win32. + * Added missing 64bit functions in sftp. + * Added support for ~/ and SSH_DIR/ in filenames instead of %s/. + * Fixed Fix channel_get_exit_status bug. + * Fixed calltrace logging to make it optional. + * Fixed compilation on Solaris. + * Fixed resolving of ip addresses. + * Fixed libssh compilation without server support. + * Fixed possible memory corruptions (ticket #14). + +version 0.3.4 (released 2009-09-14) + * Added ssh_basename and ssh_dirname. + * Added a portable ssh_mkdir function. + * Added a sftp_tell64() function. + * Added missing NULL pointer checks to crypt_set_algorithms_server. + * Fixed ssh_write_knownhost if ~/.ssh doesn't exist. + * Fixed a possible integer overflow in buffer_get_data(). + * Fixed possible security bug in packet_decrypt(). + * Fixed a possible stack overflow in agent code. + +version 0.3.3 (released 2009-08-18) + * Fixed double free pointer crash in dsa_public_to_string. + * Fixed channel_get_exit_status bug. + * Fixed ssh_finalize which didn't clear the flag. + * Fixed memory leak introduced by previous bugfix. + * Fixed channel_poll broken when delayed EOF recvd. + * Fixed stupid "can't parse known host key" bug. + * Fixed possible memory corruption (ticket #14). + +version 0.3.2 (released 2009-08-05) + * Added ssh_init() function. + * Added sftp_readlink() function. + * Added sftp_symlink() function. + * Fixed ssh_write_knownhost(). + * Fixed compilation on Solaris. + * Fixed SSHv1 compilation. + +version 0.3.1 (released 2009-07-14) + * Added return code SSH_SERVER_FILE_NOT_FOUND. + * Fixed compilation of SSHv1. + * Fixed several memory leaks. + * Fixed possible infinite loops. + * Fixed a possible crash bug. + * Fixed build warnings. + * Fixed cmake on BSD. + +version 0.3 (released 2009-05-21) + * Added support for ssh-agent authentication. + * Added POSIX like sftp implementation. + * Added error checking to all functions. + * Added const to arguments where it was needed. + * Added a channel_get_exit_status() function. + * Added a channel_read_buffer() function, channel_read() is now + a POSIX like function. + * Added a more generic auth callback function. + * Added printf attribute checking for log and error functions. + * Added runtime function tracer support. + * Added NSIS build support with CPack. + * Added openssh hashed host support. + * Added API documentation for all public functions. + * Added asynchronous SFTP read function. + * Added a ssh_bind_set_fd() function. + * Fixed known_hosts parsing. + * Fixed a lot of build warnings. + * Fixed the Windows build. + * Fixed a lot of memory leaks. + * Fixed a double free corruption in the server support. + * Fixed the "ssh_accept:" bug in server support. + * Fixed important channel bugs. + * Refactored the socket handling. + * Switched to CMake build system. + * Improved performance. + +version 0.2 (released 2007-11-29) + * General cleanup + * More comprehensive API + * Up-to-date Doxygen documentation of each public function + * Basic server-based support + * Libgcrypt support (alternative to openssl and its license) + * SSH1 support (disabled by default) + * Added 3des-cbc + * A lot of bugfixes + +version 0.11-dev + * Server implementation development. + * Small bug corrected when connecting to sun ssh servers. + * Channel weirdness corrected (writing huge data packets) + * Channel_read_nonblocking added + * Channel bug where stderr wasn't correctly read fixed. + * Added sftp_file_set_nonblocking(), which is nonblocking SFTP IO + * Connect_status callback. + * Priv.h contains the internal functions, libssh.h the public interface + * Options_set_timeout (thx marcelo) really working. + * Tcp tunneling through channel_open_forward. + * Channel_request_exec() + * Channel_request_env() + * Ssh_get_pubkey_hash() + * Ssh_is_server_known() + * Ssh_write_known_host() + * Options_set_ssh_dir + * How could this happen ! there weren't any channel_close ! + * Nasty channel_free bug resolved. + * Removed the unsigned long all around the code. use only u8,u32 & u64. + * It now compiles and runs under amd64 ! + * Channel_request_pty_size + * Channel_change_pty_size + * Options_copy() + * Ported the doc to an HTML file. + * Small bugfix in packet.c + * Prefixed error constants with SSH_ + * Sftp_stat, sftp_lstat, sftp_fstat. thanks Michel Bardiaux for the patch. + * Again channel number mismatch fixed. + * Fixed a bug in ssh_select making the select fail when a signal has been + caught. + * Keyboard-interactive authentication working. + +version 0.1 (released 2004-03-05) + * Beginning of sftp subsystem implementation. + * Some cleanup into channels implementation + * Now every channel functions is called by its CHANNEL handler. + * Added channel_poll() and channel_read(). + * Changed the client so it uses the new channel_poll and channel_read interface + * Small use-after-free bug with channels resolved + * Changed stupidities in lot of function names. + * Removed a debug output file opened by default. + * Added API.txt, the libssh programmer handbook. + * Various bug fixes from Nick Zitzmann. + * Developed a cryptographic structure for handling protocols. + * An autoconf script which took me half of a day to set up. + * A ssh_select wrapper has been written. + +version 0.0.4 (released 2003-10-10) + * Some terminal code (eof handling) added + * Channels bugfix (it still needs some tweaking though) + * Zlib support + * Added a wrapper.c file. The goal is to provide a similar API to every + cryptographic functions. bignums and sha/md5 are wrapped now. + * More work than it first looks. + * Support for other crypto libs planned (lighter libs) + * Fixed stupid select() bug. + * Libssh now compiles and links with openssl 0.9.6 + * RSA pubkey authentication code now works ! + +version 0.0.3 (released 2003-09-15) + * Added install target in makefile + * Some cleanup in headers files and source code + * Change default banner and project name to libssh. + * New file auth.c to support more and more authentication ways + * Bugfix(read offbyone) in send_kex + * A base64 parser. don't read the source, it's awful. pure 0xbadc0de. + * Changed the client filename to "ssh". logic isn't it ? + * Dss publickey authentication ! still need to wait for the rsa one + * Bugfix in packet.c + * New misc.c contains misc functions + +version 0.0.2 (released 2003-09-03) + * Initial release. + * Client supports both ssh and dss hostkey verification, but doesn't compare them to openssh's files. (~/.ssh/known_hosts) + * The only supported authentication method is password. + * Compiles on linux and openbsd. freebsd and netbsd should work, too + * Lot of work which hasn't been discussed here. diff --git a/src/libs/libssh-0.12.2/CMakeLists.txt b/src/libs/libssh-0.12.2/CMakeLists.txt new file mode 100644 index 000000000000..c242461d9487 --- /dev/null +++ b/src/libs/libssh-0.12.2/CMakeLists.txt @@ -0,0 +1,284 @@ +cmake_minimum_required(VERSION 3.14.0) + +# Specify search path for CMake modules to be loaded by include() +# and find_package() +list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake/Modules") + +# Add defaults for cmake +# Those need to be set before the project() call. +include(DefineCMakeDefaults) +include(DefineCompilerFlags) + +project(libssh VERSION 0.12.2 LANGUAGES C) + +# global needed variable +set(APPLICATION_NAME ${PROJECT_NAME}) + +# SOVERSION scheme: CURRENT.AGE.REVISION +# If there was an incompatible interface change: +# Increment CURRENT. Set AGE and REVISION to 0 +# If there was a compatible interface change: +# Increment AGE. Set REVISION to 0 +# If the source code was changed, but there were no interface changes: +# Increment REVISION. +set(LIBRARY_VERSION "4.12.0") +set(LIBRARY_SOVERSION "4") + +# where to look first for cmake modules, before ${CMAKE_ROOT}/Modules/ is checked + +# add definitions +include(DefinePlatformDefaults) +include(DefineOptions.cmake) +include(CPackConfig.cmake) +include(GNUInstallDirs) + +include(CompilerChecks.cmake) + +# disallow in-source build +include(MacroEnsureOutOfSourceBuild) +macro_ensure_out_of_source_build("${PROJECT_NAME} requires an out of source build. Please create a separate build directory and run 'cmake /path/to/${PROJECT_NAME} [options]' there.") + +# Copy library files to a lib sub-directory +set(CMAKE_LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/lib") + +set(LIBSSSH_PC_REQUIRES_PRIVATE "") + +# search for libraries +if (WITH_ZLIB) + find_package(ZLIB REQUIRED) +endif (WITH_ZLIB) + +if (WITH_GCRYPT) + find_package(GCrypt 1.5.0 REQUIRED) + message(WARNING "libgcrypt cryptographic backend is deprecated and will be removed in future releases.") +elseif(WITH_MBEDTLS) + find_package(MbedTLS REQUIRED) +else() + find_package(OpenSSL 1.1.1 REQUIRED) +endif() + +if (UNIT_TESTING) + find_package(CMocka REQUIRED) +endif () + +# Find out if we have threading available +set(CMAKE_THREAD_PREFER_PTHREADS ON) +set(THREADS_PREFER_PTHREAD_FLAG ON) +find_package(Threads) + +if (WITH_GSSAPI) + find_package(GSSAPI) + list(APPEND LIBSSH_PC_REQUIRES_PRIVATE ${GSSAPI_PC_REQUIRES}) +endif (WITH_GSSAPI) + +if (WITH_NACL) + find_package(NaCl) + if (NOT NACL_FOUND) + set(WITH_NACL OFF) + endif (NOT NACL_FOUND) +endif (WITH_NACL) + +if (WITH_FIDO2) + find_package(libfido2) + if (LIBFIDO2_FOUND) + set(HAVE_LIBFIDO2 ON) + else (LIBFIDO2_FOUND) + set(HAVE_LIBFIDO2 OFF) + message(WARNING "libfido2 was not found. Internal support for interacting with FIDO2/U2F devices using the USB HID protocol will not be available.") + endif (LIBFIDO2_FOUND) +endif (WITH_FIDO2) + +# Disable symbol versioning in non UNIX platforms +if (UNIX) + find_package(ABIMap 0.4.0) +else (UNIX) + set(WITH_SYMBOL_VERSIONING OFF) +endif (UNIX) + +# config.h checks +include(ConfigureChecks.cmake) +configure_file(config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/config.h) + +if (NOT HAVE_ARGP_PARSE) + find_package(Argp) +endif (NOT HAVE_ARGP_PARSE) + +# check subdirectories +add_subdirectory(doc) +add_subdirectory(include) +add_subdirectory(src) + +# pkg-config file +if (UNIX OR MINGW) +configure_file(libssh.pc.cmake ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc @ONLY) +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/libssh.pc + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/pkgconfig + COMPONENT + pkgconfig +) +endif (UNIX OR MINGW) + +# CMake config files +include(CMakePackageConfigHelpers) + +set(LIBSSH_LIBRARY_NAME ${CMAKE_SHARED_LIBRARY_PREFIX}ssh${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# libssh-config-version.cmake +write_basic_package_version_file(libssh-config-version.cmake + VERSION ${PROJECT_VERSION} + COMPATIBILITY SameMajorVersion) + +install( + FILES + ${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}-config-version.cmake + DESTINATION + ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME} + COMPONENT + devel) + +if (WITH_EXAMPLES) + add_subdirectory(examples) +endif (WITH_EXAMPLES) + +if (UNIT_TESTING) + include(AddCMockaTest) + add_subdirectory(tests) +endif (UNIT_TESTING) + +### SOURCE PACKAGE +if (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) + # Get the current ABI version from source + get_filename_component(current_abi_path + "${CMAKE_SOURCE_DIR}/src/ABI/current" + ABSOLUTE) + + # Check if the ABI version should be updated + file(READ ${current_abi_path} CURRENT_ABI_CONTENT) + string(STRIP "${CURRENT_ABI_CONTENT}" CURRENT_ABI_VERSION) + + if (LIBRARY_VERSION VERSION_GREATER CURRENT_ABI_VERSION) + set(UPDATE_ABI TRUE) + endif () + + if (UPDATE_ABI) + message(STATUS "Library version bumped to ${LIBRARY_VERSION}: Updating ABI") + + # Get the list of header files + get_file_list(${PROJECT_NAME}_header_list + DIRECTORIES "${CMAKE_SOURCE_DIR}/include/libssh" + FILES_PATTERNS "*.h") + + # Extract the symbols marked as "LIBSSH_API" from the header files + extract_symbols(${PROJECT_NAME}.symbols + HEADERS_LIST ${PROJECT_NAME}_header_list + FILTER_PATTERN "LIBSSH_API" + COPY_TO "${CMAKE_SOURCE_DIR}/src/ABI/${PROJECT_NAME}-${LIBRARY_VERSION}.symbols") + + if (WITH_ABI_BREAK) + set(ALLOW_ABI_BREAK "BREAK_ABI") + endif() + + if (WITH_FINAL) + set(FINAL "FINAL") + endif() + + # Target we can depend on in 'make dist' + set(_SYMBOL_TARGET "${PROJECT_NAME}.map") + + # Set the path to the current map file + set(MAP_PATH "${CMAKE_SOURCE_DIR}/src/${_SYMBOL_TARGET}") + + # Generate the symbol version map file + generate_map_file(${_SYMBOL_TARGET} + SYMBOLS ${PROJECT_NAME}.symbols + RELEASE_NAME_VERSION ${PROJECT_NAME}_${LIBRARY_VERSION} + CURRENT_MAP ${MAP_PATH} + COPY_TO ${MAP_PATH} + ${FINAL} + ${ALLOW_ABI_BREAK}) + + # Write the current version to the source + file(WRITE ${current_abi_path} ${LIBRARY_VERSION}) + endif(UPDATE_ABI) +endif (WITH_SYMBOL_VERSIONING AND ABIMAP_FOUND) + +# Coverage +if (WITH_COVERAGE) + ENABLE_LANGUAGE(CXX) + include(CodeCoverage) + setup_target_for_coverage_lcov( + NAME "coverage" + EXECUTABLE make test + DEPENDENCIES ssh tests) + set(GCOVR_ADDITIONAL_ARGS --xml-pretty --exclude-unreachable-branches --print-summary --gcov-ignore-parse-errors) + setup_target_for_coverage_gcovr_xml( + NAME "coverage_xml" + EXECUTABLE make test + DEPENDENCIES ssh tests) +endif (WITH_COVERAGE) + +add_custom_target(dist COMMAND ${CMAKE_MAKE_PROGRAM} package_source DEPENDS ${_SYMBOL_TARGET} VERBATIM) + +get_directory_property(hasParent PARENT_DIRECTORY) +if(NOT(hasParent)) + # Link compile database for clangd if we are the master project + execute_process(COMMAND ${CMAKE_COMMAND} -E create_symlink + "${CMAKE_BINARY_DIR}/compile_commands.json" + "${CMAKE_SOURCE_DIR}/compile_commands.json") +endif() + +message(STATUS "********************************************") +message(STATUS "********** ${PROJECT_NAME} build options : **********") + +message(STATUS "Build type: ${CMAKE_BUILD_TYPE}") +message(STATUS "Coverage: ${WITH_COVERAGE}") +message(STATUS "zlib support: ${WITH_ZLIB}") +message(STATUS "libgcrypt support: ${WITH_GCRYPT}") +message(STATUS "libmbedTLS support: ${WITH_MBEDTLS}") +message(STATUS "libnacl support: ${WITH_NACL}") +message(STATUS "SFTP support: ${WITH_SFTP}") +message(STATUS "Server support : ${WITH_SERVER}") +message(STATUS "GSSAPI support : ${WITH_GSSAPI}") +message(STATUS "GEX support : ${WITH_GEX}") +message(STATUS "Support insecure none cipher and MAC : ${WITH_INSECURE_NONE}") +message(STATUS "Support exec : ${WITH_EXEC}") +message(STATUS "Pcap debugging support : ${WITH_PCAP}") +message(STATUS "Build shared library: ${BUILD_SHARED_LIBS}") +message(STATUS "Unit testing: ${UNIT_TESTING}") +message(STATUS "Client code testing: ${CLIENT_TESTING}") +message(STATUS "Blowfish cipher support: ${HAVE_BLOWFISH}") +message(STATUS "PKCS #11 URI support: ${WITH_PKCS11_URI}") +message(STATUS "With PKCS #11 provider support: ${WITH_PKCS11_PROVIDER}") +message(STATUS "With FIDO2/U2F support: ${WITH_FIDO2}") +if (WITH_FIDO2) + message(STATUS "With libfido2 (internal usb-hid support): ${HAVE_LIBFIDO2}") +endif (WITH_FIDO2) +set(_SERVER_TESTING OFF) +if (WITH_SERVER) + set(_SERVER_TESTING ${SERVER_TESTING}) +endif() +message(STATUS "Server code testing: ${_SERVER_TESTING}") +if (WITH_INTERNAL_DOC) + message(STATUS "Internal documentation generation") +else (WITH_INTERNAL_DOC) + message(STATUS "Public API documentation generation") +endif (WITH_INTERNAL_DOC) +message(STATUS "Benchmarks: ${WITH_BENCHMARKS}") +message(STATUS "Symbol versioning: ${WITH_SYMBOL_VERSIONING}") +message(STATUS "Allow ABI break: ${WITH_ABI_BREAK}") +message(STATUS "Release is final: ${WITH_FINAL}") +if (WITH_HERMETIC_USR) + message(STATUS "User global client config: ${USR_GLOBAL_CLIENT_CONFIG}") +endif () +message(STATUS "Global client config: ${GLOBAL_CLIENT_CONFIG}") +if (WITH_SERVER) + if (WITH_HERMETIC_USR) + message(STATUS "User global bind config: ${USR_GLOBAL_BIND_CONFIG}") + endif () + message(STATUS "Global bind config: ${GLOBAL_BIND_CONFIG}") +endif() +message(STATUS "********************************************") + diff --git a/src/libs/libssh-0.12.2/CONTRIBUTING.md b/src/libs/libssh-0.12.2/CONTRIBUTING.md new file mode 100644 index 000000000000..6c606b9c6d1c --- /dev/null +++ b/src/libs/libssh-0.12.2/CONTRIBUTING.md @@ -0,0 +1,600 @@ +# How to contribute a patch to libssh + +Please checkout the libssh source code using git. + +For contributions we prefer Merge Requests on Gitlab: + +https://gitlab.com/libssh/libssh-mirror/ + +This way you get continuous integration which runs the complete libssh +testsuite for you. + +For larger code changes, breaking the changes up into a set of simple +patches, each of which does a single thing, are much easier to review. +Patch sets like that will most likely have an easier time being merged +into the libssh code than large single patches that make lots of +changes in one large diff. + +Also bugfixes and new features should be covered by tests. We use the cmocka +and cwrap framework for our testing and you can simply run it locally by +calling `make test`. + +## Ownership of the contributed code + +libssh is a project with distributed copyright ownership, which means +we prefer the copyright on parts of libssh to be held by individuals +rather than corporations if possible. There are historical legal +reasons for this, but one of the best ways to explain it is that it's +much easier to work with individuals who have ownership than corporate +legal departments if we ever need to make reasonable compromises with +people using and working with libssh. + +We track the ownership of every part of libssh via https://git.libssh.org, +our source code control system, so we know the provenance of every piece +of code that is committed to libssh. + +So if possible, if you're doing libssh changes on behalf of a company +who normally owns all the work you do please get them to assign +personal copyright ownership of your changes to you as an individual, +that makes things very easy for us to work with and avoids bringing +corporate legal departments into the picture. + +If you can't do this we can still accept patches from you owned by +your employer under a standard employment contract with corporate +copyright ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux +Kernel community, so it should be very easy to get a sign off from +your corporate legal department. The only changes we've made are to +accommodate the license we use, which is LGPLv2 (or later) whereas the +Linux kernel uses GPLv2. + +The process is called signing. + +## How to sign your work + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + + +``` +libssh Developer's Certificate of Origin. Version 1.0 + + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project under the terms of the + GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of + the License, or (at the option of the project) any later version. + + https://www.gnu.org/licenses/lgpl-2.1.html +``` + +We will maintain a copy of that email as a record that you have the +rights to contribute code to libssh under the required licenses whilst +working for the company where the email came from. + +Then when sending in a patch via the normal mechanisms described +above, add a line that states: + + Signed-off-by: Random J Developer + +using your real name and the email address you sent the original email +you used to send the libssh Developer's Certificate of Origin to us +(sorry, no pseudonyms or anonymous contributions.) + +That's it! Such code can then quite happily contain changes that have +copyright messages such as: + + (c) Example Corporation. + +and can be merged into the libssh codebase in the same way as patches +from any other individual. You don't need to send in a copy of the +libssh Developer's Certificate of Origin for each patch, or inside each +patch. Just the sign-off message is all that is required once we've +received the initial email. + +## Continuous Integration + +Contributing patches through Merge Request workflow on Gitlab allows us to run +various checks on various configuration as part of Gitlab CI. Unfortunately, +some pipelines are slower (as they involve building dependencies) so the default +timeout of 1 hour needs to be extended at least to 2 hours. This can be done in +project settings of your libssh fork: + +https://docs.gitlab.com/ee/ci/pipelines/settings.html#set-a-limit-for-how-long-jobs-can-run + +Otherwise you will encounter errors like these, usually on visualstudio builds: + +``` +ERROR: Job failed: execution took longer than 1h0m0s seconds +The script exceeded the maximum execution time set for the job +``` + +Note, that the built dependencies are cached so after successful build in your +namespace, the rebuilds should be much faster. + +## Running GitLab CI locally (optional helper) + +For contributors working on CI, build system changes, or adding new CI jobs, it can be useful to run GitLab CI pipelines locally before pushing. + +libssh provides a small helper script based on `gitlab-ci-local` that can: + +- List all jobs defined in `.gitlab-ci.yml` +- Run a specific job or the full pipeline locally +- Automatically pick up new jobs when they are added to the CI configuration +- Optionally clean up CI Docker images after execution + +### Requirements + +- Docker (daemon running) +- git +- gitlab-ci-local + https://github.com/firecow/gitlab-ci-local + +### Usage + +```bash +./.gitlab-ci/local-ci.sh --list +./.gitlab-ci/local-ci.sh --run fedora/libressl/x86_64 +./.gitlab-ci/local-ci.sh --all +./.gitlab-ci/local-ci.sh --run fedora/libressl/x86_64 --clean +``` + +# Coding conventions in the libssh tree + +## Quick Start + +Coding style guidelines are about reducing the number of unnecessary +reformatting patches and making things easier for developers to work together. + +You don't have to like them or even agree with them, but once put in place we +all have to abide by them (or vote to change them). However, coding style +should never outweigh coding itself and so the guidelines described here are +hopefully easy enough to follow as they are very common and supported by tools +and editors. + +The basic style for C code, is the Linux kernel coding style (See +Documentation/CodingStyle in the kernel source tree). This closely matches what +libssh developers use already anyways, with a few exceptions as mentioned +below. + +But to save you the trouble of reading the Linux kernel style guide, here +are the highlights. + +* Maximum Line Width is 80 Characters + The reason is not about people with low-res screens but rather sticking + to 80 columns prevents you from easily nesting more than one level of + if statements or other code blocks. + +* Use 4 Spaces to Indent + +* No Trailing Whitespace + Clean up your files before committing. + +* Follow the K&R guidelines. We won't go through all of them here. Do you + have a copy of "The C Programming Language" anyways right? + + +## Editor Hints + +### Emacs + +Add the follow to your $HOME/.emacs file: + + (add-hook 'c-mode-hook + (lambda () + (c-set-style "linux") + (c-toggle-auto-state))) + + +## Neovim/VIM + +For the basic vi editor included with all variants of \*nix, add the +following to ~/.config/nvim/init.rc or ~/.vimrc: + + set ts=4 sw=4 et cindent + +You can use the Vim gitmodline plugin to store this in the git config: + +https://git.cryptomilk.org/projects/vim-gitmodeline.git/ + +For Vim, the following settings in $HOME/.vimrc will also deal with +displaying trailing whitespace: + + if has("syntax") && (&t_Co > 2 || has("gui_running")) + syntax on + function! ActivateInvisibleCharIndicator() + syntax match TrailingSpace "[ \t]\+$" display containedin=ALL + highlight TrailingSpace ctermbg=Red + endf + autocmd BufNewFile,BufRead * call ActivateInvisibleCharIndicator() + endif + " Show tabs, trailing whitespace, and continued lines visually + set list listchars=tab:»·,trail:·,extends:… + + " highlight overly long lines same as TODOs. + set textwidth=80 + autocmd BufNewFile,BufRead *.c,*.h exec 'match Todo /\%>' . &textwidth . 'v.\+/' + + +## FAQ & Statement Reference + +### Comments + +Comments should always use the standard C syntax. C++ style comments are not +currently allowed. + +The lines before a comment should be empty. If the comment directly belongs to +the following code, there should be no empty line after the comment, except if +the comment contains a summary of multiple following code blocks. + +This is good: + + ... + int i; + + /* + * This is a multi line comment, + * which explains the logical steps we have to do: + * + * 1. We need to set i=5, because... + * 2. We need to call complex_fn1 + */ + + /* This is a one line comment about i = 5. */ + i = 5; + + /* + * This is a multi line comment, + * explaining the call to complex_fn1() + */ + ret = complex_fn1(); + if (ret != 0) { + ... + + /** + * @brief This is a doxygen comment. + * + * This is a more detailed explanation of + * this simple function. + * + * @param[in] param1 The parameter value of the function. + * + * @param[out] result1 The result value of the function. + * + * @return 0 on success and -1 on error. + */ + int example(int param1, int *result1); + +This is bad: + + ... + int i; + /* + * This is a multi line comment, + * which explains the logical steps we have to do: + * + * 1. We need to set i=5, because... + * 2. We need to call complex_fn1 + */ + /* This is a one line comment about i = 5. */ + i = 5; + /* + * This is a multi line comment, + * explaining the call to complex_fn1() + */ + ret = complex_fn1(); + if (ret != 0) { + ... + + /*This is a one line comment.*/ + + /* This is a multi line comment, + with some more words...*/ + + /* + * This is a multi line comment, + * with some more words...*/ + +### Indentation & Whitespace & 80 columns + +To avoid confusion, indentations have to be 4 spaces. Do not use tabs!. When +wrapping parameters for function calls, align the parameter list with the first +parameter on the previous line. For example, + + var1 = foo(arg1, + arg2, + arg3); + +The previous example is intended to illustrate alignment of function +parameters across lines and not as encourage for gratuitous line +splitting. Never split a line before columns 70 - 79 unless you +have a really good reason. Be smart about formatting. + + +### If, switch, & Code blocks + +Always follow an 'if' keyword with a space but don't include additional +spaces following or preceding the parentheses in the conditional. +This is good: + + if (x == 1) + +This is bad: + + if ( x == 1 ) + +or + + if (x==1) + +Yes we have a lot of code that uses the second and third form and we are trying +to clean it up without being overly intrusive. + +Note that this is a rule about parentheses following keywords and not +functions. Don't insert a space between the name and left parentheses when +invoking functions. + +Braces for code blocks used by for, if, switch, while, do..while, etc. should +begin on the same line as the statement keyword and end on a line of their own. +You should always include braces, even if the block only contains one +statement. **NOTE**: Functions are different and the beginning left brace should +be located in the first column on the next line. + +If the beginning statement has to be broken across lines due to length, the +beginning brace should be on a line of its own. + +The exception to the ending rule is when the closing brace is followed by +another language keyword such as else or the closing while in a do..while loop. + +Good examples: + + if (x == 1) { + printf("good\n"); + } + + for (x = 1; x < 10; x++) { + print("%d\n", x); + } + + for (really_really_really_really_long_var_name = 0; + really_really_really_really_long_var_name < 10; + really_really_really_really_long_var_name++) + { + print("%d\n", really_really_really_really_long_var_name); + } + + do { + printf("also good\n"); + } while (1); + +Bad examples: + + while (1) + { + print("I'm in a loop!\n"); } + + for (x=1; + x<10; + x++) + { + print("no good\n"); + } + + if (i < 10) + print("I should be in braces.\n"); + + +### Goto + +While many people have been academically taught that "goto"s are fundamentally +evil, they can greatly enhance readability and reduce memory leaks when used as +the single exit point from a function. But in no libssh world what so ever is a +goto outside of a function or block of code a good idea. + +Good Examples: + + int function foo(int y) + { + int *z = NULL; + int rc = 0; + + if (y < 10) { + z = malloc(sizeof(int)*y); + if (z == NULL) { + rc = 1; + goto done; + } + } + + print("Allocated %d elements.\n", y); + + done: + if (z != NULL) { + free(z); + } + + return rc; + } + +### Initialize pointers + +All pointer variables **MUST** be initialized to `NULL`. History has +demonstrated that uninitialized pointer variables have lead to various +bugs and security issues. + +Pointers **MUST** be initialized even if the assignment directly follows +the declaration, like pointer2 in the example below, because the +instructions sequence may change over time. + +Good Example: + + char *pointer1 = NULL; + char *pointer2 = NULL; + + pointer2 = some_func2(); + + ... + + pointer1 = some_func1(); + +### Typedefs + +libssh tries to avoid `typedef struct { .. } x_t;` so we do always try to use +`struct x { .. };`. We know there are still such typedefs in the code, but for +new code, please don't do that anymore. + +### Make use of helper variables + +Please try to avoid passing function calls as function parameters in new code. +This makes the code much easier to read and it's also easier to use the "step" +command within gdb. + +Good Example: + + char *name; + + name = get_some_name(); + if (name == NULL) { + ... + } + + rc = some_function_my_name(name); + ... + + +Bad Example: + + rc = some_function_my_name(get_some_name()); + ... + +Please try to avoid passing function return values to if- or while-conditions. +The reason for this is better handling of code under a debugger. + +Good example: + + x = malloc(sizeof(short) * 10); + if (x == NULL) { + fprintf(stderr, "Unable to alloc memory!\n"); + } + +Bad example: + + if ((x = malloc(sizeof(short)*10)) == NULL ) { + fprintf(stderr, "Unable to alloc memory!\n"); + } + +There are exceptions to this rule. One example is walking a data structure in +an iterator style: + + while ((opt = poptGetNextOpt(pc)) != -1) { + ... do something with opt ... + } + +But in general, please try to avoid this pattern. + + +### Control-Flow changing macros + +Macros like `STATUS_NOT_OK_RETURN` that change control flow (return/goto/etc) +from within the macro are considered bad, because they look like function calls +that never change control flow. Please do not introduce them. + +### Switch/case indentation + +The `case` should not be indented to avoid wasting too much horizontal space. +When the case block contains local variables that need to be wrapped in braces, +they should not be indented again either. + +Good example: + + switch (x) { + case 0: + do_stuff(); + break; + case 1: { + int y; + do_stuff(); + break; + } + default: + do_other_stuff(); + break; + } + +Bad example: + + switch (x) { + case 0: + do_stuff(); + break; + case 1: + { + int y; + do_stuff(); + break; + } + default: + do_other_stuff(); + break; + } + +## ABI Versioning and Symbol Management + +To maintain [ABI](https://en.wikipedia.org/wiki/Application_binary_interface) stability +and ensure backward compatibility, libssh uses **symbol versioning** to track and manage +exported functions and variables. This allows libssh to introduce new symbols or modify +existing functions in an ABI-compatible way. + +When introducing a new symbol: + +1. Use the `LIBSSH_API` macro to mark the symbol as part of the public API. +2. If you have [abimap](https://github.com/ansasaki/abimap) installed, the new symbols are +automatically generated in the `src/libssh_dev.map` file in the **build** directory and used automatically for building the updated library. But, depending on the version of `abimap` under use, you may face linker errors like: `unable to find version dependency LIBSSH_4_9_0`. In this case, you need to manually replace the existing `src/libssh.map` file with the generated `libssh_dev.map` file to update the symbol versioning. +3. If you do not have abimap installed, the modified/added symbols must manually be added to the +`src/libssh.map` file. The symbols must be added in the following format (assuming that 4_10_0 is the latest released version): + +``` +LIBSSH_AFTER_4_10_0 +{ + global: + new_function; + new_variable; +} LIBSSH_4_10_0; +``` +4. After following either of the above steps, the library can be successfully built and +tested without any linker errors. + +5. When submitting the patch, make sure that any new symbols have been added to `libssh.map` as described in step 3, so that the new additions may not be excluded from the next release due to human error. + +Also, to maintain ABI compatibility, existing symbols must not be removed. Instead, they can +be marked as deprecated using the `LIBSSH_DEPRECATED` macro. This allows the symbol to be +removed in a future release without breaking the ABI. + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/src/libs/libssh-0.12.2/COPYING b/src/libs/libssh-0.12.2/COPYING new file mode 100644 index 000000000000..ff91d172e44f --- /dev/null +++ b/src/libs/libssh-0.12.2/COPYING @@ -0,0 +1,469 @@ + GNU LESSER GENERAL PUBLIC LICENSE + Version 2.1, February 1999 + + Copyright (C) 1991, 1999 Free Software Foundation, Inc. + 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + +[This is the first released version of the Lesser GPL. It also counts + as the successor of the GNU Library Public License, version 2, hence + the version number 2.1.] + + Preamble + + The licenses for most software are designed to take away your +freedom to share and change it. By contrast, the GNU General Public +Licenses are intended to guarantee your freedom to share and change +free software--to make sure the software is free for all its users. + + This license, the Lesser General Public License, applies to some +specially designated software packages--typically libraries--of the +Free Software Foundation and other authors who decide to use it. You +can use it too, but we suggest you first think carefully about whether +this license or the ordinary General Public License is the better +strategy to use in any particular case, based on the explanations below. + + When we speak of free software, we are referring to freedom of use, +not price. Our General Public Licenses are designed to make sure that +you have the freedom to distribute copies of free software (and charge +for this service if you wish); that you receive source code or can get +it if you want it; that you can change the software and use pieces of +it in new free programs; and that you are informed that you can do +these things. + + To protect your rights, we need to make restrictions that forbid +distributors to deny you these rights or to ask you to surrender these +rights. These restrictions translate to certain responsibilities for +you if you distribute copies of the library or if you modify it. + + For example, if you distribute copies of the library, whether gratis +or for a fee, you must give the recipients all the rights that we gave +you. You must make sure that they, too, receive or can get the source +code. If you link other code with the library, you must provide +complete object files to the recipients, so that they can relink them +with the library after making changes to the library and recompiling +it. And you must show them these terms so they know their rights. + + We protect your rights with a two-step method: (1) we copyright the +library, and (2) we offer you this license, which gives you legal +permission to copy, distribute and/or modify the library. + + To protect each distributor, we want to make it very clear that +there is no warranty for the free library. Also, if the library is +modified by someone else and passed on, the recipients should know +that what they have is not the original version, so that the original +author's reputation will not be affected by problems that might be +introduced by others. + + Finally, software patents pose a constant threat to the existence of +any free program. We wish to make sure that a company cannot +effectively restrict the users of a free program by obtaining a +restrictive license from a patent holder. Therefore, we insist that +any patent license obtained for a version of the library must be +consistent with the full freedom of use specified in this license. + + Most GNU software, including some libraries, is covered by the +ordinary GNU General Public License. This license, the GNU Lesser +General Public License, applies to certain designated libraries, and +is quite different from the ordinary General Public License. We use +this license for certain libraries in order to permit linking those +libraries into non-free programs. + + When a program is linked with a library, whether statically or using +a shared library, the combination of the two is legally speaking a +combined work, a derivative of the original library. The ordinary +General Public License therefore permits such linking only if the +entire combination fits its criteria of freedom. The Lesser General +Public License permits more lax criteria for linking other code with +the library. + + We call this license the "Lesser" General Public License because it +does Less to protect the user's freedom than the ordinary General +Public License. It also provides other free software developers Less +of an advantage over competing non-free programs. These disadvantages +are the reason we use the ordinary General Public License for many +libraries. However, the Lesser license provides advantages in certain +special circumstances. + + For example, on rare occasions, there may be a special need to +encourage the widest possible use of a certain library, so that it becomes +a de-facto standard. To achieve this, non-free programs must be +allowed to use the library. A more frequent case is that a free +library does the same job as widely used non-free libraries. In this +case, there is little to gain by limiting the free library to free +software only, so we use the Lesser General Public License. + + In other cases, permission to use a particular library in non-free +programs enables a greater number of people to use a large body of +free software. For example, permission to use the GNU C Library in +non-free programs enables many more people to use the whole GNU +operating system, as well as its variant, the GNU/Linux operating +system. + + Although the Lesser General Public License is Less protective of the +users' freedom, it does ensure that the user of a program that is +linked with the Library has the freedom and the wherewithal to run +that program using a modified version of the Library. + + The precise terms and conditions for copying, distribution and +modification follow. Pay close attention to the difference between a +"work based on the library" and a "work that uses the library". The +former contains code derived from the library, whereas the latter must +be combined with the library in order to run. + + GNU LESSER GENERAL PUBLIC LICENSE + TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION + + 0. This License Agreement applies to any software library or other +program which contains a notice placed by the copyright holder or +other authorized party saying it may be distributed under the terms of +this Lesser General Public License (also called "this License"). +Each licensee is addressed as "you". + + A "library" means a collection of software functions and/or data +prepared so as to be conveniently linked with application programs +(which use some of those functions and data) to form executables. + + The "Library", below, refers to any such software library or work +which has been distributed under these terms. A "work based on the +Library" means either the Library or any derivative work under +copyright law: that is to say, a work containing the Library or a +portion of it, either verbatim or with modifications and/or translated +straightforwardly into another language. (Hereinafter, translation is +included without limitation in the term "modification".) + + "Source code" for a work means the preferred form of the work for +making modifications to it. For a library, complete source code means +all the source code for all modules it contains, plus any associated +interface definition files, plus the scripts used to control compilation +and installation of the library. + + Activities other than copying, distribution and modification are not +covered by this License; they are outside its scope. The act of +running a program using the Library is not restricted, and output from +such a program is covered only if its contents constitute a work based +on the Library (independent of the use of the Library in a tool for +writing it). Whether that is true depends on what the Library does +and what the program that uses the Library does. + + 1. You may copy and distribute verbatim copies of the Library's +complete source code as you receive it, in any medium, provided that +you conspicuously and appropriately publish on each copy an +appropriate copyright notice and disclaimer of warranty; keep intact +all the notices that refer to this License and to the absence of any +warranty; and distribute a copy of this License along with the +Library. + + You may charge a fee for the physical act of transferring a copy, +and you may at your option offer warranty protection in exchange for a +fee. + + 2. You may modify your copy or copies of the Library or any portion +of it, thus forming a work based on the Library, and copy and +distribute such modifications or work under the terms of Section 1 +above, provided that you also meet all of these conditions: + + a) The modified work must itself be a software library. + + b) You must cause the files modified to carry prominent notices + stating that you changed the files and the date of any change. + + c) You must cause the whole of the work to be licensed at no + charge to all third parties under the terms of this License. + + d) If a facility in the modified Library refers to a function or a + table of data to be supplied by an application program that uses + the facility, other than as an argument passed when the facility + is invoked, then you must make a good faith effort to ensure that, + in the event an application does not supply such function or + table, the facility still operates, and performs whatever part of + its purpose remains meaningful. + + (For example, a function in a library to compute square roots has + a purpose that is entirely well-defined independent of the + application. Therefore, Subsection 2d requires that any + application-supplied function or table used by this function must + be optional: if the application does not supply it, the square + root function must still compute square roots.) + +These requirements apply to the modified work as a whole. If +identifiable sections of that work are not derived from the Library, +and can be reasonably considered independent and separate works in +themselves, then this License, and its terms, do not apply to those +sections when you distribute them as separate works. But when you +distribute the same sections as part of a whole which is a work based +on the Library, the distribution of the whole must be on the terms of +this License, whose permissions for other licensees extend to the +entire whole, and thus to each and every part regardless of who wrote +it. + +Thus, it is not the intent of this section to claim rights or contest +your rights to work written entirely by you; rather, the intent is to +exercise the right to control the distribution of derivative or +collective works based on the Library. + +In addition, mere aggregation of another work not based on the Library +with the Library (or with a work based on the Library) on a volume of +a storage or distribution medium does not bring the other work under +the scope of this License. + + 3. You may opt to apply the terms of the ordinary GNU General Public +License instead of this License to a given copy of the Library. To do +this, you must alter all the notices that refer to this License, so +that they refer to the ordinary GNU General Public License, version 2, +instead of to this License. (If a newer version than version 2 of the +ordinary GNU General Public License has appeared, then you can specify +that version instead if you wish.) Do not make any other change in +these notices. + + Once this change is made in a given copy, it is irreversible for +that copy, so the ordinary GNU General Public License applies to all +subsequent copies and derivative works made from that copy. + + This option is useful when you wish to copy part of the code of +the Library into a program that is not a library. + + 4. You may copy and distribute the Library (or a portion or +derivative of it, under Section 2) in object code or executable form +under the terms of Sections 1 and 2 above provided that you accompany +it with the complete corresponding machine-readable source code, which +must be distributed under the terms of Sections 1 and 2 above on a +medium customarily used for software interchange. + + If distribution of object code is made by offering access to copy +from a designated place, then offering equivalent access to copy the +source code from the same place satisfies the requirement to +distribute the source code, even though third parties are not +compelled to copy the source along with the object code. + + 5. A program that contains no derivative of any portion of the +Library, but is designed to work with the Library by being compiled or +linked with it, is called a "work that uses the Library". Such a +work, in isolation, is not a derivative work of the Library, and +therefore falls outside the scope of this License. + + However, linking a "work that uses the Library" with the Library +creates an executable that is a derivative of the Library (because it +contains portions of the Library), rather than a "work that uses the +library". The executable is therefore covered by this License. +Section 6 states terms for distribution of such executables. + + When a "work that uses the Library" uses material from a header file +that is part of the Library, the object code for the work may be a +derivative work of the Library even though the source code is not. +Whether this is true is especially significant if the work can be +linked without the Library, or if the work is itself a library. The +threshold for this to be true is not precisely defined by law. + + If such an object file uses only numerical parameters, data +structure layouts and accessors, and small macros and small inline +functions (ten lines or less in length), then the use of the object +file is unrestricted, regardless of whether it is legally a derivative +work. (Executables containing this object code plus portions of the +Library will still fall under Section 6.) + + Otherwise, if the work is a derivative of the Library, you may +distribute the object code for the work under the terms of Section 6. +Any executables containing that work also fall under Section 6, +whether or not they are linked directly with the Library itself. + + 6. As an exception to the Sections above, you may also combine or +link a "work that uses the Library" with the Library to produce a +work containing portions of the Library, and distribute that work +under terms of your choice, provided that the terms permit +modification of the work for the customer's own use and reverse +engineering for debugging such modifications. + + You must give prominent notice with each copy of the work that the +Library is used in it and that the Library and its use are covered by +this License. You must supply a copy of this License. If the work +during execution displays copyright notices, you must include the +copyright notice for the Library among them, as well as a reference +directing the user to the copy of this License. Also, you must do one +of these things: + + a) Accompany the work with the complete corresponding + machine-readable source code for the Library including whatever + changes were used in the work (which must be distributed under + Sections 1 and 2 above); and, if the work is an executable linked + with the Library, with the complete machine-readable "work that + uses the Library", as object code and/or source code, so that the + user can modify the Library and then relink to produce a modified + executable containing the modified Library. (It is understood + that the user who changes the contents of definitions files in the + Library will not necessarily be able to recompile the application + to use the modified definitions.) + + b) Use a suitable shared library mechanism for linking with the + Library. A suitable mechanism is one that (1) uses at run time a + copy of the library already present on the user's computer system, + rather than copying library functions into the executable, and (2) + will operate properly with a modified version of the library, if + the user installs one, as long as the modified version is + interface-compatible with the version that the work was made with. + + c) Accompany the work with a written offer, valid for at + least three years, to give the same user the materials + specified in Subsection 6a, above, for a charge no more + than the cost of performing this distribution. + + d) If distribution of the work is made by offering access to copy + from a designated place, offer equivalent access to copy the above + specified materials from the same place. + + e) Verify that the user has already received a copy of these + materials or that you have already sent this user a copy. + + For an executable, the required form of the "work that uses the +Library" must include any data and utility programs needed for +reproducing the executable from it. However, as a special exception, +the materials to be distributed need not include anything that is +normally distributed (in either source or binary form) with the major +components (compiler, kernel, and so on) of the operating system on +which the executable runs, unless that component itself accompanies +the executable. + + It may happen that this requirement contradicts the license +restrictions of other proprietary libraries that do not normally +accompany the operating system. Such a contradiction means you cannot +use both them and the Library together in an executable that you +distribute. + + 7. You may place library facilities that are a work based on the +Library side-by-side in a single library together with other library +facilities not covered by this License, and distribute such a combined +library, provided that the separate distribution of the work based on +the Library and of the other library facilities is otherwise +permitted, and provided that you do these two things: + + a) Accompany the combined library with a copy of the same work + based on the Library, uncombined with any other library + facilities. This must be distributed under the terms of the + Sections above. + + b) Give prominent notice with the combined library of the fact + that part of it is a work based on the Library, and explaining + where to find the accompanying uncombined form of the same work. + + 8. You may not copy, modify, sublicense, link with, or distribute +the Library except as expressly provided under this License. Any +attempt otherwise to copy, modify, sublicense, link with, or +distribute the Library is void, and will automatically terminate your +rights under this License. However, parties who have received copies, +or rights, from you under this License will not have their licenses +terminated so long as such parties remain in full compliance. + + 9. You are not required to accept this License, since you have not +signed it. However, nothing else grants you permission to modify or +distribute the Library or its derivative works. These actions are +prohibited by law if you do not accept this License. Therefore, by +modifying or distributing the Library (or any work based on the +Library), you indicate your acceptance of this License to do so, and +all its terms and conditions for copying, distributing or modifying +the Library or works based on it. + + 10. Each time you redistribute the Library (or any work based on the +Library), the recipient automatically receives a license from the +original licensor to copy, distribute, link with or modify the Library +subject to these terms and conditions. You may not impose any further +restrictions on the recipients' exercise of the rights granted herein. +You are not responsible for enforcing compliance by third parties with +this License. + + 11. If, as a consequence of a court judgment or allegation of patent +infringement or for any other reason (not limited to patent issues), +conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot +distribute so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you +may not distribute the Library at all. For example, if a patent +license would not permit royalty-free redistribution of the Library by +all those who receive copies directly or indirectly through you, then +the only way you could satisfy both it and this License would be to +refrain entirely from distribution of the Library. + +If any portion of this section is held invalid or unenforceable under any +particular circumstance, the balance of the section is intended to apply, +and the section as a whole is intended to apply in other circumstances. + +It is not the purpose of this section to induce you to infringe any +patents or other property right claims or to contest validity of any +such claims; this section has the sole purpose of protecting the +integrity of the free software distribution system which is +implemented by public license practices. Many people have made +generous contributions to the wide range of software distributed +through that system in reliance on consistent application of that +system; it is up to the author/donor to decide if he or she is willing +to distribute software through any other system and a licensee cannot +impose that choice. + +This section is intended to make thoroughly clear what is believed to +be a consequence of the rest of this License. + + 12. If the distribution and/or use of the Library is restricted in +certain countries either by patents or by copyrighted interfaces, the +original copyright holder who places the Library under this License may add +an explicit geographical distribution limitation excluding those countries, +so that distribution is permitted only in or among countries not thus +excluded. In such case, this License incorporates the limitation as if +written in the body of this License. + + 13. The Free Software Foundation may publish revised and/or new +versions of the Lesser General Public License from time to time. +Such new versions will be similar in spirit to the present version, +but may differ in detail to address new problems or concerns. + +Each version is given a distinguishing version number. If the Library +specifies a version number of this License which applies to it and +"any later version", you have the option of following the terms and +conditions either of that version or of any later version published by +the Free Software Foundation. If the Library does not specify a +license version number, you may choose any version ever published by +the Free Software Foundation. + + 14. If you wish to incorporate parts of the Library into other free +programs whose distribution conditions are incompatible with these, +write to the author to ask for permission. For software which is +copyrighted by the Free Software Foundation, write to the Free +Software Foundation; we sometimes make exceptions for this. Our +decision will be guided by the two goals of preserving the free status +of all derivatives of our free software and of promoting the sharing +and reuse of software generally. + + NO WARRANTY + + 15. BECAUSE THE LIBRARY IS LICENSED FREE OF CHARGE, THERE IS NO +WARRANTY FOR THE LIBRARY, TO THE EXTENT PERMITTED BY APPLICABLE LAW. +EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR +OTHER PARTIES PROVIDE THE LIBRARY "AS IS" WITHOUT WARRANTY OF ANY +KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE +LIBRARY IS WITH YOU. SHOULD THE LIBRARY PROVE DEFECTIVE, YOU ASSUME +THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN +WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY +AND/OR REDISTRIBUTE THE LIBRARY AS PERMITTED ABOVE, BE LIABLE TO YOU +FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE +LIBRARY (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING +RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A +FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF +SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH +DAMAGES. + + Linking with OpenSSL + + 17. In addition, as a special exception, we give permission to link the code +of its release of libssh with the OpenSSL project's "OpenSSL" library (or with +modified versions of it that use the same license as the "OpenSSL" library), +and distribute the linked executables. You must obey the GNU Lesser General +Public License in all respects for all of the code used other than "OpenSSL". +If you modify this file, you may extend this exception to your version of the +file, but you are not obligated to do so. If you do not wish to do so, delete +this exception statement from your version. + + END OF TERMS AND CONDITIONS diff --git a/src/libs/libssh-0.12.2/CPackConfig.cmake b/src/libs/libssh-0.12.2/CPackConfig.cmake new file mode 100644 index 000000000000..81ed6d0608eb --- /dev/null +++ b/src/libs/libssh-0.12.2/CPackConfig.cmake @@ -0,0 +1,44 @@ +### GENERAL SETTINGS +set(CPACK_PACKAGE_NAME ${PROJECT_NAME}) +set(CPACK_PACKAGE_DESCRIPTION_SUMMARY "The SSH Library") +set(CPACK_PACKAGE_DESCRIPTION_FILE "${CMAKE_CURRENT_SOURCE_DIR}/README") +set(CPACK_PACKAGE_VENDOR "The SSH Library Development Team") +set(CPACK_PACKAGE_INSTALL_DIRECTORY ${CPACK_PACKAGE_NAME}) +set(CPACK_RESOURCE_FILE_LICENSE "${CMAKE_CURRENT_SOURCE_DIR}/COPYING") + +set(CPACK_PACKAGE_VERSION ${PROJECT_VERSION}) + +# SOURCE GENERATOR +set(CPACK_SOURCE_GENERATOR "TXZ") +set(CPACK_SOURCE_IGNORE_FILES "~$;[.]swp$;/[.]bare/;/[.]git/;/[.]git;/[.]clangd/;/[.]cache/;.gitignore;/build*;/obj*;tags;cscope.*;compile_commands.json;.*\.patch") +set(CPACK_SOURCE_PACKAGE_FILE_NAME "${CPACK_PACKAGE_NAME}-${CPACK_PACKAGE_VERSION}") + +### NSIS INSTALLER +if (WIN32) + set(CPACK_GENERATOR "ZIP") + + ### nsis generator + find_package(NSIS) + if (NSIS_MAKE) + set(CPACK_GENERATOR "${CPACK_GENERATOR};NSIS") + set(CPACK_NSIS_DISPLAY_NAME "The SSH Library") + set(CPACK_NSIS_COMPRESSOR "/SOLID zlib") + set(CPACK_NSIS_MENU_LINKS "https://www.libssh.org/" "libssh homepage") + endif (NSIS_MAKE) +endif (WIN32) + +set(CPACK_PACKAGE_INSTALL_DIRECTORY "libssh") + +set(CPACK_PACKAGE_FILE_NAME ${APPLICATION_NAME}-${CPACK_PACKAGE_VERSION}) + +set(CPACK_COMPONENT_LIBRARIES_DISPLAY_NAME "Libraries") +set(CPACK_COMPONENT_HEADERS_DISPLAY_NAME "C/C++ Headers") +set(CPACK_COMPONENT_LIBRARIES_DESCRIPTION + "Libraries used to build programs which use libssh") +set(CPACK_COMPONENT_HEADERS_DESCRIPTION + "C/C++ header files for use with libssh") +set(CPACK_COMPONENT_HEADERS_DEPENDS libraries) +set(CPACK_COMPONENT_LIBRARIES_GROUP "Development") +set(CPACK_COMPONENT_HEADERS_GROUP "Development") + +include(CPack) diff --git a/src/libs/libssh-0.12.2/CTestConfig.cmake b/src/libs/libssh-0.12.2/CTestConfig.cmake new file mode 100644 index 000000000000..7a1867287272 --- /dev/null +++ b/src/libs/libssh-0.12.2/CTestConfig.cmake @@ -0,0 +1,9 @@ +set(UPDATE_TYPE "true") + +set(CTEST_PROJECT_NAME "libssh") +set(CTEST_NIGHTLY_START_TIME "01:00:00 UTC") + +set(CTEST_DROP_METHOD "https") +set(CTEST_DROP_SITE "test.libssh.org") +set(CTEST_DROP_LOCATION "/submit.php?project=libssh") +set(CTEST_DROP_SITE_CDASH TRUE) diff --git a/src/libs/libssh-0.12.2/CompilerChecks.cmake b/src/libs/libssh-0.12.2/CompilerChecks.cmake new file mode 100644 index 000000000000..e9890e2f954c --- /dev/null +++ b/src/libs/libssh-0.12.2/CompilerChecks.cmake @@ -0,0 +1,133 @@ +include(AddCCompilerFlag) +include(CheckCCompilerFlagSSP) + +if (UNIX) + # + # Check for -Werror turned on if possible + # + # This will prevent that compiler flags are detected incorrectly. + # + check_c_compiler_flag("-Werror" REQUIRED_FLAGS_WERROR) + if (REQUIRED_FLAGS_WERROR) + set(CMAKE_REQUIRED_FLAGS "-Werror") + + if (PICKY_DEVELOPER) + list(APPEND SUPPORTED_COMPILER_FLAGS "-Werror") + endif() + endif() + + add_c_compiler_flag("-Wpedantic" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wall" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wshadow" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wmissing-prototypes" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wcast-align" SUPPORTED_COMPILER_FLAGS) + #add_c_compiler_flag("-Wcast-qual" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=address" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wstrict-prototypes" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=strict-prototypes" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wwrite-strings" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=write-strings" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror-implicit-function-declaration" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wpointer-arith" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=pointer-arith" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wdeclaration-after-statement" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=declaration-after-statement" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wreturn-type" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=return-type" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wuninitialized" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=uninitialized" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wimplicit-fallthrough" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=strict-overflow" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wstrict-overflow=2" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wno-format-zero-length" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wmissing-field-initializers" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wsign-compare" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wold-style-definition" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=old-style-definition" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wimplicit-int" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=implicit-int" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wint-conversion" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=int-conversion" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=unused-variable" SUPPORTED_COMPILER_FLAGS) + + check_c_compiler_flag("-Wformat" REQUIRED_FLAGS_WFORMAT) + if (REQUIRED_FLAGS_WFORMAT) + list(APPEND SUPPORTED_COMPILER_FLAGS "-Wformat") + set(CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS} -Wformat") + endif() + add_c_compiler_flag("-Wformat-security" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Werror=format-security" SUPPORTED_COMPILER_FLAGS) + + # Allow zero for a variadic macro argument + string(TOLOWER "${CMAKE_C_COMPILER_ID}" _C_COMPILER_ID) + if ("${_C_COMPILER_ID}" STREQUAL "clang") + add_c_compiler_flag("-Wno-gnu-zero-variadic-macro-arguments" SUPPORTED_COMPILER_FLAGS) + endif() + + add_c_compiler_flag("-fno-common" SUPPORTED_COMPILER_FLAGS) + + if (CMAKE_BUILD_TYPE) + string(TOLOWER "${CMAKE_BUILD_TYPE}" CMAKE_BUILD_TYPE_LOWER) + if (CMAKE_BUILD_TYPE_LOWER MATCHES (release|relwithdebinfo|minsizerel)) + add_c_compiler_flag("-Wp,-D_FORTIFY_SOURCE=2" SUPPORTED_COMPILER_FLAGS) + endif() + endif() + + check_c_compiler_flag_ssp("-fstack-protector-strong" WITH_STACK_PROTECTOR_STRONG) + if (WITH_STACK_PROTECTOR_STRONG) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-protector-strong") + # This is needed as Solaris has a separate libssp + if (SOLARIS) + list(APPEND SUPPORTED_LINKER_FLAGS "-fstack-protector-strong") + endif() + else (WITH_STACK_PROTECTOR_STRONG) + check_c_compiler_flag_ssp("-fstack-protector" WITH_STACK_PROTECTOR) + if (WITH_STACK_PROTECTOR) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-protector") + # This is needed as Solaris has a separate libssp + if (SOLARIS) + list(APPEND SUPPORTED_LINKER_FLAGS "-fstack-protector") + endif() + endif() + endif (WITH_STACK_PROTECTOR_STRONG) + + if (NOT WINDOWS AND NOT CYGWIN) + # apple m* chips do not support this option + if (NOT ${CMAKE_SYSTEM_PROCESSOR} STREQUAL arm64) + check_c_compiler_flag_ssp("-fstack-clash-protection" WITH_STACK_CLASH_PROTECTION) + if (WITH_STACK_CLASH_PROTECTION) + list(APPEND SUPPORTED_COMPILER_FLAGS "-fstack-clash-protection") + endif() + endif() + endif() + + if (PICKY_DEVELOPER) + add_c_compiler_flag("-Wno-error=deprecated-declarations" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("-Wno-error=tautological-compare" SUPPORTED_COMPILER_FLAGS) + endif() + + add_c_compiler_flag("-Wno-deprecated-declarations" DEPRECATION_COMPILER_FLAGS) + + # Unset CMAKE_REQUIRED_FLAGS + unset(CMAKE_REQUIRED_FLAGS) +endif() + +if (MSVC) + add_c_compiler_flag("/D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES=1" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("/D _CRT_SECURE_CPP_OVERLOAD_STANDARD_NAMES_COUNT=1" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("/D _CRT_NONSTDC_NO_WARNINGS=1" SUPPORTED_COMPILER_FLAGS) + add_c_compiler_flag("/D _CRT_SECURE_NO_WARNINGS=1" SUPPORTED_COMPILER_FLAGS) +endif() + +# This removes this annoying warning +# "warning: 'BN_CTX_free' is deprecated: first deprecated in OS X 10.7 [-Wdeprecated-declarations]" +if (OSX) + add_c_compiler_flag("-Wno-deprecated-declarations" SUPPORTED_COMPILER_FLAGS) +endif() + +set(DEFAULT_C_COMPILE_FLAGS ${SUPPORTED_COMPILER_FLAGS} CACHE INTERNAL "Default C Compiler Flags" FORCE) +set(DEFAULT_LINK_FLAGS ${SUPPORTED_LINKER_FLAGS} CACHE INTERNAL "Default C Linker Flags" FORCE) + +if (DEPRECATION_COMPILER_FLAGS) + set(DEFAULT_C_NO_DEPRECATION_FLAGS ${DEPRECATION_COMPILER_FLAGS} CACHE INTERNAL "Default no deprecation flags" FORCE) +endif() diff --git a/src/libs/libssh-0.12.2/ConfigureChecks.cmake b/src/libs/libssh-0.12.2/ConfigureChecks.cmake new file mode 100644 index 000000000000..4e32ea531b5f --- /dev/null +++ b/src/libs/libssh-0.12.2/ConfigureChecks.cmake @@ -0,0 +1,487 @@ +include(CheckIncludeFile) +include(CheckIncludeFiles) +include(CheckSymbolExists) +include(CheckFunctionExists) +include(CheckLibraryExists) +include(CheckTypeSize) +include(CheckStructHasMember) +include(TestBigEndian) + +set(PACKAGE ${PROJECT_NAME}) +set(VERSION ${PROJECT_VERSION}) +set(SYSCONFDIR ${CMAKE_INSTALL_SYSCONFDIR}) + +set(BINARYDIR ${CMAKE_BINARY_DIR}) +set(SOURCEDIR ${CMAKE_SOURCE_DIR}) + +function(COMPILER_DUMPVERSION _OUTPUT_VERSION) + # Remove whitespaces from the argument. + # This is needed for CC="ccache gcc" cmake .. + string(REPLACE " " "" _C_COMPILER_ARG "${CMAKE_C_COMPILER_ARG1}") + + execute_process( + COMMAND + ${CMAKE_C_COMPILER} ${_C_COMPILER_ARG} -dumpversion + OUTPUT_VARIABLE _COMPILER_VERSION + ) + + string(REGEX REPLACE "([0-9])\\.([0-9])(\\.[0-9])?" "\\1\\2" + _COMPILER_VERSION "${_COMPILER_VERSION}") + + set(${_OUTPUT_VERSION} ${_COMPILER_VERSION} PARENT_SCOPE) +endfunction() + +if(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) + compiler_dumpversion(GNUCC_VERSION) + if (NOT GNUCC_VERSION EQUAL 34) + set(CMAKE_REQUIRED_FLAGS "-fvisibility=hidden") + check_c_source_compiles( +"void __attribute__((visibility(\"default\"))) test() {} +int main(void){ return 0; } +" WITH_VISIBILITY_HIDDEN) + unset(CMAKE_REQUIRED_FLAGS) + endif (NOT GNUCC_VERSION EQUAL 34) +endif(CMAKE_COMPILER_IS_GNUCC AND NOT MINGW AND NOT OS2) + +# HEADER FILES +check_function_exists(argp_parse HAVE_ARGP_PARSE) + +set(CMAKE_REQUIRED_INCLUDES ${CMAKE_REQUIRED_INCLUDES} ${ARGP_INCLUDE_DIR}) +check_include_file(argp.h HAVE_ARGP_H) +unset(CMAKE_REQUIRED_INCLUDES) + +check_include_file(pty.h HAVE_PTY_H) +check_include_file(utmp.h HAVE_UTMP_H) +check_include_file(termios.h HAVE_TERMIOS_H) +check_include_file(unistd.h HAVE_UNISTD_H) +check_include_file(stdint.h HAVE_STDINT_H) +check_include_file(util.h HAVE_UTIL_H) +check_include_file(libutil.h HAVE_LIBUTIL_H) +check_include_file(sys/time.h HAVE_SYS_TIME_H) +check_include_file(sys/utime.h HAVE_SYS_UTIME_H) +check_include_file(sys/param.h HAVE_SYS_PARAM_H) +check_include_file(arpa/inet.h HAVE_ARPA_INET_H) +check_include_file(byteswap.h HAVE_BYTESWAP_H) +check_include_file(glob.h HAVE_GLOB_H) +check_include_file(valgrind/valgrind.h HAVE_VALGRIND_VALGRIND_H) +check_include_file(ifaddrs.h HAVE_IFADDRS_H) + +if (WIN32) + check_include_file(io.h HAVE_IO_H) + + check_include_files("winsock2.h;ws2tcpip.h;wspiapi.h" HAVE_WSPIAPI_H) + if (NOT HAVE_WSPIAPI_H) + message(STATUS "WARNING: Without wspiapi.h, this build will only work on Windows XP and newer versions") + endif (NOT HAVE_WSPIAPI_H) + check_include_files("winsock2.h;ws2tcpip.h" HAVE_WS2TCPIP_H) +endif (WIN32) + +if (OPENSSL_FOUND) + set(CMAKE_REQUIRED_INCLUDES ${OPENSSL_INCLUDE_DIR}) + set(CMAKE_REQUIRED_LIBRARIES OpenSSL::Crypto) + + check_include_file(openssl/des.h HAVE_OPENSSL_DES_H) + if (NOT HAVE_OPENSSL_DES_H) + message(FATAL_ERROR "Could not detect openssl/des.h") + endif() + + check_include_file(openssl/aes.h HAVE_OPENSSL_AES_H) + if (NOT HAVE_OPENSSL_AES_H) + message(FATAL_ERROR "Could not detect openssl/aes.h") + endif() + + if (WITH_BLOWFISH_CIPHER) + check_include_file(openssl/blowfish.h HAVE_BLOWFISH) + endif() + + check_include_file(openssl/ecdh.h HAVE_OPENSSL_ECDH_H) + check_include_file(openssl/ec.h HAVE_OPENSSL_EC_H) + check_include_file(openssl/ecdsa.h HAVE_OPENSSL_ECDSA_H) + + check_function_exists(EVP_KDF_CTX_new_id HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID) + check_function_exists(EVP_KDF_CTX_new HAVE_OPENSSL_EVP_KDF_CTX_NEW) + check_function_exists(FIPS_mode HAVE_OPENSSL_FIPS_MODE) + check_function_exists(RAND_priv_bytes HAVE_OPENSSL_RAND_PRIV_BYTES) + check_function_exists(EVP_chacha20 HAVE_OPENSSL_EVP_CHACHA20) + + # Check for ML-KEM availability (OpenSSL 3.5+) + if (OPENSSL_VERSION VERSION_GREATER_EQUAL "3.5.0") + set(HAVE_OPENSSL_MLKEM 1) + set(HAVE_MLKEM1024 1) + endif () + + unset(CMAKE_REQUIRED_INCLUDES) + unset(CMAKE_REQUIRED_LIBRARIES) +endif() + +if (CMAKE_HAVE_PTHREAD_H) + set(HAVE_PTHREAD_H 1) +endif (CMAKE_HAVE_PTHREAD_H) + +if (NOT WITH_GCRYPT AND NOT WITH_MBEDTLS) + if (HAVE_OPENSSL_EC_H AND HAVE_OPENSSL_ECDSA_H) + set(HAVE_OPENSSL_ECC 1) + endif (HAVE_OPENSSL_EC_H AND HAVE_OPENSSL_ECDSA_H) + + if (HAVE_OPENSSL_ECC) + set(HAVE_ECC 1) + endif (HAVE_OPENSSL_ECC) + + if (HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID OR HAVE_OPENSSL_EVP_KDF_CTX_NEW) + set(HAVE_OPENSSL_EVP_KDF_CTX 1) + endif (HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID OR HAVE_OPENSSL_EVP_KDF_CTX_NEW) + +endif () + +# FUNCTIONS + +check_function_exists(isblank HAVE_ISBLANK) +check_function_exists(strncpy HAVE_STRNCPY) +check_function_exists(strndup HAVE_STRNDUP) +check_function_exists(strtoull HAVE_STRTOULL) +check_function_exists(explicit_bzero HAVE_EXPLICIT_BZERO) +check_function_exists(memset_explicit HAVE_MEMSET_EXPLICIT) +check_function_exists(memset_s HAVE_MEMSET_S) + +if (HAVE_GLOB_H) + check_struct_has_member(glob_t gl_flags glob.h HAVE_GLOB_GL_FLAGS_MEMBER) + check_function_exists(glob HAVE_GLOB) +endif (HAVE_GLOB_H) + +if (NOT WIN32) + check_function_exists(vsnprintf HAVE_VSNPRINTF) + check_function_exists(snprintf HAVE_SNPRINTF) +endif (NOT WIN32) + +if (WIN32) + check_symbol_exists(vsnprintf "stdio.h" HAVE_VSNPRINTF) + check_symbol_exists(snprintf "stdio.h" HAVE_SNPRINTF) + + check_symbol_exists(_vsnprintf_s "stdio.h" HAVE__VSNPRINTF_S) + check_symbol_exists(_vsnprintf "stdio.h" HAVE__VSNPRINTF) + check_symbol_exists(_snprintf "stdio.h" HAVE__SNPRINTF) + check_symbol_exists(_snprintf_s "stdio.h" HAVE__SNPRINTF_S) + + if (HAVE_WSPIAPI_H OR HAVE_WS2TCPIP_H) + check_symbol_exists(ntohll winsock2.h HAVE_NTOHLL) + check_symbol_exists(htonll winsock2.h HAVE_HTONLL) + + set(CMAKE_REQUIRED_LIBRARIES ws2_32) + check_symbol_exists(select "winsock2.h;ws2tcpip.h" HAVE_SELECT) + check_symbol_exists(poll "winsock2.h;ws2tcpip.h" HAVE_SELECT) + # The getaddrinfo function is defined to the WspiapiGetAddrInfo inline function + check_symbol_exists(getaddrinfo "winsock2.h;ws2tcpip.h" HAVE_GETADDRINFO) + unset(CMAKE_REQUIRED_LIBRARIES) + endif (HAVE_WSPIAPI_H OR HAVE_WS2TCPIP_H) + + check_function_exists(_strtoui64 HAVE__STRTOUI64) + + set(HAVE_SELECT TRUE) + + check_symbol_exists(SecureZeroMemory "windows.h" HAVE_SECURE_ZERO_MEMORY) +else (WIN32) + check_function_exists(poll HAVE_POLL) + check_function_exists(select HAVE_SELECT) + check_function_exists(getaddrinfo HAVE_GETADDRINFO) + + check_symbol_exists(ntohll arpa/inet.h HAVE_NTOHLL) + check_symbol_exists(htonll arpa/inet.h HAVE_HTONLL) +endif (WIN32) + + +if (UNIX) + if (NOT LINUX) + # libsocket (Solaris) + check_library_exists(socket getaddrinfo "" HAVE_LIBSOCKET) + if (HAVE_LIBSOCKET) + set(HAVE_GETADDRINFO TRUE) + set(_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} socket) + endif (HAVE_LIBSOCKET) + + # libnsl/inet_pton (Solaris) + check_library_exists(nsl inet_pton "" HAVE_LIBNSL) + if (HAVE_LIBNSL) + set(_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} nsl) + endif (HAVE_LIBNSL) + + # librt + check_library_exists(rt nanosleep "" HAVE_LIBRT) + endif (NOT LINUX) + + check_library_exists(rt clock_gettime "" HAVE_CLOCK_GETTIME) + if (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + set(_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} rt) + endif (HAVE_LIBRT OR HAVE_CLOCK_GETTIME) + + check_library_exists(util forkpty "" HAVE_LIBUTIL) + check_function_exists(cfmakeraw HAVE_CFMAKERAW) + check_function_exists(__strtoull HAVE___STRTOULL) +endif (UNIX) + +set(LIBSSH_REQUIRED_LIBRARIES ${_REQUIRED_LIBRARIES} CACHE INTERNAL "libssh required system libraries") + +# LIBRARIES +if (OPENSSL_FOUND) + set(HAVE_LIBCRYPTO 1) +endif (OPENSSL_FOUND) + +if (GCRYPT_FOUND) + set(HAVE_LIBGCRYPT 1) + if (GCRYPT_VERSION VERSION_GREATER "1.4.6") + set(HAVE_GCRYPT_ECC 1) + set(HAVE_ECC 1) + endif (GCRYPT_VERSION VERSION_GREATER "1.4.6") + if (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") + set(HAVE_GCRYPT_CHACHA_POLY 1) + set(HAVE_GCRYPT_CURVE25519 1) + endif (NOT GCRYPT_VERSION VERSION_LESS "1.7.0") + if (GCRYPT_VERSION VERSION_GREATER_EQUAL "1.10.1") + set(HAVE_GCRYPT_MLKEM 1) + set(HAVE_MLKEM1024 1) + endif () +endif (GCRYPT_FOUND) + +if (MBEDTLS_FOUND) + set(HAVE_LIBMBEDCRYPTO 1) + set(HAVE_ECC 1) + + set(CMAKE_REQUIRED_INCLUDES "${MBEDTLS_INCLUDE_DIR}/mbedtls") + check_include_file(chacha20.h HAVE_MBEDTLS_CHACHA20_H) + check_include_file(poly1305.h HAVE_MBEDTLS_POLY1305_H) + if (MBEDTLS_VERSION VERSION_LESS "3.0.0") + check_symbol_exists(MBEDTLS_ECP_DP_CURVE25519_ENABLED "config.h" HAVE_MBEDTLS_CURVE25519) + else() + check_symbol_exists(MBEDTLS_ECP_DP_CURVE25519_ENABLED "mbedtls_config.h" HAVE_MBEDTLS_CURVE25519) + endif() + + + if (WITH_BLOWFISH_CIPHER) + check_include_file(blowfish.h HAVE_BLOWFISH) + endif() + + unset(CMAKE_REQUIRED_INCLUDES) + +endif (MBEDTLS_FOUND) + +if (CMAKE_USE_PTHREADS_INIT) + set(HAVE_PTHREAD 1) +endif (CMAKE_USE_PTHREADS_INIT) + +if (UNIT_TESTING) + if (CMOCKA_FOUND) + set(CMAKE_REQUIRED_LIBRARIES ${CMOCKA_LIBRARIES}) + check_function_exists(cmocka_set_test_filter HAVE_CMOCKA_SET_TEST_FILTER) + unset(CMAKE_REQUIRED_LIBRARIES) + endif () +endif () + +# OPTIONS +check_c_source_compiles(" +__thread int tls; + +int main(void) { + return 0; +}" HAVE_GCC_THREAD_LOCAL_STORAGE) + +check_c_source_compiles(" +__declspec(thread) int tls; + +int main(void) { + return 0; +}" HAVE_MSC_THREAD_LOCAL_STORAGE) + +########################################################### +# For detecting attributes we need to treat warnings as +# errors +if (UNIX OR MINGW) + # Get warnings for attributes + check_c_compiler_flag("-Wattributes" REQUIRED_FLAGS_WERROR) + if (REQUIRED_FLAGS_WERROR) + string(APPEND CMAKE_REQUIRED_FLAGS "-Wattributes ") + endif() + + # Turn warnings into errors + check_c_compiler_flag("-Werror" REQUIRED_FLAGS_WERROR) + if (REQUIRED_FLAGS_WERROR) + string(APPEND CMAKE_REQUIRED_FLAGS "-Werror ") + endif() +endif () + +check_c_source_compiles(" +void test_constructor_attribute(void) __attribute__ ((constructor)); + +void test_constructor_attribute(void) +{ + return; +} + +int main(void) { + return 0; +}" HAVE_CONSTRUCTOR_ATTRIBUTE) + +check_c_source_compiles(" +void test_destructor_attribute(void) __attribute__ ((destructor)); + +void test_destructor_attribute(void) +{ + return; +} + +int main(void) { + return 0; +}" HAVE_DESTRUCTOR_ATTRIBUTE) + +check_c_source_compiles(" +#define FALL_THROUGH __attribute__((fallthrough)) + +int main(void) { + int i = 2; + + switch (i) { + case 0: + FALL_THROUGH; + case 1: + break; + default: + break; + } + + return 0; +}" HAVE_FALLTHROUGH_ATTRIBUTE) + +check_c_source_compiles(" +#define WEAK __attribute__((weak)) + +WEAK int sum(int a, int b) +{ + return a + b; +} + +int main(void) +{ + int i = sum(2, 2); + + (void)i; + + return 0; +}" HAVE_WEAK_ATTRIBUTE) + +if (NOT WIN32) + check_c_source_compiles(" + #define __unused __attribute__((unused)) + + static int do_nothing(int i __unused) + { + return 0; + } + + int main(void) + { + int i; + + i = do_nothing(5); + if (i > 5) { + return 1; + } + + return 0; + }" HAVE_UNUSED_ATTRIBUTE) +endif() + +check_c_source_compiles(" +#include + +int main(void) +{ + char buf[] = \"This is some content\"; + + memset(buf, '\\\\0', sizeof(buf)); __asm__ volatile(\"\" : : \"g\"(&buf) : \"memory\"); + + return 0; +}" HAVE_GCC_VOLATILE_MEMORY_PROTECTION) + +check_c_source_compiles(" +#include +int main(void) { + printf(\"%s\", __func__); + return 0; +}" HAVE_COMPILER__FUNC__) + +check_c_source_compiles(" +#include +int main(void) { + printf(\"%s\", __FUNCTION__); + return 0; +}" HAVE_COMPILER__FUNCTION__) + +# This is only available with OpenBSD's gcc implementation */ +if (OPENBSD) +check_c_source_compiles(" +#define ARRAY_LEN 16 +void test_attr(const unsigned char *k) + __attribute__((__bounded__(__minbytes__, 2, 16))); + +int main(void) { + return 0; +}" HAVE_GCC_BOUNDED_ATTRIBUTE) +endif(OPENBSD) + +# Stop treating warnings as errors +unset(CMAKE_REQUIRED_FLAGS) + +# Check for version script support +file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/conftest.map" "VERS_1 { + global: sym; +}; +VERS_2 { + global: sym; +} VERS_1; +") + +set(CMAKE_REQUIRED_FLAGS "-Wl,--version-script=\"${CMAKE_CURRENT_BINARY_DIR}/conftest.map\"") +check_c_source_compiles("int main(void) { return 0; }" HAVE_LD_VERSION_SCRIPT) +unset(CMAKE_REQUIRED_FLAGS) +file(REMOVE "${CMAKE_CURRENT_BINARY_DIR}/conftest.map") + +if (WITH_DEBUG_CRYPTO) + set(DEBUG_CRYPTO 1) +endif (WITH_DEBUG_CRYPTO) + +if (WITH_DEBUG_PACKET) + set(DEBUG_PACKET 1) +endif (WITH_DEBUG_PACKET) + +if (WITH_DEBUG_CALLTRACE) + set(DEBUG_CALLTRACE 1) +endif (WITH_DEBUG_CALLTRACE) + +if (WITH_GSSAPI AND NOT GSSAPI_FOUND) + set(WITH_GSSAPI 0) +endif (WITH_GSSAPI AND NOT GSSAPI_FOUND) + +if (WITH_PKCS11_URI) + if (WITH_GCRYPT) + message(FATAL_ERROR "PKCS #11 is not supported for gcrypt.") + set(WITH_PKCS11_URI 0) + elseif (WITH_MBEDTLS) + message(FATAL_ERROR "PKCS #11 is not supported for mbedcrypto") + set(WITH_PKCS11_URI 0) + elseif (OPENSSL_FOUND AND OPENSSL_VERSION VERSION_GREATER_EQUAL "3.0.0") + find_library(PKCS11_PROVIDER + NAMES + pkcs11.so + PATH_SUFFIXES + ossl-modules + ) + if (NOT PKCS11_PROVIDER) + set(WITH_PKCS11_PROVIDER 0) + message(WARNING "Could not find pkcs11 provider! Falling back to engines") + message(WARNING "The support for engines is deprecated in OpenSSL and will be removed from libssh in the future releases.") + endif (NOT PKCS11_PROVIDER) + endif () +endif() + +# ENDIAN +if (NOT WIN32) + test_big_endian(WORDS_BIGENDIAN) +endif (NOT WIN32) diff --git a/src/libs/libssh-0.12.2/DefineOptions.cmake b/src/libs/libssh-0.12.2/DefineOptions.cmake new file mode 100644 index 000000000000..f788dec56408 --- /dev/null +++ b/src/libs/libssh-0.12.2/DefineOptions.cmake @@ -0,0 +1,109 @@ +option(WITH_GSSAPI "Build with GSSAPI support" ON) +option(WITH_ZLIB "Build with ZLIB support" ON) +option(WITH_SFTP "Build with SFTP support" ON) +option(WITH_SERVER "Build with SSH server support" ON) +option(WITH_DEBUG_CRYPTO "Build with crypto debug output" OFF) +option(WITH_DEBUG_PACKET "Build with packet debug output" OFF) +option(WITH_DEBUG_CALLTRACE "Build with calltrace debug output" ON) +option(WITH_GCRYPT "Compile against libgcrypt (deprecated)" OFF) +option(WITH_MBEDTLS "Compile against libmbedtls" OFF) +option(WITH_BLOWFISH_CIPHER "Compile with blowfish support" OFF) +option(WITH_PCAP "Compile with Pcap generation support" ON) +option(WITH_INTERNAL_DOC "Compile doxygen internal documentation" OFF) +option(BUILD_SHARED_LIBS "Build shared libraries" ON) +option(WITH_PKCS11_URI "Build with PKCS#11 URI support" OFF) +option(WITH_PKCS11_PROVIDER + "Use the PKCS#11 provider for accessing pkcs11 objects" OFF) +option(WITH_FIDO2 "Build with FIDO2/U2F support" OFF) +option(UNIT_TESTING "Build with unit tests" OFF) +option(CLIENT_TESTING "Build with client tests; requires openssh" OFF) +option(SERVER_TESTING "Build with server tests; requires openssh and dropbear" + OFF) +option( + GSSAPI_TESTING + "Build with GSSAPI tests; requires krb5-server,krb5-libs and krb5-workstation" + OFF) +option(WITH_BENCHMARKS + "Build benchmarks tools; enables unit testing and client tests" OFF) +option(WITH_EXAMPLES "Build examples" ON) +option(WITH_NACL "Build with libnacl (curve25519)" ON) +option(WITH_SYMBOL_VERSIONING "Build with symbol versioning" ON) +option(WITH_ABI_BREAK "Allow ABI break" OFF) +option(WITH_GEX "Enable DH Group exchange mechanisms" ON) +option( + WITH_INSECURE_NONE + "Enable insecure none cipher and MAC algorithms (not suitable for production!)" + OFF) +option( + WITH_EXEC + "Enable libssh to execute arbitrary commands from configuration files or options (match exec, proxy commands and OpenSSH-based proxy-jumps)." + ON) +option( + FUZZ_TESTING + "Build with fuzzer for the server and client (automatically enables none cipher!)" + OFF) +option(PICKY_DEVELOPER "Build with picky developer flags" OFF) +option(WITH_HERMETIC_USR "Build with support for hermetic /usr/" OFF) + +if (WITH_ZLIB) + set(WITH_LIBZ ON) +else (WITH_ZLIB) + set(WITH_LIBZ OFF) +endif (WITH_ZLIB) + +if (WITH_BENCHMARKS) + set(UNIT_TESTING ON) + set(CLIENT_TESTING ON) +endif () + +if (UNIT_TESTING + OR CLIENT_TESTING + OR SERVER_TESTING + OR GSSAPI_TESTING) + set(BUILD_STATIC_LIB ON) +endif () + +if (WITH_NACL) + set(WITH_NACL ON) +endif (WITH_NACL) + +if (WITH_ABI_BREAK) + set(WITH_SYMBOL_VERSIONING ON) +endif (WITH_ABI_BREAK) + +set(GLOBAL_CONF_DIR "/etc/ssh") +if (WIN32) + # Use PROGRAMDATA on Windows + if (DEFINED ENV{PROGRAMDATA}) + set(GLOBAL_CONF_DIR "$ENV{PROGRAMDATA}/ssh") + else () + set(GLOBAL_CONF_DIR "C:/ProgramData/ssh") + endif () + if (WITH_HERMETIC_USR) + set(USR_GLOBAL_CONF_DIR "/usr${GLOBAL_CONF_DIR}") + endif () +endif () + +if (NOT GLOBAL_BIND_CONFIG) + set(GLOBAL_BIND_CONFIG "${GLOBAL_CONF_DIR}/libssh_server_config") + + if (WITH_HERMETIC_USR) + set(USR_GLOBAL_BIND_CONFIG "/usr${GLOBAL_BIND_CONFIG}") + endif () +endif (NOT GLOBAL_BIND_CONFIG) + +if (NOT GLOBAL_CLIENT_CONFIG) + set(GLOBAL_CLIENT_CONFIG "${GLOBAL_CONF_DIR}/ssh_config") + + if (WITH_HERMETIC_USR) + set(USR_GLOBAL_CLIENT_CONFIG "/usr${GLOBAL_CLIENT_CONFIG}") + endif () +endif (NOT GLOBAL_CLIENT_CONFIG) + +if (FUZZ_TESTING) + set(WITH_INSECURE_NONE ON) +endif (FUZZ_TESTING) + +if (WIN32) + set(WITH_EXEC 0) +endif (WIN32) diff --git a/src/libs/libssh-0.12.2/INSTALL b/src/libs/libssh-0.12.2/INSTALL new file mode 100644 index 000000000000..a51914cc596e --- /dev/null +++ b/src/libs/libssh-0.12.2/INSTALL @@ -0,0 +1,125 @@ +# How to build from source + +## Requirements + +### Common requirements + +In order to build libssh, you need to install several components: + +- A C compiler +- [CMake](https://www.cmake.org) >= 3.12.0 +- [libz](https://www.zlib.net) >= 1.2 +- [openssl](https://www.openssl.org) >= 1.1.1 +or +- [gcrypt](https://www.gnu.org/directory/Security/libgcrypt.html) >= 1.5 +or +- [Mbed TLS](https://www.trustedfirmware.org/projects/mbed-tls/) + +optional: +- [cmocka](https://cmocka.org/) >= 1.1.0 +- [socket_wrapper](https://cwrap.org/) >= 1.1.5 +- [nss_wrapper](https://cwrap.org/) >= 1.1.2 +- [uid_wrapper](https://cwrap.org/) >= 1.2.0 +- [pam_wrapper](https://cwrap.org/) >= 1.0.1 +- [priv_wrapper](https://cwrap.org/) >= 1.0.0 + +Note that these version numbers are version we know works correctly. If you +build and run libssh successfully with an older version, please let us know. + +For Windows use vcpkg: + +https://github.com/Microsoft/vcpkg + +which you can use to install openssl and zlib. libssh itself is also part of +vcpkg! + +## Building +First, you need to configure the compilation, using CMake. Go inside the +`build` dir. Create it if it doesn't exist. + +GNU/Linux, MacOS X, MSYS/MinGW: + cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug .. + make + +On Windows you should choose a makefile generator with -G or use + + cmake-gui.exe .. + +To enable building tests use -DUNIT_TESTING=ON. For this, the +[cmocka](https://cmocka.org) dependency is required. + +To enable additional client tests against a local OpenSSH server, add the +compile option -DCLIENT_TESTING=ON. These tests require an OpenSSH +server package and some wrapper libraries (see optional requirements) to +be installed. + +If you're interested in server testing, then a OpenSSH client should be +installed on the system and if possible also dropbear. Once that is done +enable server support with -DWITH_SERVER=ON and enable testing of it with +-DSERVER_TESTING=ON. + +## Testing build + + make test + +### CMake standard options +Here is a list of the most interesting options provided out of the box by +CMake. + +- CMAKE_BUILD_TYPE: The type of build (can be Debug Release MinSizeRel + RelWithDebInfo) +- CMAKE_INSTALL_PREFIX: The prefix to use when running make install (Default + to /usr/local on GNU/Linux and MacOS X) +- CMAKE_C_COMPILER: The path to the C compiler +- CMAKE_CXX_COMPILER: The path to the C++ compiler + +### CMake options defined for libssh + +Options are defined in the following files: + +- DefineOptions.cmake + +They can be changed with the -D option: + +`cmake -DCMAKE_INSTALL_PREFIX=/usr -DCMAKE_BUILD_TYPE=Debug -DWITH_ZLIB=OFF ..` + +### Browsing/editing CMake options + +In addition to passing options on the command line, you can browse and edit +CMake options using `cmakesetup` (Windows), `cmake-gui` or `ccmake` (GNU/Linux +and MacOS X). + +- Go to the build dir +- On Windows: run `cmakesetup` +- On GNU/Linux and MacOS X: run `ccmake ..` + +### Useful Windows options: + +If you have installed OpenSSL or ZLIB in non standard directories, maybe you +want to set: + +OPENSSL_ROOT_DIR + +and + +ZLIB_ROOT_DIR + +## Installing + +If you want to install libssh after compilation run: + + make install + +## Running + +The libssh binary can be found in the `build/src` directory. +You can use `build/examples/samplessh` which is a sample client to +test libssh on UNIX. + +## About this document + +This document is written using [Markdown][] syntax, making it possible to +provide usable information in both plain text and HTML format. Whenever +modifying this document please use [Markdown][] syntax. + +[markdown]: https://www.daringfireball.net/projects/markdown diff --git a/src/libs/libssh-0.12.2/Makefile.kmk b/src/libs/libssh-0.12.2/Makefile.kmk new file mode 100644 index 000000000000..cb3f96fa4783 --- /dev/null +++ b/src/libs/libssh-0.12.2/Makefile.kmk @@ -0,0 +1,130 @@ +# $Id: Makefile.kmk 114891 2026-08-07 11:59:56Z aleksey.ilyushin@oracle.com $ +## @file +# Sub-Makefile for libssh. +# + +# +# Copyright (C) 2020-2026 Oracle and/or its affiliates. +# +# This file is part of VirtualBox base platform packages, as +# available from https://www.virtualbox.org. +# +# This program is free software; you can redistribute it and/or +# modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, in version 3 of the +# License. +# +# This program is distributed in the hope that it will be useful, but +# WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +# General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, see . +# +# SPDX-License-Identifier: GPL-3.0-only +# + +SUB_DEPTH = ../../.. +include $(KBUILD_PATH)/subheader.kmk + +DLLS += VBoxLibSsh +VBoxLibSsh_TEMPLATE = VBoxR3DllNonPedantic +VBoxLibSsh_DEFS = LIBSSH_EXPORTS +VBoxLibSsh_SDKS = VBoxZlib VBoxOpenSsl +VBoxLibSsh_LIBS += $(LIB_RUNTIME) +# -wd4146: fe25519.c(90): warning C4146: unary minus operator applied to unsigned type, result still unsigned +# -wd4206: agent.c(595): warning C4206: nonstandard extension used: translation unit is empty +# -wd4701: pki_container_openssh.c(475) : warning C4701: potentially uninitialized local variable 'cipher' used +# -wd4204: pki_crypto.c(802): warning C4204: nonstandard extension used: non-constant aggregate initializer +VBoxLibSsh_CFLAGS.win += -wd4146 -wd4206 -wd4701 -wd4204 +VBoxLibSsh_LDFLAGS.darwin += \ + -install_name $(VBOX_DYLD_EXECUTABLE_PATH)/VBoxLibSsh.dylib +VBoxLibSsh_INCS = \ + include \ + src +VBoxLibSsh_SOURCES = \ + src/agent.c \ + src/auth.c \ + src/base64.c \ + src/bignum.c \ + src/buffer.c \ + src/callbacks.c \ + src/channels.c \ + src/client.c \ + src/config.c \ + src/connect.c \ + src/connector.c \ + src/crypto_common.c \ + src/curve25519.c \ + src/curve25519_crypto.c \ + src/dh.c \ + src/ecdh.c \ + src/error.c \ + src/getpass.c \ + src/getrandom_crypto.c \ + src/hybrid_mlkem.c \ + src/init.c \ + src/kdf.c \ + src/kex.c \ + src/known_hosts.c \ + src/knownhosts.c \ + src/legacy.c \ + src/log.c \ + src/match.c \ + src/messages.c \ + src/md_crypto.c \ + src/misc.c \ + src/mlkem.c \ + src/mlkem_native.c \ + src/options.c \ + src/packet.c \ + src/packet_cb.c \ + src/packet_crypt.c \ + src/pcap.c \ + src/pki.c \ + src/pki_container_openssh.c \ + src/pki_context.c \ + src/poll.c \ + src/session.c \ + src/sntrup761.c \ + src/socket.c \ + src/string.c \ + src/threads.c \ + src/wrapper.c \ + src/external/bcrypt_pbkdf.c \ + src/external/blowfish.c \ + src/external/chacha.c \ + src/external/libcrux_mlkem768_sha3.c \ + src/external/poly1305.c \ + src/external/sntrup761.c \ + src/chachapoly.c \ + src/config_parser.c \ + src/token.c \ + src/ttyopts.c \ + src/pki_ed25519_common.c + +VBoxLibSsh_SOURCES += \ + src/gzip.c + +# OpenSSL +VBoxLibSsh_SOURCES += \ + src/threads/libcrypto.c \ + src/pki_crypto.c \ + src/ecdh_crypto.c \ + src/libcrypto.c \ + src/dh_crypto.c + +# TODO: threads? +if1of ($(KBUILD_TARGET), win) + VBoxLibSsh_SOURCES += \ + src/threads/noop.c \ + src/threads/winlocks.c +else + VBoxLibSsh_SOURCES += \ + src/threads/noop.c \ + src/threads/pthread.c +endif + +include $(FILE_KBUILD_SUB_FOOTER) + diff --git a/src/libs/libssh-0.12.2/README b/src/libs/libssh-0.12.2/README new file mode 100644 index 000000000000..09a7e5696f90 --- /dev/null +++ b/src/libs/libssh-0.12.2/README @@ -0,0 +1,44 @@ + _ _ _ _ + (_) (_) (_) (_) + (_) _ (_) _ _ _ _ _ (_) _ + (_) (_) (_)(_) _ (_)(_) (_)(_) (_)(_) _ + (_) (_) (_) (_) _ (_) _ (_) (_) (_) + (_) (_) (_)(_)(_) (_)(_) (_)(_) (_) (_).org + + The SSH library +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +1* Why ? +-_-_-_-_-_ + +Why not ? :) I've began to work on my own implementation of the ssh protocol +because i didn't like the currently public ones. +Not any allowed you to import and use the functions as a powerful library, +and so i worked on a library-based SSH implementation which was non-existing +in the free and open source software world. + + +2* How/Who ? +-_-_-_-_-_-_-_ + +If you downloaded this file, you must know what it is : a library for +accessing ssh client services through C libraries calls in a simple manner. +Everybody can use this software under the terms of the LGPL - see the COPYING +file + +If you ask yourself how to compile libssh, please read INSTALL before anything. + +3* Where ? +-_-_-_-_-_-_ + +https://www.libssh.org + +4* Contributing +-_-_-_-_-_-_-_-_-_ + +Please read the file 'CONTRIBUTING.md' next to this README file. It explains +our copyright policy and how you should send patches for upstream inclusion. + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/src/libs/libssh-0.12.2/README.mbedtls b/src/libs/libssh-0.12.2/README.mbedtls new file mode 100644 index 000000000000..fdf3b25d5af4 --- /dev/null +++ b/src/libs/libssh-0.12.2/README.mbedtls @@ -0,0 +1,11 @@ +mbedTLS and libssh in multithreaded applications +================================================== + +To use libssh with mbedTLS in a multithreaded application, mbedTLS has to be +built with threading support enabled. + +If threading support is not available and multi threading is used, ssh_init +will fail. + +More information about building mbedTLS with threading support can be found +in the mbedTLS documentation. diff --git a/src/libs/libssh-0.12.2/README.md b/src/libs/libssh-0.12.2/README.md new file mode 100644 index 000000000000..cd6b9eaa2754 --- /dev/null +++ b/src/libs/libssh-0.12.2/README.md @@ -0,0 +1,45 @@ +[![pipeline status](https://gitlab.com/libssh/libssh-mirror/badges/master/pipeline.svg)](https://gitlab.com/libssh/libssh-mirror/commits/master) +[![Fuzzing Status](https://oss-fuzz-build-logs.storage.googleapis.com/badges/libssh.svg)](https://bugs.chromium.org/p/oss-fuzz/issues/list?sort=-opened&can=1&q=proj:libssh) + +``` + _ _ _ _ + (_) (_) (_) (_) + (_) _ (_) _ _ _ _ _ (_) _ + (_) (_) (_)(_) _ (_)(_) (_)(_) (_)(_) _ + (_) (_) (_) (_) _ (_) _ (_) (_) (_) + (_) (_) (_)(_)(_) (_)(_) (_)(_) (_) (_).org + + The SSH library + +``` + +# Why? + +Why not ? :) I've began to work on my own implementation of the ssh protocol +because i didn't like the currently public ones. +Not any allowed you to import and use the functions as a powerful library, +and so i worked on a library-based SSH implementation which was non-existing +in the free and open source software world. + + +# How/Who? + +If you downloaded this file, you must know what it is : a library for +accessing ssh client services through C libraries calls in a simple manner. +Everybody can use this software under the terms of the LGPL - see the COPYING +file + +If you ask yourself how to compile libssh, please read INSTALL before anything. + +# Where ? + +https://www.libssh.org + +# Contributing + +Please read the file 'CONTRIBUTING.md' next to this README file. It explains +our copyright policy and how you should send patches for upstream inclusion. + +Have fun and happy libssh hacking! + +The libssh Team diff --git a/src/libs/libssh-0.12.2/cmake/Modules/AddCCompilerFlag.cmake b/src/libs/libssh-0.12.2/cmake/Modules/AddCCompilerFlag.cmake new file mode 100644 index 000000000000..c24c215c3882 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/AddCCompilerFlag.cmake @@ -0,0 +1,21 @@ +# +# add_c_compiler_flag("-Werror" SUPPORTED_CFLAGS) +# +# Copyright (c) 2018 Andreas Schneider +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +include(CheckCCompilerFlag) + +macro(add_c_compiler_flag _COMPILER_FLAG _OUTPUT_VARIABLE) + string(TOUPPER ${_COMPILER_FLAG} _COMPILER_FLAG_NAME) + string(REGEX REPLACE "^-" "" _COMPILER_FLAG_NAME "${_COMPILER_FLAG_NAME}") + string(REGEX REPLACE "(-|=|\ )" "_" _COMPILER_FLAG_NAME "${_COMPILER_FLAG_NAME}") + + check_c_compiler_flag("${_COMPILER_FLAG}" WITH_${_COMPILER_FLAG_NAME}_FLAG) + if (WITH_${_COMPILER_FLAG_NAME}_FLAG) + #string(APPEND ${_OUTPUT_VARIABLE} "${_COMPILER_FLAG} ") + list(APPEND ${_OUTPUT_VARIABLE} ${_COMPILER_FLAG}) + endif() +endmacro() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/AddCMockaTest.cmake b/src/libs/libssh-0.12.2/cmake/Modules/AddCMockaTest.cmake new file mode 100644 index 000000000000..f49961ba4365 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/AddCMockaTest.cmake @@ -0,0 +1,125 @@ +# +# Copyright (c) 2007 Daniel Gollub +# Copyright (c) 2007-2018 Andreas Schneider +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +#.rst: +# AddCMockaTest +# ------------- +# +# This file provides a function to add a test +# +# Functions provided +# ------------------ +# +# :: +# +# add_cmocka_test(target_name +# SOURCES src1 src2 ... srcN +# [COMPILE_OPTIONS opt1 opt2 ... optN] +# [LINK_LIBRARIES lib1 lib2 ... libN] +# [LINK_OPTIONS lopt1 lop2 .. loptN] +# ) +# +# ``target_name``: +# Required, expects the name of the test which will be used to define a target +# +# ``SOURCES``: +# Required, expects one or more source files names +# +# ``COMPILE_OPTIONS``: +# Optional, expects one or more options to be passed to the compiler +# +# ``LINK_LIBRARIES``: +# Optional, expects one or more libraries to be linked with the test +# executable. +# +# ``LINK_OPTIONS``: +# Optional, expects one or more options to be passed to the linker +# +# +# Example: +# +# .. code-block:: cmake +# +# add_cmocka_test(my_test +# SOURCES my_test.c other_source.c +# COMPILE_OPTIONS -g -Wall +# LINK_LIBRARIES mylib +# LINK_OPTIONS -Wl,--enable-syscall-fixup +# ) +# +# Where ``my_test`` is the name of the test, ``my_test.c`` and +# ``other_source.c`` are sources for the binary, ``-g -Wall`` are compiler +# options to be used, ``mylib`` is a target of a library to be linked, and +# ``-Wl,--enable-syscall-fixup`` is an option passed to the linker. +# + +enable_testing() +include(CTest) + +if (CMAKE_CROSSCOMPILING) + if (WIN32) + find_program(WINE_EXECUTABLE + NAMES wine) + set(TARGET_SYSTEM_EMULATOR ${WINE_EXECUTABLE}) + endif() +endif() + +function(ADD_CMOCKA_TEST _TARGET_NAME) + + set(one_value_arguments + ) + + set(multi_value_arguments + SOURCES + COMPILE_OPTIONS + LINK_LIBRARIES + LINK_OPTIONS + ) + + cmake_parse_arguments(_add_cmocka_test + "" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + if (NOT DEFINED _add_cmocka_test_SOURCES) + message(FATAL_ERROR "No sources provided for target ${_TARGET_NAME}") + endif() + + add_executable(${_TARGET_NAME} ${_add_cmocka_test_SOURCES}) + + if (DEFINED _add_cmocka_test_COMPILE_OPTIONS) + target_compile_options(${_TARGET_NAME} + PRIVATE ${_add_cmocka_test_COMPILE_OPTIONS} + ) + endif() + + if (DEFINED _add_cmocka_test_LINK_LIBRARIES) + target_link_libraries(${_TARGET_NAME} + PRIVATE ${_add_cmocka_test_LINK_LIBRARIES} + ) + endif() + + if (DEFINED _add_cmocka_test_LINK_OPTIONS) + set_target_properties(${_TARGET_NAME} + PROPERTIES LINK_FLAGS + ${_add_cmocka_test_LINK_OPTIONS} + ) + endif() + + add_test(${_TARGET_NAME} + ${TARGET_SYSTEM_EMULATOR} ${_TARGET_NAME} + ) + if (WITH_COVERAGE) + ENABLE_LANGUAGE(CXX) + include(CodeCoverage) + append_coverage_compiler_flags_to_target(${_TARGET_NAME}) + endif (WITH_COVERAGE) + +endfunction (ADD_CMOCKA_TEST) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/COPYING-CMAKE-SCRIPTS b/src/libs/libssh-0.12.2/cmake/Modules/COPYING-CMAKE-SCRIPTS new file mode 100644 index 000000000000..4b417765f3a8 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/COPYING-CMAKE-SCRIPTS @@ -0,0 +1,22 @@ +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions +are met: + +1. Redistributions of source code must retain the copyright + notice, this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. +3. The name of the author may not be used to endorse or promote products + derived from this software without specific prior written permission. + +THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR +IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES +OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, +INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT +NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF +THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/src/libs/libssh-0.12.2/cmake/Modules/CheckCCompilerFlagSSP.cmake b/src/libs/libssh-0.12.2/cmake/Modules/CheckCCompilerFlagSSP.cmake new file mode 100644 index 000000000000..ab206ca70c70 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/CheckCCompilerFlagSSP.cmake @@ -0,0 +1,29 @@ +# - Check whether the C compiler supports a given flag in the +# context of a stack checking compiler option. + +# CHECK_C_COMPILER_FLAG_SSP(FLAG VARIABLE) +# +# FLAG - the compiler flag +# VARIABLE - variable to store the result +# +# This actually calls check_c_source_compiles. +# See help for CheckCSourceCompiles for a listing of variables +# that can modify the build. + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +# Requires cmake 3.10 +#include_guard(GLOBAL) +include(CheckCSourceCompiles) + +macro(CHECK_C_COMPILER_FLAG_SSP _FLAG _RESULT) + set(SAFE_CMAKE_REQUIRED_FLAGS "${CMAKE_REQUIRED_FLAGS}") + set(CMAKE_REQUIRED_FLAGS "${_FLAG}") + + check_c_source_compiles("int main(int argc, char **argv) { char buffer[256]; return buffer[argc]=0;}" ${_RESULT}) + + set(CMAKE_REQUIRED_FLAGS "${SAFE_CMAKE_REQUIRED_FLAGS}") +endmacro(CHECK_C_COMPILER_FLAG_SSP) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/CodeCoverage.cmake b/src/libs/libssh-0.12.2/cmake/Modules/CodeCoverage.cmake new file mode 100644 index 000000000000..0fd70ae2062c --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/CodeCoverage.cmake @@ -0,0 +1,750 @@ +# Copyright (c) 2012 - 2017, Lars Bilke +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# 1. Redistributions of source code must retain the above copyright notice, this +# list of conditions and the following disclaimer. +# +# 2. Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# 3. Neither the name of the copyright holder nor the names of its contributors +# may be used to endorse or promote products derived from this software without +# specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +# DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR +# ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +# (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; +# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON +# ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS +# SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +# CHANGES: +# +# 2012-01-31, Lars Bilke +# - Enable Code Coverage +# +# 2013-09-17, Joakim Söderberg +# - Added support for Clang. +# - Some additional usage instructions. +# +# 2016-02-03, Lars Bilke +# - Refactored functions to use named parameters +# +# 2017-06-02, Lars Bilke +# - Merged with modified version from github.com/ufz/ogs +# +# 2019-05-06, Anatolii Kurotych +# - Remove unnecessary --coverage flag +# +# 2019-12-13, FeRD (Frank Dana) +# - Deprecate COVERAGE_LCOVR_EXCLUDES and COVERAGE_GCOVR_EXCLUDES lists in favor +# of tool-agnostic COVERAGE_EXCLUDES variable, or EXCLUDE setup arguments. +# - CMake 3.4+: All excludes can be specified relative to BASE_DIRECTORY +# - All setup functions: accept BASE_DIRECTORY, EXCLUDE list +# - Set lcov basedir with -b argument +# - Add automatic --demangle-cpp in lcovr, if 'c++filt' is available (can be +# overridden with NO_DEMANGLE option in setup_target_for_coverage_lcovr().) +# - Delete output dir, .info file on 'make clean' +# - Remove Python detection, since version mismatches will break gcovr +# - Minor cleanup (lowercase function names, update examples...) +# +# 2019-12-19, FeRD (Frank Dana) +# - Rename Lcov outputs, make filtered file canonical, fix cleanup for targets +# +# 2020-01-19, Bob Apthorpe +# - Added gfortran support +# +# 2020-02-17, FeRD (Frank Dana) +# - Make all add_custom_target()s VERBATIM to auto-escape wildcard characters +# in EXCLUDEs, and remove manual escaping from gcovr targets +# +# 2021-01-19, Robin Mueller +# - Add CODE_COVERAGE_VERBOSE option which will allow to print out commands which are run +# - Added the option for users to set the GCOVR_ADDITIONAL_ARGS variable to supply additional +# flags to the gcovr command +# +# 2020-05-04, Mihchael Davis +# - Add -fprofile-abs-path to make gcno files contain absolute paths +# - Fix BASE_DIRECTORY not working when defined +# - Change BYPRODUCT from folder to index.html to stop ninja from complaining about double defines +# +# 2021-05-10, Martin Stump +# - Check if the generator is multi-config before warning about non-Debug builds +# +# 2022-02-22, Marko Wehle +# - Change gcovr output from -o for --xml and --html output respectively. +# This will allow for Multiple Output Formats at the same time by making use of GCOVR_ADDITIONAL_ARGS, e.g. GCOVR_ADDITIONAL_ARGS "--txt". +# +# 2022-09-28, Sebastian Mueller +# - fix append_coverage_compiler_flags_to_target to correctly add flags +# - replace "-fprofile-arcs -ftest-coverage" with "--coverage" (equivalent) +# +# USAGE: +# +# 1. Copy this file into your cmake modules path. +# +# 2. Add the following line to your CMakeLists.txt (best inside an if-condition +# using a CMake option() to enable it just optionally): +# include(CodeCoverage) +# +# 3. Append necessary compiler flags for all supported source files: +# append_coverage_compiler_flags() +# Or for specific target: +# append_coverage_compiler_flags_to_target(YOUR_TARGET_NAME) +# +# 3.a (OPTIONAL) Set appropriate optimization flags, e.g. -O0, -O1 or -Og +# +# 4. If you need to exclude additional directories from the report, specify them +# using full paths in the COVERAGE_EXCLUDES variable before calling +# setup_target_for_coverage_*(). +# Example: +# set(COVERAGE_EXCLUDES +# '${PROJECT_SOURCE_DIR}/src/dir1/*' +# '/path/to/my/src/dir2/*') +# Or, use the EXCLUDE argument to setup_target_for_coverage_*(). +# Example: +# setup_target_for_coverage_lcov( +# NAME coverage +# EXECUTABLE testrunner +# EXCLUDE "${PROJECT_SOURCE_DIR}/src/dir1/*" "/path/to/my/src/dir2/*") +# +# 4.a NOTE: With CMake 3.4+, COVERAGE_EXCLUDES or EXCLUDE can also be set +# relative to the BASE_DIRECTORY (default: PROJECT_SOURCE_DIR) +# Example: +# set(COVERAGE_EXCLUDES "dir1/*") +# setup_target_for_coverage_gcovr_html( +# NAME coverage +# EXECUTABLE testrunner +# BASE_DIRECTORY "${PROJECT_SOURCE_DIR}/src" +# EXCLUDE "dir2/*") +# +# 5. Use the functions described below to create a custom make target which +# runs your test executable and produces a code coverage report. +# +# 6. Build a Debug build: +# cmake -DCMAKE_BUILD_TYPE=Debug .. +# make +# make my_coverage_target +# + +include(CMakeParseArguments) + +option(CODE_COVERAGE_VERBOSE "Verbose information" FALSE) + +# Check prereqs +find_program( GCOV_PATH gcov ) +find_program( LCOV_PATH NAMES lcov lcov.bat lcov.exe lcov.perl) +find_program( FASTCOV_PATH NAMES fastcov fastcov.py ) +find_program( GENHTML_PATH NAMES genhtml genhtml.perl genhtml.bat ) +find_program( GCOVR_PATH gcovr PATHS ${CMAKE_SOURCE_DIR}/scripts/test) +find_program( CPPFILT_PATH NAMES c++filt ) + +if(NOT GCOV_PATH) + message(FATAL_ERROR "gcov not found! Aborting...") +endif() # NOT GCOV_PATH + +# Check supported compiler (Clang, GNU and Flang) +get_property(LANGUAGES GLOBAL PROPERTY ENABLED_LANGUAGES) +foreach(LANG ${LANGUAGES}) + if("${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(Apple)?[Cc]lang") + if("${CMAKE_${LANG}_COMPILER_VERSION}" VERSION_LESS 3) + message(FATAL_ERROR "Clang version must be 3.0.0 or greater! Aborting...") + endif() + elseif(NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "GNU" + AND NOT "${CMAKE_${LANG}_COMPILER_ID}" MATCHES "(LLVM)?[Ff]lang") + message(FATAL_ERROR "Compiler is not GNU or Flang! Aborting...") + endif() +endforeach() + +set(COVERAGE_COMPILER_FLAGS "-g --coverage -fprofile-update=atomic" + CACHE INTERNAL "") + +if(CMAKE_CXX_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCXXCompilerFlag) + check_cxx_compiler_flag(-fprofile-abs-path HAVE_cxx_fprofile_abs_path) + if(HAVE_cxx_fprofile_abs_path) + set(COVERAGE_CXX_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() +if(CMAKE_C_COMPILER_ID MATCHES "(GNU|Clang)") + include(CheckCCompilerFlag) + check_c_compiler_flag(-fprofile-abs-path HAVE_c_fprofile_abs_path) + if(HAVE_c_fprofile_abs_path) + set(COVERAGE_C_COMPILER_FLAGS "${COVERAGE_COMPILER_FLAGS} -fprofile-abs-path") + endif() +endif() + +set(CMAKE_Fortran_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the Fortran compiler during coverage builds." + FORCE ) +set(CMAKE_CXX_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C++ compiler during coverage builds." + FORCE ) +set(CMAKE_C_FLAGS_COVERAGE + ${COVERAGE_COMPILER_FLAGS} + CACHE STRING "Flags used by the C compiler during coverage builds." + FORCE ) +set(CMAKE_EXE_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used for linking binaries during coverage builds." + FORCE ) +set(CMAKE_SHARED_LINKER_FLAGS_COVERAGE + "" + CACHE STRING "Flags used by the shared libraries linker during coverage builds." + FORCE ) +mark_as_advanced( + CMAKE_Fortran_FLAGS_COVERAGE + CMAKE_CXX_FLAGS_COVERAGE + CMAKE_C_FLAGS_COVERAGE + CMAKE_EXE_LINKER_FLAGS_COVERAGE + CMAKE_SHARED_LINKER_FLAGS_COVERAGE ) + +get_property(GENERATOR_IS_MULTI_CONFIG GLOBAL PROPERTY GENERATOR_IS_MULTI_CONFIG) +if(NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG)) + message(WARNING "Code coverage results with an optimised (non-Debug) build may be misleading") +endif() # NOT (CMAKE_BUILD_TYPE STREQUAL "Debug" OR GENERATOR_IS_MULTI_CONFIG) + +if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + link_libraries(gcov) +endif() + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_lcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# ) +function(setup_target_for_coverage_lcov) + + set(options NO_DEMANGLE SONARQUBE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES LCOV_ARGS GENHTML_ARGS) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT LCOV_PATH) + message(FATAL_ERROR "lcov not found! Aborting...") + endif() # NOT LCOV_PATH + + if(NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() # NOT GENHTML_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(LCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_LCOV_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND LCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES LCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Setting up commands which will be run to generate coverage data. + # Cleanup lcov + set(LCOV_CLEAN_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -directory . + -b ${BASEDIR} --zerocounters + ) + # Create baseline to make sure untouched files show up in the report + set(LCOV_BASELINE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -c -i -d . -b + ${BASEDIR} -o ${Coverage_NAME}.base + ) + # Run tests + set(LCOV_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Capturing lcov counters and generating report + set(LCOV_CAPTURE_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --directory . -b + ${BASEDIR} --capture --output-file ${Coverage_NAME}.capture + ) + # add baseline counters + set(LCOV_BASELINE_COUNT_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} -a ${Coverage_NAME}.base + -a ${Coverage_NAME}.capture --output-file ${Coverage_NAME}.total + ) + # filter collected data to final coverage report + set(LCOV_FILTER_CMD + ${LCOV_PATH} ${Coverage_LCOV_ARGS} --gcov-tool ${GCOV_PATH} --remove + ${Coverage_NAME}.total ${LCOV_EXCLUDES} --output-file ${Coverage_NAME}.info + ) + # Generate HTML output + set(LCOV_GEN_HTML_CMD + ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} -o + ${Coverage_NAME} ${Coverage_NAME}.info + ) + if(${Coverage_SONARQUBE}) + # Generate SonarQube output + set(GCOVR_XML_CMD + ${GCOVR_PATH} --sonarqube ${Coverage_NAME}_sonarqube.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + set(GCOVR_XML_CMD_COMMAND + COMMAND ${GCOVR_XML_CMD} + ) + set(GCOVR_XML_CMD_BYPRODUCTS ${Coverage_NAME}_sonarqube.xml) + set(GCOVR_XML_CMD_COMMENT COMMENT "SonarQube code coverage info report saved in ${Coverage_NAME}_sonarqube.xml.") + endif() + + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + message(STATUS "Command to clean up lcov: ") + string(REPLACE ";" " " LCOV_CLEAN_CMD_SPACED "${LCOV_CLEAN_CMD}") + message(STATUS "${LCOV_CLEAN_CMD_SPACED}") + + message(STATUS "Command to create baseline: ") + string(REPLACE ";" " " LCOV_BASELINE_CMD_SPACED "${LCOV_BASELINE_CMD}") + message(STATUS "${LCOV_BASELINE_CMD_SPACED}") + + message(STATUS "Command to run the tests: ") + string(REPLACE ";" " " LCOV_EXEC_TESTS_CMD_SPACED "${LCOV_EXEC_TESTS_CMD}") + message(STATUS "${LCOV_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to capture counters and generate report: ") + string(REPLACE ";" " " LCOV_CAPTURE_CMD_SPACED "${LCOV_CAPTURE_CMD}") + message(STATUS "${LCOV_CAPTURE_CMD_SPACED}") + + message(STATUS "Command to add baseline counters: ") + string(REPLACE ";" " " LCOV_BASELINE_COUNT_CMD_SPACED "${LCOV_BASELINE_COUNT_CMD}") + message(STATUS "${LCOV_BASELINE_COUNT_CMD_SPACED}") + + message(STATUS "Command to filter collected data: ") + string(REPLACE ";" " " LCOV_FILTER_CMD_SPACED "${LCOV_FILTER_CMD}") + message(STATUS "${LCOV_FILTER_CMD_SPACED}") + + message(STATUS "Command to generate lcov HTML output: ") + string(REPLACE ";" " " LCOV_GEN_HTML_CMD_SPACED "${LCOV_GEN_HTML_CMD}") + message(STATUS "${LCOV_GEN_HTML_CMD_SPACED}") + + if(${Coverage_SONARQUBE}) + message(STATUS "Command to generate SonarQube XML output: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + COMMAND ${LCOV_CLEAN_CMD} + COMMAND ${LCOV_BASELINE_CMD} + COMMAND ${LCOV_EXEC_TESTS_CMD} + COMMAND ${LCOV_CAPTURE_CMD} + COMMAND ${LCOV_BASELINE_COUNT_CMD} + COMMAND ${LCOV_FILTER_CMD} + COMMAND ${LCOV_GEN_HTML_CMD} + ${GCOVR_XML_CMD_COMMAND} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.base + ${Coverage_NAME}.capture + ${Coverage_NAME}.total + ${Coverage_NAME}.info + ${GCOVR_XML_CMD_BYPRODUCTS} + ${Coverage_NAME}/index.html + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero.\nProcessing code coverage counters and generating report." + ) + + # Show where to find the lcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Lcov code coverage info report saved in ${Coverage_NAME}.info." + ${GCOVR_XML_CMD_COMMENT} + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_lcov + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_xml( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_xml) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_XML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Running gcovr + set(GCOVR_XML_CMD + ${GCOVR_PATH} --xml ${Coverage_NAME}.xml -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_XML_EXEC_TESTS_CMD_SPACED "${GCOVR_XML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_XML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to generate gcovr XML coverage data: ") + string(REPLACE ";" " " GCOVR_XML_CMD_SPACED "${GCOVR_XML_CMD}") + message(STATUS "${GCOVR_XML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_XML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_XML_CMD} + + BYPRODUCTS ${Coverage_NAME}.xml + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce Cobertura code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Cobertura code coverage report saved in ${Coverage_NAME}.xml." + ) +endfunction() # setup_target_for_coverage_gcovr_xml + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_gcovr_html( +# NAME ctest_coverage # New target name +# EXECUTABLE ctest -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES executable_target # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/*" "src/dir2/*" # Patterns to exclude (can be relative +# # to BASE_DIRECTORY, with CMake 3.4+) +# ) +# The user can set the variable GCOVR_ADDITIONAL_ARGS to supply additional flags to the +# GCVOR command. +function(setup_target_for_coverage_gcovr_html) + + set(options NONE) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT GCOVR_PATH) + message(FATAL_ERROR "gcovr not found! Aborting...") + endif() # NOT GCOVR_PATH + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(DEFINED Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (CMake 3.4+: Also compute absolute paths) + set(GCOVR_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_GCOVR_EXCLUDES}) + if(CMAKE_VERSION VERSION_GREATER 3.4) + get_filename_component(EXCLUDE ${EXCLUDE} ABSOLUTE BASE_DIR ${BASEDIR}) + endif() + list(APPEND GCOVR_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES GCOVR_EXCLUDES) + + # Combine excludes to several -e arguments + set(GCOVR_EXCLUDE_ARGS "") + foreach(EXCLUDE ${GCOVR_EXCLUDES}) + list(APPEND GCOVR_EXCLUDE_ARGS "-e") + list(APPEND GCOVR_EXCLUDE_ARGS "${EXCLUDE}") + endforeach() + + # Set up commands which will be run to generate coverage data + # Run tests + set(GCOVR_HTML_EXEC_TESTS_CMD + ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS} + ) + # Create folder + set(GCOVR_HTML_FOLDER_CMD + ${CMAKE_COMMAND} -E make_directory ${PROJECT_BINARY_DIR}/${Coverage_NAME} + ) + # Running gcovr + set(GCOVR_HTML_CMD + ${GCOVR_PATH} --html ${Coverage_NAME}/index.html --html-details -r ${BASEDIR} ${GCOVR_ADDITIONAL_ARGS} + ${GCOVR_EXCLUDE_ARGS} --object-directory=${PROJECT_BINARY_DIR} + ) + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Executed command report") + + message(STATUS "Command to run tests: ") + string(REPLACE ";" " " GCOVR_HTML_EXEC_TESTS_CMD_SPACED "${GCOVR_HTML_EXEC_TESTS_CMD}") + message(STATUS "${GCOVR_HTML_EXEC_TESTS_CMD_SPACED}") + + message(STATUS "Command to create a folder: ") + string(REPLACE ";" " " GCOVR_HTML_FOLDER_CMD_SPACED "${GCOVR_HTML_FOLDER_CMD}") + message(STATUS "${GCOVR_HTML_FOLDER_CMD_SPACED}") + + message(STATUS "Command to generate gcovr HTML coverage data: ") + string(REPLACE ";" " " GCOVR_HTML_CMD_SPACED "${GCOVR_HTML_CMD}") + message(STATUS "${GCOVR_HTML_CMD_SPACED}") + endif() + + add_custom_target(${Coverage_NAME} + COMMAND ${GCOVR_HTML_EXEC_TESTS_CMD} + COMMAND ${GCOVR_HTML_FOLDER_CMD} + COMMAND ${GCOVR_HTML_CMD} + + BYPRODUCTS ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html # report directory + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Running gcovr to produce HTML code coverage report." + ) + + # Show info where to find the report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ; + COMMENT "Open ./${Coverage_NAME}/index.html in your browser to view the coverage report." + ) + +endfunction() # setup_target_for_coverage_gcovr_html + +# Defines a target for running and collection code coverage information +# Builds dependencies, runs the given executable and outputs reports. +# NOTE! The executable should always have a ZERO as exit code otherwise +# the coverage generation will not complete. +# +# setup_target_for_coverage_fastcov( +# NAME testrunner_coverage # New target name +# EXECUTABLE testrunner -j ${PROCESSOR_COUNT} # Executable in PROJECT_BINARY_DIR +# DEPENDENCIES testrunner # Dependencies to build first +# BASE_DIRECTORY "../" # Base directory for report +# # (defaults to PROJECT_SOURCE_DIR) +# EXCLUDE "src/dir1/" "src/dir2/" # Patterns to exclude. +# NO_DEMANGLE # Don't demangle C++ symbols +# # even if c++filt is found +# SKIP_HTML # Don't create html report +# POST_CMD perl -i -pe s!${PROJECT_SOURCE_DIR}/!!g ctest_coverage.json # E.g. for stripping source dir from file paths +# ) +function(setup_target_for_coverage_fastcov) + + set(options NO_DEMANGLE SKIP_HTML) + set(oneValueArgs BASE_DIRECTORY NAME) + set(multiValueArgs EXCLUDE EXECUTABLE EXECUTABLE_ARGS DEPENDENCIES FASTCOV_ARGS GENHTML_ARGS POST_CMD) + cmake_parse_arguments(Coverage "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN}) + + if(NOT FASTCOV_PATH) + message(FATAL_ERROR "fastcov not found! Aborting...") + endif() + + if(NOT Coverage_SKIP_HTML AND NOT GENHTML_PATH) + message(FATAL_ERROR "genhtml not found! Aborting...") + endif() + + # Set base directory (as absolute path), or default to PROJECT_SOURCE_DIR + if(Coverage_BASE_DIRECTORY) + get_filename_component(BASEDIR ${Coverage_BASE_DIRECTORY} ABSOLUTE) + else() + set(BASEDIR ${PROJECT_SOURCE_DIR}) + endif() + + # Collect excludes (Patterns, not paths, for fastcov) + set(FASTCOV_EXCLUDES "") + foreach(EXCLUDE ${Coverage_EXCLUDE} ${COVERAGE_EXCLUDES} ${COVERAGE_FASTCOV_EXCLUDES}) + list(APPEND FASTCOV_EXCLUDES "${EXCLUDE}") + endforeach() + list(REMOVE_DUPLICATES FASTCOV_EXCLUDES) + + # Conditional arguments + if(CPPFILT_PATH AND NOT ${Coverage_NO_DEMANGLE}) + set(GENHTML_EXTRA_ARGS "--demangle-cpp") + endif() + + # Set up commands which will be run to generate coverage data + set(FASTCOV_EXEC_TESTS_CMD ${Coverage_EXECUTABLE} ${Coverage_EXECUTABLE_ARGS}) + + set(FASTCOV_CAPTURE_CMD ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --process-gcno + --output ${Coverage_NAME}.json + --exclude ${FASTCOV_EXCLUDES} + ) + + set(FASTCOV_CONVERT_CMD ${FASTCOV_PATH} + -C ${Coverage_NAME}.json --lcov --output ${Coverage_NAME}.info + ) + + if(Coverage_SKIP_HTML) + set(FASTCOV_HTML_CMD ";") + else() + set(FASTCOV_HTML_CMD ${GENHTML_PATH} ${GENHTML_EXTRA_ARGS} ${Coverage_GENHTML_ARGS} + -o ${Coverage_NAME} ${Coverage_NAME}.info + ) + endif() + + set(FASTCOV_POST_CMD ";") + if(Coverage_POST_CMD) + set(FASTCOV_POST_CMD ${Coverage_POST_CMD}) + endif() + + if(CODE_COVERAGE_VERBOSE) + message(STATUS "Code coverage commands for target ${Coverage_NAME} (fastcov):") + + message(" Running tests:") + string(REPLACE ";" " " FASTCOV_EXEC_TESTS_CMD_SPACED "${FASTCOV_EXEC_TESTS_CMD}") + message(" ${FASTCOV_EXEC_TESTS_CMD_SPACED}") + + message(" Capturing fastcov counters and generating report:") + string(REPLACE ";" " " FASTCOV_CAPTURE_CMD_SPACED "${FASTCOV_CAPTURE_CMD}") + message(" ${FASTCOV_CAPTURE_CMD_SPACED}") + + message(" Converting fastcov .json to lcov .info:") + string(REPLACE ";" " " FASTCOV_CONVERT_CMD_SPACED "${FASTCOV_CONVERT_CMD}") + message(" ${FASTCOV_CONVERT_CMD_SPACED}") + + if(NOT Coverage_SKIP_HTML) + message(" Generating HTML report: ") + string(REPLACE ";" " " FASTCOV_HTML_CMD_SPACED "${FASTCOV_HTML_CMD}") + message(" ${FASTCOV_HTML_CMD_SPACED}") + endif() + if(Coverage_POST_CMD) + message(" Running post command: ") + string(REPLACE ";" " " FASTCOV_POST_CMD_SPACED "${FASTCOV_POST_CMD}") + message(" ${FASTCOV_POST_CMD_SPACED}") + endif() + endif() + + # Setup target + add_custom_target(${Coverage_NAME} + + # Cleanup fastcov + COMMAND ${FASTCOV_PATH} ${Coverage_FASTCOV_ARGS} --gcov ${GCOV_PATH} + --search-directory ${BASEDIR} + --zerocounters + + COMMAND ${FASTCOV_EXEC_TESTS_CMD} + COMMAND ${FASTCOV_CAPTURE_CMD} + COMMAND ${FASTCOV_CONVERT_CMD} + COMMAND ${FASTCOV_HTML_CMD} + COMMAND ${FASTCOV_POST_CMD} + + # Set output files as GENERATED (will be removed on 'make clean') + BYPRODUCTS + ${Coverage_NAME}.info + ${Coverage_NAME}.json + ${Coverage_NAME}/index.html # report directory + + WORKING_DIRECTORY ${PROJECT_BINARY_DIR} + DEPENDS ${Coverage_DEPENDENCIES} + VERBATIM # Protect arguments to commands + COMMENT "Resetting code coverage counters to zero. Processing code coverage counters and generating report." + ) + + set(INFO_MSG "fastcov code coverage info report saved in ${Coverage_NAME}.info and ${Coverage_NAME}.json.") + if(NOT Coverage_SKIP_HTML) + string(APPEND INFO_MSG " Open ${PROJECT_BINARY_DIR}/${Coverage_NAME}/index.html in your browser to view the coverage report.") + endif() + # Show where to find the fastcov info report + add_custom_command(TARGET ${Coverage_NAME} POST_BUILD + COMMAND ${CMAKE_COMMAND} -E echo ${INFO_MSG} + ) + +endfunction() # setup_target_for_coverage_fastcov + +function(append_coverage_compiler_flags) + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + set(CMAKE_Fortran_FLAGS "${CMAKE_Fortran_FLAGS} ${COVERAGE_COMPILER_FLAGS}" PARENT_SCOPE) + message(STATUS "Appending code coverage compiler flags: ${COVERAGE_COMPILER_FLAGS}") +endfunction() # append_coverage_compiler_flags + +# Setup coverage for specific library +function(append_coverage_compiler_flags_to_target name) + separate_arguments(_flag_list NATIVE_COMMAND "${COVERAGE_COMPILER_FLAGS}") + target_compile_options(${name} PRIVATE ${_flag_list}) + if(CMAKE_C_COMPILER_ID STREQUAL "GNU" OR CMAKE_CXX_COMPILER_ID STREQUAL "GNU" OR CMAKE_Fortran_COMPILER_ID STREQUAL "GNU") + target_link_libraries(${name} PRIVATE gcov) + endif() +endfunction() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/DefineCMakeDefaults.cmake b/src/libs/libssh-0.12.2/cmake/Modules/DefineCMakeDefaults.cmake new file mode 100644 index 000000000000..6f369faaa262 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/DefineCMakeDefaults.cmake @@ -0,0 +1,21 @@ +# Always include srcdir and builddir in include path +# This saves typing ${CMAKE_CURRENT_SOURCE_DIR} ${CMAKE_CURRENT_BINARY} in +# about every subdir +# since cmake 2.4.0 +set(CMAKE_INCLUDE_CURRENT_DIR ON) + +# Put the include dirs which are in the source or build tree +# before all other include dirs, so the headers in the sources +# are preferred over the already installed ones +# since cmake 2.4.1 +set(CMAKE_INCLUDE_DIRECTORIES_PROJECT_BEFORE ON) + +# Use colored output +# since cmake 2.4.0 +set(CMAKE_COLOR_MAKEFILE ON) + +# Create the compile command database for clang by default +set(CMAKE_EXPORT_COMPILE_COMMANDS ON) + +# Always build with -fPIC +set(CMAKE_POSITION_INDEPENDENT_CODE ON) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/DefineCompilerFlags.cmake b/src/libs/libssh-0.12.2/cmake/Modules/DefineCompilerFlags.cmake new file mode 100644 index 000000000000..39378a10ed25 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/DefineCompilerFlags.cmake @@ -0,0 +1,49 @@ +if (UNIX AND NOT WIN32) + # Activate with: -DCMAKE_BUILD_TYPE=Profiling + set(CMAKE_C_FLAGS_PROFILING "-O0 -g -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the C compiler during PROFILING builds.") + set(CMAKE_CXX_FLAGS_PROFILING "-O0 -g -fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the CXX compiler during PROFILING builds.") + set(CMAKE_SHARED_LINKER_FLAGS_PROFILING "-fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the linker during the creation of shared libraries during PROFILING builds.") + set(CMAKE_MODULE_LINKER_FLAGS_PROFILING "-fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the linker during the creation of shared libraries during PROFILING builds.") + set(CMAKE_EXEC_LINKER_FLAGS_PROFILING "-fprofile-arcs -ftest-coverage" + CACHE STRING "Flags used by the linker during PROFILING builds.") + + # Activate with: -DCMAKE_BUILD_TYPE=AddressSanitizer + set(CMAKE_C_FLAGS_ADDRESSSANITIZER "-g -O1 -fsanitize=address -fno-omit-frame-pointer" + CACHE STRING "Flags used by the C compiler during ADDRESSSANITIZER builds.") + set(CMAKE_CXX_FLAGS_ADDRESSSANITIZER "-g -O1 -fsanitize=address -fno-omit-frame-pointer" + CACHE STRING "Flags used by the CXX compiler during ADDRESSSANITIZER builds.") + set(CMAKE_SHARED_LINKER_FLAGS_ADDRESSSANITIZER "-fsanitize=address" + CACHE STRING "Flags used by the linker during the creation of shared libraries during ADDRESSSANITIZER builds.") + set(CMAKE_MODULE_LINKER_FLAGS_ADDRESSSANITIZER "-fsanitize=address" + CACHE STRING "Flags used by the linker during the creation of shared libraries during ADDRESSSANITIZER builds.") + set(CMAKE_EXEC_LINKER_FLAGS_ADDRESSSANITIZER "-fsanitize=address" + CACHE STRING "Flags used by the linker during ADDRESSSANITIZER builds.") + + # Activate with: -DCMAKE_BUILD_TYPE=MemorySanitizer + set(CMAKE_C_FLAGS_MEMORYSANITIZER "-g -O2 -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" + CACHE STRING "Flags used by the C compiler during MEMORYSANITIZER builds.") + set(CMAKE_CXX_FLAGS_MEMORYSANITIZER "-g -O2 -fsanitize=memory -fsanitize-memory-track-origins=2 -fno-omit-frame-pointer" + CACHE STRING "Flags used by the CXX compiler during MEMORYSANITIZER builds.") + set(CMAKE_SHARED_LINKER_FLAGS_MEMORYSANITIZER "-fsanitize=memory" + CACHE STRING "Flags used by the linker during the creation of shared libraries during MEMORYSANITIZER builds.") + set(CMAKE_MODULE_LINKER_FLAGS_MEMORYSANITIZER "-fsanitize=memory" + CACHE STRING "Flags used by the linker during the creation of shared libraries during MEMORYSANITIZER builds.") + set(CMAKE_EXEC_LINKER_FLAGS_MEMORYSANITIZER "-fsanitize=memory" + CACHE STRING "Flags used by the linker during MEMORYSANITIZER builds.") + + # Activate with: -DCMAKE_BUILD_TYPE=UndefinedSanitizer + set(CMAKE_C_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover=undefined,integer" + CACHE STRING "Flags used by the C compiler during UNDEFINEDSANITIZER builds.") + set(CMAKE_CXX_FLAGS_UNDEFINEDSANITIZER "-g -O1 -fsanitize=undefined -fsanitize=null -fsanitize=alignment -fno-sanitize-recover=undefined,integer" + CACHE STRING "Flags used by the CXX compiler during UNDEFINEDSANITIZER builds.") + set(CMAKE_SHARED_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" + CACHE STRING "Flags used by the linker during the creation of shared libraries during UNDEFINEDSANITIZER builds.") + set(CMAKE_MODULE_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" + CACHE STRING "Flags used by the linker during the creation of shared libraries during UNDEFINEDSANITIZER builds.") + set(CMAKE_EXEC_LINKER_FLAGS_UNDEFINEDSANITIZER "-fsanitize=undefined" + CACHE STRING "Flags used by the linker during UNDEFINEDSANITIZER builds.") +endif() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/DefinePlatformDefaults.cmake b/src/libs/libssh-0.12.2/cmake/Modules/DefinePlatformDefaults.cmake new file mode 100644 index 000000000000..77f8a4618562 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/DefinePlatformDefaults.cmake @@ -0,0 +1,32 @@ +# Set system vars + +if (CMAKE_SYSTEM_NAME MATCHES "Linux") + set(LINUX TRUE) +endif(CMAKE_SYSTEM_NAME MATCHES "Linux") + +if (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + set(FREEBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "FreeBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + set(OPENBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OpenBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + set(NETBSD TRUE) + set(BSD TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "NetBSD") + +if (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + set(SOLARIS TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "(Solaris|SunOS)") + +if (CMAKE_SYSTEM_NAME MATCHES "OS2") + set(OS2 TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "OS2") + +if (CMAKE_SYSTEM_NAME MATCHES "Darwin") + set (OSX TRUE) +endif (CMAKE_SYSTEM_NAME MATCHES "Darwin") diff --git a/src/libs/libssh-0.12.2/cmake/Modules/ExtractSymbols.cmake b/src/libs/libssh-0.12.2/cmake/Modules/ExtractSymbols.cmake new file mode 100644 index 000000000000..95c850b66442 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/ExtractSymbols.cmake @@ -0,0 +1,105 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# ExtractSymbols +# -------------- +# +# This is a helper script for FindABImap.cmake. +# +# Extract symbols from header files and output a list to a file. +# This script is run in build time to extract symbols from the provided header +# files. This way, symbols added or removed can be checked and used to update +# the symbol version script. +# +# All symbols followed by the character ``'('`` are extracted. If a +# ``FILTER_PATTERN`` is provided, only the lines containing the given string are +# considered. +# +# Expected defined variables +# -------------------------- +# +# ``HEADERS_LIST_FILE``: +# Required, expects a file containing the list of header files to be parsed. +# +# ``OUTPUT_PATH``: +# Required, expects the output file path. +# +# Optionally defined variables +# ---------------------------- +# +# ``FILTER_PATTERN``: +# Expects a string. Only lines containing the given string will be considered +# when extracting symbols. +# + +if (NOT DEFINED OUTPUT_PATH) + message(SEND_ERROR "OUTPUT_PATH not defined") +endif() + +if (NOT DEFINED HEADERS_LIST_FILE) + message(SEND_ERROR "HEADERS not defined") +endif() + +file(READ ${HEADERS_LIST_FILE} HEADERS_LIST) + +set(symbols) +foreach(header ${HEADERS_LIST}) + file(READ ${header} header_content) + + # Filter only lines containing the FILTER_PATTERN + # separated from the function name with one optional newline + string(REGEX MATCHALL + "${FILTER_PATTERN}[^(\n]*\n?[^(\n]*[(]" + contain_filter + "${header_content}" + ) + + # Remove the optional newline now + string(REGEX REPLACE + "(.+)\n?(.*)" + "\\1\\2" + oneline + "${contain_filter}" + ) + + # Remove function-like macros + # and anything with two underscores that sounds suspicious + foreach(line ${oneline}) + if (NOT ${line} MATCHES ".*(#[ ]*define|__)") + list(APPEND not_macro ${line}) + endif() + endforeach() + + set(functions) + + # Get only the function names followed by '(' + foreach(line ${not_macro}) + string(REGEX MATCHALL "[a-zA-Z0-9_]+[ ]*[(]" func ${line}) + list(APPEND functions ${func}) + endforeach() + + set(extracted_symbols) + + # Remove '(' + foreach(line ${functions}) + string(REGEX REPLACE "[(]" "" symbol ${line}) + string(STRIP "${symbol}" symbol) + list(APPEND extracted_symbols ${symbol}) + endforeach() + + list(APPEND symbols ${extracted_symbols}) +endforeach() + +list(REMOVE_DUPLICATES symbols) + +list(SORT symbols) + +string(REPLACE ";" "\n" symbols_list "${symbols}") + +file(WRITE ${OUTPUT_PATH} "${symbols_list}") diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindABIMap.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindABIMap.cmake new file mode 100644 index 000000000000..e7f725d216b3 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindABIMap.cmake @@ -0,0 +1,491 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# FindABIMap +# ---------- +# +# This file provides functions to generate the symbol version script. It uses +# the ``abimap`` tool to generate and update the linker script file. It can be +# installed by calling:: +# +# $ pip install abimap +# +# The ``function generate_map_file`` generates a symbol version script +# containing the provided symbols. It defines a custom command which sets +# ``target_name`` as its ``OUTPUT``. +# +# The experimental function ``extract_symbols()`` is provided as a simple +# parser to extract the symbols from C header files. It simply extracts symbols +# followed by an opening '``(``'. It is recommended to use a filter pattern to +# select the lines to be considered. It defines a custom command which sets +# ``target_name`` as its output. +# +# The helper function ``get_files_list()`` is provided to find files given a +# name pattern. It defines a custom command which sets ``target_name`` as its +# output. +# +# Functions provided +# ------------------ +# +# :: +# +# generate_map_file(target_name +# RELEASE_NAME_VERSION release_name +# SYMBOLS symbols_target +# [CURRENT_MAP cur_map] +# [FINAL] +# [BREAK_ABI] +# [COPY_TO output] +# ) +# +# ``target_name``: +# Required, expects the name of the file to receive the generated symbol +# version script. It should be added as a dependency for the library. Use the +# linker option ``--version-script filename`` to add the version information +# to the symbols when building the library. +# +# ``RELEASE_NAME_VERSION``: +# Required, expects a string containing the name and version information to be +# added to the symbols in the format ``lib_name_1_2_3``. +# +# ``SYMBOLS``: +# Required, expects a target with the property ``LIST_FILE`` containing a path +# to a file containing the list of symbols to be added to the symbol version +# script. +# +# ``CURRENT_MAP``: +# Optional. If given, the new set of symbols will be checked against the +# ones contained in the ``cur_map`` file and updated properly. If an +# incompatible change is detected and ``BREAK_ABI`` is not defined, the build +# will fail. +# +# ``FINAL``: +# Optional. If given, will provide the ``--final`` option to ``abimap`` tool, +# which will mark the modified release in the symbol version script with a +# special comment, preventing later changes. This option should be set when +# creating a library release and the resulting map file should be stored with +# the source code. +# +# ``BREAK_ABI``: +# Optional. If provided, will use ``abimap`` ``--allow-abi-break`` option, which +# accepts incompatible changes to the set of symbols. This is necessary if any +# previously existing symbol were removed. +# +# ``COPY_TO``: +# Optional, expects a string containing the path to where the generated +# map file will be copied. +# +# Example: +# +# .. code-block:: cmake +# +# find_package(ABIMap) +# generate_map_file("lib.map" +# RELEASE_NAME_VERSION "lib_1_0_0" +# SYMBOLS symbols +# ) +# +# Where the target ``symbols`` has its property ``LIST_FILE`` set to the path to +# a file containing:: +# +# ``symbol1`` +# ``symbol2`` +# +# This example would result in the symbol version script to be created in +# ``${CMAKE_CURRENT_BINARY_DIR}/lib.map`` containing the provided symbols. +# +# :: +# +# get_files_list(target_name +# DIRECTORIES dir1 [dir2 ...] +# FILES_PATTERNS exp1 [exp2 ...] +# [COPY_TO output] +# ) +# +# ``target_name``: +# Required, expects the name of the target to be created. A file named as +# ``${target_name}.list`` will be created in +# ``${CMAKE_CURRENT_BINARY_DIR}`` to receive the list of files found. +# +# ``DIRECTORIES``: +# Required, expects a list of directories paths. Only absolute paths are +# supported. +# +# ``FILES_PATTERN``: +# Required, expects a list of matching expressions to find the files to be +# considered in the directories. +# +# ``COPY_TO``: +# Optional, expects a string containing the path to where the file containing +# the list of files will be copied. +# +# This command searches the directories provided in ``DIRECTORIES`` for files +# matching any of the patterns provided in ``FILES_PATTERNS``. The obtained list +# is written to the path specified by ``output``. A target named ``target_name`` +# will be created and its property ``LIST_FILE`` will be set to contain +# ``${CMAKE_CURRENT_BINARY_DIR}/${target_name}.list`` +# +# Example: +# +# .. code-block:: cmake +# +# find_package(ABIMap) +# get_files_list(target +# DIRECTORIES "/include/mylib" +# FILES_PATTERNS "*.h" +# COPY_TO "my_list.txt" +# ) +# +# Consider that ``/include/mylib`` contains 3 files, ``h1.h``, ``h2.h``, and +# ``h3.hpp`` +# +# Will result in a file ``my_list.txt`` containing:: +# +# ``h1.h;h2.h`` +# +# And the target ``target`` will have its property ``LIST_FILE`` set to contain +# ``${CMAKE_CURRENT_BINARY_DIR}/target.list`` +# +# :: +# +# extract_symbols(target_name +# HEADERS_LIST headers_list_target +# [FILTER_PATTERN pattern] +# [COPY_TO output] +# ) +# +# ``target_name``: +# Required, expects the name of the target to be created. A file named after +# the string given in ``target_name`` will be created in +# ``${CMAKE_CURRENT_BINARY_DIR}`` to receive the list of symbols. +# +# ``HEADERS_LIST``: +# Required, expects a target with the property ``LIST_FILE`` set, containing a +# file path. Such file must contain a list of files paths. +# +# ``FILTER_PATTERN``: +# Optional, expects a string. Only the lines containing the filter pattern +# will be considered. +# +# ``COPY_TO``: +# Optional, expects a string containing the path to where the file containing +# the found symbols will be copied. +# +# This command extracts the symbols from the files listed in +# ``headers_list`` and write them on the ``output`` file. If ``pattern`` +# is provided, then only the lines containing the string given in ``pattern`` +# will be considered. It is recommended to provide a ``FILTER_PATTERN`` to mark +# the lines containing exported function declaration, since this function is +# experimental and can return wrong symbols when parsing the header files. A +# target named ``target_name`` will be created with the property ``LIST_FILE`` +# set to contain ``${CMAKE_CURRENT_BINARY_DIR}/${target_name}.list``. +# +# Example: +# +# .. code-block:: cmake +# +# find_package(ABIMap) +# extract_symbols("lib.symbols" +# HEADERS_LIST "headers_target" +# FILTER_PATTERN "API_FUNCTION" +# ) +# +# Where ``LIST_FILE`` property in ``headers_target`` points to a file +# containing:: +# +# header1.h;header2.h +# +# Where ``header1.h`` contains:: +# +# API_FUNCTION int exported_func1(int a, int b); +# +# ``header2.h`` contains:: +# +# API_FUNCTION int exported_func2(int a); +# +# int private_func2(int b); +# +# Will result in a file ``lib.symbols.list`` in ``${CMAKE_CURRENT_BINARY_DIR}`` +# containing:: +# +# ``exported_func1`` +# ``exported_func2`` +# + +# Search for python which is required +if (ABIMap_FIND_REQURIED) + find_package(Python REQUIRED) +else() + find_package(Python) +endif() + +if (TARGET Python::Interpreter) + # Search for abimap tool used to generate the map files + find_program(ABIMAP_EXECUTABLE NAMES abimap DOC "path to the abimap executable") + mark_as_advanced(ABIMAP_EXECUTABLE) + + if (NOT ABIMAP_EXECUTABLE AND UNIX) + message(STATUS "Could not find `abimap` in PATH." + " It can be found in PyPI as `abimap`" + " (try `pip install abimap`)") + endif () + + if (ABIMAP_EXECUTABLE) + # Get the abimap version + execute_process(COMMAND ${ABIMAP_EXECUTABLE} version + OUTPUT_VARIABLE ABIMAP_VERSION_STRING + OUTPUT_STRIP_TRAILING_WHITESPACE) + + # If the version string starts with abimap-, strip it + if ("abimap" STRLESS_EQUAL ${ABIMAP_VERSION_STRING}) + string(REGEX REPLACE "abimap-" "" ABIMAP_VERSION_STRING "${ABIMAP_VERSION_STRING}") + endif() + endif() + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(ABIMap + REQUIRED_VARS ABIMAP_EXECUTABLE + VERSION_VAR ABIMAP_VERSION_STRING) +endif() + + +if (ABIMAP_FOUND) + +# Define helper scripts +set(_EXTRACT_SYMBOLS_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/ExtractSymbols.cmake) +set(_GENERATE_MAP_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/GenerateMap.cmake) +set(_GET_FILES_LIST_SCRIPT ${CMAKE_CURRENT_LIST_DIR}/GetFilesList.cmake) + +function(get_file_list _TARGET_NAME) + + set(one_value_arguments + COPY_TO + ) + + set(multi_value_arguments + DIRECTORIES + FILES_PATTERNS + ) + + cmake_parse_arguments(_get_files_list + "" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + # The DIRS argument is required + if (NOT DEFINED _get_files_list_DIRECTORIES) + message(FATAL_ERROR "No directories paths provided. Provide a list of" + " directories paths containing header files.") + endif() + + # The FILES_PATTERNS argument is required + if (NOT DEFINED _get_files_list_FILES_PATTERNS) + message(FATAL_ERROR "No matching expressions provided. Provide a list" + " of matching patterns for the header files.") + endif() + + set(_FILES_LIST_OUTPUT_PATH ${CMAKE_CURRENT_BINARY_DIR}/${_TARGET_NAME}.list) + + get_filename_component(_get_files_list_OUTPUT_PATH + "${_FILES_LIST_OUTPUT_PATH}" + ABSOLUTE) + + add_custom_target( + ${_TARGET_NAME}_int ALL + COMMAND ${CMAKE_COMMAND} + -DOUTPUT_PATH=${_get_files_list_OUTPUT_PATH} + -DDIRECTORIES=${_get_files_list_DIRECTORIES} + -DFILES_PATTERNS=${_get_files_list_FILES_PATTERNS} + -P ${_GET_FILES_LIST_SCRIPT} + COMMENT + "Searching for files" + VERBATIM + ) + + if (DEFINED _get_files_list_COPY_TO) + # Copy the generated file back to the COPY_TO + add_custom_target(${_TARGET_NAME} ALL + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${_FILES_LIST_OUTPUT_PATH} ${_get_files_list_COPY_TO} + DEPENDS ${_TARGET_NAME}_int + COMMENT "Copying ${_TARGET_NAME} to ${_get_files_list_COPY_TO}" + VERBATIM + ) + else() + add_custom_target(${_TARGET_NAME} ALL + DEPENDS ${_TARGET_NAME}_int + ) + endif() + + set_target_properties(${_TARGET_NAME} + PROPERTIES LIST_FILE ${_FILES_LIST_OUTPUT_PATH} + ) + +endfunction() + +function(extract_symbols _TARGET_NAME) + + set(one_value_arguments + FILTER_PATTERN + HEADERS_LIST + COPY_TO + ) + + set(multi_value_arguments + ) + + cmake_parse_arguments(_extract_symbols + "" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + # The HEADERS_LIST_FILE argument is required + if (NOT DEFINED _extract_symbols_HEADERS_LIST) + message(FATAL_ERROR "No target provided in HEADERS_LIST. Provide a" + " target with the property LIST_FILE set as the" + " path to the file containing the list of headers.") + endif() + + get_filename_component(_SYMBOLS_OUTPUT_PATH + "${CMAKE_CURRENT_BINARY_DIR}/${_TARGET_NAME}.list" + ABSOLUTE + ) + + get_target_property(_HEADERS_LIST_FILE + ${_extract_symbols_HEADERS_LIST} + LIST_FILE + ) + + add_custom_target( + ${_TARGET_NAME}_int ALL + COMMAND ${CMAKE_COMMAND} + -DOUTPUT_PATH=${_SYMBOLS_OUTPUT_PATH} + -DHEADERS_LIST_FILE=${_HEADERS_LIST_FILE} + -DFILTER_PATTERN=${_extract_symbols_FILTER_PATTERN} + -P ${_EXTRACT_SYMBOLS_SCRIPT} + DEPENDS ${_extract_symbols_HEADERS_LIST} + COMMENT "Extracting symbols from headers" + VERBATIM + ) + + if (DEFINED _extract_symbols_COPY_TO) + # Copy the generated file back to the COPY_TO + add_custom_target(${_TARGET_NAME} ALL + COMMAND + ${CMAKE_COMMAND} -E copy_if_different + ${_SYMBOLS_OUTPUT_PATH} ${_extract_symbols_COPY_TO} + DEPENDS ${_TARGET_NAME}_int + COMMENT "Copying ${_TARGET_NAME} to ${_extract_symbols_COPY_TO}" + VERBATIM + ) + else() + add_custom_target(${_TARGET_NAME} ALL + DEPENDS ${_TARGET_NAME}_int + ) + endif() + + set_target_properties(${_TARGET_NAME} + PROPERTIES LIST_FILE ${_SYMBOLS_OUTPUT_PATH} + ) + +endfunction() + +function(generate_map_file _TARGET_NAME) + + set(options + FINAL + BREAK_ABI + ) + + set(one_value_arguments + RELEASE_NAME_VERSION + SYMBOLS + CURRENT_MAP + COPY_TO + ) + + set(multi_value_arguments + ) + + cmake_parse_arguments(_generate_map_file + "${options}" + "${one_value_arguments}" + "${multi_value_arguments}" + ${ARGN} + ) + + if (NOT DEFINED _generate_map_file_SYMBOLS) + message(FATAL_ERROR "No target provided in SYMBOLS. Provide a target" + " with the property LIST_FILE set as the path to" + " the file containing the list of symbols.") + endif() + + if (NOT DEFINED _generate_map_file_RELEASE_NAME_VERSION) + message(FATAL_ERROR "Release name and version not provided." + " (e.g. libname_1_0_0)") + endif() + + + get_target_property(_SYMBOLS_FILE + ${_generate_map_file_SYMBOLS} + LIST_FILE + ) + + # Set generated map file path + get_filename_component(_MAP_OUTPUT_PATH + "${CMAKE_CURRENT_BINARY_DIR}/${_TARGET_NAME}" + ABSOLUTE + ) + + add_custom_target( + ${_TARGET_NAME}_int ALL + COMMAND ${CMAKE_COMMAND} + -DABIMAP_EXECUTABLE=${ABIMAP_EXECUTABLE} + -DSYMBOLS=${_SYMBOLS_FILE} + -DCURRENT_MAP=${_generate_map_file_CURRENT_MAP} + -DOUTPUT_PATH=${_MAP_OUTPUT_PATH} + -DFINAL=${_generate_map_file_FINAL} + -DBREAK_ABI=${_generate_map_file_BREAK_ABI} + -DRELEASE_NAME_VERSION=${_generate_map_file_RELEASE_NAME_VERSION} + -P ${_GENERATE_MAP_SCRIPT} + DEPENDS ${_generate_map_file_SYMBOLS} + COMMENT "Generating the map ${_TARGET_NAME}" + VERBATIM + ) + + # Add a custom command setting the map as OUTPUT to allow it to be added as + # a generated source + add_custom_command( + OUTPUT ${_MAP_OUTPUT_PATH} + DEPENDS ${_TARGET_NAME}_copy + ) + + if (DEFINED _generate_map_file_COPY_TO) + # Copy the generated map back to the COPY_TO + add_custom_target(${_TARGET_NAME}_copy ALL + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${_MAP_OUTPUT_PATH} + ${_generate_map_file_COPY_TO} + DEPENDS ${_TARGET_NAME}_int + COMMENT "Copying ${_MAP_OUTPUT_PATH} to ${_generate_map_file_COPY_TO}" + VERBATIM + ) + else() + add_custom_target(${_TARGET_NAME}_copy ALL + DEPENDS ${_TARGET_NAME}_int + ) + endif() +endfunction() + +endif (ABIMAP_FOUND) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindArgp.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindArgp.cmake new file mode 100644 index 000000000000..13d74637699b --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindArgp.cmake @@ -0,0 +1,70 @@ +# - Try to find ARGP +# +# The argp can be either shipped as part of libc (ex. glibc) or as a separate +# library that requires additional linking (ex. Windows, Mac, musl libc, ...) +# +# Once done this will define +# +# ARGP_ROOT_DIR - Set this variable to the root installation of ARGP +# +# Read-Only variables: +# ARGP_FOUND - system has ARGP +# ARGP_INCLUDE_DIR - the ARGP include directory +# ARGP_LIBRARIES - Link these to use ARGP +# ARGP_DEFINITIONS - Compiler switches required for using ARGP +# +#============================================================================= +# Copyright (c) 2011-2016 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_ARGP_ROOT_HINTS +) + +set(_ARGP_ROOT_PATHS + "$ENV{PROGRAMFILES}/argp" +) + +find_path(ARGP_ROOT_DIR + NAMES + include/argp.h + HINTS + ${_ARGP_ROOT_HINTS} + PATHS + ${_ARGP_ROOT_PATHS} +) +mark_as_advanced(ARGP_ROOT_DIR) + +find_path(ARGP_INCLUDE_DIR + NAMES + argp.h + PATHS + ${ARGP_ROOT_DIR}/include +) + +find_library(ARGP_LIBRARY + NAMES + argp + PATHS + ${ARGP_ROOT_DIR}/lib +) + +if (ARGP_LIBRARY) + set(ARGP_LIBRARIES + ${ARGP_LIBRARIES} + ${ARGP_LIBRARY} + ) +endif (ARGP_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(Argp DEFAULT_MSG ARGP_LIBRARIES ARGP_INCLUDE_DIR) + +# show the ARGP_INCLUDE_DIR and ARGP_LIBRARIES variables only in the advanced view +mark_as_advanced(ARGP_INCLUDE_DIR ARGP_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindCMocka.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindCMocka.cmake new file mode 100644 index 000000000000..76b4ba74d7a5 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindCMocka.cmake @@ -0,0 +1,66 @@ +# - Try to find CMocka +# Once done this will define +# +# CMOCKA_ROOT_DIR - Set this variable to the root installation of CMocka +# +# Read-Only variables: +# CMOCKA_FOUND - system has CMocka +# CMOCKA_INCLUDE_DIR - the CMocka include directory +# CMOCKA_LIBRARIES - Link these to use CMocka +# CMOCKA_DEFINITIONS - Compiler switches required for using CMocka +# +#============================================================================= +# Copyright (c) 2011-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_CMOCKA_ROOT_HINTS +) + +set(_CMOCKA_ROOT_PATHS + "$ENV{PROGRAMFILES}/cmocka" +) + +find_path(CMOCKA_ROOT_DIR + NAMES + include/cmocka.h + HINTS + ${_CMOCKA_ROOT_HINTS} + PATHS + ${_CMOCKA_ROOT_PATHS} +) +mark_as_advanced(CMOCKA_ROOT_DIR) + +find_path(CMOCKA_INCLUDE_DIR + NAMES + cmocka.h + PATHS + ${CMOCKA_ROOT_DIR}/include +) + +find_library(CMOCKA_LIBRARY + NAMES + cmocka + PATHS + ${CMOCKA_ROOT_DIR}/lib +) + +if (CMOCKA_LIBRARY) + set(CMOCKA_LIBRARIES + ${CMOCKA_LIBRARIES} + ${CMOCKA_LIBRARY} + ) +endif (CMOCKA_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(CMocka DEFAULT_MSG CMOCKA_LIBRARIES CMOCKA_INCLUDE_DIR) + +# show the CMOCKA_INCLUDE_DIR and CMOCKA_LIBRARIES variables only in the advanced view +mark_as_advanced(CMOCKA_INCLUDE_DIR CMOCKA_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindGCrypt.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindGCrypt.cmake new file mode 100644 index 000000000000..e28cb8462d4a --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindGCrypt.cmake @@ -0,0 +1,118 @@ +# - Try to find GCrypt +# Once done this will define +# +# GCRYPT_FOUND - system has GCrypt +# GCRYPT_INCLUDE_DIRS - the GCrypt include directory +# GCRYPT_LIBRARIES - Link these to use GCrypt +# GCRYPT_DEFINITIONS - Compiler switches required for using GCrypt +# +#============================================================================= +# Copyright (c) 2009-2012 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_GCRYPT_ROOT_HINTS + $ENV{GCRYTPT_ROOT_DIR} + ${GCRYPT_ROOT_DIR}) + +set(_GCRYPT_ROOT_PATHS + "$ENV{PROGRAMFILES}/libgcrypt") + +set(_GCRYPT_ROOT_HINTS_AND_PATHS + HINTS ${_GCRYPT_ROOT_HINTS} + PATHS ${_GCRYPT_ROOT_PATHS}) + + +find_path(GCRYPT_INCLUDE_DIR + NAMES + gcrypt.h + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_path(GCRYPT_ERROR_INCLUDE_DIR + NAMES + gpg-error.h + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_library(GCRYPT_LIBRARY + NAMES + gcrypt + gcrypt11 + libgcrypt-11 + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) +find_library(GCRYPT_ERROR_LIBRARY + NAMES + gpg-error + libgpg-error-0 + libgpg-error6-0 + HINTS + ${_GCRYPT_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) +set(GCRYPT_LIBRARIES ${GCRYPT_ERROR_LIBRARY} ${GCRYPT_LIBRARY}) + +if (GCRYPT_INCLUDE_DIR) + file(STRINGS "${GCRYPT_INCLUDE_DIR}/gcrypt.h" _gcrypt_version_str REGEX "^#define GCRYPT_VERSION \"[0-9]+\\.[0-9]+\\.[0-9]") + + string(REGEX REPLACE "^.*GCRYPT_VERSION.*([0-9]+\\.[0-9]+\\.[0-9]+).*" "\\1" GCRYPT_VERSION "${_gcrypt_version_str}") +endif (GCRYPT_INCLUDE_DIR) + +include(FindPackageHandleStandardArgs) +if (GCRYPT_VERSION) + find_package_handle_standard_args(GCrypt + REQUIRED_VARS + GCRYPT_INCLUDE_DIR + GCRYPT_LIBRARIES + VERSION_VAR + GCRYPT_VERSION + FAIL_MESSAGE + "Could NOT find GCrypt, try to set the path to GCrypt root folder in the system variable GCRYPT_ROOT_DIR" + ) +else (GCRYPT_VERSION) + find_package_handle_standard_args(GCrypt + "Could NOT find GCrypt, try to set the path to GCrypt root folder in the system variable GCRYPT_ROOT_DIR" + GCRYPT_INCLUDE_DIR + GCRYPT_LIBRARIES) +endif (GCRYPT_VERSION) + +# show the GCRYPT_INCLUDE_DIRS, GCRYPT_LIBRARIES and GCRYPT_ERROR_INCLUDE_DIR variables only in the advanced view +mark_as_advanced(GCRYPT_INCLUDE_DIR GCRYPT_ERROR_INCLUDE_DIR GCRYPT_LIBRARIES) + +if(GCRYPT_FOUND) + if(NOT TARGET libgcrypt::libgcrypt) + add_library(libgcrypt::libgcrypt UNKNOWN IMPORTED) + set_target_properties(libgcrypt::libgcrypt PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GCRYPT_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES libgcrypt::libgcrypt + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${GCRYPT_LIBRARY}") + endif() + + if(NOT TARGET libgpg-error::libgpg-error) + add_library(libgpg-error::libgpg-error UNKNOWN IMPORTED) + set_target_properties(libgpg-error::libgpg-error PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${GCRYPT_ERROR_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES libgpg-error::libgpg-error + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${GCRYPT_ERROR_LIBRARY}") + endif() +endif() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindGSSAPI.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindGSSAPI.cmake new file mode 100644 index 000000000000..630d7c162bcf --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindGSSAPI.cmake @@ -0,0 +1,344 @@ +# - Try to find GSSAPI +# Once done this will define +# +# KRB5_CONFIG - Path to krb5-config +# GSSAPI_ROOT_DIR - Set this variable to the root installation of GSSAPI +# +# Read-Only variables: +# GSSAPI_FLAVOR_MIT - set to TRUE if MIT Kerberos has been found +# GSSAPI_FLAVOR_HEIMDAL - set to TRUE if Heimdal Keberos has been found +# GSSAPI_FOUND - system has GSSAPI +# GSSAPI_INCLUDE_DIR - the GSSAPI include directory +# GSSAPI_LIBRARIES - Link these to use GSSAPI +# GSSAPI_DEFINITIONS - Compiler switches required for using GSSAPI +# GSSAPI_PC_REQUIRES - pkg-config module name if found, needed for +# Requires.private for static linking +# +#============================================================================= +# Copyright (c) 2013 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +set(_mit_modname "mit-krb5-gssapi") +set(_heimdal_modname "heimdal-gssapi") + +if(NOT _GSSAPI_ROOT_HINTS AND NOT _GSSAPI_ROOT_PATHS) + find_package(PkgConfig QUIET) + if (PKG_CONFIG_FOUND) + pkg_search_module(_GSSAPI ${_mit_modname} ${_heimdal_modname}) + endif() +endif() + +find_path(GSSAPI_ROOT_DIR + NAMES + include/gssapi.h + include/gssapi/gssapi.h + HINTS + ${_GSSAPI_ROOT_HINTS} + "${_GSSAPI_INCLUDEDIR}" + PATHS + ${_GSSAPI_ROOT_PATHS} +) +mark_as_advanced(GSSAPI_ROOT_DIR) + +if (UNIX) + find_program(KRB5_CONFIG + NAMES + krb5-config + PATHS + ${GSSAPI_ROOT_DIR}/bin + /opt/local/bin) + mark_as_advanced(KRB5_CONFIG) + + if (KRB5_CONFIG) + # Check if we have MIT KRB5 + execute_process( + COMMAND + ${KRB5_CONFIG} --vendor + RESULT_VARIABLE + _GSSAPI_VENDOR_RESULT + OUTPUT_VARIABLE + _GSSAPI_VENDOR_STRING) + + if ((_GSSAPI_VENDOR_STRING MATCHES ".*Massachusetts.*") OR (_GSSAPI_VENDOR_STRING + MATCHES ".*MITKerberosShim.*")) + set(GSSAPI_FLAVOR_MIT TRUE) + else() + execute_process( + COMMAND + ${KRB5_CONFIG} --libs gssapi + RESULT_VARIABLE + _GSSAPI_LIBS_RESULT + OUTPUT_VARIABLE + _GSSAPI_LIBS_STRING) + + if (_GSSAPI_LIBS_STRING MATCHES ".*roken.*") + set(GSSAPI_FLAVOR_HEIMDAL TRUE) + endif() + endif() + + # Get the include dir + execute_process( + COMMAND + ${KRB5_CONFIG} --cflags gssapi + RESULT_VARIABLE + _GSSAPI_INCLUDE_RESULT + OUTPUT_VARIABLE + _GSSAPI_INCLUDE_STRING) + string(REGEX REPLACE "(\r?\n)+$" "" _GSSAPI_INCLUDE_STRING "${_GSSAPI_INCLUDE_STRING}") + string(REGEX REPLACE " *-I" "" _GSSAPI_INCLUDEDIR "${_GSSAPI_INCLUDE_STRING}") + endif() + + if (NOT GSSAPI_FLAVOR_MIT AND NOT GSSAPI_FLAVOR_HEIMDAL) + # Check for HEIMDAL + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(_GSSAPI heimdal-gssapi) + endif (PKG_CONFIG_FOUND) + + if (_GSSAPI_FOUND) + set(GSSAPI_FLAVOR_HEIMDAL TRUE) + else() + find_path(_GSSAPI_ROKEN + NAMES + roken.h + PATHS + ${GSSAPI_ROOT_DIR}/include + ${_GSSAPI_INCLUDEDIR}) + if (_GSSAPI_ROKEN) + set(GSSAPI_FLAVOR_HEIMDAL TRUE) + endif() + endif () + endif() +endif (UNIX) + +find_path(GSSAPI_INCLUDE_DIR + NAMES + gssapi.h + gssapi/gssapi.h + PATHS + ${GSSAPI_ROOT_DIR}/include + ${_GSSAPI_INCLUDEDIR} +) + +if (GSSAPI_FLAVOR_MIT) + find_library(GSSAPI_LIBRARY + NAMES + gssapi_krb5 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(KRB5_LIBRARY + NAMES + krb5 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(K5CRYPTO_LIBRARY + NAMES + k5crypto + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(COM_ERR_LIBRARY + NAMES + com_err + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + if (GSSAPI_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${GSSAPI_LIBRARY} + ) + endif (GSSAPI_LIBRARY) + + if (KRB5_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${KRB5_LIBRARY} + ) + endif (KRB5_LIBRARY) + + if (K5CRYPTO_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${K5CRYPTO_LIBRARY} + ) + endif (K5CRYPTO_LIBRARY) + + if (COM_ERR_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${COM_ERR_LIBRARY} + ) + endif (COM_ERR_LIBRARY) +endif (GSSAPI_FLAVOR_MIT) + +if (GSSAPI_FLAVOR_HEIMDAL) + find_library(GSSAPI_LIBRARY + NAMES + gssapi + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(KRB5_LIBRARY + NAMES + krb5 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(HCRYPTO_LIBRARY + NAMES + hcrypto + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(COM_ERR_LIBRARY + NAMES + com_err + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(HEIMNTLM_LIBRARY + NAMES + heimntlm + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(HX509_LIBRARY + NAMES + hx509 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(ASN1_LIBRARY + NAMES + asn1 + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(WIND_LIBRARY + NAMES + wind + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + find_library(ROKEN_LIBRARY + NAMES + roken + PATHS + ${GSSAPI_ROOT_DIR}/lib + ${_GSSAPI_LIBDIR} + ) + + if (GSSAPI_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${GSSAPI_LIBRARY} + ) + endif (GSSAPI_LIBRARY) + + if (KRB5_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${KRB5_LIBRARY} + ) + endif (KRB5_LIBRARY) + + if (HCRYPTO_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${HCRYPTO_LIBRARY} + ) + endif (HCRYPTO_LIBRARY) + + if (COM_ERR_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${COM_ERR_LIBRARY} + ) + endif (COM_ERR_LIBRARY) + + if (HEIMNTLM_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${HEIMNTLM_LIBRARY} + ) + endif (HEIMNTLM_LIBRARY) + + if (HX509_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${HX509_LIBRARY} + ) + endif (HX509_LIBRARY) + + if (ASN1_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${ASN1_LIBRARY} + ) + endif (ASN1_LIBRARY) + + if (WIND_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${WIND_LIBRARY} + ) + endif (WIND_LIBRARY) + + if (ROKEN_LIBRARY) + set(GSSAPI_LIBRARIES + ${GSSAPI_LIBRARIES} + ${WIND_LIBRARY} + ) + endif (ROKEN_LIBRARY) +endif (GSSAPI_FLAVOR_HEIMDAL) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(GSSAPI DEFAULT_MSG GSSAPI_LIBRARIES GSSAPI_INCLUDE_DIR) + +if(GSSAPI_FOUND) + if(_GSSAPI_FOUND) # via pkg-config + if (GSSAPI_FLAVOR_MIT) + set(GSSAPI_PC_REQUIRES ${_mit_modname}) + elseif (GSSAPI_FLAVOR_HEIMDAL) + set(GSSAPI_PC_REQUIRES ${_heimdal_modname}) + endif() + endif() +endif() + +# show the GSSAPI_INCLUDE_DIR and GSSAPI_LIBRARIES variables only in the advanced view +mark_as_advanced(GSSAPI_INCLUDE_DIR GSSAPI_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindMbedTLS.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindMbedTLS.cmake new file mode 100644 index 000000000000..7c5379c667bd --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindMbedTLS.cmake @@ -0,0 +1,143 @@ +# - Try to find mbedTLS +# Once done this will define +# +# MBEDTLS_FOUND - system has mbedTLS +# MBEDTLS_INCLUDE_DIRS - the mbedTLS include directory +# MBEDTLS_LIBRARIES - Link these to use mbedTLS +# MBEDTLS_DEFINITIONS - Compiler switches required for using mbedTLS +#============================================================================= +# Copyright (c) 2017 Sartura d.o.o. +# +# Author: Juraj Vijtiuk +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + + +set(_MBEDTLS_ROOT_HINTS + $ENV{MBEDTLS_ROOT_DIR} + ${MBEDTLS_ROOT_DIR}) + +set(_MBEDTLS_ROOT_PATHS + "$ENV{PROGRAMFILES}/libmbedtls") + +set(_MBEDTLS_ROOT_HINTS_AND_PATHS + HINTS ${_MBEDTLS_ROOT_HINTS} + PATHS ${_MBEDTLS_ROOT_PATHS}) + + +find_path(MBEDTLS_INCLUDE_DIR + NAMES + mbedtls/ssl.h + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_library(MBEDTLS_SSL_LIBRARY + NAMES + mbedtls + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + +) + +find_library(MBEDTLS_CRYPTO_LIBRARY + NAMES + mbedcrypto + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) + +find_library(MBEDTLS_X509_LIBRARY + NAMES + mbedx509 + HINTS + ${_MBEDTLS_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib +) + +set(MBEDTLS_LIBRARIES ${MBEDTLS_SSL_LIBRARY} ${MBEDTLS_CRYPTO_LIBRARY} + ${MBEDTLS_X509_LIBRARY}) + +# mbedtls 2.8 +if (MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/version.h" _mbedtls_version_str REGEX + "^#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"[0-9]+.[0-9]+.[0-9]+\"") + + string(REGEX REPLACE "^.*MBEDTLS_VERSION_STRING.*([0-9]+\\.[0-9]+\\.[0-9]+).*$" + "\\1" MBEDTLS_VERSION "${_mbedtls_version_str}") +endif() + +# mbedtls 3.6 +if (NOT MBEDTLS_VERSION AND MBEDTLS_INCLUDE_DIR AND EXISTS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h") + file(STRINGS "${MBEDTLS_INCLUDE_DIR}/mbedtls/build_info.h" _mbedtls_version_str REGEX + "^#[\t ]*define[\t ]+MBEDTLS_VERSION_STRING[\t ]+\"[0-9]+.[0-9]+.[0-9]+\"") + + string(REGEX REPLACE "^.*MBEDTLS_VERSION_STRING.*([0-9]+\\.[0-9]+\\.[0-9]+).*$" + "\\1" MBEDTLS_VERSION "${_mbedtls_version_str}") +endif() + +include(FindPackageHandleStandardArgs) +if (MBEDTLS_VERSION) + find_package_handle_standard_args(MbedTLS + REQUIRED_VARS + MBEDTLS_INCLUDE_DIR + MBEDTLS_LIBRARIES + VERSION_VAR + MBEDTLS_VERSION + FAIL_MESSAGE + "Could NOT find mbedTLS, try to set the path to mbedTLS root folder + in the system variable MBEDTLS_ROOT_DIR" + ) +else (MBEDTLS_VERSION) + find_package_handle_standard_args(MbedTLS + "Could NOT find mbedTLS, try to set the path to mbedTLS root folder in + the system variable MBEDTLS_ROOT_DIR" + MBEDTLS_INCLUDE_DIR + MBEDTLS_LIBRARIES) +endif (MBEDTLS_VERSION) + +# show the MBEDTLS_INCLUDE_DIRS and MBEDTLS_LIBRARIES variables only in the advanced view +mark_as_advanced(MBEDTLS_INCLUDE_DIR MBEDTLS_LIBRARIES) + +if(MBEDTLS_FOUND) + if(NOT TARGET MbedTLS::mbedcrypto) + add_library(MbedTLS::mbedcrypto UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedcrypto PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES MbedTLS::mbedcrypto + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_CRYPTO_LIBRARY}") + endif() + + if(NOT TARGET MbedTLS::mbedx509) + add_library(MbedTLS::mbedx509 UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedx509 PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES MbedTLS::mbedx509 + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_X509_LIBRARY}") + endif() + + if(NOT TARGET MbedTLS::mbedtls) + add_library(MbedTLS::mbedtls UNKNOWN IMPORTED) + set_target_properties(MbedTLS::mbedtls PROPERTIES + INTERFACE_INCLUDE_DIRECTORIES "${MBEDTLS_INCLUDE_DIR}" + INTERFACE_LINK_LIBRARIES MbedTLS::mbedtls + IMPORTED_LINK_INTERFACE_LANGUAGES "C" + IMPORTED_LOCATION "${MBEDTLS_LIBRARY}") + endif() +endif() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindNSIS.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindNSIS.cmake new file mode 100644 index 000000000000..9f1ab176c00a --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindNSIS.cmake @@ -0,0 +1,54 @@ +# - Try to find NSIS +# Once done this will define +# +# NSIS_ROOT_PATH - Set this variable to the root installation of NSIS +# +# Read-Only variables: +# +# NSIS_FOUND - system has NSIS +# NSIS_MAKE - NSIS creator executable +# +#============================================================================= +# Copyright (c) 2010-2013 Andreas Schneider +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + +if (WIN32) + set(_x86 "(x86)") + + set(_NSIS_ROOT_PATHS + "$ENV{ProgramFiles}/NSIS" + "$ENV{ProgramFiles${_x86}}/NSIS" + "[HKEY_LOCAL_MACHINE\\SOFTWARE\\Wow6432Node\\NSIS;Default]") + + find_path(NSIS_ROOT_PATH + NAMES + Include/Library.nsh + PATHS + ${_NSIS_ROOT_PATHS} + ) + mark_as_advanced(NSIS_ROOT_PATH) +endif (WIN32) + +find_program(NSIS_MAKE + NAMES + makensis + PATHS + ${NSIS_ROOT_PATH} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(NSIS DEFAULT_MSG NSIS_MAKE) + +if (NSIS_MAKE) + set(NSIS_FOUND TRUE) +endif (NSIS_MAKE) + +mark_as_advanced(NSIS_MAKE) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/FindNaCl.cmake b/src/libs/libssh-0.12.2/cmake/Modules/FindNaCl.cmake new file mode 100644 index 000000000000..b1a8da457d71 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/FindNaCl.cmake @@ -0,0 +1,61 @@ +# - Try to find NaCl +# Once done this will define +# +# NACL_FOUND - system has NaCl +# NACL_INCLUDE_DIRS - the NaCl include directory +# NACL_LIBRARIES - Link these to use NaCl +# NACL_DEFINITIONS - Compiler switches required for using NaCl +# +# Copyright (c) 2010 Andreas Schneider +# Copyright (c) 2013 Aris Adamantiadis +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +if (NACL_LIBRARIES AND NACL_INCLUDE_DIRS) + # in cache already + set(NACL_FOUND TRUE) +else (NACL_LIBRARIES AND NACL_INCLUDE_DIRS) + + find_path(NACL_INCLUDE_DIR + NAMES + nacl/crypto_box_curve25519xsalsa20poly1305.h + PATHS + /usr/include + /usr/local/include + /opt/local/include + /sw/include + ) + + find_library(NACL_LIBRARY + NAMES + nacl + PATHS + /usr/lib + /usr/local/lib + /opt/local/lib + /sw/lib + ) + + set(NACL_INCLUDE_DIRS + ${NACL_INCLUDE_DIR} + ) + + if (NACL_LIBRARY) + set(NACL_LIBRARIES + ${NACL_LIBRARIES} + ${NACL_LIBRARY} + ) + endif (NACL_LIBRARY) + + include(FindPackageHandleStandardArgs) + find_package_handle_standard_args(NaCl DEFAULT_MSG NACL_LIBRARIES NACL_INCLUDE_DIRS) + + # show the NACL_INCLUDE_DIRS and NACL_LIBRARIES variables only in the advanced view + mark_as_advanced(NACL_INCLUDE_DIRS NACL_LIBRARIES) + +endif (NACL_LIBRARIES AND NACL_INCLUDE_DIRS) + diff --git a/src/libs/libssh-0.12.2/cmake/Modules/Findlibfido2.cmake b/src/libs/libssh-0.12.2/cmake/Modules/Findlibfido2.cmake new file mode 100644 index 000000000000..7111a919c980 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/Findlibfido2.cmake @@ -0,0 +1,63 @@ +# - Try to find libfido2 +# Once done this will define +# +# LIBFIDO2_ROOT_DIR - Set this variable to the root installation of libfido2 +# +# Read-Only variables: +# LIBFIDO2_FOUND - system has libfido2 +# LIBFIDO2_INCLUDE_DIR - the libfido2 include directory +# LIBFIDO2_LIBRARIES - Link these to use libfido2 +# +# The libfido2 library provides support for communicating +# with FIDO2/U2F devices over USB/NFC. +# +# Copyright (c) 2025 Praneeth Sarode +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + + +set(_LIBFIDO2_ROOT_HINTS + $ENV{LIBFIDO2_ROOT_DIR} + ${LIBFIDO2_ROOT_DIR} +) + +set(_LIBFIDO2_ROOT_PATHS + "$ENV{PROGRAMFILES}/libfido2" +) + +set(_LIBFIDO2_ROOT_HINTS_AND_PATHS + HINTS ${_LIBFIDO2_ROOT_HINTS} + PATHS ${_LIBFIDO2_ROOT_PATHS} +) + +find_path(LIBFIDO2_INCLUDE_DIR + NAMES + fido.h + HINTS + ${_LIBFIDO2_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + include +) + +find_library(LIBFIDO2_LIBRARY + NAMES + fido2 + HINTS + ${_LIBFIDO2_ROOT_HINTS_AND_PATHS} + PATH_SUFFIXES + lib + lib64 +) + +set(LIBFIDO2_LIBRARIES + ${LIBFIDO2_LIBRARY} +) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(libfido2 DEFAULT_MSG LIBFIDO2_LIBRARIES LIBFIDO2_INCLUDE_DIR) + +# show the LIBFIDO2_INCLUDE_DIR and LIBFIDO2_LIBRARIES variables only in the advanced view +mark_as_advanced(LIBFIDO2_INCLUDE_DIR LIBFIDO2_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/Findsofthsm.cmake b/src/libs/libssh-0.12.2/cmake/Modules/Findsofthsm.cmake new file mode 100644 index 000000000000..3a29b6d039bd --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/Findsofthsm.cmake @@ -0,0 +1,36 @@ +# - Try to find softhsm +# Once done this will define +# +# SOFTHSM_FOUND - system has softhsm +# SOFTHSM_LIBRARIES - Link these to use softhsm +# +#============================================================================= +# Copyright (c) 2019 Sahana Prasad +# +# Distributed under the OSI-approved BSD License (the "License"); +# see accompanying file Copyright.txt for details. +# +# This software is distributed WITHOUT ANY WARRANTY; without even the +# implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. +# See the License for more information. +#============================================================================= +# + + +find_library(SOFTHSM2_LIBRARY + NAMES + softhsm2 +) + +if (SOFTHSM2_LIBRARY) + set(SOFTHSM_LIBRARIES + ${SOFTHSM_LIBRARIES} + ${SOFTHSM2_LIBRARY} + ) +endif (SOFTHSM2_LIBRARY) + +include(FindPackageHandleStandardArgs) +find_package_handle_standard_args(softhsm DEFAULT_MSG SOFTHSM_LIBRARIES) + +# show the SOFTHSM_INCLUDE_DIR and SOFTHSM_LIBRARIES variables only in the advanced view +mark_as_advanced(SOFTHSM_LIBRARIES) diff --git a/src/libs/libssh-0.12.2/cmake/Modules/GenerateMap.cmake b/src/libs/libssh-0.12.2/cmake/Modules/GenerateMap.cmake new file mode 100644 index 000000000000..c22dfbc3c330 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/GenerateMap.cmake @@ -0,0 +1,118 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# GenerateMap +# ----------- +# +# This is a helper script for FindABImap.cmake. +# +# Generates a symbols version script using the abimap tool. +# This script is run in build time to use the correct command depending on the +# existence of the file provided ``CURRENT_MAP``. +# +# If the file exists, the ``abimap update`` subcommand is used to update the +# existing map. Otherwise, the ``abimap new`` subcommand is used to create a new +# map file. +# +# If the file provided in ``CURRENT_MAP`` exists, it is copied to the +# ``OUTPUT_PATH`` before updating. +# This is required because ``abimap`` do not generate output if no symbols were +# changed when updating an existing file. +# +# Expected defined variables +# -------------------------- +# +# ``SYMBOLS``: +# Required file containing the symbols to be used as input. Usually this is +# the ``OUTPUT`` generated by ``extract_symbols()`` function provided in +# FindABImap.cmake +# +# ``RELEASE_NAME_VERSION``: +# Required, expects the library name and version information to be added to +# the symbols in the format ``library_name_1_2_3`` +# +# ``CURRENT_MAP``: +# Required, expects the path to the current map file (or the path were it +# should be) +# +# ``OUTPUT_PATH``: +# Required, expects the output file path. +# +# ``ABIMAP_EXECUTABLE``: +# Required, expects the path to the ``abimap`` tool. +# +# Optionally defined variables +# ---------------------------- +# +# ``FINAL``: +# If defined, will mark the modified set of symbols in the symbol version +# script as final, preventing later changes using ``abimap``. +# +# ``BREAK_ABI``: +# If defined, the build will not fail if symbols were removed. +# If defined and a symbol is removed, a new release is created containing +# all symbols from all released versions. This makes an incompatible release. +# + +if (NOT DEFINED RELEASE_NAME_VERSION) + message(SEND_ERROR "RELEASE_NAME_VERSION not defined") +endif() + +if (NOT DEFINED SYMBOLS) + message(SEND_ERROR "SYMBOLS not defined") +endif() + +if (NOT DEFINED CURRENT_MAP) + message(SEND_ERROR "CURRENT_MAP not defined") +endif() + +if (NOT DEFINED OUTPUT_PATH) + message(SEND_ERROR "OUTPUT_PATH not defined") +endif() + +if (NOT ABIMAP_EXECUTABLE) + message(SEND_ERROR "ABIMAP_EXECUTABLE not defined") +endif() + +set(ARGS_LIST) + +if (FINAL) + list(APPEND ARGS_LIST "--final") +endif() + +if (EXISTS ${CURRENT_MAP}) + if (BREAK_ABI) + list(APPEND ARGS_LIST "--allow-abi-break") + endif() + + execute_process( + COMMAND + ${CMAKE_COMMAND} -E copy_if_different ${CURRENT_MAP} ${OUTPUT_PATH} + COMMAND + ${ABIMAP_EXECUTABLE} update ${ARGS_LIST} + -r ${RELEASE_NAME_VERSION} + -i ${SYMBOLS} + -o ${OUTPUT_PATH} + ${CURRENT_MAP} + RESULT_VARIABLE result + ) +else () + execute_process( + COMMAND + ${ABIMAP_EXECUTABLE} new ${ARGS_LIST} + -r ${RELEASE_NAME_VERSION} + -i ${SYMBOLS} + -o ${OUTPUT_PATH} + RESULT_VARIABLE result + ) +endif() + +if (NOT "${result}" STREQUAL "0") + message(SEND_ERROR "Map generation failed") +endif() diff --git a/src/libs/libssh-0.12.2/cmake/Modules/GetFilesList.cmake b/src/libs/libssh-0.12.2/cmake/Modules/GetFilesList.cmake new file mode 100644 index 000000000000..e3e8a2a8b807 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/GetFilesList.cmake @@ -0,0 +1,59 @@ +# +# Copyright (c) 2018 Anderson Toshiyuki Sasaki +# +# Redistribution and use is allowed according to the terms of the New +# BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. +# + +#.rst: +# GetFilesList +# ------------ +# +# This is a helper script for FindABImap.cmake. +# +# Search in the provided directories for files matching the provided pattern. +# The list of files is then written to the output file. +# +# Expected defined variables +# -------------------------- +# +# ``DIRECTORIES``: +# Required, expects a list of directories paths. +# +# ``FILES_PATTERNS``: +# Required, expects a list of patterns to be used to search files +# +# ``OUTPUT_PATH``: +# Required, expects the output file path. + +if (NOT DEFINED DIRECTORIES) + message(SEND_ERROR "DIRECTORIES not defined") +endif() + +if (NOT DEFINED FILES_PATTERNS) + message(SEND_ERROR "FILES_PATTERNS not defined") +endif() + +if (NOT DEFINED OUTPUT_PATH) + message(SEND_ERROR "OUTPUT_PATH not defined") +endif() + +string(REPLACE " " ";" DIRECTORIES_LIST "${DIRECTORIES}") +string(REPLACE " " ";" FILES_PATTERNS_LIST "${FILES_PATTERNS}") + +# Create the list of expressions for the files +set(glob_expressions) +foreach(dir ${DIRECTORIES_LIST}) + foreach(exp ${FILES_PATTERNS_LIST}) + list(APPEND glob_expressions + "${dir}/${exp}" + ) + endforeach() +endforeach() + +# Create the list of files +file(GLOB files ${glob_expressions}) + +# Write to the output +file(WRITE ${OUTPUT_PATH} "${files}") diff --git a/src/libs/libssh-0.12.2/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake b/src/libs/libssh-0.12.2/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake new file mode 100644 index 000000000000..a2e948099269 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Modules/MacroEnsureOutOfSourceBuild.cmake @@ -0,0 +1,17 @@ +# - MACRO_ENSURE_OUT_OF_SOURCE_BUILD() +# MACRO_ENSURE_OUT_OF_SOURCE_BUILD() + +# Copyright (c) 2006, Alexander Neundorf, +# +# Redistribution and use is allowed according to the terms of the BSD license. +# For details see the accompanying COPYING-CMAKE-SCRIPTS file. + +macro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD _errorMessage) + + string(COMPARE EQUAL "${CMAKE_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" _insource) + if (_insource) + message(SEND_ERROR "${_errorMessage}") + message(FATAL_ERROR "Remove the file CMakeCache.txt in ${CMAKE_SOURCE_DIR} first.") + endif (_insource) + +endmacro (MACRO_ENSURE_OUT_OF_SOURCE_BUILD) diff --git a/src/libs/libssh-0.12.2/cmake/Toolchain-cross-m32.cmake b/src/libs/libssh-0.12.2/cmake/Toolchain-cross-m32.cmake new file mode 100644 index 000000000000..7918c604c622 --- /dev/null +++ b/src/libs/libssh-0.12.2/cmake/Toolchain-cross-m32.cmake @@ -0,0 +1,23 @@ +set(CMAKE_C_FLAGS "-m32" CACHE STRING "C compiler flags" FORCE) +set(CMAKE_CXX_FLAGS "-m32" CACHE STRING "C++ compiler flags" FORCE) + +set(LIB32 /usr/lib) # Fedora + +if(EXISTS /usr/lib32) + set(LIB32 /usr/lib32) # Arch, Solus +endif() + +set(CMAKE_SYSTEM_LIBRARY_PATH ${LIB32} CACHE STRING "system library search path" FORCE) +set(CMAKE_LIBRARY_PATH ${LIB32} CACHE STRING "library search path" FORCE) + +# this is probably unlikely to be needed, but just in case +set(CMAKE_EXE_LINKER_FLAGS "-m32 -L${LIB32}" CACHE STRING "executable linker flags" FORCE) +set(CMAKE_SHARED_LINKER_FLAGS "-m32 -L${LIB32}" CACHE STRING "shared library linker flags" FORCE) +set(CMAKE_MODULE_LINKER_FLAGS "-m32 -L${LIB32}" CACHE STRING "module linker flags" FORCE) + +# on Fedora and Arch and similar, point pkgconfig at 32 bit .pc files. We have +# to include the regular system .pc files as well (at the end), because some +# are not always present in the 32 bit directory +if(EXISTS ${LIB32}/pkgconfig) + set(ENV{PKG_CONFIG_LIBDIR} ${LIB32}/pkgconfig:/usr/share/pkgconfig:/usr/lib/pkgconfig:/usr/lib64/pkgconfig) +endiF() diff --git a/src/libs/libssh-0.12.2/config.h.cmake b/src/libs/libssh-0.12.2/config.h.cmake new file mode 100644 index 000000000000..14e1031cd2b0 --- /dev/null +++ b/src/libs/libssh-0.12.2/config.h.cmake @@ -0,0 +1,304 @@ +/* Name of package */ +#cmakedefine PACKAGE "${PROJECT_NAME}" + +/* Version number of package */ +#cmakedefine VERSION "${PROJECT_VERSION}" + +#cmakedefine SYSCONFDIR "${SYSCONFDIR}" +#cmakedefine BINARYDIR "${BINARYDIR}" +#cmakedefine SOURCEDIR "${SOURCEDIR}" + +/* Global configuration directory */ +#cmakedefine USR_GLOBAL_CONF_DIR "${USR_GLOBAL_CONF_DIR}" +#cmakedefine GLOBAL_CONF_DIR "${GLOBAL_CONF_DIR}" + +/* Global bind configuration file path */ +#cmakedefine USR_GLOBAL_BIND_CONFIG "${USR_GLOBAL_BIND_CONFIG}" +#cmakedefine GLOBAL_BIND_CONFIG "${GLOBAL_BIND_CONFIG}" + +/* Global client configuration file path */ +#cmakedefine USR_GLOBAL_CLIENT_CONFIG "${USR_GLOBAL_CLIENT_CONFIG}" +#cmakedefine GLOBAL_CLIENT_CONFIG "${GLOBAL_CLIENT_CONFIG}" + +/************************** HEADER FILES *************************/ + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ARGP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_GLOB_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_VALGRIND_VALGRIND_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PTY_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UTMP_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UTIL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_LIBUTIL_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_TIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_SYS_UTIME_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IO_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_UNISTD_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_IFADDRS_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_AES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_WSPIAPI_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_DES_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_ECDH_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_EC_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_OPENSSL_ECDSA_H 1 + +/* Define to 1 if you have the header file. */ +#cmakedefine HAVE_PTHREAD_H 1 + +/* Define to 1 if you have elliptic curve cryptography in openssl */ +#cmakedefine HAVE_OPENSSL_ECC 1 + +/* Define to 1 if mbedTLS supports curve25519 */ +#cmakedefine HAVE_MBEDTLS_CURVE25519 1 + +/* Define to 1 if you have elliptic curve cryptography in gcrypt */ +#cmakedefine HAVE_GCRYPT_ECC 1 + +/* Define to 1 if you have elliptic curve cryptography */ +#cmakedefine HAVE_ECC 1 + +/* Define to 1 if you have gl_flags as a glob_t struct member */ +#cmakedefine HAVE_GLOB_GL_FLAGS_MEMBER 1 + +/* Define to 1 if you have gcrypt with ChaCha20/Poly1305 support */ +#cmakedefine HAVE_GCRYPT_CHACHA_POLY 1 + +/* Define to 1 if you have gcrypt with curve25519 support */ +#cmakedefine HAVE_GCRYPT_CURVE25519 + +/*************************** FUNCTIONS ***************************/ + +/* Define to 1 if you have the `EVP_chacha20' function. */ +#cmakedefine HAVE_OPENSSL_EVP_CHACHA20 1 + +/* Define to 1 if you have the `EVP_KDF_CTX_new_id' or `EVP_KDF_CTX_new` function. */ +#cmakedefine HAVE_OPENSSL_EVP_KDF_CTX 1 + +/* Define to 1 if you have the `FIPS_mode' function. */ +#cmakedefine HAVE_OPENSSL_FIPS_MODE 1 + +/* Define to 1 if you have the `snprintf' function. */ +#cmakedefine HAVE_SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf' function. */ +#cmakedefine HAVE__SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf_s' function. */ +#cmakedefine HAVE__SNPRINTF_S 1 + +/* Define to 1 if you have the `vsnprintf' function. */ +#cmakedefine HAVE_VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf' function. */ +#cmakedefine HAVE__VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf_s' function. */ +#cmakedefine HAVE__VSNPRINTF_S 1 + +/* Define to 1 if you have the `isblank' function. */ +#cmakedefine HAVE_ISBLANK 1 + +/* Define to 1 if you have the `strncpy' function. */ +#cmakedefine HAVE_STRNCPY 1 + +/* Define to 1 if you have the `strndup' function. */ +#cmakedefine HAVE_STRNDUP 1 + +/* Define to 1 if you have the `cfmakeraw' function. */ +#cmakedefine HAVE_CFMAKERAW 1 + +/* Define to 1 if you have the `getaddrinfo' function. */ +#cmakedefine HAVE_GETADDRINFO 1 + +/* Define to 1 if you have the `poll' function. */ +#cmakedefine HAVE_POLL 1 + +/* Define to 1 if you have the `select' function. */ +#cmakedefine HAVE_SELECT 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +#cmakedefine HAVE_CLOCK_GETTIME 1 + +/* Define to 1 if you have the `ntohll' function. */ +#cmakedefine HAVE_NTOHLL 1 + +/* Define to 1 if you have the `htonll' function. */ +#cmakedefine HAVE_HTONLL 1 + +/* Define to 1 if you have the `strtoull' function. */ +#cmakedefine HAVE_STRTOULL 1 + +/* Define to 1 if you have the `__strtoull' function. */ +#cmakedefine HAVE___STRTOULL 1 + +/* Define to 1 if you have the `_strtoui64' function. */ +#cmakedefine HAVE__STRTOUI64 1 + +/* Define to 1 if you have the `glob' function. */ +#cmakedefine HAVE_GLOB 1 + +/* Define to 1 if you have the `explicit_bzero' function. */ +#cmakedefine HAVE_EXPLICIT_BZERO 1 + +/* Define to 1 if you have the `memset_explicit' function. */ +#cmakedefine HAVE_MEMSET_EXPLICIT 1 + +/* Define to 1 if you have the `memset_s' function. */ +#cmakedefine HAVE_MEMSET_S 1 + +/* Define to 1 if you have the `SecureZeroMemory' function. */ +#cmakedefine HAVE_SECURE_ZERO_MEMORY 1 + +/* Define to 1 if you have the `cmocka_set_test_filter' function. */ +#cmakedefine HAVE_CMOCKA_SET_TEST_FILTER 1 + +/* Define to 1 if we have support for blowfish */ +#cmakedefine HAVE_BLOWFISH 1 + +/* Define to 1 if we have support for ML-KEM in libgcrypt */ +#cmakedefine HAVE_GCRYPT_MLKEM 1 + +/* Define to 1 if we have support for ML-KEM in OpenSSL */ +#cmakedefine HAVE_OPENSSL_MLKEM 1 + +/* Define to 1 if we have support for ML-KEM1024 in either backend */ +#cmakedefine HAVE_MLKEM1024 1 + +/*************************** LIBRARIES ***************************/ + +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#cmakedefine HAVE_LIBCRYPTO 1 + +/* Define to 1 if you have the `gcrypt' library (-lgcrypt). */ +#cmakedefine HAVE_LIBGCRYPT 1 + +/* Define to 1 if you have the 'mbedTLS' library (-lmbedtls). */ +#cmakedefine HAVE_LIBMBEDCRYPTO 1 + +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#cmakedefine HAVE_PTHREAD 1 + +/* Define to 1 if you have the `cmocka' library (-lcmocka). */ +#cmakedefine HAVE_CMOCKA 1 + +/* Define to 1 if you have the `libfido2' library (-lfido2). + * This is required for interacting with FIDO2/U2F devices over USB-HID. */ +#cmakedefine HAVE_LIBFIDO2 1 + +/**************************** OPTIONS ****************************/ + +#cmakedefine HAVE_GCC_THREAD_LOCAL_STORAGE 1 +#cmakedefine HAVE_MSC_THREAD_LOCAL_STORAGE 1 + +#cmakedefine HAVE_FALLTHROUGH_ATTRIBUTE 1 +#cmakedefine HAVE_UNUSED_ATTRIBUTE 1 +#cmakedefine HAVE_WEAK_ATTRIBUTE 1 + +#cmakedefine HAVE_CONSTRUCTOR_ATTRIBUTE 1 +#cmakedefine HAVE_DESTRUCTOR_ATTRIBUTE 1 + +#cmakedefine HAVE_GCC_VOLATILE_MEMORY_PROTECTION 1 + +#cmakedefine HAVE_COMPILER__FUNC__ 1 +#cmakedefine HAVE_COMPILER__FUNCTION__ 1 + +#cmakedefine HAVE_GCC_BOUNDED_ATTRIBUTE 1 + +/* Define to 1 if you want to enable GSSAPI */ +#cmakedefine WITH_GSSAPI 1 + +/* Define to 1 if you want to enable ZLIB */ +#cmakedefine WITH_ZLIB 1 + +/* Define to 1 if you want to enable SFTP */ +#cmakedefine WITH_SFTP 1 + +/* Define to 1 if you want to enable server support */ +#cmakedefine WITH_SERVER 1 + +/* Define to 1 if you want to enable DH group exchange algorithms */ +#cmakedefine WITH_GEX 1 + +/* Define to 1 if you want to enable insecure none cipher and MAC */ +#cmakedefine WITH_INSECURE_NONE 1 + +/* Define to 1 if you want to allow libssh to execute arbitrary commands from + * configuration files or options (match exec, proxy commands and OpenSSH-based + * proxy-jumps). */ +#cmakedefine WITH_EXEC 1 + +/* Define to 1 if you want to enable blowfish cipher support */ +#cmakedefine WITH_BLOWFISH_CIPHER 1 + +/* Define to 1 if you want to enable debug output for crypto functions */ +#cmakedefine DEBUG_CRYPTO 1 + +/* Define to 1 if you want to enable debug output for packet functions */ +#cmakedefine DEBUG_PACKET 1 + +/* Define to 1 if you want to enable pcap output support (experimental) */ +#cmakedefine WITH_PCAP 1 + +/* Define to 1 if you want to enable calltrace debug output */ +#cmakedefine DEBUG_CALLTRACE 1 + +/* Define to 1 if you want to enable NaCl support */ +#cmakedefine WITH_NACL 1 + +/* Define to 1 if you want to enable PKCS #11 URI support */ +#cmakedefine WITH_PKCS11_URI 1 + +/* Define to 1 if we want to build a support for PKCS #11 provider. */ +#cmakedefine WITH_PKCS11_PROVIDER 1 + +/* Define to 1 if you want to enable FIDO2/U2F support */ +#cmakedefine WITH_FIDO2 1 + +/*************************** ENDIAN *****************************/ + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +#cmakedefine WORDS_BIGENDIAN 1 diff --git a/src/libs/libssh-0.12.2/doc/CMakeLists.txt b/src/libs/libssh-0.12.2/doc/CMakeLists.txt new file mode 100644 index 000000000000..0e7a95218b48 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/CMakeLists.txt @@ -0,0 +1,247 @@ +# +# Build the documentation +# +# To build the documentation with a local doxygen-awesome-css directory: +# +# cmake -S . -B obj \ +# -DDOXYGEN_AWESOME_CSS_DIR=/path/to/doxygen-awesome-css +# cmake --build obj --target docs +# +# The tarball can be downloaded from: +# https://github.com/jothepro/doxygen-awesome-css/archive/refs/tags/v2.4.1.tar.gz +# +find_package(Doxygen) + +if (DOXYGEN_FOUND) + set(DOXYGEN_AWESOME_CSS_PROJECT + "https://github.com/jothepro/doxygen-awesome-css") + set(DOXYGEN_AWESOME_CSS_VERSION "2.4.1") + set(DOXYGEN_AWESOME_CSS_URL + "${DOXYGEN_AWESOME_CSS_PROJECT}/archive/refs/tags/v${DOXYGEN_AWESOME_CSS_VERSION}.tar.gz" + ) + + # Allow specifying a local doxygen-awesome-css directory (useful for + # packaging) + if (NOT DEFINED DOXYGEN_AWESOME_CSS_DIR) + # Custom target to download doxygen-awesome-css at build time + add_custom_target( + doxygen-awesome-css + COMMAND + ${CMAKE_COMMAND} -DURL=${DOXYGEN_AWESOME_CSS_URL} + -DDEST_DIR=${CMAKE_CURRENT_BINARY_DIR} + -DVERSION=${DOXYGEN_AWESOME_CSS_VERSION} -P + ${CMAKE_CURRENT_SOURCE_DIR}/fetch_doxygen_awesome.cmake + COMMENT "Fetching doxygen-awesome-css theme") + + set(AWESOME_CSS_DIR + "${CMAKE_CURRENT_BINARY_DIR}/doxygen-awesome-css-${DOXYGEN_AWESOME_CSS_VERSION}" + ) + else () + message( + STATUS + "Using doxygen-awesome-css from ${DOXYGEN_AWESOME_CSS_DIR}") + set(AWESOME_CSS_DIR "${DOXYGEN_AWESOME_CSS_DIR}") + endif () + + # Project title shown in documentation + set(DOXYGEN_PROJECT_NAME ${PROJECT_NAME}) + # Project version number shown in documentation + set(DOXYGEN_PROJECT_NUMBER ${PROJECT_VERSION}) + # Brief description shown below project name + set(DOXYGEN_PROJECT_BRIEF "The SSH library") + # Project favicon (browser tab icon) + set(DOXYGEN_PROJECT_ICON ${CMAKE_CURRENT_SOURCE_DIR}/favicon.png) + + # Number of spaces used for indentation in code blocks + set(DOXYGEN_TAB_SIZE 4) + # Generate output optimized for C (vs C++) + set(DOXYGEN_OPTIMIZE_OUTPUT_FOR_C YES) + # Enable parsing of markdown in comments + set(DOXYGEN_MARKDOWN_SUPPORT YES) + # Warn about undocumented members to improve documentation quality + set(DOXYGEN_WARN_IF_UNDOCUMENTED YES) + # Do not extract private class members + set(DOXYGEN_EXTRACT_PRIVATE NO) + if (WITH_INTERNAL_DOC) + # Include internal documentation + set(DOXYGEN_INTERNAL_DOCS YES) + else () + # Do not include internal documentation + set(DOXYGEN_INTERNAL_DOCS NO) + endif( WITH_INTERNAL_DOC) + # Disable built-in clipboard (using doxygen-awesome extension instead) + set(DOXYGEN_HTML_COPY_CLIPBOARD NO) + # Disable page outline panel (using interactive TOC extension instead) + set(DOXYGEN_PAGE_OUTLINE_PANEL NO) + + # Required configuration for doxygen-awesome-css theme Generate treeview + # sidebar for navigation + set(DOXYGEN_GENERATE_TREEVIEW YES) + # Enable default index pages + set(DOXYGEN_DISABLE_INDEX NO) + # Use top navigation bar instead of full sidebar (required for theme + # compatibility) + set(DOXYGEN_FULL_SIDEBAR NO) + # Use light color style (required for Doxygen >= 1.9.5) + set(DOXYGEN_HTML_COLORSTYLE LIGHT) + + # Disable diagram generation (not relevant for C projects) + set(DOXYGEN_HAVE_DOT NO) + set(DOXYGEN_CLASS_DIAGRAMS NO) + set(DOXYGEN_CALL_GRAPH NO) + set(DOXYGEN_CALLER_GRAPH NO) + + # Preprocessor defines to use when parsing code + set(DOXYGEN_PREDEFINED DOXYGEN WITH_SERVER WITH_SFTP + PRINTF_ATTRIBUTE\(x,y\)) + + # Exclude patterns for files we don't want to document + set(DOXYGEN_EXCLUDE_PATTERNS */src/external/* fe25519.h ge25519.h sc25519.h + blf.h) + # Exclude internal structures from documentation + set(DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS + chacha20_poly1305_keysched, + dh_ctx, + dh_ctx, + dh_keypair, + error_struct, + packet_struct, + pem_get_password_struct, + ssh_tokens_st, + sftp_attributes_struct, + sftp_client_message_struct, + sftp_dir_struct, + sftp_ext_struct, + sftp_file_struct, + sftp_message_struct, + sftp_packet_struct, + sftp_request_queue_struct, + sftp_session_struct, + sftp_status_message_struct, + ssh_agent_state_struct, + ssh_agent_struct, + ssh_auth_auto_state_struct, + ssh_auth_request, + ssh_bind_config_keyword_table_s, + ssh_bind_config_match_keyword_table_s, + ssh_bind_struct, + ssh_buffer_struct, + ssh_channel_callbacks_struct, + ssh_channel_read_termination_struct, + ssh_channel_request, + ssh_channel_request_open, + ssh_channel_struct, + ssh_cipher_struct, + ssh_common_struct, + ssh_config_keyword_table_s, + ssh_config_match_keyword_table_s, + ssh_connector_struct, + ssh_counter_struct, + ssh_crypto_struct, + ssh_event_fd_wrapper, + ssh_event_struct, + ssh_global_request, + ssh_gssapi_struct, + ssh_hmac_struct, + ssh_iterator, + ssh_kbdint_struct, + ssh_kex_struct, + ssh_key_struct, + ssh_knownhosts_entry, + ssh_list, + ssh_mac_ctx_struct, + ssh_message_struct, + ssh_packet_callbacks_struct, + ssh_packet_header, + ssh_poll_ctx_struct, + ssh_poll_handle_struct, + ssh_pollfd_struct, + ssh_private_key_struct, + ssh_public_key_struct, + ssh_scp_struct, + ssh_service_request, + ssh_session_struct, + ssh_signature_struct, + ssh_socket_struct, + ssh_string_struct, + ssh_threads_callbacks_struct, + ssh_timestamp) + set(DOXYGEN_EXCLUDE_SYMBOLS_MACRO + SSH_FXP*, + SSH_SOCKET*, + SERVERBANNER, + SOCKOPT_TYPE_ARG4, + SSH_FILEXFER*, + SSH_FXF*, + SSH_S_*, + SFTP_*, + NSS_BUFLEN_PASSWD, + CLOCK, + MAX_LINE_SIZE, + PKCS11_URI, + KNOWNHOSTS_MAXTYPES) + set(DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS + sftp_attributes, + sftp_client_message, + sftp_dir, + sftp_ext, + sftp_file, + sftp_message, + sftp_packet, + sftp_request_queue, + sftp_status_message, + sftp_statvfs_t, + poll_fn, + ssh_callback_int, + ssh_callback_data, + ssh_callback_int_int, + ssh_message_callback, + ssh_channel_callback_int, + ssh_channel_callback_data, + ssh_callbacks, + ssh_gssapi_select_oid_callback, + ssh_gssapi_accept_sec_ctx_callback, + ssh_gssapi_verify_mic_callback, + ssh_server_callbacks, + ssh_socket_callbacks, + ssh_packet_callbacks, + ssh_channel_callbacks, + ssh_bind, + ssh_bind_callbacks) + set(DOXYGEN_EXCLUDE_SYMBOLS + ${DOXYGEN_EXCLUDE_SYMBOLS_STRUCTS} ${DOXYGEN_EXCLUDE_SYMBOLS_MACRO} + ${DOXYGEN_EXCLUDE_SYMBOLS_TYPEDEFS}) + + # Custom layout file to rename "Topics" to "API Reference" and simplify + # navigation + set(DOXYGEN_LAYOUT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/DoxygenLayout.xml) + # Custom HTML header with doxygen-awesome extension initialization + set(DOXYGEN_HTML_HEADER ${CMAKE_CURRENT_SOURCE_DIR}/header.html) + # Modern CSS theme for documentation with custom libssh.org color scheme + set(DOXYGEN_HTML_EXTRA_STYLESHEET + ${AWESOME_CSS_DIR}/doxygen-awesome.css + ${CMAKE_CURRENT_SOURCE_DIR}/doxygen-custom.css) + # JavaScript extensions: dark mode toggle, copy button, paragraph links, + # interactive TOC + set(DOXYGEN_HTML_EXTRA_FILES + ${AWESOME_CSS_DIR}/doxygen-awesome-darkmode-toggle.js + ${AWESOME_CSS_DIR}/doxygen-awesome-fragment-copy-button.js + ${AWESOME_CSS_DIR}/doxygen-awesome-paragraph-link.js + ${AWESOME_CSS_DIR}/doxygen-awesome-interactive-toc.js) + + set(_doxyfile_template "${CMAKE_BINARY_DIR}/CMakeDoxyfile.in") + set(_target_doxyfile "${CMAKE_CURRENT_BINARY_DIR}/Doxyfile.docs") + configure_file("${_doxyfile_template}" "${_target_doxyfile}") + + doxygen_add_docs(docs ${CMAKE_SOURCE_DIR}/include/libssh + ${CMAKE_SOURCE_DIR}/src ${CMAKE_CURRENT_SOURCE_DIR}) + + # Make docs depend on doxygen-awesome-css download (if not using local dir) + if (TARGET doxygen-awesome-css) + add_dependencies(docs doxygen-awesome-css) + endif () + + add_custom_target( + docs_coverage COMMAND ${CMAKE_SOURCE_DIR}/doc/doc_coverage.sh + ${CMAKE_BINARY_DIR}) +endif (DOXYGEN_FOUND) diff --git a/src/libs/libssh-0.12.2/doc/DoxygenLayout.xml b/src/libs/libssh-0.12.2/doc/DoxygenLayout.xml new file mode 100644 index 000000000000..14a905052165 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/DoxygenLayout.xml @@ -0,0 +1,242 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/libs/libssh-0.12.2/doc/README.gitlab.freebsd.md b/src/libs/libssh-0.12.2/doc/README.gitlab.freebsd.md new file mode 100644 index 000000000000..65628d2d73a6 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/README.gitlab.freebsd.md @@ -0,0 +1,101 @@ +# Install a FreeBSD CI instance + +Install the following packages: + +``` +pkg install -y bash git gmake cmake cmocka openssl wget pkgconf ccache bash +``` + +Create gitlab-runner user: + +``` +pw group add -n gitlab-runner +pw user add -n gitlab-runner -g gitlab-runner -s /usr/local/bin/bash +mkdir /home/gitlab-runner +chown gitlab-runner:gitlab-runner /home/gitlab-runner +``` + +Get the gitlab-runner binary for freebsd: + +``` +wget -O /usr/local/bin/gitlab-runner https://gitlab-runner-downloads.s3.amazonaws.com/latest/binaries/gitlab-runner-freebsd-amd64 +chmod +x /usr/local/bin/gitlab-runner +``` + +Create a log file and allow access: + +``` +touch /var/log/gitlab_runner.log && chown gitlab-runner:gitlab-runner /var/log/gitlab_runner.log +``` + +We need a start script to run it on boot: + +``` +mkdir -p /usr/local/etc/rc.d +cat > /usr/local/etc/rc.d/gitlab_runner << EOF +#!/usr/local/bin/bash +# PROVIDE: gitlab_runner +# REQUIRE: DAEMON NETWORKING +# BEFORE: +# KEYWORD: + +. /etc/rc.subr + +name="gitlab_runner" +rcvar="gitlab_runner_enable" + +load_rc_config $name + +user="gitlab-runner" +user_home="/home/gitlab-runner" +command="/usr/local/bin/gitlab-runner run" +pidfile="/var/run/${name}.pid" + +start_cmd="gitlab_runner_start" +stop_cmd="gitlab_runner_stop" +status_cmd="gitlab_runner_status" + +gitlab_runner_start() +{ + export USER=${user} + export HOME=${user_home} + + if checkyesno ${rcvar}; then + cd ${user_home} + /usr/sbin/daemon -u ${user} -p ${pidfile} ${command} > /var/log/gitlab_runner.log 2>&1 + fi +} + +gitlab_runner_stop() +{ + if [ -f ${pidfile} ]; then + kill `cat ${pidfile}` + fi +} + +gitlab_runner_status() +{ + if [ ! -f ${pidfile} ] || kill -0 `cat ${pidfile}`; then + echo "Service ${name} is not running." + else + echo "${name} appears to be running." + fi +} + +run_rc_command $1 +EOF +chmod +x /usr/local/etc/rc.d/gitlab_runner +``` + +Register your gitlab-runner with your gitlab project + +``` +su gitlab-runner -c 'gitlab-runner register' +``` + +Start the gitlab runner service: + +``` +sysrc -f /etc/rc.conf "gitlab_runner_enable=YES" +service gitlab_runner start +``` diff --git a/src/libs/libssh-0.12.2/doc/authentication.dox b/src/libs/libssh-0.12.2/doc/authentication.dox new file mode 100644 index 000000000000..a0b2df843c03 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/authentication.dox @@ -0,0 +1,378 @@ +/** +@page libssh_tutor_authentication Chapter 2: A deeper insight on authentication +@section authentication_details A deeper insight on authentication + +In our guided tour, we merely mentioned that the user needed to authenticate. +We didn't explain much in detail how that was supposed to happen. +This chapter explains better the four authentication methods: with public keys, +with a password, with challenges and responses (keyboard-interactive), and with +no authentication at all. + +If your software is supposed to connect to an arbitrary server, then you +might need to support all authentication methods. If your software will +connect only to a given server, then it might be enough for your software +to support only the authentication methods used by that server. If you are +the administrator of the server, it might be your call to choose those +authentication methods. + +It is not the purpose of this document to review in detail the advantages +and drawbacks of each authentication method. You are therefore invited +to read the abundant documentation on this topic to fully understand the +advantages and security risks linked to each method. + + +@subsection pubkeys Authenticating with public keys + +libssh is fully compatible with the openssh public and private keys. You +can either use the automatic public key authentication method provided by +libssh, or roll your own using the public key functions. + +The process of authenticating by public key to a server is the following: + - you scan a list of files that contain public keys. each key is sent to + the SSH server, until the server acknowledges a key (a key it knows can be + used to authenticate the user). + - then, you retrieve the private key for this key and send a message + proving that you know that private key. + - when several identity files are specified, then the order of processing of + these files is from the last-mentioned to the first one + (if specified in the ~/.ssh/config, then starting from the bottom to the top). + +The function ssh_userauth_autopubkey() does this using the available keys in +"~/.ssh/". The return values are the following: + - SSH_AUTH_ERROR: some serious error happened during authentication + - SSH_AUTH_DENIED: no key matched + - SSH_AUTH_SUCCESS: you are now authenticated + - SSH_AUTH_PARTIAL: some key matched but you still have to provide an other + mean of authentication (like a password). + +The ssh_userauth_publickey_auto() function also tries to authenticate using the +SSH agent, if you have one running, or the "none" method otherwise. + +If you wish to authenticate with public key by your own, follow these steps: + - Retrieve the public key with ssh_pki_import_pubkey_file(). + - Offer the public key to the SSH server using ssh_userauth_try_publickey(). + If the return value is SSH_AUTH_SUCCESS, the SSH server accepts to + authenticate using the public key and you can go to the next step. + - Retrieve the private key, using the ssh_pki_import_privkey_file() function. + If a passphrase is needed, either the passphrase specified as argument or + a callback will be used. + - Authenticate using ssh_userauth_publickey() with your private key. + - Do not forget cleaning up memory using ssh_key_free(). + +Here is a minimalistic example of public key authentication: + +@code +int authenticate_pubkey(ssh_session session) +{ + int rc; + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@see ssh_userauth_publickey_auto() +@see ssh_userauth_try_publickey() +@see ssh_userauth_publickey() +@see ssh_pki_import_pubkey_file() +@see ssh_pki_import_privkey_file() +@see ssh_key_free() + + +@subsection password Authenticating with a password + +The function ssh_userauth_password() serves the purpose of authenticating +using a password. It will return SSH_AUTH_SUCCESS if the password worked, +or one of other constants otherwise. It's your work to ask the password +and to deallocate it in a secure manner. + +If your server complains that the password is wrong, but you can still +authenticate using openssh's client (issuing password), it's probably +because openssh only accept keyboard-interactive. Switch to +keyboard-interactive authentication, or try to configure plain text passwords +on the SSH server. + +Here is a small example of password authentication: + +@code +int authenticate_password(ssh_session session) +{ + char *password = NULL; + int rc; + + password = getpass("Enter your password: "); + rc = ssh_userauth_password(session, NULL, password); + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@see ssh_userauth_password + + +@subsection keyb_int The keyboard-interactive authentication method + +The keyboard-interactive method is, as its name tells, interactive. The +server will issue one or more challenges that the user has to answer, +until the server takes an authentication decision. + +ssh_userauth_kbdint() is the the main keyboard-interactive function. +It will return SSH_AUTH_SUCCESS,SSH_AUTH_DENIED, SSH_AUTH_PARTIAL, +SSH_AUTH_ERROR, or SSH_AUTH_INFO, depending on the result of the request. + +The keyboard-interactive authentication method of SSH2 is a feature that +permits the server to ask a certain number of questions in an interactive +manner to the client, until it decides to accept or deny the login. + +To begin, you call ssh_userauth_kbdint() (just set user and submethods to +NULL) and store the answer. + +If the answer is SSH_AUTH_INFO, it means that the server has sent a few +questions that you should ask the user. You can retrieve these questions +with the following functions: ssh_userauth_kbdint_getnprompts(), +ssh_userauth_kbdint_getname(), ssh_userauth_kbdint_getinstruction(), and +ssh_userauth_kbdint_getprompt(). + +Set the answer for each question in the challenge using +ssh_userauth_kbdint_setanswer(). + +Then, call again ssh_userauth_kbdint() and start the process again until +these functions returns something else than SSH_AUTH_INFO. + +Here are a few remarks: + - Even the first call can return SSH_AUTH_DENIED or SSH_AUTH_SUCCESS. + - The server can send an empty question set (this is the default behavior + on my system) after you have sent the answers to the first questions. + You must still parse the answer, it might contain some + message from the server saying hello or such things. Just call + ssh_userauth_kbdint() until needed. + - The meaning of "name", "prompt", "instruction" may be a little + confusing. An explanation is given in the RFC section that follows. + +Here is a little note about how to use the information from +keyboard-interactive authentication, coming from the RFC itself (rfc4256): + +@verbatim + + 3.3 User Interface Upon receiving a request message, the client SHOULD + prompt the user as follows: A command line interface (CLI) client SHOULD + print the name and instruction (if non-empty), adding newlines. Then for + each prompt in turn, the client SHOULD display the prompt and read the + user input. + + A graphical user interface (GUI) client has many choices on how to prompt + the user. One possibility is to use the name field (possibly prefixed + with the application's name) as the title of a dialog window in which + the prompt(s) are presented. In that dialog window, the instruction field + would be a text message, and the prompts would be labels for text entry + fields. All fields SHOULD be presented to the user, for example an + implementation SHOULD NOT discard the name field because its windows lack + titles; it SHOULD instead find another way to display this information. If + prompts are presented in a dialog window, then the client SHOULD NOT + present each prompt in a separate window. + + All clients MUST properly handle an instruction field with embedded + newlines. They SHOULD also be able to display at least 30 characters for + the name and prompts. If the server presents names or prompts longer than 30 + characters, the client MAY truncate these fields to the length it can + display. If the client does truncate any fields, there MUST be an obvious + indication that such truncation has occurred. + + The instruction field SHOULD NOT be truncated. Clients SHOULD use control + character filtering as discussed in [SSH-ARCH] to avoid attacks by + including terminal control characters in the fields to be displayed. + + For each prompt, the corresponding echo field indicates whether or not + the user input should be echoed as characters are typed. Clients SHOULD + correctly echo/mask user input for each prompt independently of other + prompts in the request message. If a client does not honor the echo field + for whatever reason, then the client MUST err on the side of + masking input. A GUI client might like to have a checkbox toggling + echo/mask. Clients SHOULD NOT add any additional characters to the prompt + such as ": " (colon-space); the server is responsible for supplying all + text to be displayed to the user. Clients MUST also accept empty responses + from the user and pass them on as empty strings. +@endverbatim + +The following example shows how to perform keyboard-interactive authentication: + +@code +int authenticate_kbdint(ssh_session session) +{ + int rc; + + rc = ssh_userauth_kbdint(session, NULL, NULL); + while (rc == SSH_AUTH_INFO) + { + const char *name = NULL, *instruction = NULL; + int nprompts, iprompt; + + name = ssh_userauth_kbdint_getname(session); + instruction = ssh_userauth_kbdint_getinstruction(session); + nprompts = ssh_userauth_kbdint_getnprompts(session); + + if (strlen(name) > 0) + printf("%s\n", name); + if (strlen(instruction) > 0) + printf("%s\n", instruction); + for (iprompt = 0; iprompt < nprompts; iprompt++) + { + const char *prompt = NULL; + char echo; + + prompt = ssh_userauth_kbdint_getprompt(session, iprompt, &echo); + if (echo) + { + char buffer[128], *ptr; + + printf("%s", prompt); + if (fgets(buffer, sizeof(buffer), stdin) == NULL) + return SSH_AUTH_ERROR; + buffer[sizeof(buffer) - 1] = '\0'; + if ((ptr = strchr(buffer, '\n')) != NULL) + *ptr = '\0'; + if (ssh_userauth_kbdint_setanswer(session, iprompt, buffer) < 0) + return SSH_AUTH_ERROR; + memset(buffer, 0, strlen(buffer)); + } + else + { + char *ptr = NULL; + + ptr = getpass(prompt); + if (ssh_userauth_kbdint_setanswer(session, iprompt, ptr) < 0) + return SSH_AUTH_ERROR; + } + } + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + return rc; +} +@endcode + +@see ssh_userauth_kbdint() +@see ssh_userauth_kbdint_getnprompts() +@see ssh_userauth_kbdint_getname() +@see ssh_userauth_kbdint_getinstruction() +@see ssh_userauth_kbdint_getprompt() +@see ssh_userauth_kbdint_setanswer() + + +@subsection none Authenticating with "none" method + +The primary purpose of the "none" method is to get authenticated **without** +any credential. Don't do that, use one of the other authentication methods, +unless you really want to grant anonymous access. + +If the account has no password, and if the server is configured to let you +pass, ssh_userauth_none() might answer SSH_AUTH_SUCCESS. + +The following example shows how to perform "none" authentication: + +@code +int authenticate_none(ssh_session session) +{ + int rc; + + rc = ssh_userauth_none(session, NULL); + return rc; +} +@endcode + +@subsection auth_list Getting the list of supported authentications + +You are not meant to choose a given authentication method, you can +let the server tell you which methods are available. Once you know them, +you try them one after the other. + +The following example shows how to get the list of available authentication +methods with ssh_userauth_list() and how to use the result: + +@code +int test_several_auth_methods(ssh_session session) +{ + int method, rc; + + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_ERROR) { + return rc; + } + + method = ssh_userauth_list(session, NULL); + + if (method & SSH_AUTH_METHOD_NONE) + { // For the source code of function authenticate_none(), + // refer to the corresponding example + rc = authenticate_none(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_PUBLICKEY) + { // For the source code of function authenticate_pubkey(), + // refer to the corresponding example + rc = authenticate_pubkey(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_INTERACTIVE) + { // For the source code of function authenticate_kbdint(), + // refer to the corresponding example + rc = authenticate_kbdint(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + if (method & SSH_AUTH_METHOD_PASSWORD) + { // For the source code of function authenticate_password(), + // refer to the corresponding example + rc = authenticate_password(session); + if (rc == SSH_AUTH_SUCCESS) return rc; + } + return SSH_AUTH_ERROR; +} +@endcode + + +@subsection banner Getting the banner + +The SSH server might send a banner, which you can retrieve with +ssh_get_issue_banner(), then display to the user. + +The following example shows how to retrieve and dispose the issue banner: + +@code +int display_banner(ssh_session session) +{ + int rc; + char *banner = NULL; + +/* + *** Does not work without calling ssh_userauth_none() first *** + *** That will be fixed *** +*/ + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_ERROR) + return rc; + + banner = ssh_get_issue_banner(session); + if (banner) + { + printf("%s\n", banner); + free(banner); + } + + return rc; +} +@endcode + +*/ diff --git a/src/libs/libssh-0.12.2/doc/command.dox b/src/libs/libssh-0.12.2/doc/command.dox new file mode 100644 index 000000000000..dde323489602 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/command.dox @@ -0,0 +1,100 @@ +/** +@page libssh_tutor_command Chapter 4: Passing a remote command +@section remote_command Passing a remote command + +Previous chapter has shown how to open a full shell session, with an attached +terminal or not. If you only need to execute a command on the remote end, +you don't need all that complexity. + +The method described here is suited for executing only one remote command. +If you need to issue several commands in a row, you should consider using +a non-interactive remote shell, as explained in previous chapter. + +@see shell + + +@subsection exec_remote Executing a remote command + +The first steps for executing a remote command are identical to those +for opening remote shells. You first need a SSH channel, and then +a SSH session that uses this channel: + +@code +int show_remote_files(ssh_session session) +{ + ssh_channel channel = NULL; + int rc; + + channel = ssh_channel_new(session); + if (channel == NULL) return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } +@endcode + +Once a session is open, you can start the remote command with +ssh_channel_request_exec(): + +@code + rc = ssh_channel_request_exec(channel, "ls -l"); + if (rc != SSH_OK) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return rc; + } +@endcode + +If the remote command displays data, you get them with ssh_channel_read(). +This function returns the number of bytes read. If there is no more +data to read on the channel, this function returns 0, and you can go to next step. +If an error has been encountered, it returns a negative value: + +@code + char buffer[256]; + int nbytes; + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) + { + if (fwrite(buffer, 1, nbytes, stdout) != nbytes) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } +@endcode + +Once you read the result of the remote command, you send an +end-of-file to the channel, close it, and free the memory +that it used: + +@code + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + +Warning: In a single channel, only ONE command can be executed! +If you want to executed multiple commands, allocate separate channels for +them or consider opening interactive shell. +Attempting to run multiple consecutive commands in one channel will fail. + + +*/ diff --git a/src/libs/libssh-0.12.2/doc/curve25519-sha256@libssh.org.txt b/src/libs/libssh-0.12.2/doc/curve25519-sha256@libssh.org.txt new file mode 100644 index 000000000000..04d88575fbe6 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/curve25519-sha256@libssh.org.txt @@ -0,0 +1,119 @@ +curve25519-sha256@libssh.org.txt Aris Adamantiadis + 21/9/2013 + +1. Introduction + +This document describes the key exchange method curve25519-sha256@libssh.org +for SSH version 2 protocol. It is provided as an alternative to the existing +key exchange mechanisms based on either Diffie-Hellman or Elliptic Curve Diffie- +Hellman [RFC5656]. +The reason is the following : During summer of 2013, revelations from ex- +consultant at NSA Edward Snowden gave proof that NSA willingly inserts backdoors +into software, hardware components and published standards. While it is still +believed that the mathematics behind ECC cryptography are still sound and solid, +some people (including Bruce Schneier [SCHNEIER]), showed their lack of confidence +in NIST-published curves such as nistp256, nistp384, nistp521, for which constant +parameters (including the generator point) are defined without explanation. It +is also believed that NSA had a word to say in their definition. These curves +are not the most secure or fastest possible for their key sizes [DJB], and +researchers think it is possible that NSA have ways of cracking NIST curves. +It is also interesting to note that SSH belongs to the list of protocols the NSA +claims to be able to eavesdrop. Having a secure replacement would make passive +attacks much harder if such a backdoor exists. + +However an alternative exists in the form of Curve25519. This algorithm has been +proposed in 2006 by DJB [Curve25519]. Its main strengths are its speed, its +constant-time run time (and resistance against side-channel attacks), and its +lack of nebulous hard-coded constants. + +The reference version being used in this document is the one described in +[Curve25519] as implemented in the library NaCl [NaCl]. +This document does not attempt to provide alternatives to the ecdsa-sha1-* +authentication keys. + +2. Key exchange + +The key exchange procedure is very similar to the one described chapter 4 of +[RFC5656]. Public ephemeral keys are transmitted over SSH encapsulated into +standard SSH strings. + +The following is an overview of the key exchange process: + +Client Server +------ ------ +Generate ephemeral key pair. +SSH_MSG_KEX_ECDH_INIT --------> + Verify that client public key + length is 32 bytes. + Generate ephemeral key pair. + Compute shared secret. + Generate and sign exchange hash. + <-------- SSH_MSG_KEX_ECDH_REPLY +Verify that server public key length is 32 bytes. +* Verify host keys belong to server. +Compute shared secret. +Generate exchange hash. +Verify server's signature. + +* Optional but strongly recommended as this protects against MITM attacks. + +This is implemented using the same messages as described in RFC5656 chapter 4 + +3. Method Name + +The name of this key exchange method is "curve25519-sha256@libssh.org". + +4. Implementation considerations + +The whole method is based on the curve25519 scalar multiplication. In this +method, a private key is a scalar of 256 bits, and a public key is a point +of 256 bits. + +4.1. Private key generation + +A 32 bytes private key should be generated for each new connection, + using a secure PRNG. The following actions must be done on the private key: + mysecret[0] &= 248; + mysecret[31] &= 127; + mysecret[31] |= 64; +In order to keep the key valid. However, many cryptographic libraries will do +this automatically. +It should be noted that, in opposition to NIST curves, no special validation +should be done to ensure the result is a valid and secure private key. + +4.2 Public key generation + +The 32 bytes public key of either a client or a server must be generated using +the 32 bytes private key and a common generator base. This base is defined as 9 +followed by all zeroes: + const unsigned char basepoint[32] = {9}; + +The public key is calculated using the cryptographic scalar multiplication: + const unsigned char privkey[32]; + unsigned char pubkey[32]; + crypto_scalarmult (pubkey, privkey, basepoint); +However some cryptographic libraries may provide a combined function: + crypto_scalarmult_base (pubkey, privkey); + +It should be noted that, in opposition to NIST curves, no special validation +should be done to ensure the received public keys are valid curves point. The +Curve25519 algorithm ensure that every possible public key maps to a valid +ECC Point. + +4.3 Shared secret generation + +The shared secret, k, is defined in SSH specifications to be a big integer. +This number is calculated using the following procedure: + + X is the 32 bytes point obtained by the scalar multiplication of the other + side's public key and the local private key scalar. + + The whole 32 bytes of the number X are then converted into a big integer k. + This conversion follows the network byte order. This step differs from + RFC5656. + +[RFC5656] https://tools.ietf.org/html/rfc5656 +[SCHNEIER] https://www.schneier.com/blog/archives/2013/09/the_nsa_is_brea.html#c1675929 +[DJB] https://cr.yp.to/talks/2013.05.31/slides-dan+tanja-20130531-4x3.pdf +[Curve25519] "Curve25519: new Diffie-Hellman speed records." + https://cr.yp.to/ecdh/curve25519-20060209.pdf diff --git a/src/libs/libssh-0.12.2/doc/doc_coverage.sh b/src/libs/libssh-0.12.2/doc/doc_coverage.sh new file mode 100644 index 000000000000..2f6532757bee --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/doc_coverage.sh @@ -0,0 +1,52 @@ +#!/bin/bash +################################################################################ +# .doc_coverage.sh # +# Script to detect overall documentation coverage of libssh. The script uses # +# doxygen to generate the documentation then parses it's output. # +# # +# maintainer: Norbert Pocs # +################################################################################ +BUILD_DIR="$1" +DOXYFILE_PATH="$BUILD_DIR/doc/Doxyfile.docs" +INDEX_XML_PATH="$BUILD_DIR/doc/xml/index.xml" +# filters +F_EXCLUDE_FILES=' wrapper.h legacy.h crypto.h priv.h chacha.h curve25519.h ' +F_UNDOC_FUNC='(function).*is not documented' +F_FUNC='kind="function"' +F_HEADERS='libssh_8h_|group__libssh__' +F_CUT_BEFORE='.*' +F_CUT_AFTER='<\/name><\/member>' +# Doxygen options +O_QUIET='QUIET=YES' +O_GEN_XML='GENERATE_XML=YES' + +# check if build dir given +if [ $# -eq 0 ]; then + echo "Please provide the build directory e.g.: ./build" + exit 255 +fi + +# modify doxyfile to our needs: +# QUIET - less output +# GENERATE_XML - xml needed to inspect all the functions +# (note: the options are needed to be on separate lines) +# We want to exclude irrelevant files +MOD_DOXYFILE=$(cat "$DOXYFILE_PATH"; echo "$O_QUIET"; echo "$O_GEN_XML") +MOD_DOXYFILE=${MOD_DOXYFILE//EXCLUDE_PATTERNS.*=/EXCLUDE_PATTERNS=$F_EXCLUDE_FILES/g} + +# call doxygen to get the warning messages +# and also generate the xml for inspection +DOXY_WARNINGS=$(echo "$MOD_DOXYFILE" | doxygen - 2>&1) + +# get the number of undocumented functions +UNDOC_FUNC=$(echo "$DOXY_WARNINGS" | grep -cE "$F_UNDOC_FUNC") + +# filter out the lines consisting of functions of our interest +FUNC_LINES=$(grep "$F_FUNC" "$INDEX_XML_PATH" | grep -E "$F_HEADERS") +# cut the irrelevant information and leave just the function names +ALL_FUNC=$(echo "$FUNC_LINES" | sed -e "s/$F_CUT_BEFORE//g" -e "s/$F_CUT_AFTER//") +# remove duplicates and get the number of functions +ALL_FUNC=$(echo "$ALL_FUNC" | sort - | uniq | wc -l) + +# percentage of the documented functions +awk "BEGIN {printf \"Documentation coverage is %.2f%\n\", 100 - (${UNDOC_FUNC}/${ALL_FUNC}*100)}" diff --git a/src/libs/libssh-0.12.2/doc/doxygen-custom.css b/src/libs/libssh-0.12.2/doc/doxygen-custom.css new file mode 100644 index 000000000000..0c68d5759328 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/doxygen-custom.css @@ -0,0 +1,127 @@ +/** + * Custom color scheme for libssh documentation + * Based on libssh.org color palette + */ + +html { + /* Primary colors - using libssh.org orange accent */ + --primary-color: #F78C40; + --primary-dark-color: #f57900; + --primary-light-color: #fab889; + + /* Accent color - neutral gray */ + --primary-lighter-color: #5A5A5A; + + /* Page colors - clean white background */ + --page-background-color: #ffffff; + --page-foreground-color: #333333; + --page-secondary-foreground-color: #666666; + + /* Links - use the warm orange color */ + --link-color: #F78C40; + --link-hover-color: #f0690a; + + /* Code blocks and fragments - very light background */ + --code-background: #f9f9f9; + --fragment-background: #f9f9f9; + + /* Borders - subtle light grey */ + --separator-color: #e0e0e0; + --border-light-color: #f0f0f0; + + /* Side navigation - pure white */ + --side-nav-background: #ffffff; + + /* Menu colors - warm orange accent */ + --menu-selected-background: #F78C40; + + /* Tables and boxes - lighter */ + --tablehead-background: #fbc7a2; + --tablehead-foreground: #333333; +} + +/* Header styling with libssh brand colors */ +#titlearea { + background-color: #5A5A5A; + background-image: linear-gradient(to right, #5A5A5A, #6a6a6a); + border-bottom: 3px solid #F78C40; +} + +#projectname { + color: #ffffff !important; +} + +#projectbrief { + color: #fab889 !important; +} + +/* Top navigation tabs */ +#top { + background: linear-gradient(to bottom, #5A5A5A 0%, #6a6a6a 100%); +} + +.tabs, .tabs2, .tabs3 { + background-image: none; + background-color: transparent; +} + +.tablist li { + background: rgba(255, 255, 255, 0.1); + border-right: 1px solid rgba(255, 255, 255, 0.2); +} + +.tablist li:hover { + background: rgba(255, 255, 255, 0.2); +} + +.tablist li.current { + background: #F78C40; + border-bottom: 3px solid #f57900; +} + +/* Tab text colors - comprehensive selectors */ +#nav-path ul li a, +.tabs a, +.tabs2 a, +.tabs3 a, +.tablist a, +.tablist a:link, +.tablist a:visited, +.tablist li a, +#main-nav a, +.sm > li > a, +.sm > li > a .sub-arrow { + color: #ffffff !important; + text-shadow: 0px 1px 2px rgba(0, 0, 0, 0.3); +} + +/* Active/current tab text */ +#nav-path ul li.current a, +.tabs .current a, +.tabs2 .current a, +.tabs3 .current a, +.tablist .current a, +.tablist .current a:link, +.tablist .current a:visited, +.tablist li.current a, +#main-nav .current a, +.sm .current a { + color: #333333 !important; + text-shadow: none; +} + +/* Dropdown arrow - white color for top menu */ +.sm-dox a span.sub-arrow { + border-right-color: #ffffff !important; + border-bottom-color: #ffffff !important; +} + +/* Dropdown menu text - must be dark on white background */ +/* Make this as specific as possible to override white color */ +.sm-dox > li > ul > li > a, +.sm-dox li ul li a, +.sm-dox ul li a, +#main-menu ul li a { + color: #333333 !important; + text-shadow: none !important; +} diff --git a/src/libs/libssh-0.12.2/doc/favicon.png b/src/libs/libssh-0.12.2/doc/favicon.png new file mode 100644 index 000000000000..0b343c153696 Binary files /dev/null and b/src/libs/libssh-0.12.2/doc/favicon.png differ diff --git a/src/libs/libssh-0.12.2/doc/fetch_doxygen_awesome.cmake b/src/libs/libssh-0.12.2/doc/fetch_doxygen_awesome.cmake new file mode 100644 index 000000000000..1fa896740047 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/fetch_doxygen_awesome.cmake @@ -0,0 +1,41 @@ +# Script to download doxygen-awesome-css at build time +# +# Usage: +# cmake -P fetch_doxygen_awesome.cmake \ +# -DURL= \ +# -DDEST_DIR= \ +# -DVERSION= + +if(NOT DEFINED URL) + message(FATAL_ERROR "URL not specified") +endif() +if(NOT DEFINED DEST_DIR) + message(FATAL_ERROR "DEST_DIR not specified") +endif() +if(NOT DEFINED VERSION) + message(FATAL_ERROR "VERSION not specified") +endif() + +set(EXTRACT_DIR "${DEST_DIR}/doxygen-awesome-css-${VERSION}") + +if(NOT EXISTS "${EXTRACT_DIR}/doxygen-awesome.css") + message(STATUS "Downloading doxygen-awesome-css ${VERSION}...") + set(TARBALL "${DEST_DIR}/doxygen-awesome-css.tar.gz") + file(DOWNLOAD + "${URL}" + "${TARBALL}" + STATUS download_status + SHOW_PROGRESS + ) + list(GET download_status 0 status_code) + if(NOT status_code EQUAL 0) + list(GET download_status 1 error_msg) + message(FATAL_ERROR "Download failed: ${error_msg}") + endif() + message(STATUS "Extracting doxygen-awesome-css...") + file(ARCHIVE_EXTRACT + INPUT "${TARBALL}" + DESTINATION "${DEST_DIR}" + ) + file(REMOVE "${TARBALL}") +endif() diff --git a/src/libs/libssh-0.12.2/doc/fido2.dox b/src/libs/libssh-0.12.2/doc/fido2.dox new file mode 100644 index 000000000000..98f1ca1344ab --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/fido2.dox @@ -0,0 +1,601 @@ +/** + +@page libssh_tutor_fido2 Chapter 11: FIDO2/U2F Keys Support + +@section fido2_intro Introduction + +The traditional SSH public key model stores the private key on disk +and anyone who obtains that file (and possibly its passphrase) can impersonate +the user. FIDO2 authenticators, such as USB security keys, are hardware tokens +that generate or securely store private key material within a secure element +and may require explicit user interaction such as a touch, PIN, or biometric +verification for use. Hence, security keys are far safer from theft or +exfiltration than traditional file-based SSH keys. libssh provides support +for FIDO2/U2F security keys as hardware-backed SSH authentication credentials. + +This chapter explains the concepts, build prerequisites, the API, and +usage patterns for enrolling (creating) and using security key-backed SSH +keys, including resident (discoverable) credentials. + +@subsection fido2_resident_keys Resident Keys + +Two credential storage modes exist for security keys: + + - Non-resident (default): A credential ID (key handle) and metadata are + stored on the client-side in a key file. This key handle must be + presented to the FIDO2/U2F device while signing. This is somewhat + similar to traditional SSH keys, except that the key handle is not the + private key itself, but used in combination with the device's master key + to derive the actual private key. + + - Resident (discoverable): The credential (and metadata like user id) is + stored on the device. No local file is needed; the device can enumerate or + locate the credential internally when queried. + +Advantages of resident keys include portability (using the same device +across hosts) and resilience (no loss if the local machine is destroyed). +Although, they may be limited by the storage of the authenticator. + +@subsection fido2_presence_verification User Presence vs. User Verification + +FIDO2 distinguishes between: + + - User Presence (UP): A simple physical interaction (touch) to confirm a + human is present. + + - User Verification (UV): Verification of the user’s identity through + biometric authentication or a PIN. + +Requiring UV provides additional protection if the device is stolen +and used without the PIN/biometric. + +libssh exposes flags controlling these requirements (see below). + +@subsection fido2_callbacks The Callback Abstraction + +Different environments may need to access security keys through different +transport layers (e.g., USB-HID, NFC, Bluetooth, etc.). To accommodate +this variability, libssh does not hard-code a single implementation. + +Instead, it defines a small callback interface (`ssh_sk_callbacks`) used for all +security key operations. Any implementation of this callback interface can be used +by higher-level PKI functions to perform enroll/sign/load_resident_keys +operations without needing to know the transport specifics. Hence, users can +define their own implementations for these callbacks to support different +transport protocols or custom hardware. Refer @ref fido2_custom_callbacks +for additional details. + +The callback interface is defined in `libssh/callbacks.h` and the behaviour +and return values are specified by `libssh/sk_api.h`, which is the same +interface defined by OpenSSH for its security key support. This means that +any callback implementations (also called "middleware" in OpenSSH terminology) +developed for OpenSSH can be adapted to libssh with minimal changes. + +The following operations are abstracted by the callback interface: + + - api_version(): Report the version of the SK API that the callback implementation + is based on, so that libssh can check whether this implementation would be + compatible with the SK API version that it supports. + Refer @ref fido2_custom_callbacks_version for additional details. + - enroll(): Create (enroll) a new credential, returning public key, key + handle, attestation data. + - sign(): Produce a signature for supplied inputs using an existing key + handle. + - load_resident_keys(): Enumerate resident (discoverable) credentials stored + on the authenticator. + +libssh provides a default implementation of the `ssh_sk_callbacks` using +the libfido2 library for the USB-HID transport protocol. Hence, by default, +libssh can interact with any FIDO2/U2F device that supports USB-HID and is +compatible with libfido2, without requiring any additional modifications. + +@subsection fido2_build Building with FIDO2 Support + +To enable FIDO2/U2F support, libssh must be built with the WITH_FIDO2 +build option as follows: + +@verbatim + cmake -DWITH_FIDO2=ON .. +@endverbatim + +libssh will also build the default USB-HID `ssh_sk_callbacks`, if the +libfido2 library and headers are installed on your system. + +@warning If built without libfido2, support for interacting with FIDO2/U2F +devices over USB-HID will not be available. + +@subsection fido2_api_overview API Overview + +Security key operations are configured through the `ssh_pki_ctx` +which allows to specify both general PKI options and FIDO2-specific +options such as the sk_callbacks, challenge data, application string, flags, etc. + +The following sections describe the options that can be configured and how +the `ssh_pki_ctx` is used in conjunction with `ssh_key` to perform +enrollment, signing, and resident key loading operations. + +@subsection fido2_key_objects Security Key Objects & Metadata + +Security keys are surfaced as `ssh_key` objects of type +`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519` (corresponding to the +OpenSSH public key algorithm names `sk-ecdsa-sha2-nistp256@openssh.com` and +`sk-ssh-ed25519@openssh.com`). In addition to standard key handling, libssh +exposes the following helper functions to retrieve embedded SK metadata: + + - ssh_key_get_sk_application(): Returns the relying party / application + (RP ID) string. The Relying Party ID (RP ID) is a string + that identifies the application or service requesting key enrollment. It + ensures that a credential is bound to a specific origin, preventing + phishing across sites. During registration, the authenticator associates + the credential with this RP ID so that it can later only be used for + authentication requests from the same relying party. For SSH keys, the + common format is "ssh:user@host". + + - ssh_key_get_sk_user_id(): Returns a copy of the user ID associated with a key + which represents a unique identifier for the user within the relying + party (application) context. It is typically a string (such as an + email, or a random identifier) that helps distinguish credentials + belonging to different users for the same application. + + Though the user ID can be binary data according to the FIDO2 spec, libssh only + supports NUL-terminated strings for enrolling new keys in order to remain compatible + with the OpenSSH's sk-api interface. + + However, libssh does support loading existing resident keys with user IDs containing + arbitrary binary data. It does so by using an `ssh_string` to store the loaded key's + user_id, and an `ssh_string` can contain arbitrary binary data that can not be stored + in a traditional NUL-terminated string (like null bytes). + + @note The user_id is NOT stored in the key file for non-resident keys. It is only + available for resident (discoverable) keys loaded from the authenticator via + ssh_sk_resident_keys_load(). For keys imported from files, this function returns + NULL. + + - ssh_key_get_sk_flags(): Returns the flags associated with the key. The + following are the supported flags and they can be combined using + bitwise OR: + - SSH_SK_USER_PRESENCE_REQD : Require user presence (touch). + - SSH_SK_USER_VERIFICATION_REQD : Require user verification + (PIN/biometric). + - SSH_SK_RESIDENT_KEY : Request a resident discoverable credential. + - SSH_SK_FORCE_OPERATION : Force resident (discoverable) credential + creation even if one with same application and user_id already + exists. + +These functions perform no additional communication with the +authenticator, this metadata is captured during enrollment/loading and +cached in the `ssh_key`. + +@subsection fido2_options Setting Security Key Context Options + +Options are set via ssh_pki_ctx_options_set(). + +Representative security key options: + - SSH_PKI_OPTION_SK_APPLICATION (const char *): Required relying party ID + If not set, a default value of "ssh:" is used. + - SSH_PKI_OPTION_SK_FLAGS (uint8_t *): Flags described above. If not set, + defaults to SSH_SK_USER_PRESENCE_REQD. This is because OpenSSH `sshd` + requires user presence for security key authentication by default. + - SSH_PKI_OPTION_SK_USER_ID (const char *): Represents a unique identifier + for the user within the relying party (application) context. + It is typically a string (such as an email, or a random identifier) that + helps distinguish credentials belonging to different users for the same + application. If not set, defaults to 64 zeros. + - SSH_PKI_OPTION_SK_CHALLENGE (ssh_buffer): Custom challenge; if omitted a + random 32-byte challenge is generated. + - SSH_PKI_OPTION_SK_CALLBACKS (ssh_sk_callbacks): Replace the default + callbacks with custom callbacks. + +PIN callback: Use ssh_pki_ctx_set_sk_pin_callback() to register a function +matching `ssh_auth_callback` to prompt for and supply a PIN. The callback may +be called multiple times to ask for the pin depending on the authenticator policy. + +Callback options: Callback implementations may accept additional configuration +name/value options such as the path to the fido device. These options can be provided via +`ssh_pki_ctx_sk_callbacks_option_set()`. Refer @ref fido2_custom_callbacks_options +for additional details. + +The built-in callback implementation provided by libssh supports additional options, +with their names defined in `libssh.h` prefixed with `SSH_SK_OPTION_NAME_*`, such as: + +SSH_SK_OPTION_NAME_DEVICE_PATH: Used for specifying a device path. +If the device path is not specified and multiple devices are connected, then +depending upon the operation and the flags set, the callback implementation may +automatically select a suitable device, or the user may be prompted to touch the +device they want to use. + +SSH_SK_OPTION_NAME_USER_ID: Used for setting the user ID. +Note that the user ID can also be set using the ssh_pki_ctx_options_set() API. + +@subsection fido2_enrollment Enrollment Example + +An enrollment operation creates a new credential on the authenticator and +returns an ssh_key object representing it. The application and user_id +fields are required for creating the credential. The other options are +optional. A successful enrollment returns the public key, key handle, and +metadata which are stored in the ssh_key object, and may optionally return +attestation data which is used for verifying the authenticator model and +firmware version. + +Below is a simple example enrolling an Ed25519 security key (non-resident) +requiring user presence only: + +@code +#include +#include + +static int pin_cb(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void)prompt; + (void)echo; + (void)verify; + (void)userdata; + + /* In a real application, the user would be prompted to enter the PIN */ + const char *pin = "4242"; + size_t l = strlen(pin); + if (l + 1 > len) { + return SSH_ERROR; + } + + memcpy(buf, pin, l + 1); + return SSH_OK; +} + +int enroll_sk_key() +{ + const char *app = "ssh:user@host"; + const char *user_id = "alice"; + uint8_t flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD; + const char *device_path = "/dev/hidraw6"; /* Optional device path */ + + ssh_pki_ctx pki_ctx = ssh_pki_ctx_new(); + ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_APPLICATION, app); + ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_USER_ID, user_id); + ssh_pki_ctx_options_set(pki_ctx, SSH_PKI_OPTION_SK_FLAGS, &flags); + + ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); + + ssh_pki_ctx_sk_callbacks_option_set(pki_ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + device_path, + true); + + ssh_key enrolled = NULL; + int rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ED25519, + pki_ctx, + &enrolled); /* produces sk-ed25519 key */ + + /* Save enrolled key using ssh_pki_export_privkey_file, retrieve attestation + * buffer etc. */ + + /* Free context and key when done */ +} +@endcode + +After a successful enrollment, you can retrieve the attestation buffer +(if provided by the authenticator) from the PKI context: + +@code +ssh_buffer att_buf = NULL; +rc = ssh_pki_ctx_get_sk_attestation_buffer(pki_ctx, &att_buf); +if (rc == SSH_OK && att_buf != NULL) { + /* att_buf now contains the serialized attestation + * ("ssh-sk-attest-v01"). You can inspect, save, or + * parse the buffer as needed + */ + ssh_buffer_free(att_buf); +} +@endcode + +Notes: +- The attestation buffer is only populated if the enrollment operation + succeeds and the authenticator provides attestation data. +- `ssh_pki_ctx_get_sk_attestation_buffer()` returns a copy of the attestation + buffer; the caller must free it with `ssh_buffer_free()`. + +@subsection fido2_signing Authenticating with a Stored Security Key Public Key + +To authenticate using a security key, the application typically loads the +previously enrolled sk-* private key, establishes an SSH connection, and +calls `ssh_userauth_publickey()`. libssh automatically recognizes security +key types and transparently handles the required hardware-backed +authentication steps such as prompting for a touch or PIN using the +configured security key callbacks. + +Example: +@code +#include +#include + +int auth_with_sk_file(const char *host, + const char *user, + const char *privkey_path) +{ + ssh_session session = NULL; + ssh_key privkey = NULL; + int rc = SSH_ERROR; + + session = ssh_new(); + ssh_options_set(session, SSH_OPTIONS_HOST, host); + ssh_options_set(session, SSH_OPTIONS_USER, user); + ssh_connect(session); + + ssh_pki_import_privkey_file(privkey_path, NULL, NULL, NULL, &privkey); + + ssh_pki_ctx pki_ctx = ssh_pki_ctx_new(); + /* Optionally set PIN callback, device path, etc. */ + /* ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); */ + + ssh_options_set(session, SSH_OPTIONS_PKI_CONTEXT, pki_ctx); + + rc = ssh_userauth_publickey(session, user, privkey); + if (rc == SSH_AUTH_SUCCESS) { + printf("Authenticated with security key.\n"); + rc = SSH_OK; + } else { + fprintf(stderr, + "Authentication failed rc=%d err=%s\n", + rc, + ssh_get_error(session)); + rc = SSH_ERROR; + } + + /* Free resources */ +} +@endcode + +@subsection fido2_resident Resident Key Enumeration + +Resident keys stored on the device can be discovered and loaded with +ssh_sk_resident_keys_load() which takes a PKI context (configured with +a PIN callback) and returns each key as an ssh_key and the number of keys loaded. + +Example: + +@code +#include +#include +#include + +static int pin_cb(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void)prompt; + (void)echo; + (void)verify; + (void)userdata; + const char *pin = "4242"; + size_t l = strlen(pin); + + if (l + 1 > len) { + return SSH_ERROR; + } + + memcpy(buf, pin, l + 1); + return SSH_OK; +} + +int auth_with_resident(const char *host, + const char *user, + const char *application, + const char *user_id) +{ + ssh_pki_ctx pki_ctx = NULL; + size_t num_found = 0; + ssh_key *keys = NULL; + ssh_key final_key = NULL; + int rc = SSH_ERROR; + + ssh_string cur_application = NULL; + ssh_string cur_user_id = NULL; + ssh_string expected_application = NULL; + ssh_string expected_user_id = NULL; + + pki_ctx = ssh_pki_ctx_new(); + ssh_pki_ctx_set_sk_pin_callback(pki_ctx, pin_cb, NULL); + + expected_application = ssh_string_from_char(application); + expected_user_id = ssh_string_from_char(user_id); + + rc = ssh_sk_resident_keys_load(pki_ctx, &keys, &num_found); + for (size_t i = 0; i < num_found; i++) { + cur_application = ssh_key_get_sk_application(keys[i]); + cur_user_id = ssh_key_get_sk_user_id(keys[i]); + + if (ssh_string_cmp(cur_application, expected_application) == 0 && + ssh_string_cmp(cur_user_id, expected_user_id) == 0) { + SSH_STRING_FREE(cur_application); + SSH_STRING_FREE(cur_user_id); + final_key = keys[i]; + break; + } + + SSH_STRING_FREE(cur_application); + SSH_STRING_FREE(cur_user_id); + } + + SSH_STRING_FREE(expected_application); + SSH_STRING_FREE(expected_user_id); + + /* Continue with authentication using the ssh_key with + * ssh_userauth_publickey as usual, and free resources when done. */ +} +@endcode + +@subsection fido2_sshsig Signing using the sshsig API + +Security keys can also be used for general-purpose signing of arbitrary data +(without SSH authentication) using the existing `sshsig_sign()` and `sshsig_verify()` +functions. These functions work seamlessly with security key types +(`SSH_KEYTYPE_SK_ECDSA` and `SSH_KEYTYPE_SK_ED25519`) and will automatically +invoke the configured security key callbacks to perform hardware-backed signing +operations. + +@subsection fido2_custom_callbacks Implementing Custom Callback Implementations + +Users may need to implement custom callback implementations to support +different transport protocols (e.g., NFC, Bluetooth) beyond the default USB-HID +support. This section describes how to implement and integrate custom callback +implementations. + +To implement custom callbacks, you must include the following headers: + +@code +#include /* For ssh_sk_callbacks_struct */ +#include /* For SK API constants and data structures */ +@endcode + +The `libssh/sk_api.h` header provides the complete interface specification including +request/response structures, flags, and version macros. + +@subsubsection fido2_custom_callbacks_version API Version Compatibility + +libssh validates callback implementations by checking the API version returned by +the `api_version()` callback. To ensure compatibility, libssh compares the major +version (upper 16 bits) of the returned value with `LIBSSH_SK_API_VERSION_MAJOR`. +If they don't match, libssh will reject the callback implementation. +This ensures that the callbacks' SK API matches the major version expected by libssh, +while allowing minor version differences. + +@subsubsection fido2_custom_callbacks_implementation Implementation Example + +Here's a minimal example of defining and using custom callbacks: + +@code +#include +#include +#include + +/* Your custom API version callback */ +static uint32_t my_sk_api_version(void) +{ + /* Match the major version, set your own minor version */ + return SSH_SK_VERSION_MAJOR | 0x0001; +} + +/* Your custom enroll callback */ +static int my_sk_enroll(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response) +{ + /* Parse options array to extract custom parameters */ + if (options != NULL) { + for (size_t i = 0; options[i] != NULL; i++) { + if (strcmp(options[i]->name, "my_custom_option") == 0) { + /* Use options[i]->value */ + } + } + } + + /* Implement your enroll logic here */ + /* ... */ + + return SSH_SK_ERR_GENERAL; /* Return appropriate error code */ +} + +/* Implement other required callbacks: sign, load_resident_keys */ +/* ... */ + +/* Define your callback structure */ +static struct ssh_sk_callbacks_struct my_sk_callbacks = { + .size = sizeof(struct ssh_sk_callbacks_struct), + .api_version = my_sk_api_version, + .enroll = my_sk_enroll, + .sign = my_sk_sign, /* Your implementation */ + .load_resident_keys = my_sk_load_resident_keys, /* Your implementation */ +}; + +/* Usage example */ +void use_custom_callbacks(void) +{ + ssh_pki_ctx pki_ctx = ssh_pki_ctx_new(); + + /* Set your custom callbacks */ + ssh_pki_ctx_options_set(pki_ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + &my_sk_callbacks); + + /* Pass custom options to your callbacks */ + ssh_pki_ctx_sk_callbacks_option_set(pki_ctx, + "my_custom_option", + "my_custom_value", + false); + + /* Use the context for enrollment, signing, etc. */ +} +@endcode + +@subsubsection fido2_custom_callbacks_options Passing Custom Options + +The `ssh_pki_ctx_sk_callbacks_option_set()` function allows you to pass +implementation-specific options as name/value string pairs: + +@code +ssh_pki_ctx_sk_callbacks_option_set(pki_ctx, + "option_name", + "option_value", + required); +@endcode + +Parameters: +- `option_name`: The name of the option (e.g., "device_path", "my_custom_param") +- `option_value`: The string value for this option +- `required`: If true, this option must be processed by the callback implementation + and cannot be ignored. If false, the option is advisory and can be skipped if the + callback implementation does not support it. + +These options are passed to your callbacks in the `struct sk_option **options` +parameter as a NULL-terminated array. Each `sk_option` has the following fields: +- `name`: The option name (char *) +- `value`: The option value (char *) +- `required`: Whether the option must be processed (uint8_t, non-zero = required) + +@subsubsection fido2_custom_callbacks_openssh OpenSSH Middleware Compatibility + +Since libssh uses the same SK API as OpenSSH, middleware implementations developed +for OpenSSH can be adapted with minimal changes. +To adapt an OpenSSH middleware for libssh, create a wrapper that populates +`ssh_sk_callbacks_struct` with pointers to the middleware's functions. + +@subsection fido2_testing Testing and Environment Variables + +Unit tests covering USB-HID enroll/sign/load_resident_keys operations can be found +in the `tests/unittests/torture_sk_usbhid.c` file. To run these tests you +must have libfido2 installed and the WITH_FIDO2=ON build option set. +Additionally, you must ensure the following: + + - An actual FIDO2 device must be connected to the test machine. + - The TORTURE_SK_USBHID environment variable must be set. + - The environment variable TORTURE_SK_PIN= must be set. + +If these are not set, the tests are skipped. + +The higher level PKI integration tests can be found in +`tests/unittests/torture_pki_sk.c` and the tests related to the sshsig API +can be found in `tests/unittests/torture_pki_sshsig.c`. +These use the callback implementation provided by OpenSSH's sk-dummy.so, +which simulates an authenticator without requiring any hardware. Hence, these tests +can be run in the CI environment. +However, these tests can also be configured to use the default USB-HID callbacks +by setting the same environment variables as described above. + +The following devices were tested during development: + +- Yubico Security Key NFC - USB-A + +*/ diff --git a/src/libs/libssh-0.12.2/doc/forwarding.dox b/src/libs/libssh-0.12.2/doc/forwarding.dox new file mode 100644 index 000000000000..3ca3aa8a0f2f --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/forwarding.dox @@ -0,0 +1,236 @@ +/** +@page libssh_tutor_forwarding Chapter 7: Forwarding connections (tunnel) +@section forwarding_connections Forwarding connections + +Port forwarding comes in SSH protocol in two different flavours: +direct or reverse port forwarding. Direct port forwarding is also +named local port forwarding, and reverse port forwarding is also called +remote port forwarding. SSH also allows X11 tunnels. + + + +@subsection forwarding_direct Direct port forwarding + +Direct port forwarding is from client to server. The client opens a tunnel, +and forwards whatever data to the server. Then, the server connects to an +end point. The end point can reside on another machine or on the SSH +server itself. + +Example of use of direct port forwarding: +@verbatim +Mail client application Google Mail + | ^ + 5555 (arbitrary) | + | 143 (IMAP2) + V | + SSH client =====> SSH server + +Legend: +--P-->: port connections through port P +=====>: SSH tunnel +@endverbatim +A mail client connects to port 5555 of a client. An encrypted tunnel is +established to the server. The server connects to port 143 of Google Mail (the +end point). Now the local mail client can retrieve mail. + + +@subsection forwarding_reverse Reverse port forwarding + +The reverse forwarding is slightly different. It goes from server to client, +even though the client has the initiative of establishing the tunnel. +Once the tunnel is established, the server will listen on a port. Whenever +a connection to this port is made, the server forwards the data to the client. + +Example of use of reverse port forwarding: +@verbatim + Local mail server Mail client application + ^ | + | 5555 (arbitrary) + 143 (IMAP2) | + | V + SSH client <===== SSH server + +Legend: +--P-->: port connections through port P +=====>: SSH tunnel +@endverbatim +In this example, the SSH client establishes the tunnel, +but it is used to forward the connections established at +the server to the client. + + +@subsection forwarding_x11 X11 tunnels + +X11 tunnels allow a remote application to display locally. + +Example of use of X11 tunnels: +@verbatim + Local display Graphical application + (X11 server) (X11 client) + ^ | + | V + SSH client <===== SSH server + +Legend: +----->: X11 connection through X11 display number +=====>: SSH tunnel +@endverbatim +The SSH tunnel is established by the client. + +How to establish X11 tunnels with libssh has already been described in +this tutorial. + +@see x11 + + +@subsection libssh_direct Doing direct port forwarding with libssh + +To do direct port forwarding, call function ssh_channel_open_forward(): + - you need a separate channel for the tunnel as first parameter; + - second and third parameters are the remote endpoint; + - fourth and fifth parameters are sent to the remote server + so that they can be logged on that server. + +If you don't plan to forward the data you will receive to any local port, +just put fake values like "localhost" and 5555 as your local host and port. + +The example below shows how to open a direct channel that would be +used to retrieve google's home page from the remote SSH server. + +@code +int direct_forwarding(ssh_session session) +{ + ssh_channel forwarding_channel = NULL; + int rc = SSH_ERROR; + char *http_get = "GET / HTTP/1.1\nHost: www.google.com\n\n"; + int nbytes, nwritten; + + forwarding_channel = ssh_channel_new(session); + if (forwarding_channel == NULL) { + return rc; + } + + rc = ssh_channel_open_forward(forwarding_channel, + "www.google.com", 80, + "localhost", 5555); + if (rc != SSH_OK) + { + ssh_channel_free(forwarding_channel); + return rc; + } + + nbytes = strlen(http_get); + nwritten = ssh_channel_write(forwarding_channel, + http_get, + nbytes); + if (nbytes != nwritten) + { + ssh_channel_free(forwarding_channel); + return SSH_ERROR; + } + + ... + + ssh_channel_free(forwarding_channel); + return SSH_OK; +} +@endcode + +The data sent by Google can be retrieved for example with ssh_select() +and ssh_channel_read(). Goggle's home page can then be displayed on the +local SSH client, saved into a local file, made available on a local port, +or whatever use you have for it. + + +@subsection libssh_reverse Doing reverse port forwarding with libssh + +To do reverse port forwarding, call ssh_channel_listen_forward(), +then ssh_channel_accept_forward(). + +When you call ssh_channel_listen_forward(), you can let the remote server +chose the non-privileged port it should listen to. Otherwise, you can chose +your own privileged or non-privileged port. Beware that you should have +administrative privileges on the remote server to open a privileged port +(port number < 1024). + +Below is an example of a very rough web server waiting for connections on port +8080 of remote SSH server. The incoming connections are passed to the +local libssh application, which handles them: + +@code +int web_server(ssh_session session) +{ + int rc; + ssh_channel channel = NULL; + char buffer[256]; + int nbytes, nwritten; + int port = 0; + char *peer_address = NULL; + int peer_port = 0; + char *helloworld = "" +"HTTP/1.1 200 OK\n" +"Content-Type: text/html\n" +"Content-Length: 113\n" +"\n" +"\n" +" \n" +" Hello, World!\n" +" \n" +" \n" +"

Hello, World!

\n" +" \n" +"\n"; + + rc = ssh_channel_listen_forward(session, NULL, 8080, NULL); + if (rc != SSH_OK) + { + fprintf(stderr, "Error opening remote port: %s\n", + ssh_get_error(session)); + return rc; + } + + channel = ssh_channel_open_forward_port(session, 60000, &port, + &peer_address, &peer_port); + if (channel == NULL) + { + fprintf(stderr, "Error waiting for incoming connection: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + while (1) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) + { + fprintf(stderr, "Error reading incoming data: %s\n", + ssh_get_error(session)); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + ssh_string_free_char(peer_address); + return SSH_ERROR; + } + if (strncmp(buffer, "GET /", 5)) continue; + + nbytes = strlen(helloworld); + nwritten = ssh_channel_write(channel, helloworld, nbytes); + if (nwritten != nbytes) + { + fprintf(stderr, "Error sending answer: %s\n", + ssh_get_error(session)); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + ssh_string_free_char(peer_address); + return SSH_ERROR; + } + printf("Sent answer to %s:%d\n", peer_address, peer_port); + } + + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + ssh_string_free_char(peer_address); + return SSH_OK; +} +@endcode + +*/ diff --git a/src/libs/libssh-0.12.2/doc/guided_tour.dox b/src/libs/libssh-0.12.2/doc/guided_tour.dox new file mode 100644 index 000000000000..4169d60a59ba --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/guided_tour.dox @@ -0,0 +1,490 @@ +/** +@page libssh_tutor_guided_tour Chapter 1: A typical SSH session +@section ssh_session A typical SSH session + +A SSH session goes through the following steps: + + - Before connecting to the server, you can set up if you wish one or other + server public key authentication, i.e. RSA, ED25519 or ECDSA. You can choose + cryptographic algorithms you trust and compression algorithms if any. You + must of course set up the hostname. + + - The connection is established. A secure handshake is made, and resulting from + it, a public key from the server is gained. You MUST verify that the public + key is legitimate, using for instance the MD5 fingerprint or the known hosts + file. + + - The client must authenticate: the classical ways are password, or + public keys (from ecdsa, ed25519 and rsa key-pairs generated by openssh). + If a SSH agent is running, it is possible to use it. + + - Now that the user has been authenticated, you must open one or several + channels. Channels are different subways for information into a single ssh + connection. Each channel has a standard stream (stdout) and an error stream + (stderr). You can theoretically open an infinity of channels. + + - With the channel you opened, you can do several things: + - Execute a single command. + - Open a shell. You may want to request a pseudo-terminal before. + - Invoke the sftp subsystem to transfer files. + - Invoke the scp subsystem to transfer files. + - Invoke your own subsystem. This is outside the scope of this document, + but can be done. + + - When everything is finished, just close the channels, and then the connection. + +The sftp and scp subsystems use channels, but libssh hides them to +the programmer. If you want to use those subsystems, instead of a channel, +you'll usually open a "sftp session" or a "scp session". + + +@subsection setup Creating the session and setting options + +The most important object in a SSH connection is the SSH session. In order +to allocate a new SSH session, you use ssh_new(). Don't forget to +always verify that the allocation succeeded. +@code +#include +#include + +int main() +{ + ssh_session my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + ... + ssh_free(my_ssh_session); +} +@endcode + +libssh follows the allocate-it-deallocate-it pattern. Each object that you allocate +using xxxxx_new() must be deallocated using xxxxx_free(). In this case, ssh_new() +does the allocation and ssh_free() does the contrary. + +The ssh_options_set() function sets the options of the session. The most important options are: + - SSH_OPTIONS_HOST: the name of the host you want to connect to + - SSH_OPTIONS_PORT: the used port (default is port 22) + - SSH_OPTIONS_USER: the system user under which you want to connect + - SSH_OPTIONS_LOG_VERBOSITY: the quantity of messages that are printed + +The complete list of options can be found in the documentation of ssh_options_set(). +The only mandatory option is SSH_OPTIONS_HOST. If you don't use SSH_OPTIONS_USER, +the local username of your account will be used. + +Here is a small example of how to use it: + +@code +#include +#include + +int main() +{ + ssh_session my_ssh_session = NULL; + int verbosity = SSH_LOG_PROTOCOL; + int port = 22; + + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(my_ssh_session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(my_ssh_session, SSH_OPTIONS_PORT, &port); + + ... + + ssh_free(my_ssh_session); +} +@endcode + +Please notice that all parameters are passed to ssh_options_set() as pointers, +even if you need to set an integer value. + +@see ssh_new +@see ssh_free +@see ssh_options_set +@see ssh_options_parse_config +@see ssh_options_copy +@see ssh_options_getopt + + +@subsection connect Connecting to the server + +Once all settings have been made, you can connect using ssh_connect(). That +function will return SSH_OK if the connection worked, SSH_ERROR otherwise. + +You can get the English error string with ssh_get_error() in order to show the +user what went wrong. Then, use ssh_disconnect() when you want to stop +the session. + +Here's an example: + +@code +#include +#include +#include + +int main() +{ + ssh_session my_ssh_session = NULL; + int rc; + + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + + rc = ssh_connect(my_ssh_session); + if (rc != SSH_OK) + { + fprintf(stderr, "Error connecting to localhost: %s\n", + ssh_get_error(my_ssh_session)); + exit(-1); + } + + ... + + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); +} +@endcode + + +@subsection serverauth Authenticating the server + +Once you're connected, the following step is mandatory: you must check that the server +you just connected to is known and safe to use (remember, SSH is about security and +authentication). + +There are two ways of doing this: + - The first way (recommended) is to use the ssh_session_is_known_server() + function. This function will look into the known host file + (~/.ssh/known_hosts on UNIX), look for the server hostname's pattern, + and determine whether this host is present or not in the list. + - The second way is to use ssh_get_pubkey_hash() to get a binary version + of the public key hash value. You can then use your own database to check + if this public key is known and secure. + +You can also use the ssh_get_pubkey_hash() to show the public key hash +value to the user, in case he knows what the public key hash value is +(some paranoid people write their public key hash values on paper before +going abroad, just in case ...). + +If the remote host is being used to for the first time, you can ask the user whether +he/she trusts it. Once he/she concluded that the host is valid and worth being +added in the known hosts file, you use ssh_write_knownhost() to register it in +the known hosts file, or any other way if you use your own database. + +Note: When GSSAPI key exchange is used, the server identity is already verified +via mutual GSSAPI authentication initiated by the SSH client. In such cases, +most SSH servers won't send their host keys at all. If it fits your use case +(e.g., you don't expect the server to send a host key), you can use +ssh_session_kex_is_gss() to check if GSSAPI key exchange indeed took place +and skip the knownhosts check. + +The following example is part of the examples suite available in the +examples/ directory: + +@code +#include +#include + +int verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + unsigned char *hash = NULL; + ssh_key srv_pubkey = NULL; + size_t hlen; + char buf[10]; + char *p = NULL; + int cmp; + int rc; + + /* If GSSAPI key exchange was used, the server identity was already + * verified via Kerberos mutual authentication (MIC). We might skip + * the host key verification, especially if we don't expect the server + * to send its key. Alternatively, we could proceed without this check + * and handle the scenario when the server does not provide its host key + * later. In that case, ssh_session_is_known_server will return + * SSH_KNOWN_HOSTS_UNKNOWN. + */ + if (ssh_session_kex_is_gss(session)) { + return 0; + } + + rc = ssh_get_server_publickey(session, &srv_pubkey); + if (rc < 0) { + return -1; + } + + rc = ssh_get_publickey_hash(srv_pubkey, + SSH_PUBLICKEY_HASH_SHA256, + &hash, + &hlen); + ssh_key_free(srv_pubkey); + if (rc < 0) { + return -1; + } + + state = ssh_session_is_known_server(session); + switch (state) { + case SSH_KNOWN_HOSTS_OK: + /* OK */ + + break; + case SSH_KNOWN_HOSTS_CHANGED: + fprintf(stderr, "Host key for server changed: it is now:\n"); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + fprintf(stderr, "For security reasons, connection will be stopped\n"); + ssh_clean_pubkey_hash(&hash); + + return -1; + case SSH_KNOWN_HOSTS_OTHER: + fprintf(stderr, "The host key for this server was not found but an other" + "type of key exists.\n"); + fprintf(stderr, "An attacker might change the default server key to" + "confuse your client into thinking the key does not exist\n"); + ssh_clean_pubkey_hash(&hash); + + return -1; + case SSH_KNOWN_HOSTS_NOT_FOUND: + fprintf(stderr, "Could not find known host file.\n"); + fprintf(stderr, "If you accept the host key here, the file will be" + "automatically created.\n"); + + /* FALL THROUGH to SSH_SERVER_NOT_KNOWN behavior */ + + case SSH_KNOWN_HOSTS_UNKNOWN: + fprintf(stderr,"The server is unknown. Do you trust the host key?\n"); + fprintf(stderr, "Public key hash: "); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + ssh_clean_pubkey_hash(&hash); + p = fgets(buf, sizeof(buf), stdin); + if (p == NULL) { + return -1; + } + + cmp = strncasecmp(buf, "yes", 3); + if (cmp != 0) { + return -1; + } + + rc = ssh_session_update_known_hosts(session); + if (rc < 0) { + fprintf(stderr, "Error %s\n", strerror(errno)); + return -1; + } + + break; + case SSH_KNOWN_HOSTS_ERROR: + fprintf(stderr, "Error %s", ssh_get_error(session)); + ssh_clean_pubkey_hash(&hash); + return -1; + } + + ssh_clean_pubkey_hash(&hash); + return 0; +} +@endcode + +@see ssh_connect +@see ssh_disconnect +@see ssh_get_error +@see ssh_get_error_code +@see ssh_get_server_publickey +@see ssh_get_publickey_hash +@see ssh_session_is_known_server +@see ssh_session_update_known_hosts + + +@subsection auth Authenticating the user + +The authentication process is the way a service provider can identify a +user and verify his/her identity. The authorization process is about enabling +the authenticated user the access to resources. In SSH, the two concepts +are linked. After authentication, the server can grant the user access to +several resources such as port forwarding, shell, sftp subsystem, and so on. + +libssh supports several methods of authentication: + - "none" method. This method allows to get the available authentications + methods. It also gives the server a chance to authenticate the user with + just his/her login. Some very old hardware uses this feature to fallback + the user on a "telnet over SSH" style of login. + - password method. A password is sent to the server, which accepts it or not. + - keyboard-interactive method. The server sends several challenges to the + user, who must answer correctly. This makes possible the authentication + via a codebook for instance ("give code at 23:R on page 3"). + - public key method. The host knows the public key of the user, and the + user must prove he knows the associated private key. This can be done + manually, or delegated to the SSH agent as we'll see later. + +All these methods can be combined. You can for instance force the user to +authenticate with at least two of the authentication methods. In that case, +one speaks of "Partial authentication". A partial authentication is a +response from authentication functions stating that your credential was +accepted, but yet another one is required to get in. + +The example below shows an authentication with password: + +@code +#include +#include +#include + +int main() +{ + ssh_session my_ssh_session = NULL; + int rc; + char *password = NULL; + + // Open session and set options + my_ssh_session = ssh_new(); + if (my_ssh_session == NULL) + exit(-1); + ssh_options_set(my_ssh_session, SSH_OPTIONS_HOST, "localhost"); + + // Connect to server + rc = ssh_connect(my_ssh_session); + if (rc != SSH_OK) + { + fprintf(stderr, "Error connecting to localhost: %s\n", + ssh_get_error(my_ssh_session)); + ssh_free(my_ssh_session); + exit(-1); + } + + // Verify the server's identity + // For the source code of verify_knownhost(), check previous example + if (verify_knownhost(my_ssh_session) < 0) + { + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); + exit(-1); + } + + // Authenticate ourselves + password = getpass("Password: "); + rc = ssh_userauth_password(my_ssh_session, NULL, password); + if (rc != SSH_AUTH_SUCCESS) + { + fprintf(stderr, "Error authenticating with password: %s\n", + ssh_get_error(my_ssh_session)); + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); + exit(-1); + } + + ... + + ssh_disconnect(my_ssh_session); + ssh_free(my_ssh_session); +} +@endcode + +@see @ref authentication_details + + +@subsection using_ssh Doing something + +At this point, the authenticity of both server and client is established. +Time has come to take advantage of the many possibilities offered by the SSH +protocol: execute a remote command, open remote shells, transfer files, +forward ports, etc. + +The example below shows how to execute a remote command: + +@code +int show_remote_processes(ssh_session session) +{ + ssh_channel channel = NULL; + int rc; + char buffer[256]; + int nbytes; + + channel = ssh_channel_new(session); + if (channel == NULL) + return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } + + rc = ssh_channel_request_exec(channel, "ps aux"); + if (rc != SSH_OK) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return rc; + } + + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + while (nbytes > 0) + { + if (write(1, buffer, nbytes) != (unsigned int) nbytes) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + } + + if (nbytes < 0) + { + ssh_channel_close(channel); + ssh_channel_free(channel); + return SSH_ERROR; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + +Each ssh_channel_request_exec() needs to be run on freshly created +and connected (with ssh_channel_open_session()) channel. + +@see @ref opening_shell +@see @ref remote_command +@see @ref sftp_subsystem +@see @ref scp_subsystem + + +@subsection errors Handling the errors + +All the libssh functions which return an error value also set an English error message +describing the problem. + +Error values are typically SSH_ERROR for integer values, or NULL for pointers. + +The function ssh_get_error() returns a pointer to the static error message. + +ssh_error_code() returns the error code number : SSH_NO_ERROR, +SSH_REQUEST_DENIED, SSH_INVALID_REQUEST, SSH_CONNECTION_LOST, SSH_FATAL, +or SSH_INVALID_DATA. SSH_REQUEST_DENIED means the ssh server refused your +request, but the situation is recoverable. The others mean something happened +to the connection (some encryption problems, server problems, ...). +SSH_INVALID_REQUEST means the library got some garbage from server, but +might be recoverable. SSH_FATAL means the connection has an important +problem and isn't probably recoverable. + +Most of time, the error returned are SSH_FATAL, but some functions +(generally the ssh_request_xxx ones) may fail because of server denying request. +In these cases, SSH_REQUEST_DENIED is returned. + +For thread safety, errors are bound to ssh_session objects. +As long as your ssh_session object is not NULL, you can retrieve the last error +message and error code from the ssh_session using ssh_get_error() and +ssh_get_error_code() respectively. + +The SFTP subsystem has its own error codes, in addition to libssh ones. + + +*/ diff --git a/src/libs/libssh-0.12.2/doc/header.html b/src/libs/libssh-0.12.2/doc/header.html new file mode 100644 index 000000000000..6e43e8198179 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/header.html @@ -0,0 +1,92 @@ + + + + + + + + +$projectname: $title +$title + + + + + + + + + + + + +$treeview +$search +$mathjax +$darkmode + +$extrastylesheet + + + + + + + + + + + +
+ + +
+ + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+
$projectname $projectnumber +
+
$projectbrief
+
+
$projectbrief
+
$searchbox
$searchbox
+
+ + diff --git a/src/libs/libssh-0.12.2/doc/introduction.dox b/src/libs/libssh-0.12.2/doc/introduction.dox new file mode 100644 index 000000000000..4415c1bb108a --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/introduction.dox @@ -0,0 +1,55 @@ +/** +@page libssh_tutorial The Tutorial +@section introduction Introduction + +libssh is a C library that enables you to write a program that uses the +SSH protocol. With it, you can remotely execute programs, transfer +files, or use a secure and transparent tunnel for your remote programs. +The SSH protocol is encrypted, ensures data integrity, and provides strong +means of authenticating both the server of the client. The library hides +a lot of technical details from the SSH protocol, but this does not +mean that you should not try to know about and understand these details. + +libssh is a Free Software / Open Source project. The libssh library +is distributed under LGPL license. The libssh project has nothing to do with +"libssh2", which is a completely different and independent project. + +libssh can run on top of either libcrypto, mbedtls or libgcrypt (deprecated) +general-purpose cryptographic libraries. + +This tutorial concentrates for its main part on the "client" side of libssh. +To learn how to accept incoming SSH connections (how to write a SSH server), +you'll have to jump to the end of this document. + +This tutorial describes libssh version 0.5.0. This version is a little different +from the 0.4.X series. However, the examples should work with +little changes on versions like 0.4.2 and later. + + +Table of contents: + +@subpage libssh_tutor_guided_tour + +@subpage libssh_tutor_authentication + +@subpage libssh_tutor_shell + +@subpage libssh_tutor_command + +@subpage libssh_tutor_sftp + +@subpage libssh_tutor_scp + +@subpage libssh_tutor_forwarding + +@subpage libssh_tutor_threads + +@subpage libssh_tutor_pkcs11 + +@subpage libssh_tutor_sftp_aio + +@subpage libssh_tutor_fido2 + +@subpage libssh_tutor_todo + +*/ diff --git a/src/libs/libssh-0.12.2/doc/linking.dox b/src/libs/libssh-0.12.2/doc/linking.dox new file mode 100644 index 000000000000..7ae0d31fb1e0 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/linking.dox @@ -0,0 +1,33 @@ +/** + +@page libssh_linking The Linking HowTo + +@section dynamic Dynamic Linking + +On UNIX and Windows systems its the same, you need at least the libssh.h +header file and the libssh shared library. + +@section static Static Linking + +@warning The libssh library is licensed under the LGPL! Make sure you +understand what this means to your codebase if you want to distribute +binaries and link statically against LGPL code! + +On UNIX systems linking against the static version of the library is the +same as linking against the shared library. Both have the same name. Some +build system require to use the full path to the static library. + +To be able to compile the application you're developing you need to either pass +LIBSSH_STATIC as a define in the compiler command line or define it before you +include libssh.h. This is required cause the dynamic library needs to specify +the dllimport attribute. + +@code +#define LIBSSH_STATIC 1 +#include +@endcode + +If you're are statically linking with OpenSSL, read the "Linking your +application" section in the NOTES.[OS] in the OpenSSL source tree! + +*/ diff --git a/src/libs/libssh-0.12.2/doc/mainpage.dox b/src/libs/libssh-0.12.2/doc/mainpage.dox new file mode 100644 index 000000000000..a0ed67767269 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/mainpage.dox @@ -0,0 +1,254 @@ +/** + +@mainpage + +This is the online reference for developing with the libssh library. It +documents the libssh C API and the C++ wrapper. + +@section main-linking Linking + +We created a small howto how to link libssh against your application, read +@subpage libssh_linking. + +@section main-tutorial Tutorial + +You should start by reading @subpage libssh_tutorial, then reading the documentation of +the interesting functions as you go. + +@section main-features Features + +The libssh library provides: + + - Key Exchange Methods: sntrup761x25519-sha512, sntrup761x25519-sha512@openssh.com, mlkem768x25519-sha256, mlkem768nistp256-sha256, mlkem1024nistp384-sha384, curve25519-sha256, curve25519-sha256@libssh.org, ecdh-sha2-nistp256, ecdh-sha2-nistp384, ecdh-sha2-nistp521, diffie-hellman-group1-sha1, diffie-hellman-group14-sha1 + - GSSAPI Key Exchange Methods: gss-group14-sha256-*, gss-group16-sha512-*, gss-nistp256-sha256-*, gss-curve25519-sha256-* + - Public Key Algorithms: ssh-ed25519, ecdsa-sha2-nistp256, ecdsa-sha2-nistp384, ecdsa-sha2-nistp521, ssh-rsa, rsa-sha2-512, rsa-sha2-256 + - Ciphers: aes256-ctr, aes192-ctr, aes128-ctr, aes256-cbc (rijndael-cbc@lysator.liu.se), aes192-cbc, aes128-cbc, 3des-cbc, blowfish-cbc + - Compression Schemes: zlib, zlib@openssh.com, none + - MAC hashes: hmac-sha1, hmac-sha2-256, hmac-sha2-512, hmac-md5 + - Authentication: none, password, public-key, keyboard-interactive, gssapi-with-mic, gssapi-keyex + - Channels: shell, exec (incl. SCP wrapper), direct-tcpip, subsystem, auth-agent-req@openssh.com + - Global Requests: tcpip-forward, forwarded-tcpip + - Channel Requests: x11, pty, exit-status, signal, exit-signal, keepalive@openssh.com, auth-agent-req@openssh.com + - Subsystems: sftp(version 3), OpenSSH Extensions + - SFTP: statvfs@openssh.com, fstatvfs@openssh.com + - Thread-safe: Just don't share sessions + - Non-blocking: it can be used both blocking and non-blocking + - Your sockets: the app hands over the socket, or uses libssh sockets + - OpenSSL, MBedTLS or gcrypt (deprecated): builds with either + +@section main-additional-features Additional Features + + - Client and server support + - SSHv2 protocol support + - Supports Linux, UNIX, BSD, Solaris, OS/2 and Windows + - Automated test cases with nightly tests + - Event model based on poll(2), or a poll(2)-emulation. + +@section main-copyright Copyright Policy + +libssh is a project with distributed copyright ownership, which means we prefer +the copyright on parts of libssh to be held by individuals rather than +corporations if possible. There are historical legal reasons for this, but one +of the best ways to explain it is that it’s much easier to work with +individuals who have ownership than corporate legal departments if we ever need +to make reasonable compromises with people using and working with libssh. + +We track the ownership of every part of libssh via git, our source code control +system, so we know the provenance of every piece of code that is committed to +libssh. + +So if possible, if you’re doing libssh changes on behalf of a company who +normally owns all the work you do please get them to assign personal copyright +ownership of your changes to you as an individual, that makes things very easy +for us to work with and avoids bringing corporate legal departments into the +picture. + +If you can’t do this we can still accept patches from you owned by your +employer under a standard employment contract with corporate copyright +ownership. It just requires a simple set-up process first. + +We use a process very similar to the way things are done in the Linux Kernel +community, so it should be very easy to get a sign off from your corporate +legal department. The only changes we’ve made are to accommodate the license we +use, which is LGPLv2 (or later) whereas the Linux kernel uses GPLv2. + +The process is called signing. + +How to sign your work +---------------------- + +Once you have permission to contribute to libssh from your employer, simply +email a copy of the following text from your corporate email address to: + +contributing@libssh.org + +@verbatim +libssh Developer's Certificate of Origin. Version 1.0 + +By making a contribution to this project, I certify that: + +(a) The contribution was created in whole or in part by me and I + have the right to submit it under the appropriate + version of the GNU General Public License; or + +(b) The contribution is based upon previous work that, to the best of + my knowledge, is covered under an appropriate open source license + and I have the right under that license to submit that work with + modifications, whether created in whole or in part by me, under + the GNU General Public License, in the appropriate version; or + +(c) The contribution was provided directly to me by some other + person who certified (a) or (b) and I have not modified it. + +(d) I understand and agree that this project and the contribution are + public and that a record of the contribution (including all + metadata and personal information I submit with it, including my + sign-off) is maintained indefinitely and may be redistributed + consistent with the libssh Team's policies and the requirements of + the GNU GPL where they are relevant. + +(e) I am granting this work to this project under the terms of the + GNU Lesser General Public License as published by the + Free Software Foundation; either version 2.1 of + the License, or (at the option of the project) any later version. + +https://www.gnu.org/licenses/lgpl-2.1.html +@endverbatim + +We will maintain a copy of that email as a record that you have the rights to +contribute code to libssh under the required licenses whilst working for the +company where the email came from. + +Then when sending in a patch via the normal mechanisms described above, add a +line that states: + +@verbatim + Signed-off-by: Random J Developer +@endverbatim + +using your real name and the email address you sent the original email you used +to send the libssh Developer’s Certificate of Origin to us (sorry, no +pseudonyms or anonymous contributions.) + +That’s it! Such code can then quite happily contain changes that have copyright +messages such as: + +@verbatim + (c) Example Corporation. +@endverbatim + +and can be merged into the libssh codebase in the same way as patches from any +other individual. You don’t need to send in a copy of the libssh Developer’s +Certificate of Origin for each patch, or inside each patch. Just the sign-off +message is all that is required once we’ve received the initial email. + +Have fun and happy libssh hacking! + +The libssh Team + +@section main-rfc Internet standard + +@subsection main-rfc-secsh Secure Shell (SSH) + +The following RFC documents described SSH-2 protocol as an Internet standard. + + - RFC 4250, + The Secure Shell (SSH) Protocol Assigned Numbers + - RFC 4251, + The Secure Shell (SSH) Protocol Architecture + - RFC 4252, + The Secure Shell (SSH) Authentication Protocol + - RFC 4253, + The Secure Shell (SSH) Transport Layer Protocol + - RFC 4254, + The Secure Shell (SSH) Connection Protocol + - RFC 4255, + Using DNS to Securely Publish Secure Shell (SSH) Key Fingerprints + (not implemented in libssh) + - RFC 4256, + Generic Message Exchange Authentication for the Secure Shell Protocol (SSH) + - RFC 4335, + The Secure Shell (SSH) Session Channel Break Extension + - RFC 4344, + The Secure Shell (SSH) Transport Layer Encryption Modes + +It was later modified and expanded by the following RFCs. + + - RFC 4419, + Diffie-Hellman Group Exchange for the Secure Shell (SSH) Transport Layer + Protocol + - RFC 4462, + Generic Security Service Application Program Interface (GSS-API) + Authentication and Key Exchange for the Secure Shell (SSH) Protocol + - RFC 4716, + The Secure Shell (SSH) Public Key File Format + (not implemented in libssh) + - RFC 5647, + AES Galois Counter Mode for the Secure Shell Transport Layer Protocol + (the algorithm negotiation implemented according to openssh.com) + - RFC 5656, + Elliptic Curve Algorithm Integration in the Secure Shell Transport Layer + - RFC 6594, + Use of the SHA-256 Algorithm with RSA, DSA, and ECDSA in SSHFP Resource Records + (not implemented in libssh) + - RFC 6668, + SHA-2 Data Integrity Verification for the Secure Shell (SSH) Transport Layer Protocol + - RFC 7479, + Using Ed25519 in SSHFP Resource Records + (not implemented in libssh) + - RFC 8160, + IUTF8 Terminal Mode in Secure Shell (SSH) + - RFC 8270, + Increase the Secure Shell Minimum Recommended Diffie-Hellman Modulus Size to 2048 Bits + - RFC 8308, + Extension Negotiation in the Secure Shell (SSH) Protocol + (only the "server-sig-algs" extension implemented) + - RFC 8332, + Use of RSA Keys with SHA-256 and SHA-512 in the Secure Shell (SSH) Protocol + - RFC 8709, + Ed25519 and Ed448 Public Key Algorithms for the Secure Shell (SSH) Protocol + - RFC 8731, + Secure Shell (SSH) Key Exchange Method Using Curve25519 and Curve448 + - RFC 9142, + Key Exchange (KEX) Method Updates and Recommendations for Secure Shell (SSH) + +There are also drafts that are being currently developed and followed. + + - draft-miller-ssh-agent-08 + SSH Agent Protocol + - draft-ietf-sshm-mlkem-hybrid-kex-09 + PQ/T Hybrid Key Exchange with ML-KEM in SSH + - draft-ietf-sshm-ntruprime-ssh-06 + Secure Shell (SSH) Key Exchange Method Using Hybrid Streamlined NTRU Prime sntrup761 and X25519 with SHA-512: sntrup761x25519-sha512 + - draft-ietf-sshm-chacha20-poly1305-02 + Secure Shell (SSH) authenticated encryption cipher: chacha20-poly1305 + - draft-ietf-sshm-strict-kex-01 + SSH Strict KEX extension + +Interesting cryptography documents: + + - PKCS #11, PKCS #11 reference documents, describing interface with smartcards. + +@subsection main-rfc-sftp Secure Shell File Transfer Protocol (SFTP) + +The protocol is not an Internet standard but it is still widely implemented. +OpenSSH and most other implementation implement Version 3 of the protocol. We +do the same in libssh. + + - + draft-ietf-secsh-filexfer-02.txt, + SSH File Transfer Protocol + +@subsection main-rfc-extensions Secure Shell Extensions + +The OpenSSH project has defined some extensions to the protocol. We support some of +them like the statvfs calls in SFTP or the ssh-agent. + + - + OpenSSH's deviations and extensions + - + OpenSSH's pubkey certificate authentication + - + OpenSSH private key format (openssh-key-v1) + +*/ diff --git a/src/libs/libssh-0.12.2/doc/pkcs11.dox b/src/libs/libssh-0.12.2/doc/pkcs11.dox new file mode 100644 index 000000000000..b358432c319f --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/pkcs11.dox @@ -0,0 +1,86 @@ +/** +@page libssh_tutor_pkcs11 Chapter 9: Authentication using PKCS #11 URIs +@section how_to How to use PKCS #11 URIs in libssh? + +PKCS #11 is a Cryptographic Token Interface Standard that provides an API +to devices like smart cards that store cryptographic private information. +Such cryptographic devices are referenced as tokens. A mechanism through which +objects stored on the tokens can be uniquely identified is called PKCS #11 URI +(Uniform Resource Identifier) and is defined in RFC 7512 +(https://tools.ietf.org/html/rfc7512). + +# Pre-requisites (OpenSSL < 3.0): + +OpenSSL 1.x defines an abstract layer called the "engine" to achieve +cryptographic acceleration. The engine_pkcs11 module acts like an interface +between the PKCS #11 modules and the OpenSSL application. + +To build and use libssh with PKCS #11 support: +1. Enable the cmake option: $ cmake -DWITH_PKCS11_URI=ON +2. Build with OpenSSL. +3. Install and configure engine_pkcs11 (https://github.com/OpenSC/libp11). +4. Plug in a working smart card or configure softhsm (https://www.opendnssec.org/softhsm). + +@warning The support for Engines was deprecated in OpenSSL 3.0 so this approach +is deprecated in libssh 0.11.x. + +# Pre-requisites (OpenSSL 3.0.8+) + +The OpenSSL 3.0 is deprecating usage of low-level engines in favor of high-level +"providers" to provide alternative implementation of cryptographic operations +or acceleration. + +To build and use libssh with PKCS #11 support using OpenSSL providers: +1. Install and configure pkcs11 provider (https://github.com/latchset/pkcs11-provider). +2. Enable the cmake options: $ cmake -DWITH_PKCS11_URI=ON -DWITH_PKCS11_PROVIDER=ON +3. Build with OpenSSL. +4. Plug in a working smart card or configure softhsm (https://www.opendnssec.org/softhsm). + +# New API functions + +The functions ssh_pki_import_pubkey_file() and ssh_pki_import_privkey_file() that +import the public and private keys from files respectively are now modified to support +PKCS #11 URIs. These functions automatically detect if the provided filename is a file path +or a PKCS #11 URI (when it begins with "pkcs11:"). If a PKCS #11 URI is detected, +the engine is loaded and initialized. Through the engine, the private/public key +corresponding to the PKCS #11 URI are loaded from the PKCS #11 device. + +If you wish to authenticate using public keys on your own, follow the steps mentioned under +"Authentication with public keys" in Chapter 2 - A deeper insight into authentication. + +The function pki_uri_import() is used to populate the public/private ssh_key from the +engine with PKCS #11 URIs as the look up. + +Here is a minimalistic example of public key authentication using PKCS #11 URIs: + +@code +int authenticate_pkcs11_URI(ssh_session session) +{ + int rc; + char priv_uri[1042] = "pkcs11:token=my-token;object=my-object;type=private?pin-value=1234"; + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, priv_uri); + assert_int_equal(rc, SSH_OK) + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + + if (rc == SSH_AUTH_ERROR) + { + fprintf(stderr, "Authentication with PKCS #11 URIs failed: %s\n", + ssh_get_error(session)); + return SSH_AUTH_ERROR; + } + + return rc; +} +@endcode + +@subsection Caveats + +We recommend the users to provide a specific PKCS #11 URI so that it matches only a single slot in the engine. +If the engine discovers multiple slots that could potentially contain the private keys referenced +by the provided PKCS #11 URI, the engine will not try to authenticate. + +For testing, the SoftHSM PKCS#11 library is used. + +*/ diff --git a/src/libs/libssh-0.12.2/doc/scp.dox b/src/libs/libssh-0.12.2/doc/scp.dox new file mode 100644 index 000000000000..618857ef8e7a --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/scp.dox @@ -0,0 +1,268 @@ +/** +@page libssh_tutor_scp Chapter 6: The SCP subsystem +@section scp_subsystem The SCP subsystem + +The SCP subsystem has far less functionality than the SFTP subsystem. +However, if you only need to copy files from and to the remote system, +it does its job. + + +@subsection scp_session Opening and closing a SCP session + +Like in the SFTP subsystem, you don't handle the SSH channels directly. +Instead, you open a "SCP session". + +When you open your SCP session, you have to choose between read or write mode. +You can't do both in the same session. So you specify either SSH_SCP_READ or +SSH_SCP_WRITE as the second parameter of function ssh_scp_new(). + +Another important mode flag for opening your SCP session is SSH_SCP_RECURSIVE. +When you use SSH_SCP_RECURSIVE, you declare that you are willing to emulate +the behaviour of "scp -r" command in your program, no matter it is for +reading or for writing. + +Once your session is created, you initialize it with ssh_scp_init(). When +you have finished transferring files, you terminate the SCP connection with +ssh_scp_close(). Finally, you can dispose the SCP connection with +ssh_scp_free(). + +The example below does the maintenance work to open a SCP connection for writing in +recursive mode: + +@code +int scp_write(ssh_session session) +{ + ssh_scp scp; + int rc; + + scp = ssh_scp_new + (session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, "."); + if (scp == NULL) + { + fprintf(stderr, "Error allocating scp session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = ssh_scp_init(scp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing scp session: %s\n", + ssh_get_error(session)); + ssh_scp_free(scp); + return rc; + } + + ... + + ssh_scp_close(scp); + ssh_scp_free(scp); + return SSH_OK; +} +@endcode + +The example below shows how to open a connection to read a single file: + +@code +int scp_read(ssh_session session) +{ + ssh_scp scp; + int rc; + + scp = ssh_scp_new + (session, SSH_SCP_READ, "helloworld/helloworld.txt"); + if (scp == NULL) + { + fprintf(stderr, "Error allocating scp session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = ssh_scp_init(scp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing scp session: %s\n", + ssh_get_error(session)); + ssh_scp_free(scp); + return rc; + } + + ... + + ssh_scp_close(scp); + ssh_scp_free(scp); + return SSH_OK; +} + +@endcode + + +@subsection scp_write Creating files and directories + +You create directories with ssh_scp_push_directory(). In recursive mode, +you are placed in this directory once it is created. If the directory +already exists and if you are in recursive mode, you simply enter that +directory. + +Creating files is done in two steps. First, you prepare the writing with +ssh_scp_push_file(). Then, you write the data with ssh_scp_write(). +The length of the data to write must be identical between both function calls. +There's no need to "open" nor "close" the file, this is done automatically +on the remote end. If the file already exists, it is overwritten and truncated. + +The following example creates a new directory named "helloworld/", then creates +a file named "helloworld.txt" in that directory: + +@code +int scp_helloworld(ssh_session session, ssh_scp scp) +{ + int rc; + const char *helloworld = "Hello, world!\n"; + int length = strlen(helloworld); + + rc = ssh_scp_push_directory(scp, "helloworld", S_IRWXU); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't create remote directory: %s\n", + ssh_get_error(session)); + return rc; + } + + rc = ssh_scp_push_file + (scp, "helloworld.txt", length, S_IRUSR | S_IWUSR); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't open remote file: %s\n", + ssh_get_error(session)); + return rc; + } + + rc = ssh_scp_write(scp, helloworld, length); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't write to remote file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + + +@subsection scp_recursive_write Copying full directory trees to the remote server + +Let's say you want to copy the following tree of files to the remote site: + +@verbatim + +-- file1 + +-- B --+ + | +-- file2 +-- A --+ + | +-- file3 + +-- C --+ + +-- file4 +@endverbatim + +You would do it that way: + - open the session in recursive mode + - enter directory A + - enter its subdirectory B + - create file1 in B + - create file2 in B + - leave directory B + - enter subdirectory C + - create file3 in C + - create file4 in C + - leave directory C + - leave directory A + +To leave a directory, call ssh_scp_leave_directory(). + + +@subsection scp_read Reading files and directories + + +To receive files, you pull requests from the other side with ssh_scp_pull_request(). +If this function returns SSH_SCP_REQUEST_NEWFILE, then you must get ready for +the reception. You can get the size of the data to receive with ssh_scp_request_get_size() +and allocate a buffer accordingly. When you are ready, you accept the request with +ssh_scp_accept_request(), then read the data with ssh_scp_read(). + +The following example receives a single file. The name of the file to +receive has been given earlier, when the scp session was opened: + +@code +int scp_receive(ssh_session session, ssh_scp scp) +{ + int rc; + int size, mode; + char *filename, *buffer; + + rc = ssh_scp_pull_request(scp); + if (rc != SSH_SCP_REQUEST_NEWFILE) + { + fprintf(stderr, "Error receiving information about file: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + size = ssh_scp_request_get_size(scp); + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("Receiving file %s, size %d, permissions 0%o\n", + filename, size, mode); + free(filename); + + buffer = malloc(size); + if (buffer == NULL) + { + fprintf(stderr, "Memory allocation error\n"); + return SSH_ERROR; + } + + ssh_scp_accept_request(scp); + rc = ssh_scp_read(scp, buffer, size); + if (rc == SSH_ERROR) + { + fprintf(stderr, "Error receiving file data: %s\n", + ssh_get_error(session)); + free(buffer); + return rc; + } + printf("Done\n"); + + write(1, buffer, size); + free(buffer); + + rc = ssh_scp_pull_request(scp); + if (rc != SSH_SCP_REQUEST_EOF) + { + fprintf(stderr, "Unexpected request: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + return SSH_OK; +} +@endcode + +In this example, since we just requested a single file, we expect ssh_scp_request() +to return SSH_SCP_REQUEST_NEWFILE first, then SSH_SCP_REQUEST_EOF. That's quite a +naive approach; for example, the remote server might send a warning as well +(return code SSH_SCP_REQUEST_WARNING) and the example would fail. A more comprehensive +reception program would receive the requests in a loop and analyze them carefully +until SSH_SCP_REQUEST_EOF has been received. + + +@subsection scp_recursive_read Receiving full directory trees from the remote server + +If you opened the SCP session in recursive mode, the remote end will be +telling you when to change directory. + +In that case, when ssh_scp_pull_request() answers +SSH_SCP_REQUEST_NEWDIRECTORY, you should make that local directory (if +it does not exist yet) and enter it. When ssh_scp_pull_request() answers +SSH_SCP_REQUEST_ENDDIRECTORY, you should leave the current directory. + +*/ diff --git a/src/libs/libssh-0.12.2/doc/sftp.dox b/src/libs/libssh-0.12.2/doc/sftp.dox new file mode 100644 index 000000000000..4c176a4b54b1 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/sftp.dox @@ -0,0 +1,381 @@ +/** +@page libssh_tutor_sftp Chapter 5: The SFTP subsystem +@section sftp_subsystem The SFTP subsystem + +SFTP stands for "Secure File Transfer Protocol". It enables you to safely +transfer files between the local and the remote computer. It reminds a lot +of the old FTP protocol. + +SFTP is a rich protocol. It lets you do over the network almost everything +that you can do with local files: + - send files + - modify only a portion of a file + - receive files + - receive only a portion of a file + - get file owner and group + - get file permissions + - set file owner and group + - set file permissions + - remove files + - rename files + - create a directory + - remove a directory + - retrieve the list of files in a directory + - get the target of a symbolic link + - create symbolic links + - get information about mounted filesystems. + +The current implemented version of the SFTP protocol is version 3. All functions +aren't implemented yet, but the most important are. + + +@subsection sftp_session Opening and closing a SFTP session + +Unlike with remote shells and remote commands, when you use the SFTP subsystem, +you don't handle directly the SSH channels. Instead, you open a "SFTP session". + +The function sftp_new() creates a new SFTP session. The function sftp_init() +initializes it. The function sftp_free() deletes it. + +As you see, all the SFTP-related functions start with the "sftp_" prefix +instead of the usual "ssh_" prefix. + +The example below shows how to use these functions: + +@code +#include + +int sftp_helloworld(ssh_session session) +{ + sftp_session sftp; + int rc; + + sftp = sftp_new(session); + if (sftp == NULL) + { + fprintf(stderr, "Error allocating SFTP session: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + rc = sftp_init(sftp); + if (rc != SSH_OK) + { + fprintf(stderr, "Error initializing SFTP session: code %d.\n", + sftp_get_error(sftp)); + sftp_free(sftp); + return rc; + } + + ... + + sftp_free(sftp); + return SSH_OK; +} +@endcode + + +@subsection sftp_errors Analyzing SFTP errors + +In case of a problem, the function sftp_get_error() returns a SFTP-specific +error number, in addition to the regular SSH error number returned by +ssh_get_error_number(). + +Possible errors are: + - SSH_FX_OK: no error + - SSH_FX_EOF: end-of-file encountered + - SSH_FX_NO_SUCH_FILE: file does not exist + - SSH_FX_PERMISSION_DENIED: permission denied + - SSH_FX_FAILURE: generic failure + - SSH_FX_BAD_MESSAGE: garbage received from server + - SSH_FX_NO_CONNECTION: no connection has been set up + - SSH_FX_CONNECTION_LOST: there was a connection, but we lost it + - SSH_FX_OP_UNSUPPORTED: operation not supported by libssh yet + - SSH_FX_INVALID_HANDLE: invalid file handle + - SSH_FX_NO_SUCH_PATH: no such file or directory path exists + - SSH_FX_FILE_ALREADY_EXISTS: an attempt to create an already existing file or directory has been made + - SSH_FX_WRITE_PROTECT: write-protected filesystem + - SSH_FX_NO_MEDIA: no media was in remote drive + + +@subsection sftp_mkdir Creating a directory + +The function sftp_mkdir() takes the "SFTP session" we just created as +its first argument. It also needs the name of the file to create, and the +desired permissions. The permissions are the same as for the usual mkdir() +function. To get a comprehensive list of the available permissions, use the +"man 2 stat" command. The desired permissions are combined with the remote +user's mask to determine the effective permissions. + +The code below creates a directory named "helloworld" in the current directory that +can be read and written only by its owner: + +@code +#include +#include + +int sftp_helloworld(ssh_session session, sftp_session sftp) +{ + int rc; + + rc = sftp_mkdir(sftp, "helloworld", S_IRWXU); + if (rc != SSH_OK) + { + if (sftp_get_error(sftp) != SSH_FX_FILE_ALREADY_EXISTS) + { + fprintf(stderr, "Can't create directory: %s\n", + ssh_get_error(session)); + return rc; + } + } + + ... + + return SSH_OK; +} +@endcode + +Unlike its equivalent in the SCP subsystem, this function does NOT change the +current directory to the newly created subdirectory. + + +@subsection sftp_write Writing to a file on the remote computer + +You handle the contents of a remote file just like you would do with a +local file: you open the file in a given mode, move the file pointer in it, +read or write data, and close the file. + +The sftp_open() function is very similar to the regular open() function, +excepted that it returns a file handle of type sftp_file. This file handle +is then used by the other file manipulation functions and remains valid +until you close the remote file with sftp_close(). + +The example below creates a new file named "helloworld.txt" in the +newly created "helloworld" directory. If the file already exists, it will +be truncated. It then writes the famous "Hello, World!" sentence to the +file, followed by a new line character. Finally, the file is closed: + +@code +#include +#include +#include + +int sftp_helloworld(ssh_session session, sftp_session sftp) +{ + int access_type = O_WRONLY | O_CREAT | O_TRUNC; + sftp_file file; + const char *helloworld = "Hello, World!\n"; + int length = strlen(helloworld); + int rc, nwritten; + + ... + + file = sftp_open(sftp, "helloworld/helloworld.txt", + access_type, S_IRWXU); + if (file == NULL) + { + fprintf(stderr, "Can't open file for writing: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + nwritten = sftp_write(file, helloworld, length); + if (nwritten != length) + { + fprintf(stderr, "Can't write data to file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + rc = sftp_close(file); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close the written file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + + +@subsection sftp_read Reading a file from the remote computer + +A synchronous read from a remote file is done using sftp_read(). This +section describes how to download a remote file using sftp_read(). The +next section will discuss more about synchronous/asynchronous read/write +operations using libssh sftp API. + +Files are normally transferred in chunks. A good chunk size is 16 KB. The following +example transfers the remote file "/etc/profile" in 16 KB chunks. For each chunk we +request, sftp_read() blocks till the data has been received: + +@code +// Good chunk size +#define MAX_XFER_BUF_SIZE 16384 + +int sftp_read_sync(ssh_session session, sftp_session sftp) +{ + int access_type; + sftp_file file; + char buffer[MAX_XFER_BUF_SIZE]; + int nbytes, nwritten, rc; + int fd; + + access_type = O_RDONLY; + file = sftp_open(sftp, "/etc/profile", + access_type, 0); + if (file == NULL) { + fprintf(stderr, "Can't open file for reading: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + fd = open("/path/to/profile", O_CREAT); + if (fd < 0) { + fprintf(stderr, "Can't open file for writing: %s\n", + strerror(errno)); + return SSH_ERROR; + } + + for (;;) { + nbytes = sftp_read(file, buffer, sizeof(buffer)); + if (nbytes == 0) { + break; // EOF + } else if (nbytes < 0) { + fprintf(stderr, "Error while reading file: %s\n", + ssh_get_error(session)); + sftp_close(file); + return SSH_ERROR; + } + + nwritten = write(fd, buffer, nbytes); + if (nwritten != nbytes) { + fprintf(stderr, "Error writing: %s\n", + strerror(errno)); + sftp_close(file); + return SSH_ERROR; + } + } + + rc = sftp_close(file); + if (rc != SSH_OK) { + fprintf(stderr, "Can't close the read file: %s\n", + ssh_get_error(session)); + return rc; + } + + return SSH_OK; +} +@endcode + +@subsection sftp_aio Performing an asynchronous read/write on a file on the remote computer + +sftp_read() performs a "synchronous" read operation on a remote file. +This means that sftp_read() will first request the server to read some +data from the remote file and then would wait until the server response +containing data to read (or an error) arrives at the client side. + +sftp_write() performs a "synchronous" write operation on a remote file. +This means that sftp_write() will first request the server to write some +data to the remote file and then would wait until the server response +containing information about the status of the write operation arrives at the +client side. + +If your client program wants to do something other than waiting for the +response after requesting a read/write, the synchronous sftp_read() and +sftp_write() can't be used. In such a case the "asynchronous" sftp aio API +should be used. + +Please go through @ref libssh_tutor_sftp_aio for a detailed description +of the sftp aio API. + +The sftp aio API provides two categories of functions : + - sftp_aio_begin_*() : For requesting a read/write from the server. + - sftp_aio_wait_*() : For waiting for the response of a previously + issued read/write request from the server. + +Hence, the client program can call sftp_aio_begin_*() to request a read/write +and then can perform any number of operations (other than waiting) before +calling sftp_aio_wait_*() for waiting for the response of the previously +issued request. + +We call read/write operations performed in the manner described above as +"asynchronous" read/write operations on a remote file. + +@subsection sftp_ls Listing the contents of a directory + +The functions sftp_opendir(), sftp_readdir(), sftp_dir_eof(), +and sftp_closedir() enable to list the contents of a directory. +They use a new handle_type, "sftp_dir", which gives access to the +directory being read. + +In addition, sftp_readdir() returns a "sftp_attributes" which is a pointer +to a structure with information about a directory entry: + - name: the name of the file or directory + - size: its size in bytes + - etc. + +sftp_readdir() might return NULL under two conditions: + - when the end of the directory has been met + - when an error occurred + +To tell the difference, call sftp_dir_eof(). + +The attributes must be freed with sftp_attributes_free() when no longer +needed. + +The following example reads the contents of some remote directory: + +@code +int sftp_list_dir(ssh_session session, sftp_session sftp) +{ + sftp_dir dir; + sftp_attributes attributes; + int rc; + + dir = sftp_opendir(sftp, "/var/log"); + if (!dir) + { + fprintf(stderr, "Directory not opened: %s\n", + ssh_get_error(session)); + return SSH_ERROR; + } + + printf("Name Size Perms Owner\tGroup\n"); + + while ((attributes = sftp_readdir(sftp, dir)) != NULL) + { + printf("%-20s %10llu %.8o %s(%d)\t%s(%d)\n", + attributes->name, + (long long unsigned int) attributes->size, + attributes->permissions, + attributes->owner, + attributes->uid, + attributes->group, + attributes->gid); + + sftp_attributes_free(attributes); + } + + if (!sftp_dir_eof(dir)) + { + fprintf(stderr, "Can't list directory: %s\n", + ssh_get_error(session)); + sftp_closedir(dir); + return SSH_ERROR; + } + + rc = sftp_closedir(dir); + if (rc != SSH_OK) + { + fprintf(stderr, "Can't close directory: %s\n", + ssh_get_error(session)); + return rc; + } +} +@endcode + +*/ diff --git a/src/libs/libssh-0.12.2/doc/sftp_aio.dox b/src/libs/libssh-0.12.2/doc/sftp_aio.dox new file mode 100644 index 000000000000..9c26f5e1f714 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/sftp_aio.dox @@ -0,0 +1,705 @@ +/** + +@page libssh_tutor_sftp_aio Chapter 10: The SFTP asynchronous I/O + +@section sftp_aio_api The SFTP asynchronous I/O + +NOTE : Please read @ref libssh_tutor_sftp before reading this page. The +synchronous sftp_read() and sftp_write() have been described there. + +SFTP AIO stands for "SFTP Asynchronous Input/Output". This API contains +functions which perform async read/write operations on remote files. + +File transfers performed using the asynchronous sftp aio API can be +significantly faster than the file transfers performed using the synchronous +sftp read/write API (see sftp_read() and sftp_write()). + +The sftp aio API functions are divided into two categories : + - sftp_aio_begin_*() [see sftp_aio_begin_read(), sftp_aio_begin_write()]: + These functions send a request for an i/o operation to the server and + provide the caller an sftp aio handle corresponding to the sent request. + + - sftp_aio_wait_*() [see sftp_aio_wait_read(), sftp_aio_wait_write()]: + These functions wait for the server response corresponding to a previously + issued request. Which request ? the request corresponding to the sftp aio + handle supplied by the caller to these functions. + +Conceptually, you can think of the sftp aio handle as a request identifier. + +Technically, the sftp_aio_begin_*() functions dynamically allocate memory to +store information about the i/o request they send and provide the caller a +handle to this memory, we call this handle an sftp aio handle. + +sftp_aio_wait_*() functions use the information stored in that memory (handled +by the caller supplied sftp aio handle) to identify a request, and then they +wait for that request's response. These functions also release the memory +handled by the caller supplied sftp aio handle (except when they return +SSH_AGAIN). + +sftp_aio_free() can also be used to release the memory handled by an sftp aio +handle but unlike the sftp_aio_wait_*() functions, it doesn't wait for a +response. This should be used to release the memory corresponding to an sftp +aio handle when some failure occurs. An example has been provided at the +end of this page to show the usage of sftp_aio_free(). + +To begin with, this tutorial will provide basic examples that describe the +usage of sftp aio API to perform a single read/write operation. + +The later sections describe the usage of the sftp aio API to obtain faster file +transfers as compared to the transfers performed using the synchronous sftp +read/write API. + +On encountering an error, the sftp aio API functions set the sftp and ssh +errors just like any other libssh sftp API function. These errors can be +obtained using sftp_get_error(), ssh_get_error() and ssh_get_error_code(). +The code examples provided on this page ignore error handling for the sake of +brevity. + +@subsection sftp_aio_read Using the sftp aio API for reading (a basic example) + +For performing an async read operation on a sftp file (see sftp_open()), +the first step is to call sftp_aio_begin_read() to send a read request to the +server. The caller is provided an sftp aio handle corresponding to the sent +read request. + +The second step is to pass a pointer to this aio handle to +sftp_aio_wait_read(), this function waits for the server response which +indicates the success/failure of the read request. On success, the response +indicates EOF or contains the data read from the sftp file. + +The following code example shows how a read operation can be performed +on an sftp file using the sftp aio API. + +@code +ssize_t read_chunk(sftp_file file, void *buf, size_t to_read) +{ + ssize_t bytes_requested, bytes_read; + + // Variable to store an sftp aio handle + sftp_aio aio = NULL; + + // Send a read request to the sftp server + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + // Here its possible that (bytes_requested < to_read) as specified in + // the function documentation of sftp_aio_begin_read() + + // Wait for the response of the read request corresponding to the + // sftp aio handle stored in the aio variable. + bytes_read = sftp_aio_wait_read(&aio, buf, to_read); + if (bytes_read == SSH_ERROR) { + // handle error + } + + return bytes_read; +} +@endcode + +@subsection sftp_aio_write Using the sftp aio API for writing (a basic example) + +For performing an async write operation on a sftp file (see sftp_open()), +the first step is to call sftp_aio_begin_write() to send a write request to +the server. The caller is provided an sftp aio handle corresponding to the +sent write request. + +The second step is to pass a pointer to this aio handle to +sftp_aio_wait_write(), this function waits for the server response which +indicates the success/failure of the write request. + +The following code example shows how a write operation can be performed on an +sftp file using the sftp aio API. + +@code +ssize_t write_chunk(sftp_file file, void *buf, size_t to_write) +{ + ssize_t bytes_requested, bytes_written; + + // Variable to store an sftp aio handle + sftp_aio aio = NULL; + + // Send a write request to the sftp server + bytes_requested = sftp_aio_begin_write(file, buf, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + // Here its possible that (bytes_requested < to_write) as specified in + // the function documentation of sftp_aio_begin_write() + + // Wait for the response of the write request corresponding to + // the sftp aio handle stored in the aio variable. + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + // handle error + } + + return bytes_written; +} +@endcode + +@subsection sftp_aio_actual_use Using the sftp aio API to speed up a transfer + +The above examples were provided to introduce the sftp aio API. +This is not how the sftp aio API is intended to be used, because the +above usage offers no advantage over the synchronous sftp read/write API +which does the same thing i.e issue a request and then immediately wait for +its response. + +The facility that the sftp aio API provides is that the user can do +anything between issuing a request and getting the corresponding response. +Any number of operations can be performed after calling sftp_aio_begin_*() +[which issues a request] and before calling sftp_aio_wait_*() [which waits +for a response] + +The code can leverage this feature by calling sftp_aio_begin_*() multiple times +to issue multiple requests before calling sftp_aio_wait_*() to wait for the +response of an earlier issued request. This approach will keep a certain number +of requests outstanding at the client side. + +After issuing those requests, while the client code does something else (for +example waiting for an outstanding request's response, processing an obtained +response, issuing another request or any other operation the client wants +to perform), at the same time : + + - Some of those outstanding requests may be travelling over the + network towards the server. + + - Some of the outstanding requests may have reached the server and may + be queued for processing at the server side. + + - Some of the outstanding requests may have been processed and the + corresponding responses may be travelling over the network towards the + client. + + - Some of the responses corresponding to the outstanding requests may + have already reached the client side. + +Clearly in this case, operations that the client performs and operations +involved in transfer/processing of a outstanding request can occur in +parallel. Also, operations involved in transfer/processing of two or more +outstanding requests may also occur in parallel (for example when one request +travels to the server, another request's response may be incoming towards the +client). Such kind of parallelism makes the overall transfer faster as compared +to a transfer performed using the synchronous sftp read/write API. + +When the synchronous sftp read/write API is used to perform a transfer, +a strict sequence is followed: + + - The client issues a single read/write request. + - Then waits for its response. + - On obtaining the response, the client processes it. + - After the processing ends, the client issues the next read/write request. + +A file transfer performed in this manner would be slower than the case where +multiple read/write requests are kept outstanding at the client side. Because +here at any given time, operations related to transfer/processing of only one +request/response pair occurs. This is in contrast to the multiple outstanding +requests scenario where operations related to transfer/processing of multiple +request/response pairs may occur at the same time. + +Although it's true that keeping multiple requests outstanding can speed up a +transfer, those outstanding requests come at a cost of increased memory +consumption both at the client side and the server side. Hence care must be +taken to use a reasonable limit for the number of requests kept outstanding. + +The further sections provide code examples to show how uploads/downloads +can be performed using the sftp aio API and the concept of outstanding requests +discussed in this section. In those code examples, error handling has been +ignored and at some places pseudo code has been used for the sake of brevity. + +The complete code for performing uploads/downloads using the sftp aio API, +can be found at https://gitlab.com/libssh/libssh-mirror/-/tree/master. + + - libssh benchmarks for uploads performed using the sftp aio API [See + tests/benchmarks/bench_sftp.c] + - libssh benchmarks for downloads performed using the sftp aio API. [See + tests/benchmarks/bench_sftp.c] + - libssh sftp ft API code for performing a local to remote transfer (upload). + [See src/sftp_ft.c] + - libssh sftp ft API code for performing a remote to local transfer + (download). [See src/sftp_ft.c] + +@subsection sftp_aio_cap Capping applied by the sftp aio API + +Before the code examples for uploads and downloads, its important +to know about the capping applied by the sftp aio API. + +sftp_aio_begin_read() caps the number of bytes the caller can request +to read from the remote file. That cap is the value of the max_read_length +field of the sftp_limits_t returned by sftp_limits(). Say that cap is LIM +and the caller passes x as the number of bytes to read to +sftp_aio_begin_read(), then (assuming no error occurs) : + + - if x <= LIM, then sftp_aio_begin_read() will request the server + to read x bytes from the remote file, and will return x. + + - if x > LIM, then sftp_aio_begin_read() will request the server + to read LIM bytes from the remote file and will return LIM. + +Hence to request server to read x bytes (> LIM), the caller would have +to call sftp_aio_begin_read() multiple times, typically in a loop and +break out of the loop when the summation of return values of the multiple +sftp_aio_begin_read() calls becomes equal to x. + +For the sake of simplicity, the code example for download in the upcoming +section would always ask sftp_aio_begin_read() to read x <= LIM bytes, +so that its return value is guaranteed to be x, unless an error occurs. + +Similarly, sftp_aio_begin_write() caps the number of bytes the caller +can request to write to the remote file. That cap is the value of +max_write_length field of the sftp_limits_t returned by sftp_limits(). +Say that cap is LIM and the caller passes x as the number of bytes to +write to sftp_aio_begin_write(), then (assuming no error occurs) : + + - if x <= LIM, then sftp_aio_begin_write() will request the server + to write x bytes to the remote file, and will return x. + + - if x > LIM, then sftp_aio_begin_write() will request the server + to write LIM bytes to the remote file and will return LIM. + +Hence to request server to write x bytes (> LIM), the caller would have +to call sftp_aio_begin_write() multiple times, typically in a loop and +break out of the loop when the summation of return values of the multiple +sftp_aio_begin_write() calls becomes equal to x. + +For the sake of simplicity, the code example for upload in the upcoming +section would always ask sftp_aio_begin_write() to write x <= LIM bytes, +so that its return value is guaranteed to be x, unless an error occurs. + +@subsection sftp_aio_download_example Performing a download using the sftp aio API + +Terminologies used in the following code snippets : + + - sftp : The sftp_session opened using sftp_new() and initialised using + sftp_init() + + - file : The sftp file handle of the remote file to download data + from. (See sftp_open()) + + - file_size : the size of the sftp file to download. This size can be obtained + by statting the remote file to download (e.g by using sftp_stat()) + + - We will need to maintain a queue which will be used to store the sftp aio + handles corresponding to the outstanding requests. + +First, we issue the read requests while ensuring that their count +doesn't exceed a particular limit decided by us, and the number of bytes +requested don't exceed the size of the file to download. + +@code +sftp_aio aio = NULL; + +// Chunk size to use for the transfer +size_t chunk_size; + +// For the limits structure that would be used +// by the code to set the chunk size +sftp_limits_t lim = NULL; + +// Max number of requests to keep outstanding at a time +size_t in_flight_requests = 5; + +// Number of bytes for which requests have been sent +size_t total_bytes_requested = 0; + +// Number of bytes which have been downloaded +size_t bytes_downloaded = 0; + +// Buffer to use for the download +char *buffer = NULL; + +// Helper variables +size_t to_read; +ssize_t bytes_requested; + +// Get the sftp limits +lim = sftp_limits(sftp); +if (lim == NULL) { + // handle error +} + +// Set the chunk size for download = the max limit for reading +// The reason for this has been given in the "Capping applied by +// the sftp aio API" section (Its to make the code simpler) +// +// Assigning a size_t type variable a uint64_t type value here, +// theoretically could cause an overflow, but practically +// max_read_length would never exceed SIZE_MAX so its okay. +chunk_size = lim->max_read_length; + +buffer = malloc(chunk_size); +if (buffer == NULL) { + // handle error +} + +... // Code to open the remote file (to download) using sftp_open(). +... // Code to stat the remote file's file size. +... // Code to open the local file in which downloaded data is to be stored. +... // Code to initialize the queue which will be used to store sftp aio + // handles. + +for (i = 0; + i < in_flight_requests && total_bytes_requested < file_size; + ++i) { + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + // Issue a read request + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_read) { + // Should not happen for this code, as the to_read is <= + // max limit for reading (chunk size), so there is no reason + // for sftp_aio_begin_read() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +@endcode + +At this point, at max in_flight_requests number of requests may be +outstanding. Now we wait for the response corresponding to the earliest +issued outstanding request. + +On getting that response, we issue another read request if there are +still some bytes in the sftp file (to download) for which we haven't sent the +read request. (This happens when total_bytes_requested < file_size) + +This issuing of another read request (under a condition) is done to +keep the number of outstanding requests equal to the value of the +in_flight_requests variable. + +This process has to be repeated for every remaining outstanding request. + +@code +while (the queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + // Wait for the response of the request corresponding to the aio + bytes_read = sftp_aio_wait_read(&aio, buffer, chunk_size); + if (bytes_read == SSH_ERROR) { + //handle error + } + + bytes_downloaded += bytes_read; + if (bytes_read != chunk_size && bytes_downloaded != file_size) { + // A short read encountered on the remote file before reaching EOF, + // short read before reaching EOF should never happen for the sftp aio + // API which respects the max limit for reading. This probably + // indicates a bad server. + } + + // Pseudo code + WRITE bytes_read bytes from the buffer into the local file + in which downloaded data is to be stored ; + + if (total_bytes_requested == file_size) { + // no need to issue more read requests + continue; + } + + // else issue a read request + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_read) { + // Should not happen for this code, as the to_read is <= + // max limit for reading (chunk size), so there is no reason + // for sftp_aio_begin_read() to return a lesser value. + } + + total_bytes_requested += bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +free(buffer); +sftp_limits_free(lim); + +... // Code to destroy the queue which was used to store the sftp aio + // handles. +@endcode + +After exiting the while (the queue is not empty) loop, the download +would've been complete (assuming no error occurs). + +@subsection sftp_aio_upload_example Performing an upload using the sftp aio API + +Terminologies used in the following code snippets : + + - sftp : The sftp_session opened using sftp_new() and initialised using + sftp_init() + + - file : The sftp file handle of the remote file in which uploaded data + is to be stored. (See sftp_open()) + + - file_size : The size of the local file to upload. This size can be + obtained by statting the local file to upload (e.g by using stat()) + + - We will need maintain a queue which will be used to store the sftp aio + handles corresponding to the outstanding requests. + +First, we issue the write requests while ensuring that their count +doesn't exceed a particular limit decided by us, and the number of bytes +requested to write don't exceed the size of the file to upload. + +@code +sftp_aio aio = NULL; + +// The chunk size to use for the transfer +size_t chunk_size; + +// For the limits structure that would be used by +// the code to set the chunk size +sftp_limits_t lim = NULL; + +// Max number of requests to keep outstanding at a time +size_t in_flight_requests = 5; + +// Total number of bytes for which write requests have been sent +size_t total_bytes_requested = 0; + +// Buffer to use for the upload +char *buffer = NULL; + +// Helper variables +size_t to_write; +ssize_t bytes_requested; + +// Get the sftp limits +lim = sftp_limits(sftp); +if (lim == NULL) { + // handle error +} + +// Set the chunk size for upload = the max limit for writing. +// The reason for this has been given in the "Capping applied by +// the sftp aio API" section (Its to make the code simpler) +// +// Assigning a size_t type variable a uint64_t type value here, +// theoretically could cause an overflow, but practically +// max_write_length would never exceed SIZE_MAX so its okay. +chunk_size = lim->max_write_length; + +buffer = malloc(chunk_size); +if (buffer == NULL) { + // handle error +} + +... // Code to open the local file (to upload) [e.g using open(), fopen()]. +... // Code to stat the local file's file size [e.g using stat()]. +... // Code to open the remote file in which uploaded data will be stored [see + // sftp_open()]. +... // Code to initialize the queue which will be used to store sftp aio + // handles. + +for (i = 0; + i < in_flight_requests && total_bytes_requested < file_size; + ++i) { + to_write = file_size - total_bytes_requested; + if (to_write > chunk_size) { + to_write = chunk_size; + } + + // Pseudo code + READ to_write bytes from the local file (to upload) into the buffer; + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_write) { + // Should not happen for this code, as the to_write is <= + // max limit for writing (chunk size), so there is no reason + // for sftp_aio_begin_write() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +@endcode + +At this point, at max in_flight_requests number of requests may be +outstanding. Now we wait for the response corresponding to the earliest +issued outstanding request. + +On getting that response, we issue another write request if there are +still some bytes in the local file (to upload) for which we haven't sent +the write request. (This happens when total_bytes_requested < file_size) + +This issuing of another write request (under a condition) is done to +keep the number of outstanding requests equal to the value of the +in_flight_requests variable. + +This process has to be repeated for every remaining outstanding request. + +@code +while (the queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + // Wait for the response of the request corresponding to the aio + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + // handle error + } + + // sftp_aio_wait_write() won't report a short write, so no need + // to check for a short write here. + + if (total_bytes_requested == file_size) { + // no need to issue more write requests + continue; + } + + // else issue a write request + to_write = file_size - total_bytes_requested; + if (to_write > chunk_size) { + to_write = chunk_size; + } + + // Pseudo code + READ to_write bytes from the local file (to upload) into a buffer; + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + // handle error + } + + if ((size_t)bytes_requested < to_write) { + // Should not happen for this code, as the to_write is <= + // max limit for writing (chunk size), so there is no reason + // for sftp_aio_begin_write() to return a lesser value. + } + + total_bytes_requested += (size_t)bytes_requested; + + // Pseudo code + ENQUEUE aio in the queue; +} + +free(buffer); + +... // Code to destroy the queue which was used to store the sftp aio + // handles. +@endcode + +After exiting the while (the queue is not empty) loop, the upload +would've been complete (assuming no error occurs). + +@subsection sftp_aio_free Example showing the usage of sftp_aio_free() + +The purpose of sftp_aio_free() was discussed at the beginning of this page, +the following code example shows how it can be used during cleanup. + +@code +void print_sftp_error(sftp_session sftp) +{ + if (sftp == NULL) { + return; + } + + fprintf(stderr, "sftp error : %d\n", sftp_get_error(sftp)); + fprintf(stderr, "ssh error : %s\n", ssh_get_error(sftp->session)); +} + +// Returns 0 on success, -1 on error +int write_strings(sftp_file file) +{ + const char * strings[] = { + "This is the first string", + "This is the second string", + "This is the third string", + "This is the fourth string" + }; + + size_t string_count = sizeof(strings) / sizeof(strings[0]); + size_t i; + + sftp_session sftp = NULL; + sftp_aio aio = NULL; + + int rc; + + if (file == NULL) { + return -1; + } + + ... // Code to initialize the queue which will be used to store sftp aio + // handles + + sftp = file->sftp; + for (i = 0; i < string_count; ++i) { + rc = sftp_aio_begin_write(file, + strings[i], + strlen(strings[i]), + &aio); + if (rc == SSH_ERROR) { + print_sftp_error(sftp); + goto err; + } + + // Pseudo code + ENQUEUE aio in the queue of sftp aio handles + } + + for (i = 0; i < string_count; ++i) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + rc = sftp_aio_wait_write(&aio); + if (rc == SSH_ERROR) { + print_sftp_error(sftp); + goto err; + } + } + + + ... // Code to destroy the queue in which sftp aio handles were + // stored + + return 0; + +err: + + while (queue is not empty) { + // Pseudo code + aio = DEQUEUE an sftp aio handle from the queue of sftp aio handles; + + sftp_aio_free(aio); + } + + ... // Code to destroy the queue in which sftp aio handles were + // stored. + + return -1; +} + +@endcode + +*/ diff --git a/src/libs/libssh-0.12.2/doc/shell.dox b/src/libs/libssh-0.12.2/doc/shell.dox new file mode 100644 index 000000000000..35fc5c9a027d --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/shell.dox @@ -0,0 +1,391 @@ +/** +@page libssh_tutor_shell Chapter 3: Opening a remote shell +@section opening_shell Opening a remote shell + +We already mentioned that a single SSH connection can be shared +between several "channels". Channels can be used for different purposes. + +This chapter shows how to open one of these channels, and how to use it to +start a command interpreter on a remote computer. + + +@subsection open_channel Opening and closing a channel + +The ssh_channel_new() function creates a channel. It returns the channel as +a variable of type ssh_channel. + +Once you have this channel, you open a SSH session that uses it with +ssh_channel_open_session(). + +Once you don't need the channel anymore, you can send an end-of-file +to it with ssh_channel_close(). At this point, you can destroy the channel +with ssh_channel_free(). + +The code sample below achieves these tasks: + +@code +int shell_session(ssh_session session) +{ + ssh_channel channel = NULL; + int rc; + + channel = ssh_channel_new(session); + if (channel == NULL) + return SSH_ERROR; + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) + { + ssh_channel_free(channel); + return rc; + } + + ... + + ssh_channel_close(channel); + ssh_channel_send_eof(channel); + ssh_channel_free(channel); + + return SSH_OK; +} +@endcode + + +@subsection interactive Interactive and non-interactive sessions + +A "shell" is a command interpreter. It is said to be "interactive" +if there is a human user typing the commands, one after the +other. The contrary, a non-interactive shell, is similar to +the execution of commands in the background: there is no attached +terminal. + +If you plan using an interactive shell, you need to create a +pseud-terminal on the remote side. A remote terminal is usually referred +to as a "pty", for "pseudo-teletype". The remote processes won't see the +difference with a real text-oriented terminal. + +If needed, you request the pty with the function ssh_channel_request_pty(). +If you want define its dimensions (number of rows and columns), +call ssh_channel_request_pty_size() instead. It's also possible to change +the dimensions after creating the pty with ssh_channel_change_pty_size(). + +These two functions configure the pty using the same terminal modes that +stdin has. If stdin isn't a TTY, they use default modes that configure +the pty with in canonical mode and e.g. preserving CR and LF characters. +If you want to change the terminal modes used by the pty (e.g. to change +CRLF handling), use ssh_channel_request_pty_size_modes(). This function +accepts an additional "modes" buffer that is expected to contain encoded +terminal modes according to RFC 4254 section 8. + +Be your session interactive or not, the next step is to request a +shell with ssh_channel_request_shell(). + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + ... + + return rc; +} +@endcode + + +@subsection read_data Displaying the data sent by the remote computer + +In your program, you will usually need to receive all the data "displayed" +into the remote pty. You will usually analyse, log, or display this data. + +ssh_channel_read() and ssh_channel_read_nonblocking() are the simplest +way to read data from a channel. If you only need to read from a single +channel, they should be enough. + +The example below shows how to wait for remote data using ssh_channel_read(): + +@code +int interactive_shell_session(ssh_channel channel) +{ + int rc; + char buffer[256]; + int nbytes; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) + return SSH_ERROR; + + if (nbytes > 0) + write(1, buffer, nbytes); + } + + return rc; +} +@endcode + +Unlike ssh_channel_read(), ssh_channel_read_nonblocking() never waits for +remote data to be ready. It returns immediately. + +If you plan to use ssh_channel_read_nonblocking() repeatedly in a loop, +you should use a "passive wait" function like usleep(3) in the same +loop. Otherwise, your program will consume all the CPU time, and your +computer might become unresponsive. + + +@subsection write_data Sending user input to the remote computer + +User's input is sent to the remote site with ssh_channel_write(). + +The following example shows how to combine a nonblocking read from a SSH +channel with a nonblocking read from the keyboard. The local input is then +sent to the remote computer: + +@code +/* Under Linux, this function determines whether a key has been pressed. + Under Windows, it is a standard function, so you need not redefine it. +*/ +int kbhit() +{ + struct timeval tv = { 0L, 0L }; + fd_set fds; + + FD_ZERO(&fds); + FD_SET(0, &fds); + + return select(1, &fds, NULL, NULL, &tv); +} + +/* A very simple terminal emulator: + - print data received from the remote computer + - send keyboard input to the remote computer +*/ +int interactive_shell_session(ssh_channel channel) +{ + /* Session and terminal initialization skipped */ + ... + + char buffer[256]; + int nbytes, nwritten; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + nbytes = ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = write(1, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + + if (!kbhit()) + { + usleep(50000L); // 0.05 second + continue; + } + + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = ssh_channel_write(channel, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + } + } + + return rc; +} +@endcode + +Of course, this is a poor terminal emulator, since the echo from the keys +pressed should not be done locally, but should be done by the remote side. +Also, user's input should not be sent once "Enter" key is pressed, but +immediately after each key is pressed. This can be accomplished +by setting the local terminal to "raw" mode with the cfmakeraw(3) function. +cfmakeraw() is a standard function under Linux, on other systems you can +recode it with: + +@code +static void cfmakeraw(struct termios *termios_p) +{ + termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + termios_p->c_oflag &= ~OPOST; + termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); + termios_p->c_cflag &= ~(CSIZE|PARENB); + termios_p->c_cflag |= CS8; +} +@endcode + +If you are not using a local terminal, but some kind of graphical +environment, the solution to this kind of "echo" problems will be different. + + +@subsection select_loop A more elaborate way to get the remote data + +*** Warning: ssh_select() and ssh_channel_select() are not relevant anymore, + since libssh is about to provide an easier system for asynchronous + communications. This subsection should be removed then. *** + +ssh_channel_read() and ssh_channel_read_nonblocking() functions are simple, +but they are not adapted when you expect data from more than one SSH channel, +or from other file descriptors. Last example showed how getting data from +the standard input (the keyboard) at the same time as data from the SSH +channel was complicated. The functions ssh_select() and ssh_channel_select() +provide a more elegant way to wait for data coming from many sources. + +The functions ssh_select() and ssh_channel_select() remind of the standard +UNIX select(2) function. The idea is to wait for "something" to happen: +incoming data to be read, outgoing data to block, or an exception to +occur. Both these functions do a "passive wait", i.e. you can safely use +them repeatedly in a loop, it will not consume exaggerate processor time +and make your computer unresponsive. It is quite common to use these +functions in your application's main loop. + +The difference between ssh_select() and ssh_channel_select() is that +ssh_channel_select() is simpler, but allows you only to watch SSH channels. +ssh_select() is more complete and enables watching regular file descriptors +as well, in the same function call. + +Below is an example of a function that waits both for remote SSH data to come, +as well as standard input from the keyboard: + +@code +int interactive_shell_session(ssh_session session, ssh_channel channel) +{ + /* Session and terminal initialization skipped */ + ... + + char buffer[256]; + int nbytes, nwritten; + + while (ssh_channel_is_open(channel) && + !ssh_channel_is_eof(channel)) + { + struct timeval timeout; + ssh_channel in_channels[2], out_channels[2]; + fd_set fds; + int maxfd; + + timeout.tv_sec = 30; + timeout.tv_usec = 0; + in_channels[0] = channel; + in_channels[1] = NULL; + FD_ZERO(&fds); + FD_SET(0, &fds); + FD_SET(ssh_get_fd(session), &fds); + maxfd = ssh_get_fd(session) + 1; + + ssh_select(in_channels, out_channels, maxfd, &fds, &timeout); + + if (out_channels[0] != NULL) + { + nbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = write(1, buffer, nbytes); + if (nwritten != nbytes) return SSH_ERROR; + } + } + + if (FD_ISSET(0, &fds)) + { + nbytes = read(0, buffer, sizeof(buffer)); + if (nbytes < 0) return SSH_ERROR; + if (nbytes > 0) + { + nwritten = ssh_channel_write(channel, buffer, nbytes); + if (nbytes != nwritten) return SSH_ERROR; + } + } + } + + return rc; +} +@endcode + + +@subsection x11 Using graphical applications on the remote side + +If your remote application is graphical, you can forward the X11 protocol to +your local computer. + +To do that, you first declare a callback to manage channel_open_request_x11_function. +Then you create the forwarding tunnel for the X11 protocol with ssh_channel_request_x11(). + +The following code performs channel initialization and shell session +opening, and handles a parallel X11 connection: + +@code +#include + +ssh_channel x11channel = NULL; + +ssh_channel x11_open_request_callback(ssh_session session, const char *shost, int sport, void *userdata) +{ + x11channel = ssh_channel_new(session); + return x11channel; +} + +int interactive_shell_session(ssh_channel channel) +{ + int rc; + + struct ssh_callbacks_struct cb = + { + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL + }; + + ssh_callbacks_init(&cb); + rc = ssh_set_callbacks(session, &cb); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_pty(channel); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_change_pty_size(channel, 80, 24); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_x11(channel, 0, NULL, NULL, 0); + if (rc != SSH_OK) return rc; + + rc = ssh_channel_request_shell(channel); + if (rc != SSH_OK) return rc; + + /* Read the data sent by the remote computer here */ + ... +} +@endcode + +Don't forget to check the $DISPLAY environment variable on the remote +side, or the remote applications won't try using the X11 tunnel: + +@code +$ echo $DISPLAY +localhost:10.0 +$ xclock & +@endcode + +See an implementation example at https://gitlab.com/libssh/libssh-mirror/-/tree/master/examples/ssh_X11_client.c for details. + +*/ diff --git a/src/libs/libssh-0.12.2/doc/tbd.dox b/src/libs/libssh-0.12.2/doc/tbd.dox new file mode 100644 index 000000000000..921337ed36d3 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/tbd.dox @@ -0,0 +1,14 @@ +/** +@page libssh_tutor_todo To be done + +*** To be written *** + +@section sshd Writing a libssh-based server + +*** To be written *** + +@section cpp The libssh C++ wrapper + +*** To be written *** + +*/ diff --git a/src/libs/libssh-0.12.2/doc/threading.dox b/src/libs/libssh-0.12.2/doc/threading.dox new file mode 100644 index 000000000000..87d096b74da3 --- /dev/null +++ b/src/libs/libssh-0.12.2/doc/threading.dox @@ -0,0 +1,52 @@ +/** +@page libssh_tutor_threads Chapter 8: Threads with libssh +@section threads_with_libssh How to use libssh with threads + +libssh may be used in multithreaded applications, but under several conditions : + - Your system must support libpthread or, in Windows environment, + CriticalSection based mutex control. + - Since version 0.8.0, threads initialization is called automatically in the + library constructor if libssh is dynamically linked. This means it is no + longer necessary to call ssh_init()/ssh_finalize(). + - If libssh is statically linked, threading must be initialized by calling + ssh_init() before using any of libssh provided functions. This initialization + must be done outside of any threading context. Don't forget to call + ssh_finalize() to avoid memory leak + - At all times, you may use different sessions inside threads, make parallel + connections, read/write on different sessions and so on. You *cannot* use a + single session (or channels for a single session) in several threads at the same + time. This will most likely lead to internal state corruption. This limitation is + being worked out and will maybe disappear later. + +@subsection threads_init Initialization of threads + +Since version 0.8.0, it is no longer necessary to call ssh_init()/ssh_finalize() +if libssh is dynamically linked. + +If libssh is statically linked, call ssh_init() before using any of libssh +provided functions. + +@subsection threads_pthread Using libpthread with libssh + +Since version 0.8.0, libpthread is the default threads library used by libssh. + +To use libpthread, simply link it to you application. + +If you are using libssh statically linked, don't forget to call ssh_init() +before using any of libssh provided functions (and ssh_finalize() in the end). + +@subsection threads_other Using another threading library + +Since version 0.8.0, libssh does not support custom threading libraries. +The change makes sense since the newer versions for libcrypto (OpenSSL) and +libgcrypt don't support custom threading libraries. + +The default used threading library is libpthread. +Alternatively, in Windows environment, CriticalSection based mutex control can +be used. + +If your system does not support libpthread nor CriticalSection based mutex +control, unfortunately, you cannot use libssh in multithreaded scenarios. + +Good luck ! +*/ diff --git a/src/libs/libssh-0.12.2/examples/CMakeLists.txt b/src/libs/libssh-0.12.2/examples/CMakeLists.txt new file mode 100644 index 000000000000..4fb842b0531b --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/CMakeLists.txt @@ -0,0 +1,105 @@ +project(libssh-examples C CXX) + +set(examples_SRCS + authentication.c + knownhosts.c + connect_ssh.c +) + +include_directories(${libssh_BINARY_DIR}/include ${libssh_BINARY_DIR}) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif() + +if (UNIX AND NOT WIN32) + add_executable(libssh_scp libssh_scp.c ${examples_SRCS}) + target_compile_options(libssh_scp PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(libssh_scp ssh::ssh) + + add_executable(scp_download scp_download.c ${examples_SRCS}) + target_compile_options(scp_download PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(scp_download ssh::ssh) + + add_executable(sshnetcat sshnetcat.c ${examples_SRCS}) + target_compile_options(sshnetcat PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(sshnetcat ssh::ssh) + + if (WITH_SFTP) + add_executable(samplesftp samplesftp.c ${examples_SRCS}) + target_compile_options(samplesftp PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesftp ssh::ssh) + + if (WITH_SERVER) + add_executable(sample_sftpserver sample_sftpserver.c ${examples_SRCS}) + target_compile_options(sample_sftpserver PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(sample_sftpserver ssh::ssh ${ARGP_LIBRARIES}) + endif (WITH_SERVER) + endif (WITH_SFTP) + + add_executable(ssh-client ssh_client.c ${examples_SRCS}) + target_compile_options(ssh-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh-client ssh::ssh) + + add_executable(ssh-X11-client ssh_X11_client.c ${examples_SRCS}) + target_compile_options(ssh-X11-client PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh-X11-client ssh::ssh) + + if (WITH_SERVER AND (ARGP_LIBRARIES OR HAVE_ARGP_H)) + if (HAVE_LIBUTIL) + add_executable(ssh_server_fork ssh_server.c) + target_compile_options(ssh_server_fork PRIVATE ${DEFAULT_C_COMPILE_FLAGS} -DWITH_FORK) + target_link_libraries(ssh_server_fork ssh::ssh ${ARGP_LIBRARIES} util) + + add_executable(ssh_server_pthread ssh_server.c) + target_compile_options(ssh_server_pthread PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh_server_pthread ssh::ssh ${ARGP_LIBRARIES} pthread util) + endif (HAVE_LIBUTIL) + + if (WITH_GSSAPI AND GSSAPI_FOUND) + add_executable(proxy proxy.c) + target_compile_options(proxy PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(proxy ssh::ssh ${ARGP_LIBRARIES}) + + add_executable(sshd_direct-tcpip sshd_direct-tcpip.c) + target_compile_options(sshd_direct-tcpip PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(sshd_direct-tcpip ssh::ssh ${ARGP_LIBRARIES}) + endif (WITH_GSSAPI AND GSSAPI_FOUND) + + add_executable(samplesshd-kbdint samplesshd-kbdint.c) + target_compile_options(samplesshd-kbdint PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesshd-kbdint ssh::ssh ${ARGP_LIBRARIES}) + + add_executable(keygen2 keygen2.c ${examples_SRCS}) + target_compile_options(keygen2 PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(keygen2 ssh::ssh ${ARGP_LIBRARIES}) + + endif() +endif (UNIX AND NOT WIN32) + +if (WITH_SERVER) + add_executable(samplesshd-cb samplesshd-cb.c) + target_compile_options(samplesshd-cb PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(samplesshd-cb ssh::ssh) + if (ARGP_LIBRARIES OR HAVE_ARGP_H) + target_link_libraries(samplesshd-cb ${ARGP_LIBRARIES}) + endif(ARGP_LIBRARIES OR HAVE_ARGP_H) +endif() + +add_executable(exec exec.c ${examples_SRCS}) +target_compile_options(exec PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(exec ssh::ssh) + +add_executable(senddata senddata.c ${examples_SRCS}) +target_compile_options(senddata PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(senddata ssh::ssh) + +add_executable(keygen keygen.c) +target_compile_options(keygen PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) +target_link_libraries(keygen ssh::ssh) + +add_executable(libsshpp libsshpp.cpp) +target_link_libraries(libsshpp ssh::ssh) + +add_executable(libsshpp_noexcept libsshpp_noexcept.cpp) +target_link_libraries(libsshpp_noexcept ssh::ssh) diff --git a/src/libs/libssh-0.12.2/examples/authentication.c b/src/libs/libssh-0.12.2/examples/authentication.c new file mode 100644 index 000000000000..be63f04ddf21 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/authentication.c @@ -0,0 +1,248 @@ +/* + * authentication.c + * This file contains an example of how to do an authentication to a + * SSH server using libssh + */ + +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include +#include +#include + +#include +#include "examples_common.h" + +int authenticate_kbdint(ssh_session session, const char *password) +{ + int err; + + err = ssh_userauth_kbdint(session, NULL, NULL); + while (err == SSH_AUTH_INFO) { + const char *instruction = NULL; + const char *name = NULL; + char buffer[128]; + int i, n; + + name = ssh_userauth_kbdint_getname(session); + instruction = ssh_userauth_kbdint_getinstruction(session); + n = ssh_userauth_kbdint_getnprompts(session); + + if (name && strlen(name) > 0) { + printf("%s\n", name); + } + + if (instruction && strlen(instruction) > 0) { + printf("%s\n", instruction); + } + + for (i = 0; i < n; i++) { + const char *answer = NULL; + const char *prompt = NULL; + char echo; + + prompt = ssh_userauth_kbdint_getprompt(session, i, &echo); + if (prompt == NULL) { + break; + } + + if (echo) { + char *p = NULL; + + printf("%s", prompt); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + return SSH_AUTH_ERROR; + } + + if ((p = strchr(buffer, '\n'))) { + *p = '\0'; + } + + if (ssh_userauth_kbdint_setanswer(session, i, buffer) < 0) { + return SSH_AUTH_ERROR; + } + + memset(buffer, 0, sizeof(buffer)); + } else { + if (password && strstr(prompt, "Password:")) { + answer = password; + } else { + buffer[0] = '\0'; + + if (ssh_getpass(prompt, buffer, sizeof(buffer), 0, 0) < 0) { + return SSH_AUTH_ERROR; + } + answer = buffer; + } + err = ssh_userauth_kbdint_setanswer(session, i, answer); + memset(buffer, 0, sizeof(buffer)); + if (err < 0) { + return SSH_AUTH_ERROR; + } + } + } + err=ssh_userauth_kbdint(session,NULL,NULL); + } + + return err; +} + +static int auth_keyfile(ssh_session session, char* keyfile) +{ + ssh_key key = NULL; + char pubkey[132] = {0}; // +".pub" + int rc; + + snprintf(pubkey, sizeof(pubkey), "%s.pub", keyfile); + + rc = ssh_pki_import_pubkey_file( pubkey, &key); + + if (rc != SSH_OK) + return SSH_AUTH_DENIED; + + rc = ssh_userauth_try_publickey(session, NULL, key); + + ssh_key_free(key); + + if (rc!=SSH_AUTH_SUCCESS) + return SSH_AUTH_DENIED; + + rc = ssh_pki_import_privkey_file(keyfile, NULL, NULL, NULL, &key); + + if (rc != SSH_OK) + return SSH_AUTH_DENIED; + + rc = ssh_userauth_publickey(session, NULL, key); + + ssh_key_free(key); + + return rc; +} + + +static void error(ssh_session session) +{ + fprintf(stderr,"Authentication failed: %s\n",ssh_get_error(session)); +} + +int authenticate_console(ssh_session session) +{ + int rc; + int method; + char password[128] = {0}; + char *banner = NULL; + + // Try to authenticate + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } + + method = ssh_userauth_list(session, NULL); + while (rc != SSH_AUTH_SUCCESS) { + if (method & SSH_AUTH_METHOD_GSSAPI_MIC){ + rc = ssh_userauth_gssapi(session); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + if (method & SSH_AUTH_METHOD_GSSAPI_KEYEX) { + rc = ssh_userauth_gssapi_keyex(session); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + if (method & SSH_AUTH_METHOD_PUBLICKEY) { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + { + char buffer[128] = {0}; + char *p = NULL; + + printf("Automatic pubkey failed. " + "Do you want to try a specific key? (y/n)\n"); + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + break; + } + if ((buffer[0]=='Y') || (buffer[0]=='y')) { + printf("private key filename: "); + + if (fgets(buffer, sizeof(buffer), stdin) == NULL) { + return SSH_AUTH_ERROR; + } + + buffer[sizeof(buffer) - 1] = '\0'; + if ((p = strchr(buffer, '\n'))) { + *p = '\0'; + } + + rc = auth_keyfile(session, buffer); + + if(rc == SSH_AUTH_SUCCESS) { + break; + } + fprintf(stderr, "failed with key\n"); + } + } + + // Try to authenticate with keyboard interactive"; + if (method & SSH_AUTH_METHOD_INTERACTIVE) { + rc = authenticate_kbdint(session, NULL); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + + if (ssh_getpass("Password: ", password, sizeof(password), 0, 0) < 0) { + return SSH_AUTH_ERROR; + } + + // Try to authenticate with password + if (method & SSH_AUTH_METHOD_PASSWORD) { + rc = ssh_userauth_password(session, NULL, password); + if (rc == SSH_AUTH_ERROR || !ssh_is_connected(session)) { + error(session); + return rc; + } else if (rc == SSH_AUTH_SUCCESS) { + break; + } + } + memset(password, 0, sizeof(password)); + } + + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n",banner); + SSH_STRING_FREE_CHAR(banner); + } + + return rc; +} diff --git a/src/libs/libssh-0.12.2/examples/connect_ssh.c b/src/libs/libssh-0.12.2/examples/connect_ssh.c new file mode 100644 index 000000000000..b07f824d940a --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/connect_ssh.c @@ -0,0 +1,77 @@ +/* + * connect_ssh.c + * This file contains an example of how to connect to a + * SSH server using libssh + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include +#include "examples_common.h" +#include + +ssh_session connect_ssh(const char *host, const char *port, const char *user, int verbosity) +{ + ssh_session session = NULL; + int auth = 0; + + session = ssh_new(); + if (session == NULL) { + return NULL; + } + + if (user != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + ssh_free(session); + return NULL; + } + } + + if (port != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_PORT_STR, port) < 0) { + ssh_free(session); + return NULL; + } + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + ssh_free(session); + return NULL; + } + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + if (verify_knownhost(session) < 0) { + ssh_disconnect(session); + ssh_free(session); + return NULL; + } + auth = authenticate_console(session); + if (auth == SSH_AUTH_SUCCESS) { + return session; + } else if (auth == SSH_AUTH_DENIED) { + fprintf(stderr, "Authentication failed\n"); + } else { + fprintf(stderr, + "Error while authenticating : %s\n", + ssh_get_error(session)); + } + ssh_disconnect(session); + ssh_free(session); + return NULL; +} diff --git a/src/libs/libssh-0.12.2/examples/examples_common.h b/src/libs/libssh-0.12.2/examples/examples_common.h new file mode 100644 index 000000000000..6f5a1b12c6ce --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/examples_common.h @@ -0,0 +1,26 @@ +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ +#ifndef EXAMPLES_COMMON_H_ +#define EXAMPLES_COMMON_H_ + +#include + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset(&(x), 0, sizeof(x)) + +int authenticate_console(ssh_session session); +int authenticate_kbdint(ssh_session session, const char *password); +int verify_knownhost(ssh_session session); +ssh_session connect_ssh(const char *hostname, const char *port, const char *user, int verbosity); + +#endif /* EXAMPLES_COMMON_H_ */ diff --git a/src/libs/libssh-0.12.2/examples/exec.c b/src/libs/libssh-0.12.2/examples/exec.c new file mode 100644 index 000000000000..9c1d8dbb73a3 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/exec.c @@ -0,0 +1,81 @@ +/* simple exec example */ +#include + +#include +#include "examples_common.h" + +int main(void) { + ssh_session session = NULL; + ssh_channel channel = NULL; + char buffer[256]; + int rbytes, wbytes, total = 0; + int rc; + + session = connect_ssh("localhost", NULL, NULL, 0); + if (session == NULL) { + ssh_finalize(); + return 1; + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + return 1; + } + + rc = ssh_channel_open_session(channel); + if (rc < 0) { + goto failed; + } + + rc = ssh_channel_request_exec(channel, "lsof"); + if (rc < 0) { + goto failed; + } + + rbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (rbytes <= 0) { + goto failed; + } + + do { + wbytes = fwrite(buffer + total, 1, rbytes, stdout); + if (wbytes <= 0) { + goto failed; + } + + total += wbytes; + + /* When it was not possible to write the whole buffer to stdout */ + if (wbytes < rbytes) { + rbytes -= wbytes; + continue; + } + + rbytes = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + total = 0; + } while (rbytes > 0); + + if (rbytes < 0) { + goto failed; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + + return 0; +failed: + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + + return 1; +} diff --git a/src/libs/libssh-0.12.2/examples/keygen.c b/src/libs/libssh-0.12.2/examples/keygen.c new file mode 100644 index 000000000000..99f8c98cf51a --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/keygen.c @@ -0,0 +1,41 @@ +/* keygen.c + * Sample implementation of ssh-keygen using libssh + */ + +/* +Copyright 2019 Red Hat, Inc. + +Author: Jakub Jelen + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include + +int main(void) +{ + ssh_key key = NULL; + int rv; + + /* Generate a new ED25519 private key file */ + rv = ssh_pki_generate(SSH_KEYTYPE_ED25519, 0, &key); + if (rv != SSH_OK) { + fprintf(stderr, "Failed to generate private key"); + return -1; + } + + /* Write it to a file testkey in the current directory */ + rv = ssh_pki_export_privkey_file(key, NULL, NULL, NULL, "testkey"); + if (rv != SSH_OK) { + fprintf(stderr, "Failed to write private key file"); + return -1; + } + + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/keygen2.c b/src/libs/libssh-0.12.2/examples/keygen2.c new file mode 100644 index 000000000000..73d702115704 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/keygen2.c @@ -0,0 +1,526 @@ +/* + * keygen2.c - Generate SSH keys using libssh + * Author: Anderson Toshiyuki Sasaki + */ + +/* + * Copyright (c) 2019 Red Hat, Inc. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see http://www.gnu.org/licenses/. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +struct arguments_st { + enum ssh_keytypes_e type; + unsigned long bits; + char *file; + char *passphrase; + char *format; + int action_list; +}; + +static struct argp_option options[] = { + { + .name = "bits", + .key = 'b', + .arg = "BITS", + .flags = 0, + .doc = "The size of the key to be generated. " + "If omitted, a default value is used depending on the TYPE. " + "Accepted values are: " + "1024, 2048, 3072 (default), 4096, and 8192 for TYPE=\"rsa\"; " + "256 (default), 384, and 521 for TYPE=\"ecdsa\"; " + "can be omitted for TYPE=\"ed25519\" " + "(it will be ignored if provided).\n", + .group = 0 + }, + { + .name = "file", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "The output file. " + "If not provided, the used file name will be generated " + "according to the key type as \"id_TYPE\" " + "(e.g. \"id_rsa\" for type \"rsa\"). " + "The public key file name is generated from the private key " + "file name by appending \".pub\".\n", + .group = 0 + }, + { + .name = "passphrase", + .key = 'p', + .arg = "PASSPHRASE", + .flags = 0, + .doc = "The passphrase used to encrypt the private key. " + "If omitted the file will not be encrypted.\n", + .group = 0 + }, + { + .name = "type", + .key = 't', + .arg = "TYPE", + .flags = 0, + .doc = "The type of the key to be generated. " + "Accepted values are: " + "\"rsa\", \"ecdsa\", and \"ed25519\".\n", + .group = 0 + }, + { + .name = "list", + .key = 'l', + .arg = NULL, + .flags = 0, + .doc = "List the Fingerprint of the given key\n", + .group = 0 + }, + { + .name = "format", + .key = 'm', + .arg = "FORMAT", + .flags = 0, + .doc = "Write the file in specific format. The supported values are " + "'PEM'and 'OpenSSH' file format. By default Ed25519 " + "keys are exported in OpenSSH format and others in PEM.\n", + .group = 0 + }, + { + /* End of the options */ + 0 + }, +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct arguments_st *arguments = NULL; + error_t rc = 0; + + if (state == NULL) { + return EINVAL; + } + + arguments = state->input; + if (arguments == NULL) { + fprintf(stderr, "Error: NULL pointer to arguments structure " + "provided\n"); + rc = EINVAL; + goto end; + } + + switch (key) { + case 'b': + errno = 0; + arguments->bits = strtoul(arg, NULL, 10); + if (errno != 0) { + rc = errno; + goto end; + } + break; + case 'f': + arguments->file = strdup(arg); + if (arguments->file == NULL) { + fprintf(stderr, "Error: Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'p': + arguments->passphrase = strdup(arg); + if (arguments->passphrase == NULL) { + fprintf(stderr, "Error: Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 't': + if (!strcmp(arg, "rsa")) { + arguments->type = SSH_KEYTYPE_RSA; + } + else if (!strcmp(arg, "ecdsa")) { + arguments->type = SSH_KEYTYPE_ECDSA; + } + else if (!strcmp(arg, "ed25519")) { + arguments->type = SSH_KEYTYPE_ED25519; + } + else { + fprintf(stderr, "Error: Invalid key type\n"); + argp_usage(state); + rc = EINVAL; + goto end; + } + break; + case 'l': + arguments->action_list = 1; + break; + case 'm': + arguments->format = strdup(arg); + break; + case ARGP_KEY_ARG: + if (state->arg_num > 0) { + /* Too many arguments. */ + printf("Error: Too many arguments\n"); + argp_usage(state); + } + break; + case ARGP_KEY_END: + break; + default: + return ARGP_ERR_UNKNOWN; + } + +end: + return rc; +} + +static int validate_args(struct arguments_st *args) +{ + int rc = 0; + + if (args == NULL) { + return EINVAL; + } + + /* no other arguments needed for listing key fingerprints */ + if (args->action_list) { + return 0; + } + + switch (args->type) { + case SSH_KEYTYPE_RSA: + switch (args->bits) { + case 0: + /* If not provided, use default value */ + args->bits = 3072; + break; + case 1024: + case 2048: + case 3072: + case 4096: + case 8192: + break; + default: + fprintf(stderr, "Error: Invalid bits parameter provided\n"); + rc = EINVAL; + break; + } + + if (args->file == NULL) { + args->file = strdup("id_rsa"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + case SSH_KEYTYPE_ECDSA: + switch (args->bits) { + case 0: + /* If not provided, use default value */ + args->bits = 256; + break; + case 256: + case 384: + case 521: + break; + default: + fprintf(stderr, "Error: Invalid bits parameter provided\n"); + rc = EINVAL; + break; + } + if (args->file == NULL) { + args->file = strdup("id_ecdsa"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + case SSH_KEYTYPE_ED25519: + /* Ignore value and overwrite with a zero */ + args->bits = 0; + + if (args->file == NULL) { + args->file = strdup("id_ed25519"); + if (args->file == NULL) { + rc = ENOMEM; + break; + } + } + + break; + default: + fprintf(stderr, "Error: unknown key type\n"); + rc = EINVAL; + break; + } + + return rc; +} + +/* Program documentation. */ +static char doc[] = "Generate an SSH key pair. " + "The \"--type\" (short: \"-t\") option is required."; + +/* Our argp parser */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; + +static void +list_fingerprint(char *file) +{ + ssh_key key = NULL; + unsigned char *hash = NULL; + size_t hlen = 0; + int rc; + + rc = ssh_pki_import_privkey_file(file, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + fprintf(stderr, "Failed to import private key %s\n", file); + return; + } + + rc = ssh_get_publickey_hash(key, SSH_PUBLICKEY_HASH_SHA256, &hash, &hlen); + if (rc != SSH_OK) { + fprintf(stderr, "Failed to get key fingerprint\n"); + ssh_key_free(key); + return; + } + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + + ssh_clean_pubkey_hash(&hash); + ssh_key_free(key); +} + +int main(int argc, char *argv[]) +{ + ssh_key key = NULL; + int rc = 0; + char overwrite[1024] = ""; + + char *pubkey_file = NULL; + + struct arguments_st arguments = { + .type = SSH_KEYTYPE_UNKNOWN, + .bits = 0, + .file = NULL, + .passphrase = NULL, + .action_list = 0, + }; + + if (argc < 2) { + argp_help(&argp, stdout, ARGP_HELP_DOC | ARGP_HELP_USAGE, argv[0]); + goto end; + } + + rc = argp_parse(&argp, argc, argv, 0, 0, &arguments); + if (rc != 0) { + goto end; + } + + rc = validate_args(&arguments); + if (rc != 0) { + goto end; + } + + if (arguments.file == NULL) { + fprintf(stderr, "Error: Missing argument file\n"); + goto end; + } + + if (arguments.action_list) { + list_fingerprint(arguments.file); + goto end; + } + + errno = 0; + rc = open(arguments.file, O_CREAT | O_EXCL | O_WRONLY, S_IRUSR | S_IWUSR); + if (rc < 0) { + if (errno == EEXIST) { + printf("File \"%s\" exists. Overwrite it? (y|n) ", arguments.file); + rc = scanf("%1023s", overwrite); + if (rc > 0 && tolower(overwrite[0]) == 'y') { + rc = open(arguments.file, O_WRONLY); + if (rc > 0) { + close(rc); + errno = 0; + rc = chmod(arguments.file, S_IRUSR | S_IWUSR); + if (rc != 0) { + fprintf(stderr, + "Error(%d): Could not set file permissions\n", + errno); + goto end; + } + } else { + fprintf(stderr, + "Error: Could not create private key file\n"); + goto end; + } + } else { + goto end; + } + } else { + fprintf(stderr, "Error opening \"%s\" file\n", arguments.file); + goto end; + } + } else { + close(rc); + } + + /* Generate a new private key */ + rc = ssh_pki_generate(arguments.type, arguments.bits, &key); + if (rc != SSH_OK) { + fprintf(stderr, "Error: Failed to generate keys"); + goto end; + } + + /* Write the private key */ + if (arguments.format != NULL) { + if (strcasecmp(arguments.format, "PEM") == 0) { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_PEM); + } else if (strcasecmp(arguments.format, "OpenSSH") == 0) { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_OPENSSH); + } else { + rc = ssh_pki_export_privkey_file_format(key, + arguments.passphrase, + NULL, + NULL, + arguments.file, + SSH_FILE_FORMAT_DEFAULT); + } + } else { + rc = ssh_pki_export_privkey_file(key, + arguments.passphrase, + NULL, + NULL, + arguments.file); + } + if (rc != SSH_OK) { + fprintf(stderr, "Error: Failed to write private key file"); + goto end; + } + + /* If a passphrase was provided, overwrite and free it as it is not needed + * anymore */ + if (arguments.passphrase != NULL) { +#ifdef HAVE_EXPLICIT_BZERO + explicit_bzero(arguments.passphrase, strlen(arguments.passphrase)); +#else + bzero(arguments.passphrase, strlen(arguments.passphrase)); +#endif + free(arguments.passphrase); + arguments.passphrase = NULL; + } + + pubkey_file = (char *)malloc(strlen(arguments.file) + 5); + if (pubkey_file == NULL) { + rc = ENOMEM; + goto end; + } + + sprintf(pubkey_file, "%s.pub", arguments.file); + + errno = 0; + rc = open(pubkey_file, + O_CREAT | O_EXCL | O_WRONLY, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (rc < 0) { + if (errno == EEXIST) { + printf("File \"%s\" exists. Overwrite it? (y|n) ", pubkey_file); + rc = scanf("%1023s", overwrite); + if (rc > 0 && tolower(overwrite[0]) == 'y') { + rc = open(pubkey_file, O_WRONLY); + if (rc > 0) { + close(rc); + errno = 0; + rc = chmod(pubkey_file, + S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); + if (rc != 0) { + fprintf(stderr, + "Error(%d): Could not set file permissions\n", + errno); + goto end; + } + } else { + fprintf(stderr, + "Error: Could not create public key file\n"); + goto end; + } + } else { + goto end; + } + } else { + fprintf(stderr, "Error opening \"%s\" file\n", pubkey_file); + goto end; + } + } else { + close(rc); + } + + /* Write the public key */ + rc = ssh_pki_export_pubkey_file(key, pubkey_file); + if (rc != SSH_OK) { + fprintf(stderr, "Error: Failed to write public key file"); + goto end; + } + +end: + if (key != NULL) { + ssh_key_free(key); + } + + if (arguments.file != NULL) { + free(arguments.file); + } + + if (arguments.passphrase != NULL) { +#ifdef HAVE_EXPLICIT_BZERO + explicit_bzero(arguments.passphrase, strlen(arguments.passphrase)); +#else + bzero(arguments.passphrase, strlen(arguments.passphrase)); +#endif + free(arguments.passphrase); + } + + if (pubkey_file != NULL) { + free(pubkey_file); + } + return rc; +} diff --git a/src/libs/libssh-0.12.2/examples/knownhosts.c b/src/libs/libssh-0.12.2/examples/knownhosts.c new file mode 100644 index 000000000000..fdd9960cb0c4 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/knownhosts.c @@ -0,0 +1,129 @@ +/* + * knownhosts.c + * This file contains an example of how verify the identity of a + * SSH server using libssh + */ + +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/priv.h" +#include +#include "examples_common.h" + +#ifdef _WIN32 +#define strncasecmp _strnicmp +#endif + +int verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + char buf[10]; + unsigned char *hash = NULL; + size_t hlen; + ssh_key srv_pubkey = NULL; + int rc; + + /* If GSSAPI key exchange was used, the server identity was already + * verified via Kerberos mutual authentication (MIC). We might skip + * the host key verification, especially if we don't expect the server + * to send its key. Alternatively, we could proceed without this check + * and handle the scenario when the server does not provide its host key + * later. In that case, ssh_session_is_known_server will return + * SSH_KNOWN_HOSTS_UNKNOWN. + */ + if (ssh_session_kex_is_gss(session)) { + return 0; + } + + rc = ssh_get_server_publickey(session, &srv_pubkey); + if (rc < 0) { + return -1; + } + + rc = ssh_get_publickey_hash(srv_pubkey, + SSH_PUBLICKEY_HASH_SHA256, + &hash, + &hlen); + ssh_key_free(srv_pubkey); + if (rc < 0) { + return -1; + } + + state = ssh_session_is_known_server(session); + + switch(state) { + case SSH_KNOWN_HOSTS_CHANGED: + fprintf(stderr,"Host key for server changed : server's one is now :\n"); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + ssh_clean_pubkey_hash(&hash); + fprintf(stderr,"For security reason, connection will be stopped\n"); + return -1; + case SSH_KNOWN_HOSTS_OTHER: + fprintf(stderr,"The host key for this server was not found but an other type of key exists.\n"); + fprintf(stderr,"An attacker might change the default server key to confuse your client" + "into thinking the key does not exist\n" + "We advise you to rerun the client with -d or -r for more safety.\n"); + return -1; + case SSH_KNOWN_HOSTS_NOT_FOUND: + fprintf(stderr,"Could not find known host file. If you accept the host key here,\n"); + fprintf(stderr,"the file will be automatically created.\n"); + /* fallback to SSH_SERVER_NOT_KNOWN behavior */ + FALL_THROUGH; + case SSH_SERVER_NOT_KNOWN: + fprintf(stderr, + "The server is unknown. Do you trust the host key (yes/no)?\n"); + ssh_print_hash(SSH_PUBLICKEY_HASH_SHA256, hash, hlen); + + if (fgets(buf, sizeof(buf), stdin) == NULL) { + ssh_clean_pubkey_hash(&hash); + return -1; + } + if(strncasecmp(buf,"yes",3)!=0){ + ssh_clean_pubkey_hash(&hash); + return -1; + } + fprintf(stderr,"This new key will be written on disk for further usage. do you agree ?\n"); + if (fgets(buf, sizeof(buf), stdin) == NULL) { + ssh_clean_pubkey_hash(&hash); + return -1; + } + if(strncasecmp(buf,"yes",3)==0){ + rc = ssh_session_update_known_hosts(session); + if (rc != SSH_OK) { + ssh_clean_pubkey_hash(&hash); + fprintf(stderr, "error %s\n", strerror(errno)); + return -1; + } + } + + break; + case SSH_KNOWN_HOSTS_ERROR: + ssh_clean_pubkey_hash(&hash); + fprintf(stderr,"%s",ssh_get_error(session)); + return -1; + case SSH_KNOWN_HOSTS_OK: + break; /* ok */ + } + + ssh_clean_pubkey_hash(&hash); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/libssh_scp.c b/src/libs/libssh-0.12.2/examples/libssh_scp.c new file mode 100644 index 000000000000..2b1a7627345f --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/libssh_scp.c @@ -0,0 +1,466 @@ +/* libssh_scp.c + * Sample implementation of a SCP client + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include +#include +#include +#include + +#include +#include "examples_common.h" + +#ifndef BUF_SIZE +#define BUF_SIZE 16384 +#endif + +static char **sources = NULL; +static int nsources; +static char *destination = NULL; +static int verbosity = 0; +static char *port = NULL; + +struct location { + int is_ssh; + char *user; + char *host; + char *path; + ssh_session session; + ssh_scp scp; + FILE *file; +}; + +enum { + READ, + WRITE +}; + +static void usage(const char *argv0) { + fprintf(stderr, "Usage : %s [options] [[user@]host1:]file1 ... \n" + " [[user@]host2:]destination\n" + "sample scp client - libssh-%s\n" + "Options :\n" + " -P : use port to connect to remote host\n" + " -v : increase verbosity of libssh. Can be used multiple times\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv) { + int i; + + while((i = getopt(argc, argv, "P:v")) != -1) { + switch(i) { + case 'v': + verbosity++; + break; + case 'P': + port = optarg; + break; + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(argv[0]); + return -1; + } + } + + nsources = argc - optind - 1; + if (nsources < 1) { + usage(argv[0]); + return -1; + } + + sources = malloc((nsources + 1) * sizeof(char *)); + if (sources == NULL) { + return -1; + } + + for(i = 0; i < nsources; ++i) { + sources[i] = argv[optind]; + optind++; + } + + sources[i] = NULL; + destination = argv[optind]; + return 0; +} + +static void location_free(struct location *loc) +{ + if (loc) { + if (loc->path) { + free(loc->path); + } + loc->path = NULL; + if (loc->is_ssh) { + if (loc->host) { + free(loc->host); + } + loc->host = NULL; + if (loc->user) { + free(loc->user); + } + loc->user = NULL; + } + free(loc); + } +} + +static struct location *parse_location(char *loc) +{ + struct location *location = NULL; + char *ptr = NULL; + + if (loc == NULL) { + return NULL; + } + + location = malloc(sizeof(struct location)); + if (location == NULL) { + return NULL; + } + memset(location, 0, sizeof(struct location)); + + location->host = location->user = NULL; + ptr = strchr(loc, ':'); + + if (ptr != NULL) { + location->is_ssh = 1; + location->path = strdup(ptr+1); + *ptr = '\0'; + ptr = strchr(loc, '@'); + + if (ptr != NULL) { + location->host = strdup(ptr+1); + *ptr = '\0'; + location->user = strdup(loc); + } else { + location->host = strdup(loc); + } + } else { + location->is_ssh = 0; + location->path = strdup(loc); + } + return location; +} + +static void close_location(struct location *loc) { + int rc; + + if (loc) { + if (loc->is_ssh) { + if (loc->scp) { + rc = ssh_scp_close(loc->scp); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error closing scp: %s\n", + ssh_get_error(loc->session)); + } + ssh_scp_free(loc->scp); + loc->scp = NULL; + } + if (loc->session) { + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + } + } else { + if (loc->file) { + fclose(loc->file); + loc->file = NULL; + } + } + } +} + +static int open_location(struct location *loc, int flag) { + if (loc->is_ssh && flag == WRITE) { + loc->session = connect_ssh(loc->host, port, loc->user, verbosity); + if (!loc->session) { + fprintf(stderr, "Couldn't connect to %s\n", loc->host); + return -1; + } + + loc->scp = ssh_scp_new(loc->session, SSH_SCP_WRITE, loc->path); + if (!loc->scp) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + + if (ssh_scp_init(loc->scp) == SSH_ERROR) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_scp_free(loc->scp); + loc->scp = NULL; + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + return 0; + } else if (loc->is_ssh && flag == READ) { + loc->session = connect_ssh(loc->host, port, loc->user, verbosity); + if (!loc->session) { + fprintf(stderr, "Couldn't connect to %s\n", loc->host); + return -1; + } + + loc->scp = ssh_scp_new(loc->session, SSH_SCP_READ, loc->path); + if (!loc->scp) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + + if (ssh_scp_init(loc->scp) == SSH_ERROR) { + fprintf(stderr, "error : %s\n", ssh_get_error(loc->session)); + ssh_scp_free(loc->scp); + loc->scp = NULL; + ssh_disconnect(loc->session); + ssh_free(loc->session); + loc->session = NULL; + return -1; + } + return 0; + } else if (loc->path != NULL) { + loc->file = fopen(loc->path, flag == READ ? "r":"w"); + if (!loc->file) { + if (errno == EISDIR) { + if (chdir(loc->path)) { + fprintf(stderr, + "Error changing directory to %s: %s\n", + loc->path, strerror(errno)); + return -1; + } + return 0; + } + fprintf(stderr, + "Error opening %s: %s\n", + loc->path, strerror(errno)); + return -1; + } + return 0; + } + return -1; +} + +/** @brief copies files from source location to destination + * @param src source location + * @param dest destination location + * @param recursive Copy also directories + */ +static int do_copy(struct location *src, struct location *dest, int recursive) { + size_t size; + socket_t fd; + struct stat s; + int w, r; + char buffer[BUF_SIZE]; + size_t total = 0; + mode_t mode; + char *filename = NULL; + + /* recursive mode doesn't work yet */ + (void)recursive; + /* Get the file name and size*/ + if (!src->is_ssh) { + fd = fileno(src->file); + if (fd < 0) { + fprintf(stderr, + "Invalid file pointer, error: %s\n", + strerror(errno)); + return -1; + } + r = fstat(fd, &s); + if (r < 0) { + return -1; + } + size = s.st_size; + mode = s.st_mode & ~S_IFMT; + filename = ssh_basename(src->path); + } else { + size = 0; + do { + r = ssh_scp_pull_request(src->scp); + if (r == SSH_SCP_REQUEST_NEWDIR) { + ssh_scp_deny_request(src->scp, "Not in recursive mode"); + continue; + } + if (r == SSH_SCP_REQUEST_NEWFILE) { + size = ssh_scp_request_get_size(src->scp); + filename = strdup(ssh_scp_request_get_filename(src->scp)); + mode = ssh_scp_request_get_permissions(src->scp); + //ssh_scp_accept_request(src->scp); + break; + } + if (r == SSH_ERROR) { + fprintf(stderr, + "Error: %s\n", + ssh_get_error(src->session)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } while(r != SSH_SCP_REQUEST_NEWFILE); + } + + if (dest->is_ssh) { + r = ssh_scp_push_file(dest->scp, src->path, size, mode); + // snprintf(buffer, sizeof(buffer), "C0644 %d %s\n", size, src->path); + if (r == SSH_ERROR) { + fprintf(stderr, + "error: %s\n", + ssh_get_error(dest->session)); + SSH_STRING_FREE_CHAR(filename); + ssh_scp_free(dest->scp); + dest->scp = NULL; + return -1; + } + } else { + if (!dest->file) { + dest->file = fopen(filename, "w"); + if (!dest->file) { + fprintf(stderr, + "Cannot open %s for writing: %s\n", + filename, strerror(errno)); + if (src->is_ssh) { + ssh_scp_deny_request(src->scp, "Cannot open local file"); + } + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } + if (src->is_ssh) { + ssh_scp_accept_request(src->scp); + } + } + + do { + if (src->is_ssh) { + r = ssh_scp_read(src->scp, buffer, sizeof(buffer)); + if (r == SSH_ERROR) { + fprintf(stderr, + "Error reading scp: %s\n", + ssh_get_error(src->session)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + + if (r == 0) { + break; + } + } else { + r = fread(buffer, 1, sizeof(buffer), src->file); + if (r == 0) { + break; + } + + if (r < 0) { + fprintf(stderr, + "Error reading file: %s\n", + strerror(errno)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } + + if (dest->is_ssh) { + w = ssh_scp_write(dest->scp, buffer, r); + if (w == SSH_ERROR) { + fprintf(stderr, + "Error writing in scp: %s\n", + ssh_get_error(dest->session)); + ssh_scp_free(dest->scp); + dest->scp = NULL; + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } else { + w = fwrite(buffer, r, 1, dest->file); + if (w <= 0) { + fprintf(stderr, + "Error writing in local file: %s\n", + strerror(errno)); + SSH_STRING_FREE_CHAR(filename); + return -1; + } + } + total += r; + + } while(total < size); + + SSH_STRING_FREE_CHAR(filename); + printf("wrote %zu bytes\n", total); + return 0; +} + +int main(int argc, char **argv) { + struct location *dest, *src; + int i; + int r; + if (opts(argc, argv) < 0) { + return EXIT_FAILURE; + } + + ssh_init(); + + dest = parse_location(destination); + if (dest == NULL) { + r = EXIT_FAILURE; + goto end; + } + + if (open_location(dest, WRITE) < 0) { + location_free(dest); + r = EXIT_FAILURE; + goto end; + } + + for (i = 0; i < nsources; ++i) { + src = parse_location(sources[i]); + if (src == NULL) { + r = EXIT_FAILURE; + goto close_dest; + } + + if (open_location(src, READ) < 0) { + location_free(src); + r = EXIT_FAILURE; + goto close_dest; + } + + if (do_copy(src, dest, 0) < 0) { + close_location(src); + location_free(src); + break; + } + + close_location(src); + location_free(src); + } + + r = 0; + +close_dest: + close_location(dest); + location_free(dest); +end: + ssh_finalize(); + free(sources); + return r; +} diff --git a/src/libs/libssh-0.12.2/examples/libsshpp.cpp b/src/libs/libssh-0.12.2/examples/libsshpp.cpp new file mode 100644 index 000000000000..8f042a459dc5 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/libsshpp.cpp @@ -0,0 +1,33 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +*/ + +/* This file demonstrates the use of the C++ wrapper to libssh */ + +#include +#include +#include + +int main(int argc, const char **argv){ + ssh::Session session; + try { + if(argc>1) + session.setOption(SSH_OPTIONS_HOST,argv[1]); + else + session.setOption(SSH_OPTIONS_HOST,"localhost"); + session.connect(); + session.userauthPublickeyAuto(); + session.disconnect(); + } catch (ssh::SshException e){ + std::cout << "Error during connection : "; + std::cout << e.getError() << std::endl; + } + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/libsshpp_noexcept.cpp b/src/libs/libssh-0.12.2/examples/libsshpp_noexcept.cpp new file mode 100644 index 000000000000..eff8cc191345 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/libsshpp_noexcept.cpp @@ -0,0 +1,41 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +*/ + +/* This file demonstrates the use of the C++ wrapper to libssh + * specifically, without C++ exceptions + */ + +#include +#define SSH_NO_CPP_EXCEPTIONS +#include + +int main(int argc, const char **argv){ + ssh::Session session,s2; + int err; + if(argc>1) + err=session.setOption(SSH_OPTIONS_HOST,argv[1]); + else + err=session.setOption(SSH_OPTIONS_HOST,"localhost"); + if(err==SSH_ERROR) + goto error; + err=session.connect(); + if(err==SSH_ERROR) + goto error; + err=session.userauthPublickeyAuto(); + if(err==SSH_ERROR) + goto error; + + return 0; + error: + std::cout << "Error during connection : "; + std::cout << session.getError() << std::endl; + return 1; +} diff --git a/src/libs/libssh-0.12.2/examples/proxy.c b/src/libs/libssh-0.12.2/examples/proxy.c new file mode 100644 index 000000000000..ab69b18e2a3c --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/proxy.c @@ -0,0 +1,339 @@ +/* This is a sample implementation of a libssh based SSH proxy */ +/* +Copyright 2003-2013 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include + +#ifndef BUF_SIZE +#define BUF_SIZE 2048 +#endif + +#define USER "myuser" +#define PASSWORD "mypassword" + +static int authenticated=0; +static int tries = 0; +static int error = 0; +static ssh_channel chan = NULL; +static char *username = NULL; +static ssh_gssapi_creds client_creds = NULL; + +static int auth_password(ssh_session session, const char *user, + const char *password, void *userdata){ + + (void)userdata; + + printf("Authenticating user %s pwd %s\n",user, password); + if(strcmp(user,USER) == 0 && strcmp(password, PASSWORD) == 0){ + authenticated = 1; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + if (tries >= 3){ + printf("Too many authentication tries\n"); + ssh_disconnect(session); + error = 1; + return SSH_AUTH_DENIED; + } + tries++; + return SSH_AUTH_DENIED; +} + +static int auth_gssapi_mic(ssh_session session, const char *user, const char *principal, void *userdata){ + (void)userdata; + client_creds = ssh_gssapi_get_creds(session); + printf("Authenticating user %s with gssapi principal %s\n",user, principal); + if (client_creds != NULL) + printf("Received some gssapi credentials\n"); + else + printf("Not received any forwardable creds\n"); + printf("authenticated\n"); + authenticated = 1; + username = strdup(principal); + return SSH_AUTH_SUCCESS; +} + +static int pty_request(ssh_session session, ssh_channel channel, const char *term, + int x,int y, int px, int py, void *userdata){ + (void) session; + (void) channel; + (void) term; + (void) x; + (void) y; + (void) px; + (void) py; + (void) userdata; + printf("Allocated terminal\n"); + return 0; +} + +static int shell_request(ssh_session session, ssh_channel channel, void *userdata){ + (void)session; + (void)channel; + (void)userdata; + printf("Allocated shell\n"); + return 0; +} +struct ssh_channel_callbacks_struct channel_cb = { + .channel_pty_request_function = pty_request, + .channel_shell_request_function = shell_request +}; + +static ssh_channel new_session_channel(ssh_session session, void *userdata){ + (void) session; + (void) userdata; + if(chan != NULL) + return NULL; + printf("Allocated session channel\n"); + chan = ssh_channel_new(session); + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(chan, &channel_cb); + return chan; +} + + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh proxy example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa host key (deprecated alias to 'k').", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'r': + /* deprecated */ + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event mainloop = NULL; + ssh_session client_session = NULL; + + struct ssh_server_callbacks_struct cb = { + .userdata = NULL, + .auth_password_function = auth_password, + .auth_gssapi_mic_function = auth_gssapi_mic, + .channel_open_request_session_function = new_session_channel + }; + + char buf[BUF_SIZE]; + char host[128]=""; + char *ptr = NULL; + int i,r, rc; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, "sshd_rsa"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n",ssh_get_error(sshbind)); + return 1; + } + r=ssh_bind_accept(sshbind,session); + if(r==SSH_ERROR){ + printf("error accepting a connection : %s\n",ssh_get_error(sshbind)); + return 1; + } + ssh_callbacks_init(&cb); + ssh_set_server_callbacks(session, &cb); + + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + ssh_set_auth_methods(session,SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + mainloop = ssh_event_new(); + ssh_event_add_session(mainloop, session); + + while (!(authenticated && chan != NULL)){ + if(error) + break; + r = ssh_event_dopoll(mainloop, -1); + if (r == SSH_ERROR){ + printf("Error : %s\n",ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + } + if(error){ + printf("Error, exiting loop\n"); + return 1; + } else + printf("Authenticated and got a channel\n"); + if (!client_creds){ + snprintf(buf,sizeof(buf), "Sorry, but you do not have forwardable tickets. Try again with -K\r\n"); + ssh_channel_write(chan,buf,strlen(buf)); + printf("%s",buf); + ssh_disconnect(session); + return 1; + } + snprintf(buf,sizeof(buf), "Hello %s, welcome to the Sample SSH proxy.\r\nPlease select your destination: ", username); + ssh_channel_write(chan, buf, strlen(buf)); + do{ + i=ssh_channel_read(chan,buf, sizeof(buf), 0); + if(i>0) { + ssh_channel_write(chan, buf, i); + if(strlen(host) + i < sizeof(host)){ + strncat(host, buf, i); + } + if (strchr(host, '\x0d')) { + *strchr(host, '\x0d')='\0'; + ssh_channel_write(chan, "\n", 1); + break; + } + } else { + printf ("Error: %s\n", ssh_get_error(session) ); + return 1; + } + } while (i>0); + snprintf(buf,sizeof(buf),"Trying to connect to \"%s\"\r\n", host); + ssh_channel_write(chan, buf, strlen(buf)); + printf("%s",buf); + + client_session = ssh_new(); + + /* ssh servers expect username without realm */ + ptr = strchr(username,'@'); + if(ptr) + *ptr= '\0'; + ssh_options_set(client_session, SSH_OPTIONS_HOST, host); + ssh_options_set(client_session, SSH_OPTIONS_USER, username); + ssh_gssapi_set_creds(client_session, client_creds); + rc = ssh_connect(client_session); + if (rc != SSH_OK){ + printf("Error connecting to %s: %s", host, ssh_get_error(client_session)); + return 1; + } + rc = ssh_userauth_none(client_session, NULL); + if(rc == SSH_AUTH_SUCCESS){ + printf("Authenticated using method none\n"); + } else { + rc = ssh_userauth_gssapi(client_session); + if(rc != SSH_AUTH_SUCCESS){ + printf("GSSAPI Authentication failed: %s\n",ssh_get_error(client_session)); + return 1; + } + } + snprintf(buf,sizeof(buf), "Authentication success\r\n"); + printf("%s",buf); + ssh_channel_write(chan,buf,strlen(buf)); + ssh_disconnect(client_session); + ssh_disconnect(session); + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/sample_sftpserver.c b/src/libs/libssh-0.12.2/examples/sample_sftpserver.c new file mode 100644 index 000000000000..dcab1540183a --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/sample_sftpserver.c @@ -0,0 +1,514 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2014 Audrius Butkevicius + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. +*/ + +#include "config.h" + +#include +#include +#include +#include + +#include +#ifdef HAVE_ARGP_H +#include +#endif +#include +#ifdef HAVE_LIBUTIL_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif +#include +#include +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif +#include +#include +#include +#include +#include + +/* below are for sftp */ +#include +#include +#include +#include +#include +#include + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "myuser" +#define PASS "mypassword" +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) + +static void set_default_keys(ssh_bind sshbind, + int rsa_already_set, + int ecdsa_already_set) +{ + if (!rsa_already_set) + { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + } + if (!ecdsa_already_set) + { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_ecdsa_key"); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_ed25519_key"); +} +#define DEF_STR_SIZE 1024 +char authorizedkeys[DEF_STR_SIZE] = {0}; +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh sftp server example " SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "Sftp server implemented with libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + {.name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0}, + {.name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set a host key. Can be used multiple times. " + "Implies no default keys.", + .group = 0}, + {.name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key.", + .group = 0}, + {.name = "ecdsakey", + .key = 'e', + .arg = "FILE", + .flags = 0, + .doc = "Set the ecdsa key.", + .group = 0}, + {.name = "authorizedkeys", + .key = 'a', + .arg = "FILE", + .flags = 0, + .doc = "Set the authorized keys file.", + .group = 0}, + {.name = "no-default-keys", + .key = 'n', + .arg = NULL, + .flags = 0, + .doc = "Do not set default key locations.", + .group = 0}, + {.name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0}, + {NULL, 0, NULL, 0, NULL, 0}}; + +/* Parse a single option. */ +static error_t parse_opt(int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. */ + ssh_bind sshbind = state->input; + static int no_default_keys = 0; + static int rsa_already_set = 0, ecdsa_already_set = 0; + static int verbosity = 0; + + switch (key) + { + case 'n': + no_default_keys = 1; + break; + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + /* We can't track the types of keys being added with this + option, so let's ensure we keep the keys we're adding + by just not setting the default keys */ + no_default_keys = 1; + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + rsa_already_set = 1; + break; + case 'e': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + ecdsa_already_set = 1; + break; + case 'a': + strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1); + break; + case 'v': + verbosity++; + ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &verbosity); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) + { + /* Too many arguments. */ + argp_usage(state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) + { + /* Not enough arguments. */ + argp_usage(state); + } + + if (!no_default_keys) + { + set_default_keys(sshbind, + rsa_already_set, + ecdsa_already_set); + } + + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +/* A userdata struct for channel. */ +struct channel_data_struct { + sftp_session sftp; +}; + +/* A userdata struct for session. */ +struct session_data_struct +{ + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + int auth_attempts; + int authenticated; +}; + +static int auth_password(ssh_session session, const char *user, + const char *pass, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + (void)session; + + if (strcmp(user, USER) == 0 && strcmp(pass, PASS) == 0) + { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + + sdata->auth_attempts++; + return SSH_AUTH_DENIED; +} + +static int auth_publickey(ssh_session session, + const char *user, + struct ssh_key_struct *pubkey, + char signature_state, + void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + (void)session; + (void)user; + + if (signature_state == SSH_PUBLICKEY_STATE_NONE) + { + return SSH_AUTH_SUCCESS; + } + + if (signature_state != SSH_PUBLICKEY_STATE_VALID) + { + return SSH_AUTH_DENIED; + } + + // valid so far. Now look through authorized keys for a match + if (authorizedkeys[0]) + { + ssh_key key = NULL; + int result; + struct stat buf; + + if (stat(authorizedkeys, &buf) == 0) + { + result = ssh_pki_import_pubkey_file(authorizedkeys, &key); + if ((result != SSH_OK) || (key == NULL)) + { + fprintf(stderr, + "Unable to import public key file %s\n", + authorizedkeys); + } + else + { + result = ssh_key_cmp(key, pubkey, SSH_KEY_CMP_PUBLIC); + ssh_key_free(key); + if (result == 0) + { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + } + } + } + + // no matches + sdata->authenticated = 0; + return SSH_AUTH_DENIED; +} + +static ssh_channel channel_open(ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + /* This server supports only one channel -- fail for repeated channel + * requests */ + if (sdata->channel != NULL) { + return NULL; + } + + sdata->channel = ssh_channel_new(session); + return sdata->channel; +} + +static void handle_session(ssh_event event, ssh_session session) +{ + int n; + + /* Our struct holding information about the channel. */ + struct channel_data_struct cdata = { + .sftp = NULL, + }; + + /* Our struct holding information about the session. */ + struct session_data_struct sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + }; + + struct ssh_channel_callbacks_struct channel_cb = { + .userdata = &(cdata.sftp), + .channel_data_function = sftp_channel_default_data_callback, + .channel_subsystem_request_function = sftp_channel_default_subsystem_request, + }; + + struct ssh_server_callbacks_struct server_cb = { + .userdata = &sdata, + .auth_password_function = auth_password, + .channel_open_request_session_function = channel_open, + }; + + if (authorizedkeys[0]) + { + server_cb.auth_pubkey_function = auth_publickey; + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY); + } + else + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD); + + ssh_callbacks_init(&server_cb); + ssh_callbacks_init(&channel_cb); + + ssh_set_server_callbacks(session, &server_cb); + + if (ssh_handle_key_exchange(session) != SSH_OK) + { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= 3 || n >= 100) { + return; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + n++; + } + + ssh_set_channel_callbacks(sdata.channel, &channel_cb); + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + } while (ssh_channel_is_open(sdata.channel) && + !ssh_channel_is_eof(sdata.channel)); + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } +} + +/* SIGCHLD handler for cleaning up dead children. */ +static void sigchld_handler(int signo) +{ + (void)signo; + + while (waitpid(-1, NULL, WNOHANG) > 0) + ; +} + +int main(int argc, char **argv) +{ + ssh_bind sshbind = NULL; + ssh_session session = NULL; + ssh_event event = NULL; + struct sigaction sa; + int rc; + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, NULL) != 0) + { + fprintf(stderr, "Failed to register SIGCHLD handler\n"); + return 1; + } + + rc = ssh_init(); + if (rc < 0) + { + fprintf(stderr, "ssh_init failed\n"); + goto exit; + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) + { + fprintf(stderr, "ssh_bind_new failed\n"); + goto exit; + } + +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, sshbind); +#else + (void)argc; + (void)argv; + + set_default_keys(sshbind, 0, 0); +#endif /* HAVE_ARGP_H */ + + if (ssh_bind_listen(sshbind) < 0) + { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + goto exit; + } + + while (1) + { + session = ssh_new(); + if (session == NULL) + { + fprintf(stderr, "Failed to allocate session\n"); + continue; + } + + /* Blocks until there is a new incoming connection. */ + if (ssh_bind_accept(sshbind, session) != SSH_ERROR) + { + switch (fork()) + { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) + { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + handle_session(event, session); + ssh_event_free(event); + } + else + { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + + exit(0); + case -1: + fprintf(stderr, "Failed to fork\n"); + } + } + else + { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + } + /* Since the session has been passed to a child fork, do some cleaning + * up at the parent process. */ + ssh_disconnect(session); + ssh_free(session); + } + +exit: + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/samplesftp.c b/src/libs/libssh-0.12.2/examples/samplesftp.c new file mode 100644 index 000000000000..d82a556af258 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/samplesftp.c @@ -0,0 +1,304 @@ +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +#include "examples_common.h" +#ifdef WITH_SFTP + +#ifndef BUF_SIZE +#define BUF_SIZE 65536 +#endif + +static void do_sftp(ssh_session session) { + sftp_session sftp = sftp_new(session); + sftp_dir dir; + sftp_attributes file; + sftp_statvfs_t sftpstatvfs; + struct statvfs sysstatvfs; + sftp_file source; + sftp_file to; + int len = 1; + unsigned int i; + char data[BUF_SIZE] = {0}; + char *lnk = NULL; + + unsigned int count; + + if (!sftp) { + fprintf(stderr, "sftp error initialising channel: %s\n", + ssh_get_error(session)); + goto end; + } + + if (sftp_init(sftp)) { + fprintf(stderr, "error initialising sftp: %s\n", + ssh_get_error(session)); + goto end; + } + + printf("Additional SFTP extensions provided by the server:\n"); + count = sftp_extensions_get_count(sftp); + for (i = 0; i < count; i++) { + printf("\t%s, version: %s\n", + sftp_extensions_get_name(sftp, i), + sftp_extensions_get_data(sftp, i)); + } + + /* test symlink and readlink */ + if (sftp_symlink(sftp, "/tmp/this_is_the_link", + "/tmp/sftp_symlink_test") < 0) + { + fprintf(stderr, "Could not create link (%s)\n", + ssh_get_error(session)); + goto end; + } + + lnk = sftp_readlink(sftp, "/tmp/sftp_symlink_test"); + if (lnk == NULL) { + fprintf(stderr, "Could not read link (%s)\n", ssh_get_error(session)); + goto end; + } + printf("readlink /tmp/sftp_symlink_test: %s\n", lnk); + ssh_string_free_char(lnk); + + sftp_unlink(sftp, "/tmp/sftp_symlink_test"); + + if (sftp_extension_supported(sftp, "statvfs@openssh.com", "2")) { + sftpstatvfs = sftp_statvfs(sftp, "/tmp"); + if (sftpstatvfs == NULL) { + fprintf(stderr, "statvfs failed (%s)\n", ssh_get_error(session)); + goto end; + } + + printf("sftp statvfs:\n" + "\tfile system block size: %llu\n" + "\tfundamental fs block size: %llu\n" + "\tnumber of blocks (unit f_frsize): %llu\n" + "\tfree blocks in file system: %llu\n" + "\tfree blocks for non-root: %llu\n" + "\ttotal file inodes: %llu\n" + "\tfree file inodes: %llu\n" + "\tfree file inodes for to non-root: %llu\n" + "\tfile system id: %llu\n" + "\tbit mask of f_flag values: %llu\n" + "\tmaximum filename length: %llu\n", + (unsigned long long) sftpstatvfs->f_bsize, + (unsigned long long) sftpstatvfs->f_frsize, + (unsigned long long) sftpstatvfs->f_blocks, + (unsigned long long) sftpstatvfs->f_bfree, + (unsigned long long) sftpstatvfs->f_bavail, + (unsigned long long) sftpstatvfs->f_files, + (unsigned long long) sftpstatvfs->f_ffree, + (unsigned long long) sftpstatvfs->f_favail, + (unsigned long long) sftpstatvfs->f_fsid, + (unsigned long long) sftpstatvfs->f_flag, + (unsigned long long) sftpstatvfs->f_namemax); + + sftp_statvfs_free(sftpstatvfs); + + if (statvfs("/tmp", &sysstatvfs) < 0) { + fprintf(stderr, "statvfs failed (%s)\n", strerror(errno)); + goto end; + } + + printf("sys statvfs:\n" + "\tfile system block size: %llu\n" + "\tfundamental fs block size: %llu\n" + "\tnumber of blocks (unit f_frsize): %llu\n" + "\tfree blocks in file system: %llu\n" + "\tfree blocks for non-root: %llu\n" + "\ttotal file inodes: %llu\n" + "\tfree file inodes: %llu\n" + "\tfree file inodes for to non-root: %llu\n" + "\tfile system id: %llu\n" + "\tbit mask of f_flag values: %llu\n" + "\tmaximum filename length: %llu\n", + (unsigned long long) sysstatvfs.f_bsize, + (unsigned long long) sysstatvfs.f_frsize, + (unsigned long long) sysstatvfs.f_blocks, + (unsigned long long) sysstatvfs.f_bfree, + (unsigned long long) sysstatvfs.f_bavail, + (unsigned long long) sysstatvfs.f_files, + (unsigned long long) sysstatvfs.f_ffree, + (unsigned long long) sysstatvfs.f_favail, + (unsigned long long) sysstatvfs.f_fsid, + (unsigned long long) sysstatvfs.f_flag, + (unsigned long long) sysstatvfs.f_namemax); + } + + /* the connection is made */ + /* opening a directory */ + dir = sftp_opendir(sftp, "./"); + if (!dir) { + fprintf(stderr, "Directory not opened(%s)\n", ssh_get_error(session)); + goto end; + } + + /* reading the whole directory, file by file */ + while ((file = sftp_readdir(sftp, dir))) { + fprintf(stderr, "%30s(%.8o) : %s(%.5d) %s(%.5d) : %.10llu bytes\n", + file->name, + file->permissions, + file->owner, + file->uid, + file->group, + file->gid, + (long long unsigned int) file->size); + sftp_attributes_free(file); + } + + /* when file = NULL, an error has occurred OR the directory listing is end of + * file */ + if (!sftp_dir_eof(dir)) { + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + goto end; + } + + if (sftp_closedir(dir)) { + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + goto end; + } + /* this will open a file and copy it into your /home directory */ + /* the small buffer size was intended to stress the library. of course, you + * can use a buffer till 20kbytes without problem */ + + source = sftp_open(sftp, "/usr/bin/ssh", O_RDONLY, 0); + if (!source) { + fprintf(stderr, "Error opening /usr/bin/ssh: %s\n", + ssh_get_error(session)); + goto end; + } + + /* open a file for writing... */ + to = sftp_open(sftp, "ssh-copy", O_WRONLY | O_CREAT, 0700); + if (!to) { + fprintf(stderr, "Error opening ssh-copy for writing: %s\n", + ssh_get_error(session)); + sftp_close(source); + goto end; + } + + while ((len = sftp_read(source, data, 4096)) > 0) { + if (sftp_write(to, data, len) != len) { + fprintf(stderr, "Error writing %d bytes: %s\n", + len, ssh_get_error(session)); + sftp_close(to); + sftp_close(source); + goto end; + } + } + + printf("finished\n"); + if (len < 0) { + fprintf(stderr, "Error reading file: %s\n", ssh_get_error(session)); + } + + sftp_close(source); + sftp_close(to); + printf("file closed\n"); + to = sftp_open(sftp, "/tmp/large_file", O_WRONLY|O_CREAT, 0644); + + for (i = 0; i < 1000; ++i) { + len = sftp_write(to, data, sizeof(data)); + printf("wrote %d bytes\n", len); + if (len != sizeof(data)) { + printf("chunk %d : %d (%s)\n", i, len, ssh_get_error(session)); + } + } + + sftp_close(to); +end: + /* close the sftp session */ + sftp_free(sftp); + printf("sftp session terminated\n"); +} + +static void usage(const char *argv0) { + fprintf(stderr, "Usage : %s [-v] remotehost\n" + "sample sftp test client - libssh-%s\n" + "Options :\n" + " -l user : log in as user\n" + " -p port : connect to port\n" + " -v : increase log verbosity\n", + argv0, + ssh_version(0)); + exit(0); +} + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + char *destination = NULL; + int auth = 0; + int state; + + ssh_init(); + session = ssh_new(); + + if (ssh_options_getopt(session, &argc, argv)) { + fprintf(stderr, + "Error parsing command line: %s\n", + ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); + usage(argv[0]); + return EXIT_FAILURE; + } + if (argc < 1) { + usage(argv[0]); + return EXIT_FAILURE; + } + destination = argv[1]; + + if (ssh_options_set(session, SSH_OPTIONS_HOST, destination) < 0) { + return -1; + } + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + return -1; + } + + state = verify_knownhost(session); + if (state != 0) { + return -1; + } + + auth = authenticate_console(session); + if (auth != SSH_AUTH_SUCCESS) { + return -1; + } + + do_sftp(session); + ssh_disconnect(session); + ssh_free(session); + + ssh_finalize(); + + return 0; +} + +#endif diff --git a/src/libs/libssh-0.12.2/examples/samplesshd-cb.c b/src/libs/libssh-0.12.2/examples/samplesshd-cb.c new file mode 100644 index 000000000000..31e035ff6a09 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/samplesshd-cb.c @@ -0,0 +1,342 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include + +#ifdef _WIN32 +#include +#endif + +#ifndef BUF_SIZE +#define BUF_SIZE 2049 +#endif + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "myuser" +#define PASSWORD "mypassword" + +static int authenticated=0; +static int tries = 0; +static int error = 0; +static ssh_channel chan=NULL; + +static int auth_none(ssh_session session, + const char *user, + void *userdata) +{ + ssh_string banner = NULL; + + (void)user; /* unused */ + (void)userdata; /* unused */ + + ssh_set_auth_methods(session, + SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + + banner = ssh_string_from_char("Banner Example\n"); + if (banner != NULL) { + ssh_send_issue_banner(session, banner); + } + ssh_string_free(banner); + + return SSH_AUTH_DENIED; +} + +static int auth_password(ssh_session session, const char *user, + const char *password, void *userdata){ + (void)userdata; + printf("Authenticating user %s pwd %s\n",user, password); + if(strcmp(user,USER) == 0 && strcmp(password, PASSWORD) == 0){ + authenticated = 1; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + if (tries >= 3){ + printf("Too many authentication tries\n"); + ssh_disconnect(session); + error = 1; + return SSH_AUTH_DENIED; + } + tries++; + return SSH_AUTH_DENIED; +} + +#ifdef WITH_GSSAPI +static int auth_gssapi_mic(ssh_session session, const char *user, const char *principal, void *userdata){ + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + (void)userdata; + printf("Authenticating user %s with gssapi principal %s\n",user, principal); + if (creds != NULL) + printf("Received some gssapi credentials\n"); + else + printf("Not received any forwardable creds\n"); + printf("authenticated\n"); + authenticated = 1; + return SSH_AUTH_SUCCESS; +} +#endif + +static int pty_request(ssh_session session, ssh_channel channel, const char *term, + int x,int y, int px, int py, void *userdata){ + (void) session; + (void) channel; + (void) term; + (void) x; + (void) y; + (void) px; + (void) py; + (void) userdata; + printf("Allocated terminal\n"); + return 0; +} + +static int shell_request(ssh_session session, ssh_channel channel, void *userdata){ + (void)session; + (void)channel; + (void)userdata; + printf("Allocated shell\n"); + return 0; +} +struct ssh_channel_callbacks_struct channel_cb = { + .channel_pty_request_function = pty_request, + .channel_shell_request_function = shell_request +}; + +static ssh_channel new_session_channel(ssh_session session, void *userdata){ + (void) session; + (void) userdata; + if(chan != NULL) + return NULL; + printf("Allocated session channel\n"); + chan = ssh_channel_new(session); + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(chan, &channel_cb); + return chan; +} + + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + { + .name = "config", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "Configuration file to use.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'r': + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case 'f': + ssh_bind_options_parse_config(sshbind, arg); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event mainloop = NULL; + struct ssh_server_callbacks_struct cb = { + .userdata = NULL, + .auth_none_function = auth_none, + .auth_password_function = auth_password, +#ifdef WITH_GSSAPI + .auth_gssapi_mic_function = auth_gssapi_mic, +#endif + .channel_open_request_session_function = new_session_channel + }; + + char buf[BUF_SIZE]; + int i; + int r; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n",ssh_get_error(sshbind)); + return 1; + } + r=ssh_bind_accept(sshbind,session); + if(r==SSH_ERROR){ + printf("error accepting a connection : %s\n",ssh_get_error(sshbind)); + return 1; + } + ssh_callbacks_init(&cb); + ssh_set_server_callbacks(session, &cb); + + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + ssh_set_auth_methods(session,SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + mainloop = ssh_event_new(); + ssh_event_add_session(mainloop, session); + + while (!(authenticated && chan != NULL)){ + if(error) + break; + r = ssh_event_dopoll(mainloop, -1); + if (r == SSH_ERROR){ + printf("Error : %s\n",ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + } + if(error){ + printf("Error, exiting loop\n"); + } else + printf("Authenticated and got a channel\n"); + do{ + i=ssh_channel_read(chan, buf, sizeof(buf) - 1, 0); + if(i>0) { + if (ssh_channel_write(chan, buf, i) == SSH_ERROR) { + printf("error writing to channel\n"); + return 1; + } + + buf[i] = '\0'; + printf("%s", buf); + fflush(stdout); + + if (buf[0] == '\x0d') { + if (ssh_channel_write(chan, "\n", 1) == SSH_ERROR) { + printf("error writing to channel\n"); + return 1; + } + + printf("\n"); + } + } + } while (i>0); + ssh_disconnect(session); + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/samplesshd-kbdint.c b/src/libs/libssh-0.12.2/examples/samplesshd-kbdint.c new file mode 100644 index 000000000000..919eb3383a1a --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/samplesshd-kbdint.c @@ -0,0 +1,414 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2011 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#include +#include +#include +#include + +#ifndef BUF_SIZE +#define BUF_SIZE 2048 +#endif + +#define SSHD_USER "libssh" +#define SSHD_PASSWORD "libssh" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +static int port = 22; +static bool authenticated = false; + +#ifdef WITH_PCAP +static const char *pcap_file = "debug.server.pcap"; +static ssh_pcap_file pcap; + +static void set_pcap(ssh_session session){ + if(!pcap_file) + return; + pcap=ssh_pcap_file_new(); + if(ssh_pcap_file_open(pcap,pcap_file) == SSH_ERROR){ + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap=NULL; + return; + } + ssh_set_pcap_file(session,pcap); +} + +static void cleanup_pcap(void) { + ssh_pcap_file_free(pcap); + pcap=NULL; +} +#endif + + +static int auth_password(const char *user, const char *password) +{ + int cmp; + + cmp = strcmp(user, SSHD_USER); + if (cmp != 0) { + return 0; + } + cmp = strcmp(password, SSHD_PASSWORD); + if (cmp != 0) { + return 0; + } + + authenticated = true; + return 1; // authenticated +} +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " + SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, 0, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + port = atoi(arg); + break; + case 'r': + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +static const char *name = NULL; +static const char *instruction = NULL; +static const char *prompts[2]; +static char echo[] = { 1, 0 }; + +static int kbdint_check_response(ssh_session session) { + int count; + + count = ssh_userauth_kbdint_getnanswers(session); + if(count != 2) { + instruction = "Something weird happened :("; + return 0; + } + if(strcasecmp("Arthur Dent", + ssh_userauth_kbdint_getanswer(session, 0)) != 0) { + instruction = "OK, this is not YOUR name, " + "but it's a reference to the HGTG..."; + prompts[0] = "The main character's full name: "; + return 0; + } + if(strcmp("42", ssh_userauth_kbdint_getanswer(session, 1)) != 0) { + instruction = "Make an effort !!! What is the Answer to the Ultimate " + "Question of Life, the Universe, and Everything ?"; + prompts[1] = "Answer to the Ultimate Question of Life, the Universe, " + "and Everything: "; + return 0; + } + + authenticated = true; + return 1; +} + +static int authenticate(ssh_session session) { + ssh_message message; + + name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + instruction = "Please enter your real name and your password"; + prompts[0] = "Real name: "; + prompts[1] = "Password: "; + + do { + message=ssh_message_get(session); + if(!message) + break; + switch(ssh_message_type(message)){ + case SSH_REQUEST_AUTH: + switch(ssh_message_subtype(message)){ + case SSH_AUTH_METHOD_PASSWORD: + printf("User %s wants to auth with pass %s\n", + ssh_message_auth_user(message), + ssh_message_auth_password(message)); + if(auth_password(ssh_message_auth_user(message), + ssh_message_auth_password(message))){ + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + // not authenticated, send default message + ssh_message_reply_default(message); + break; + + case SSH_AUTH_METHOD_INTERACTIVE: + if(!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, name, + instruction, 2, prompts, echo); + } else { + if(kbdint_check_response(session)) { + ssh_message_auth_reply_success(message,0); + ssh_message_free(message); + return 1; + } + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + break; + case SSH_AUTH_METHOD_NONE: + default: + printf("User %s wants to auth with unknown auth %d\n", + ssh_message_auth_user(message), + ssh_message_subtype(message)); + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + break; + } + break; + default: + ssh_message_auth_set_methods(message, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_INTERACTIVE); + ssh_message_reply_default(message); + } + ssh_message_free(message); + } while (1); + return 0; +} + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_message message = NULL; + ssh_channel chan = NULL; + char buf[BUF_SIZE]; + int auth=0; + int shell=0; + int i; + int r; + + sshbind=ssh_bind_new(); + session=ssh_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, + KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void) argc; + (void) argv; +#endif +#ifdef WITH_PCAP + set_pcap(session); +#endif + + if(ssh_bind_listen(sshbind)<0){ + printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); + return 1; + } + printf("Started sample libssh sshd on port %d\n", port); + printf("You can login as the user %s with the password %s\n", SSHD_USER, + SSHD_PASSWORD); + r = ssh_bind_accept(sshbind, session); + if(r==SSH_ERROR){ + printf("Error accepting a connection: %s\n", ssh_get_error(sshbind)); + return 1; + } + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + return 1; + } + + /* proceed to authentication */ + auth = authenticate(session); + if (!auth || !authenticated) { + printf("Authentication error: %s\n", ssh_get_error(session)); + ssh_disconnect(session); + return 1; + } + + + /* wait for a channel session */ + do { + message = ssh_message_get(session); + if(message){ + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(message) == SSH_CHANNEL_SESSION) { + chan = ssh_message_channel_request_open_reply_accept(message); + ssh_message_free(message); + break; + } else { + ssh_message_reply_default(message); + ssh_message_free(message); + } + } else { + break; + } + } while(!chan); + + if (!chan) { + printf("Error: client did not ask for a channel session (%s)\n", + ssh_get_error(session)); + ssh_finalize(); + return 1; + } + + + /* wait for a shell */ + do { + message = ssh_message_get(session); + if(message != NULL) { + if(ssh_message_type(message) == SSH_REQUEST_CHANNEL && + ssh_message_subtype(message) == SSH_CHANNEL_REQUEST_SHELL) { + shell = 1; + ssh_message_channel_request_reply_success(message); + ssh_message_free(message); + break; + } + ssh_message_reply_default(message); + ssh_message_free(message); + } else { + break; + } + } while(!shell); + + if(!shell) { + printf("Error: No shell requested (%s)\n", ssh_get_error(session)); + return 1; + } + + + printf("it works !\n"); + do{ + i=ssh_channel_read(chan,buf, sizeof(buf), 0); + if(i>0) { + if(*buf == '' || *buf == '') + break; + if(i == 1 && *buf == '\r') + ssh_channel_write(chan, "\r\n", 2); + else + ssh_channel_write(chan, buf, i); + if (write(1,buf,i) < 0) { + printf("error writing to buffer\n"); + return 1; + } + } + } while (i>0); + ssh_channel_close(chan); + ssh_disconnect(session); + ssh_bind_free(sshbind); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/scp_download.c b/src/libs/libssh-0.12.2/examples/scp_download.c new file mode 100644 index 000000000000..24355e299f91 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/scp_download.c @@ -0,0 +1,194 @@ +/* scp_download.c + * Sample implementation of a tiny SCP downloader client + */ + +/* +Copyright 2009 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. + */ + +#include +#include +#include +#include +#include + +#include +#include "examples_common.h" + +#ifndef BUF_SIZE +#define BUF_SIZE 16384 +#endif + +static int verbosity = 0; +static const char *createcommand = + "rm -fr /tmp/libssh_tests && mkdir /tmp/libssh_tests && " + "cd /tmp/libssh_tests && date > a && date > b && mkdir c && date > d"; +static char *host = NULL; + +static void usage(const char *argv0) +{ + fprintf(stderr, + "Usage : %s [options] host\n" + "sample tiny scp downloader client - libssh-%s\n" + "This program will create files in /tmp and try to fetch them\n", + argv0, + ssh_version(0)); + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc, argv, "v")) != -1) { + switch (i) { + case 'v': + verbosity++; + break; + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(argv[0]); + return -1; + } + } + host = argv[optind]; + if (host == NULL) + usage(argv[0]); + return 0; +} + +static void create_files(ssh_session session) +{ + ssh_channel channel = ssh_channel_new(session); + char buffer[1]; + int rc; + + if (channel == NULL) { + fprintf(stderr, "Error creating channel: %s\n", ssh_get_error(session)); + exit(EXIT_FAILURE); + } + if (ssh_channel_open_session(channel) != SSH_OK) { + fprintf(stderr, "Error creating channel: %s\n", ssh_get_error(session)); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + if (ssh_channel_request_exec(channel, createcommand) != SSH_OK) { + fprintf(stderr, + "Error executing command: %s\n", + ssh_get_error(session)); + ssh_channel_close(channel); + ssh_channel_free(channel); + exit(EXIT_FAILURE); + } + while (!ssh_channel_is_eof(channel)) { + rc = ssh_channel_read(channel, buffer, 1, 1); + if (rc != 1) { + fprintf(stderr, "Error reading from channel\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); + return; + } + + rc = write(1, buffer, 1); + if (rc < 0) { + fprintf(stderr, "Error writing to buffer\n"); + ssh_channel_close(channel); + ssh_channel_free(channel); + return; + } + } + ssh_channel_close(channel); + ssh_channel_free(channel); +} + +static int fetch_files(ssh_session session) +{ + int size; + char buffer[BUF_SIZE]; + int mode; + char *filename = NULL; + int r; + ssh_scp scp = ssh_scp_new(session, + SSH_SCP_READ | SSH_SCP_RECURSIVE, + "/tmp/libssh_tests/*"); + if (ssh_scp_init(scp) != SSH_OK) { + fprintf(stderr, "error initializing scp: %s\n", ssh_get_error(session)); + ssh_scp_free(scp); + return -1; + } + printf("Trying to download 3 files (a,b,d) and 1 directory (c)\n"); + do { + r = ssh_scp_pull_request(scp); + switch (r) { + case SSH_SCP_REQUEST_NEWFILE: + size = ssh_scp_request_get_size(scp); + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("downloading file %s, size %d, perms 0%o\n", + filename, + size, + mode); + free(filename); + ssh_scp_accept_request(scp); + r = ssh_scp_read(scp, buffer, sizeof(buffer)); + if (r == SSH_ERROR) { + fprintf(stderr, + "Error reading scp: %s\n", + ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + } + printf("done\n"); + break; + case SSH_ERROR: + fprintf(stderr, "Error: %s\n", ssh_get_error(session)); + ssh_scp_close(scp); + ssh_scp_free(scp); + return -1; + case SSH_SCP_REQUEST_WARNING: + fprintf(stderr, "Warning: %s\n", ssh_scp_request_get_warning(scp)); + break; + case SSH_SCP_REQUEST_NEWDIR: + filename = strdup(ssh_scp_request_get_filename(scp)); + mode = ssh_scp_request_get_permissions(scp); + printf("downloading directory %s, perms 0%o\n", filename, mode); + free(filename); + ssh_scp_accept_request(scp); + break; + case SSH_SCP_REQUEST_ENDDIR: + printf("End of directory\n"); + break; + case SSH_SCP_REQUEST_EOF: + printf("End of requests\n"); + goto end; + } + } while (1); +end: + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +} + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + if (opts(argc, argv) < 0) + return EXIT_FAILURE; + session = connect_ssh(host, NULL, NULL, verbosity); + if (session == NULL) + return EXIT_FAILURE; + create_files(session); + fetch_files(session); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/senddata.c b/src/libs/libssh-0.12.2/examples/senddata.c new file mode 100644 index 000000000000..5be65af34e1c --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/senddata.c @@ -0,0 +1,64 @@ +#include + +#include +#include "examples_common.h" + +#define LIMIT 0x100000000UL + +int main(void) +{ + ssh_session session = NULL; + ssh_channel channel = NULL; + char buffer[1024 * 1024] = {0}; + int rc; + uint64_t total = 0; + uint64_t lastshown = 4096; + session = connect_ssh("localhost", NULL, NULL, 0); + if (session == NULL) { + return 1; + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + ssh_disconnect(session); + return 1; + } + + rc = ssh_channel_open_session(channel); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + rc = ssh_channel_request_exec(channel, "cat > /dev/null"); + if (rc < 0) { + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + while ((rc = ssh_channel_write(channel, buffer, sizeof(buffer))) > 0) { + total += rc; + if (total / 2 >= lastshown) { + printf("written %llx\n", (long long unsigned int)total); + lastshown = total; + } + if (total > LIMIT) + break; + } + + if (rc < 0) { + printf("error : %s\n", ssh_get_error(session)); + ssh_channel_close(channel); + ssh_disconnect(session); + return 1; + } + + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + + ssh_disconnect(session); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/ssh_X11_client.c b/src/libs/libssh-0.12.2/examples/ssh_X11_client.c new file mode 100644 index 000000000000..195b259b39e1 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/ssh_X11_client.c @@ -0,0 +1,951 @@ +/* + * ssh.c - Simple example of SSH X11 client using libssh + * + * Copyright (C) 2022 Marco Fortina + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + * In addition, as a special exception, the copyright holders give + * permission to link the code of portions of this program with the + * OpenSSL library under certain conditions as described in each + * individual source file, and distribute linked combinations + * including the two. + * You must obey the GNU General Public License in all respects + * for all of the code used other than OpenSSL. * If you modify + * file(s) with this exception, you may extend this exception to your + * version of the file(s), but you are not obligated to do so. * If you + * do not wish to do so, delete this exception statement from your + * version. * If you delete this exception statement from all source + * files in the program, then also delete it here. + * + * + * + * ssh_X11_client + * ============== + * + * AUTHOR URL + * https://gitlab.com/marco.fortina/libssh-x11-client/ + * + * This is a simple example of SSH X11 client using libssh. + * + * Features: + * + * - support local display (e.g. :0) + * - support remote display (e.g. localhost:10.0) + * - using callbacks and event polling to significantly reduce CPU utilization + * - use X11 forwarding with authentication spoofing (like openssh) + * + * Note: + * + * - part of this code was inspired by openssh's one. + * + * Dependencies: + * + * - gcc >= 7.5.0 + * - libssh >= 0.8.0 + * - libssh-dev >= 0.8.0 + * + * To Build: + * gcc -o ssh_X11_client ssh_X11_client.c -lssh -g + * + * Donations: + * + * If you liked this work and wish to support the developer please donate to: + * Bitcoin: 1N2rQimKbeUQA8N2LU5vGopYQJmZsBM2d6 + * + */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include +#include + +/* + * Data Structures and Macros + */ + +#define _PATH_UNIX_X "/tmp/.X11-unix/X%d" +#define _XAUTH_CMD "/usr/bin/xauth list %s 2>/dev/null" + +typedef struct item { + ssh_channel channel; + int fd_in; + int fd_out; + int protected; + struct item *next; +} node_t; + +node_t *node = NULL; + + +/* + * Mutex + */ + +pthread_mutex_t mutex; + + +/* + * Function declarations + */ + +/* Linked nodes to manage channel/fd tuples */ +static int insert_item(ssh_channel channel, int fd_in, int fd_out, + int protected); +static void delete_item(ssh_channel channel); +static node_t * search_item(ssh_channel channel); + +/* X11 Display */ +const char * ssh_gai_strerror(int gaierr); +static int x11_get_proto(const char *display, char **_proto, char **_data); +static void set_nodelay(int fd); +static int connect_local_xsocket_path(const char *pathname); +static int connect_local_xsocket(int display_number); +static int x11_connect_display(void); + +/* Send data to channel */ +static int copy_fd_to_channel_callback(int fd, int revents, void *userdata); + +/* Read data from channel */ +static int copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, + void *userdata); + +/* EOF&Close channel */ +static void channel_close_callback(ssh_session session, ssh_channel channel, + void *userdata); + +/* X11 Request */ +static ssh_channel x11_open_request_callback(ssh_session session, + const char *shost, int sport, + void *userdata); + +/* Main loop */ +static int main_loop(ssh_channel channel); + +/* Internals */ +int64_t _current_timestamp(void); + +/* Global variables */ +const char *hostname = NULL; +int enableX11 = 1; + +/* + * Callbacks Data Structures + */ + +/* SSH Channel Callbacks */ +struct ssh_channel_callbacks_struct channel_cb = +{ + .channel_data_function = copy_channel_to_fd_callback, + .channel_eof_function = channel_close_callback, + .channel_close_function = channel_close_callback, + .userdata = NULL +}; + +/* SSH Callbacks */ +struct ssh_callbacks_struct cb = +{ + .channel_open_request_x11_function = x11_open_request_callback, + .userdata = NULL +}; + + +/* + * SSH Event Context + */ + +short events = POLLIN | POLLPRI | POLLERR | POLLHUP | POLLNVAL; +ssh_event event; + + +/* + * Internal data structures + */ + +struct termios _saved_tio; + + +/* + * Internal functions + */ + +int64_t _current_timestamp(void) +{ + struct timeval tv; + int64_t milliseconds; + + gettimeofday(&tv, NULL); + milliseconds = (int64_t)(tv.tv_sec) * 1000 + (tv.tv_usec / 1000); + + return milliseconds; +} + +static void _logging_callback(int priority, const char *function, + const char *buffer, void *userdata) +{ + FILE *fp = NULL; + char buf[100]; + int64_t milliseconds; + + time_t now = time(0); + + (void)userdata; + + strftime(buf, 100, "%Y-%m-%d %H:%M:%S", localtime(&now)); + + fp = fopen("debug.log","a"); + if (fp == NULL) { + printf("Error!"); + exit(-11); + } + + milliseconds = _current_timestamp(); + + fprintf(fp, "[%s.%" PRId64 ", %d] %s: %s\n", buf, milliseconds, priority, + function, buffer); + fclose(fp); +} + +static int _enter_term_raw_mode(void) +{ + struct termios tio; + int ret = tcgetattr(fileno(stdin), &tio); + if (ret != -1) { + _saved_tio = tio; + tio.c_iflag |= IGNPAR; + tio.c_iflag &= ~(ISTRIP | INLCR | IGNCR | ICRNL | IXON | IXANY | IXOFF); +#ifdef IUCLC + tio.c_iflag &= ~IUCLC; +#endif + tio.c_lflag &= ~(ISIG | ICANON | ECHO | ECHOE | ECHOK | ECHONL); +#ifdef IEXTEN + tio.c_lflag &= ~IEXTEN; +#endif + tio.c_oflag &= ~OPOST; + tio.c_cc[VMIN] = 1; + tio.c_cc[VTIME] = 0; + ret = tcsetattr(fileno(stdin), TCSADRAIN, &tio); + } + + return ret; +} + +static int _leave_term_raw_mode(void) +{ + int ret = tcsetattr(fileno(stdin), TCSADRAIN, &_saved_tio); + return ret; +} + + +/* + * Functions + */ + +static int insert_item(ssh_channel channel, int fd_in, int fd_out, + int protected) +{ + node_t *node_iterator = NULL, *new = NULL; + + pthread_mutex_lock(&mutex); + + if (node == NULL) { + /* Calloc ensure that node is full of 0 */ + node = (node_t *) calloc(1, sizeof(node_t)); + if (node == NULL) { + pthread_mutex_unlock(&mutex); + return -1; + } + node->channel = channel; + node->fd_in = fd_in; + node->fd_out = fd_out; + node->protected = protected; + node->next = NULL; + } else { + node_iterator = node; + while (node_iterator->next != NULL) { + node_iterator = node_iterator->next; + } + /* Create the new node */ + new = (node_t *) malloc(sizeof(node_t)); + if (new == NULL) { + pthread_mutex_unlock(&mutex); + return -1; + } + new->channel = channel; + new->fd_in = fd_in; + new->fd_out = fd_out; + new->protected = protected; + new->next = NULL; + node_iterator->next = new; + + } + + pthread_mutex_unlock(&mutex); + return 0; +} + + +static void delete_item(ssh_channel channel) +{ + node_t *current = NULL, *previous = NULL; + + pthread_mutex_lock(&mutex); + + for (current = node; current; previous = current, current = current->next) { + if (current->channel != channel) { + continue; + } + + if (previous == NULL) { + node = current->next; + } else { + previous->next = current->next; + } + + free(current); + pthread_mutex_unlock(&mutex); + return; + } + + pthread_mutex_unlock(&mutex); +} + + +static node_t *search_item(ssh_channel channel) +{ + node_t *current = NULL; + + pthread_mutex_lock(&mutex); + + current = node; + while (current != NULL) { + if (current->channel == channel) { + pthread_mutex_unlock(&mutex); + return current; + } else { + current = current->next; + } + } + + pthread_mutex_unlock(&mutex); + + return NULL; +} + + + +static void set_nodelay(int fd) +{ + int opt, rc; + socklen_t optlen; + + optlen = sizeof(opt); + + rc = getsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, &optlen); + if (rc == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "getsockopt TCP_NODELAY: %.100s", + strerror(errno)); + return; + } + if (opt == 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d is TCP_NODELAY", fd); + return; + } + opt = 1; + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd %d setting TCP_NODELAY", fd); + + rc = setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &opt, sizeof(opt)); + if (rc == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "setsockopt TCP_NODELAY: %.100s", + strerror(errno)); + } +} + + +const char *ssh_gai_strerror(int gaierr) +{ + if (gaierr == EAI_SYSTEM && errno != 0) { + return strerror(errno); + } + return gai_strerror(gaierr); +} + + + +static int x11_get_proto(const char *display, char **_proto, char **_cookie) +{ + char cmd[1024], line[512], xdisplay[512]; + static char proto[512], cookie[512]; + FILE *f = NULL; + int ret = 0; + + *_proto = proto; + *_cookie = cookie; + + proto[0] = cookie[0] = '\0'; + + if (strncmp(display, "localhost:", 10) == 0) { + ret = snprintf(xdisplay, sizeof(xdisplay), "unix:%s", display + 10); + if (ret < 0 || (size_t)ret >= sizeof(xdisplay)) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "display name too long. display: %s", display); + return -1; + } + display = xdisplay; + } + + snprintf(cmd, sizeof(cmd), _XAUTH_CMD, display); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "xauth cmd: %s", cmd); + + f = popen(cmd, "r"); + if (f && fgets(line, sizeof(line), f) && + sscanf(line, "%*s %511s %511s", proto, cookie) == 2) { + ret = 0; + } else { + ret = 1; + } + + if (f) { + pclose(f); + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s - ret: %d", + proto, cookie, ret); + + return ret; +} + +static int connect_local_xsocket_path(const char *pathname) +{ + int sock, rc; + struct sockaddr_un addr; + + sock = socket(AF_UNIX, SOCK_STREAM, 0); + if (sock == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", + strerror(errno)); + return -1; + } + + memset(&addr, 0, sizeof(addr)); + addr.sun_family = AF_UNIX; + addr.sun_path[0] = '\0'; + /* pathname is guaranteed to be initialized and larger than addr.sun_path[108] */ + memcpy(addr.sun_path + 1, pathname, sizeof(addr.sun_path) - 1); + rc = connect(sock, (struct sockaddr *)&addr, + offsetof(struct sockaddr_un, sun_path) + 1 + strlen(pathname)); + if (rc == 0) { + return sock; + } + close(sock); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s: %.100s", + addr.sun_path, strerror(errno)); + + return -1; +} + + +static int connect_local_xsocket(int display_number) +{ + char buf[1024] = {0}; + snprintf(buf, sizeof(buf), _PATH_UNIX_X, display_number); + return connect_local_xsocket_path(buf); +} + + +static int x11_connect_display(void) +{ + int display_number; + const char *display = NULL; + char buf[1024], *cp = NULL; + struct addrinfo hints, *ai = NULL, *aitop = NULL; + char strport[NI_MAXSERV]; + int gaierr = 0, sock = 0; + + /* Try to open a socket for the local X server. */ + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (display == 0) { + return -1; + } + + /* Check if it is a unix domain socket. */ + if (strncmp(display, "unix:", 5) == 0 || display[0] == ':') { + /* Connect to the unix domain socket. */ + if (sscanf(strrchr(display, ':') + 1, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Could not parse display number from DISPLAY: %.100s", + display); + return -1; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display_number: %d", + display_number); + + /* Create a socket. */ + sock = connect_local_xsocket(display_number); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %d", sock); + + if (sock < 0) { + return -1; + } + + /* OK, we now have a connection to the display. */ + return sock; + } + + /* Connect to an inet socket. */ + strncpy(buf, display, sizeof(buf) - 1); + cp = strchr(buf, ':'); + if (cp == 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Could not find ':' in DISPLAY: %.100s", display); + return -1; + } + *cp = 0; + if (sscanf(cp + 1, "%d", &display_number) != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Could not parse display number from DISPLAY: %.100s", + display); + return -1; + } + + /* Look up the host address */ + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_INET; + hints.ai_socktype = SOCK_STREAM; + snprintf(strport, sizeof(strport), "%u", 6000 + display_number); + gaierr = getaddrinfo(buf, strport, &hints, &aitop); + if (gaierr != 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "%.100s: unknown host. (%s)", + buf, ssh_gai_strerror(gaierr)); + return -1; + } + for (ai = aitop; ai; ai = ai->ai_next) { + /* Create a socket. */ + sock = socket(ai->ai_family, ai->ai_socktype, ai->ai_protocol); + if (sock == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "socket: %.100s", + strerror(errno)); + continue; + } + /* Connect it to the display. */ + if (connect(sock, ai->ai_addr, ai->ai_addrlen) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "connect %.100s port %u: %.100s", buf, + 6000 + display_number, strerror(errno)); + close(sock); + continue; + } + /* Success */ + break; + } + freeaddrinfo(aitop); + if (ai == 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "connect %.100s port %u: %.100s", + buf, 6000 + display_number, strerror(errno)); + return -1; + } + set_nodelay(sock); + + return sock; +} + + + +static int copy_fd_to_channel_callback(int fd, int revents, void *userdata) +{ + ssh_channel channel = (ssh_channel)userdata; + char buf[2097152]; + int sz = 0, ret = 0; + + node_t *temp_node = search_item(channel); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "event: %d - fd: %d", revents, fd); + + if (channel == NULL) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel does not exist."); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + + if (fcntl(fd, F_GETFD) == -1) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fcntl error. fd: %d", fd); + ssh_channel_close(channel); + return -1; + } + + if ((revents & POLLIN) || (revents & POLLPRI)) { + sz = read(fd, buf, sizeof(buf)); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sz: %d", sz); + if (sz > 0) { + ret = ssh_channel_write(channel, buf, sz); + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "channel_write ret: %d", ret); + } else if (sz < 0) { + ssh_channel_close(channel); + return -1; + } else { + /* sz = 0. Why the hell I'm here? */ + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Why the hell am I here?: sz: %d", sz); + if (temp_node->protected == 0) { + close(fd); + } + return -1; + } + } + + if ((revents & POLLHUP) || (revents & POLLNVAL) || (revents & POLLERR)) { + ssh_channel_close(channel); + return -1; + } + + return sz; +} + + +static int copy_channel_to_fd_callback(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, + void *userdata) +{ + node_t *temp_node = NULL; + int fd, sz; + + (void)session; + (void)is_stderr; + (void)userdata; + + temp_node = search_item(channel); + + fd = temp_node->fd_out; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "len: %d - fd: %d - is_stderr: %d", + len, fd, is_stderr); + + sz = write(fd, data, len); + + return sz; +} + + +static void channel_close_callback(ssh_session session, ssh_channel channel, + void *userdata) +{ + node_t *temp_node = NULL; + + (void)session; + (void)userdata; + + temp_node = search_item(channel); + + if (temp_node != NULL) { + int fd = temp_node->fd_in; + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "fd: %d", fd); + + delete_item(channel); + ssh_event_remove_fd(event, fd); + + if (temp_node->protected == 0) { + close(fd); + } + } +} + + +static ssh_channel x11_open_request_callback(ssh_session session, + const char *shost, int sport, + void *userdata) +{ + ssh_channel channel = NULL; + int sock, rv; + + (void)shost; + (void)sport; + (void)userdata; + + channel = ssh_channel_new(session); + + sock = x11_connect_display(); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "sock: %d", sock); + + rv = insert_item(channel, sock, sock, 0); + if (rv != 0) { + ssh_channel_free(channel); + return NULL; + } + + ssh_event_add_fd(event, sock, events, copy_fd_to_channel_callback, channel); + ssh_event_add_session(event, session); + + ssh_add_channel_callbacks(channel, &channel_cb); + + return channel; +} + + + +/* + * MAIN LOOP + */ + +static int main_loop(ssh_channel channel) +{ + ssh_session session = ssh_channel_get_session(channel); + int rv; + + rv = insert_item(channel, fileno(stdin), fileno(stdout), 1); + if (rv != 0) { + return -1; + } + + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(channel, &channel_cb); + + event = ssh_event_new(); + if (event == NULL) { + printf("Couldn't get a event\n"); + return -1; + } + + rv = ssh_event_add_fd(event, fileno(stdin), events, + copy_fd_to_channel_callback, channel); + if (rv != SSH_OK) { + printf("Couldn't add an fd to the event\n"); + return -1; + } + + rv = ssh_event_add_session(event, session); + if (rv != SSH_OK) { + printf("Couldn't add the session to the event\n"); + return -1; + } + + do { + if (ssh_event_dopoll(event, 1000) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + /* fall through */ + } + } while (!ssh_channel_is_closed(channel)); + + delete_item(channel); + ssh_event_remove_fd(event, fileno(stdin)); + ssh_event_remove_session(event, session); + ssh_event_free(event); + + return 0; +} + + +/* + * USAGE + */ + +static void usage(void) +{ + fprintf(stderr, + "Usage : ssh-X11-client [options] [login@]hostname\n" + "sample X11 client - libssh-%s\n" + "Options :\n" + " -l user : Specifies the user to log in as on the remote " + "machine.\n" + " -p port : Port to connect to on the remote host.\n" + " -v : Verbose mode. Multiple -v options increase the " + "verbosity. The maximum is 5.\n" + " -C : Requests compression of all data.\n" + " -x : Disables X11 forwarding.\n" + "\n", + ssh_version(0)); + + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc,argv,"x")) != -1) { + switch (i) { + case 'x': + enableX11 = 0; + break; + default: + fprintf(stderr, "Unknown option %c\n", optopt); + return -1; + } + } + + if (optind < argc) { + hostname = argv[optind++]; + } + + if (hostname == NULL) { + return -1; + } + + return 0; +} + +/* + * MAIN + */ + +int main(int argc, char **argv) +{ + char *password = NULL; + + ssh_session session = NULL; + ssh_channel channel = NULL; + + int ret; + + const char *display = NULL; + char *proto = NULL, *cookie = NULL; + + ssh_set_log_callback(_logging_callback); + ret = ssh_init(); + if (ret != SSH_OK) { + return ret; + } + + session = ssh_new(); + if (session == NULL) { + exit(-1); + } + + if (ssh_options_getopt(session, &argc, argv) || opts(argc, argv)) { + fprintf(stderr, "Error parsing command line: %s\n", + ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); + usage(); + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, hostname) < 0) { + return -1; + } + + ret = ssh_connect(session); + if (ret != SSH_OK) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + exit(-1); + } + + password = getpass("Password: "); + ret = ssh_userauth_password(session, NULL, password); + if (ret != SSH_AUTH_SUCCESS) { + fprintf(stderr, "Error authenticating with password: %s\n", + ssh_get_error(session)); + exit(-1); + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + return SSH_ERROR; + } + + ret = ssh_channel_open_session(channel); + if (ret != SSH_OK) { + return ret; + } + + ret = ssh_channel_request_pty(channel); + if (ret != SSH_OK) { + return ret; + } + + ret = ssh_channel_change_pty_size(channel, 80, 24); + if (ret != SSH_OK) { + return ret; + } + + if (enableX11 == 1) { + display = getenv("DISPLAY"); + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "display: %s", display); + + if (display) { + ssh_callbacks_init(&cb); + ret = ssh_set_callbacks(session, &cb); + if (ret != SSH_OK) { + return ret; + } + + ret = x11_get_proto(display, &proto, &cookie); + if (ret != 0) { + _ssh_log(SSH_LOG_FUNCTIONS, __func__, + "Using fake authentication data for X11 forwarding"); + proto = NULL; + cookie = NULL; + } + + _ssh_log(SSH_LOG_FUNCTIONS, __func__, "proto: %s - cookie: %s", + proto, cookie); + /* See https://gitlab.com/libssh/libssh-mirror/-/blob/master/src/channels.c#L2062 for details. */ + ret = ssh_channel_request_x11(channel, 0, proto, cookie, 0); + if (ret != SSH_OK) { + return ret; + } + } + } + + ret = _enter_term_raw_mode(); + if (ret != 0) { + exit(-1); + } + + ret = ssh_channel_request_shell(channel); + if (ret != SSH_OK) { + return ret; + } + + ret = main_loop(channel); + if (ret != SSH_OK) { + return ret; + } + + _leave_term_raw_mode(); + + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + ssh_finalize(); +} diff --git a/src/libs/libssh-0.12.2/examples/ssh_client.c b/src/libs/libssh-0.12.2/examples/ssh_client.c new file mode 100644 index 000000000000..1b5168300c28 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/ssh_client.c @@ -0,0 +1,450 @@ +/* ssh_client.c */ + +/* + * Copyright 2003-2015 Aris Adamantiadis + * + * This file is part of the SSH Library + * + * You are free to copy this file, modify it in any way, consider it being public + * domain. This does not apply to the rest of the library though, but it is + * allowed to cut-and-paste working code from this file to any license of + * program. + * The goal is to show the API in action. It's not a reference on how terminal + * clients must be made or how a client should react. + */ + +#include "config.h" +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_TERMIOS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif + +#include +#include +#include +#include + +#include +#include + +#include "examples_common.h" +#define MAXCMD 10 + +static char *host = NULL; +static char *user = NULL; +static char *cmds[MAXCMD]; +static char *config_file = NULL; +static struct termios terminal; + +static char *pcap_file = NULL; + +static char *proxycommand = NULL; + +static int auth_callback(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void) verify; + (void) userdata; + + return ssh_getpass(prompt, buf, len, echo, verify); +} + +struct ssh_callbacks_struct cb = { + .auth_function = auth_callback, + .userdata = NULL, +}; + +static void add_cmd(char *cmd) +{ + int n; + + for (n = 0; (n < MAXCMD) && cmds[n] != NULL; n++); + + if (n == MAXCMD) { + return; + } + + cmds[n] = cmd; +} + +static void usage(void) +{ + fprintf( + stderr, + "Usage : ssh [options] [login@]hostname\n" + "sample client - libssh-%s\n" + "Options :\n" + " -l user : log in as user\n" + " -p port : connect to port\n" + " -o option : set configuration option (e.g., -o Compression=yes)\n" + " -r : use RSA to verify host public key\n" + " -F file : parse configuration file instead of default one\n" +#ifdef WITH_PCAP + " -P file : create a pcap debugging file\n" +#endif +#ifndef _WIN32 + " -T proxycommand : command to execute as a socket proxy\n" +#endif + "\n", + ssh_version(0)); + + exit(0); +} + +static int opts(int argc, char **argv) +{ + int i; + + while ((i = getopt(argc, argv, "T:P:F:")) != -1) { + switch (i) { + case 'P': + pcap_file = optarg; + break; + case 'F': + config_file = optarg; + break; +#ifndef _WIN32 + case 'T': + proxycommand = optarg; + break; +#endif + default: + fprintf(stderr, "Unknown option %c\n", optopt); + return -1; + } + } + if (optind < argc) { + host = argv[optind++]; + } + + while(optind < argc) { + add_cmd(argv[optind++]); + } + + if (host == NULL) { + return -1; + } + + return 0; +} + +#ifndef HAVE_CFMAKERAW +static void cfmakeraw(struct termios *termios_p) +{ + termios_p->c_iflag &= ~(IGNBRK|BRKINT|PARMRK|ISTRIP|INLCR|IGNCR|ICRNL|IXON); + termios_p->c_oflag &= ~OPOST; + termios_p->c_lflag &= ~(ECHO|ECHONL|ICANON|ISIG|IEXTEN); + termios_p->c_cflag &= ~(CSIZE|PARENB); + termios_p->c_cflag |= CS8; +} +#endif + + +static void do_cleanup(int i) +{ + (void)i; + + tcsetattr(0, TCSANOW, &terminal); +} + +static void do_exit(int i) +{ + (void)i; + + do_cleanup(0); + exit(0); +} + +static int signal_delayed = 0; + +#ifdef SIGWINCH +static void sigwindowchanged(int i) +{ + (void)i; + signal_delayed = 1; +} +#endif + +static void setsignal(void) +{ +#ifdef SIGWINCH + signal(SIGWINCH, sigwindowchanged); +#endif + signal_delayed = 0; +} + +static void sizechanged(ssh_channel chan) +{ + struct winsize win = { + .ws_row = 0, + }; + + ioctl(1, TIOCGWINSZ, &win); + ssh_channel_change_pty_size(chan,win.ws_col, win.ws_row); + setsignal(); +} + +static void select_loop(ssh_session session,ssh_channel channel) +{ + ssh_connector connector_in, connector_out, connector_err; + int rc; + + ssh_event event = ssh_event_new(); + + /* stdin */ + connector_in = ssh_connector_new(session); + ssh_connector_set_out_channel(connector_in, channel, SSH_CONNECTOR_STDINOUT); + ssh_connector_set_in_fd(connector_in, STDIN_FILENO); + ssh_event_add_connector(event, connector_in); + + /* stdout */ + connector_out = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_out, STDOUT_FILENO); + ssh_connector_set_in_channel(connector_out, channel, SSH_CONNECTOR_STDINOUT); + ssh_event_add_connector(event, connector_out); + + /* stderr */ + connector_err = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_err, STDERR_FILENO); + ssh_connector_set_in_channel(connector_err, channel, SSH_CONNECTOR_STDERR); + ssh_event_add_connector(event, connector_err); + + while (ssh_channel_is_open(channel)) { + if (signal_delayed) { + sizechanged(channel); + } + rc = ssh_event_dopoll(event, 60000); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error in ssh_event_dopoll()\n"); + break; + } + } + ssh_event_remove_connector(event, connector_in); + ssh_event_remove_connector(event, connector_out); + ssh_event_remove_connector(event, connector_err); + + ssh_connector_free(connector_in); + ssh_connector_free(connector_out); + ssh_connector_free(connector_err); + + ssh_event_free(event); +} + +static void shell(ssh_session session) +{ + ssh_channel channel = NULL; + struct termios terminal_local; + int interactive = isatty(0); + + channel = ssh_channel_new(session); + if (channel == NULL) { + return; + } + + if (interactive) { + tcgetattr(0, &terminal_local); + memcpy(&terminal, &terminal_local, sizeof(struct termios)); + } + + if (ssh_channel_open_session(channel)) { + printf("Error opening channel : %s\n", ssh_get_error(session)); + ssh_channel_free(channel); + return; + } + if (interactive) { + ssh_channel_request_pty(channel); + sizechanged(channel); + } + + if (ssh_channel_request_shell(channel)) { + printf("Requesting shell : %s\n", ssh_get_error(session)); + ssh_channel_free(channel); + return; + } + + if (interactive) { + cfmakeraw(&terminal_local); + tcsetattr(0, TCSANOW, &terminal_local); + setsignal(); + } + signal(SIGTERM, do_cleanup); + select_loop(session, channel); + if (interactive) { + do_cleanup(0); + } + ssh_channel_free(channel); +} + +static void batch_shell(ssh_session session) +{ + ssh_channel channel; + char *buffer = NULL; + size_t i, s, n; + + channel = ssh_channel_new(session); + if (channel == NULL) { + return; + } + + n = 0; + for (i = 0; i < MAXCMD && cmds[i]; ++i) { + /* Including space after cmds[i] */ + n += strlen(cmds[i]) + 1; + } + /* Trailing \0 */ + n += 1; + + buffer = malloc(n); + if (buffer == NULL) { + ssh_channel_free(channel); + return; + } + + s = 0; + for (i = 0; i < MAXCMD && cmds[i]; ++i) { + s += snprintf(buffer + s, n - s, "%s ", cmds[i]); + } + + ssh_channel_open_session(channel); + if (ssh_channel_request_exec(channel, buffer)) { + printf("Error executing '%s' : %s\n", buffer, ssh_get_error(session)); + free(buffer); + ssh_channel_free(channel); + return; + } + free(buffer); + select_loop(session, channel); + ssh_channel_free(channel); +} + +static int client(ssh_session session) +{ + int auth = 0; + char *banner = NULL; + int state; + + if (user) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + return -1; + } + } + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + return -1; + } + if (proxycommand != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, proxycommand)) { + return -1; + } + } + /* Parse configuration file if specified: The command-line options will + * overwrite items loaded from configuration file */ + if (ssh_options_parse_config(session, config_file) < 0) { + return -1; + } + + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + return -1; + } + + state = verify_knownhost(session); + if (state != 0) { + return -1; + } + + ssh_userauth_none(session, NULL); + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n", banner); + free(banner); + } + auth = authenticate_console(session); + if (auth != SSH_AUTH_SUCCESS) { + return -1; + } + if (cmds[0] == NULL) { + shell(session); + } else { + batch_shell(session); + } + + return 0; +} + +static ssh_pcap_file pcap; +static void set_pcap(ssh_session session) +{ + if (pcap_file == NULL) { + return; + } + + pcap = ssh_pcap_file_new(); + if (pcap == NULL) { + return; + } + + if (ssh_pcap_file_open(pcap, pcap_file) == SSH_ERROR) { + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap = NULL; + return; + } + ssh_set_pcap_file(session, pcap); +} + +static void cleanup_pcap(void) +{ + if (pcap != NULL) { + ssh_pcap_file_free(pcap); + } + pcap = NULL; +} + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + + ssh_init(); + session = ssh_new(); + + ssh_callbacks_init(&cb); + ssh_set_callbacks(session,&cb); + + if (ssh_options_getopt(session, &argc, argv) || opts(argc, argv)) { + fprintf(stderr, + "Error parsing command line: %s\n", + ssh_get_error(session)); + ssh_free(session); + ssh_finalize(); + usage(); + } + signal(SIGTERM, do_exit); + + set_pcap(session); + client(session); + + ssh_disconnect(session); + ssh_free(session); + cleanup_pcap(); + + ssh_finalize(); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/ssh_server.c b/src/libs/libssh-0.12.2/examples/ssh_server.c new file mode 100644 index 000000000000..fb1541d92799 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/ssh_server.c @@ -0,0 +1,1015 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2014 Audrius Butkevicius + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. +*/ + +#include "config.h" + +#include +#include + +#include +#ifdef HAVE_ARGP_H +#include +#endif +#include +#ifdef HAVE_LIBUTIL_H +#include +#endif +#include +#ifdef HAVE_PTY_H +#include +#endif +#include +#include +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif +#include +#include +#include +#include + +#ifndef BUF_SIZE +#define BUF_SIZE 1048576 +#endif + +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) +#define SFTP_SERVER_PATH "/usr/lib/sftp-server" +#define AUTH_KEYS_MAX_LINE_SIZE 2048 + +#define DEF_STR_SIZE 1024 +char authorizedkeys[DEF_STR_SIZE] = {0}; +char username[128] = "myuser"; +char password[128] = "mypassword"; +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set a host key. Can be used multiple times. " + "Implies no default keys.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "ecdsakey", + .key = 'e', + .arg = "FILE", + .flags = 0, + .doc = "Set the ecdsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "authorizedkeys", + .key = 'a', + .arg = "FILE", + .flags = 0, + .doc = "Set the authorized keys file.", + .group = 0 + }, + { + .name = "user", + .key = 'u', + .arg = "USERNAME", + .flags = 0, + .doc = "Set expected username.", + .group = 0 + }, + { + .name = "pass", + .key = 'P', + .arg = "PASSWORD", + .flags = 0, + .doc = "Set expected password.", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t +parse_opt(int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'r': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'e': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'a': + strncpy(authorizedkeys, arg, DEF_STR_SIZE - 1); + break; + case 'u': + strncpy(username, arg, sizeof(username) - 1); + break; + case 'P': + strncpy(password, arg, sizeof(password) - 1); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage(state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#else +static int +parse_opt(int argc, char **argv, ssh_bind sshbind) +{ + int key; + + while((key = getopt(argc, argv, "a:e:k:p:P:r:u:v")) != -1) { + if (key == 'p') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, optarg); + } else if (key == 'k') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); + } else if (key == 'r') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); + } else if (key == 'e') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, optarg); + } else if (key == 'a') { + strncpy(authorizedkeys, optarg, DEF_STR_SIZE-1); + } else if (key == 'u') { + strncpy(username, optarg, sizeof(username) - 1); + } else if (key == 'P') { + strncpy(password, optarg, sizeof(password) - 1); + } else if (key == 'v') { + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + "3"); + } else { + break; + } + } + + if (key != -1) { + printf("Usage: %s [OPTION...] BINDADDR\n" + "libssh %s -- a Secure Shell protocol implementation\n" + "\n" + " -a, --authorizedkeys=FILE Set the authorized keys file.\n" + " -e, --ecdsakey=FILE Set the ecdsa key (deprecated alias for 'k').\n" + " -k, --hostkey=FILE Set a host key. Can be used multiple times.\n" + " Implies no default keys.\n" + " -p, --port=PORT Set the port to bind.\n" + " -P, --pass=PASSWORD Set expected password.\n" + " -r, --rsakey=FILE Set the rsa key (deprecated alias for 'k').\n" + " -u, --user=USERNAME Set expected username.\n" + " -v, --verbose Get verbose output.\n" + " -?, --help Give this help list\n" + "\n" + "Mandatory or optional arguments to long options are also mandatory or optional\n" + "for any corresponding short options.\n" + "\n" + "Report bugs to .\n", + argv[0], SSH_STRINGIFY(LIBSSH_VERSION)); + return -1; + } + + if (optind != argc - 1) { + printf("Usage: %s [OPTION...] BINDADDR\n", argv[0]); + return -1; + } + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, argv[optind]); + + return 0; +} +#endif /* HAVE_ARGP_H */ + +/* A userdata struct for channel. */ +struct channel_data_struct { + /* pid of the child process the channel will spawn. */ + pid_t pid; + /* For PTY allocation */ + socket_t pty_master; + socket_t pty_slave; + /* For communication with the child process. */ + socket_t child_stdin; + socket_t child_stdout; + /* Only used for subsystem and exec requests. */ + socket_t child_stderr; + /* Event which is used to poll the above descriptors. */ + ssh_event event; + /* Terminal size struct. */ + struct winsize *winsize; +}; + +/* A userdata struct for session. */ +struct session_data_struct { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + int auth_attempts; + int authenticated; +}; + +static int +data_function(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void)session; + (void)channel; + (void)is_stderr; + + if (len == 0 || cdata->pid < 1 || kill(cdata->pid, 0) < 0) { + return 0; + } + + return write(cdata->child_stdin, (char *)data, len); +} + +static int +pty_request(ssh_session session, + ssh_channel channel, + const char *term, + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + int rc; + + (void)session; + (void)channel; + (void)term; + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + rc = openpty(&cdata->pty_master, + &cdata->pty_slave, + NULL, + NULL, + cdata->winsize); + if (rc != 0) { + fprintf(stderr, "Failed to open pty\n"); + return SSH_ERROR; + } + return SSH_OK; +} + +static int +pty_resize(ssh_session session, + ssh_channel channel, + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void)session; + (void)channel; + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + if (cdata->pty_master != -1) { + return ioctl(cdata->pty_master, TIOCSWINSZ, cdata->winsize); + } + + return SSH_ERROR; +} + +static int +exec_pty(const char *mode, + const char *command, + struct channel_data_struct *cdata) +{ + cdata->pid = fork(); + switch (cdata->pid) { + case -1: + close(cdata->pty_master); + close(cdata->pty_slave); + fprintf(stderr, "Failed to fork\n"); + return SSH_ERROR; + case 0: + close(cdata->pty_master); + if (login_tty(cdata->pty_slave) != 0) { + exit(1); + } + execl("/bin/sh", "sh", mode, command, NULL); + exit(0); + default: + close(cdata->pty_slave); + /* pty fd is bi-directional */ + cdata->child_stdout = cdata->child_stdin = cdata->pty_master; + } + return SSH_OK; +} + +static int +exec_nopty(const char *command, struct channel_data_struct *cdata) +{ + int in[2], out[2], err[2]; + + /* Do the plumbing to be able to talk with the child process. */ + if (pipe(in) != 0) { + goto stdin_failed; + } + if (pipe(out) != 0) { + goto stdout_failed; + } + if (pipe(err) != 0) { + goto stderr_failed; + } + + cdata->pid = fork(); + switch (cdata->pid) { + case -1: + goto fork_failed; + case 0: + /* Finish the plumbing in the child process. */ + close(in[1]); + close(out[0]); + close(err[0]); + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + dup2(err[1], STDERR_FILENO); + close(in[0]); + close(out[1]); + close(err[1]); + /* exec the requested command. */ + execl("/bin/sh", "sh", "-c", command, NULL); + exit(0); + } + + close(in[0]); + close(out[1]); + close(err[1]); + + cdata->child_stdin = in[1]; + cdata->child_stdout = out[0]; + cdata->child_stderr = err[0]; + + return SSH_OK; + +fork_failed: + close(err[0]); + close(err[1]); +stderr_failed: + close(out[0]); + close(out[1]); +stdout_failed: + close(in[0]); + close(in[1]); +stdin_failed: + return SSH_ERROR; +} + +static int +exec_request(ssh_session session, + ssh_channel channel, + const char *command, + void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void)session; + (void)channel; + + if (cdata->pid > 0) { + return SSH_ERROR; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + return exec_pty("-c", command, cdata); + } + return exec_nopty(command, cdata); +} + +static int +shell_request(ssh_session session, ssh_channel channel, void *userdata) +{ + struct channel_data_struct *cdata = (struct channel_data_struct *)userdata; + + (void)session; + (void)channel; + + if (cdata->pid > 0) { + return SSH_ERROR; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + return exec_pty("-l", NULL, cdata); + } + /* Client requested a shell without a pty, let's pretend we allow that */ + return SSH_OK; +} + +static int +subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + /* subsystem requests behave similarly to exec requests. */ + if (strcmp(subsystem, "sftp") == 0) { + return exec_request(session, channel, SFTP_SERVER_PATH, userdata); + } + return SSH_ERROR; +} + +static int +auth_password(ssh_session session, + const char *user, + const char *pass, + void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + (void)session; + + if (strcmp(user, username) == 0 && strcmp(pass, password) == 0) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + + sdata->auth_attempts++; + return SSH_AUTH_DENIED; +} + +static int +auth_publickey(ssh_session session, + const char *user, + struct ssh_key_struct *pubkey, + char signature_state, + void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + ssh_key key = NULL; + FILE *fp = NULL; + char line[AUTH_KEYS_MAX_LINE_SIZE] = {0}; + char *p = NULL; + const char *q = NULL; + unsigned int lineno = 0; + int result; + int i; + enum ssh_keytypes_e type; + + (void)user; + (void)session; + + if (signature_state == SSH_PUBLICKEY_STATE_NONE) { + return SSH_AUTH_SUCCESS; + } + + if (signature_state != SSH_PUBLICKEY_STATE_VALID) { + return SSH_AUTH_DENIED; + } + + fp = fopen(authorizedkeys, "r"); + if (fp == NULL) { + fprintf(stderr, "Error: opening authorized keys file %s failed, reason: %s\n", + authorizedkeys, strerror(errno)); + return SSH_AUTH_DENIED; + } + + while (fgets(line, sizeof(line), fp)) { + lineno++; + + /* Skip leading whitespace and ignore comments */ + p = line; + + for (i = 0; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (!isspace((int)p[i])) { + break; + } + } + + if (i >= AUTH_KEYS_MAX_LINE_SIZE) { + fprintf(stderr, + "warning: The line %d in %s too long! Skipping.\n", + lineno, + authorizedkeys); + continue; + } + + if (p[i] == '#' || p[i] == '\0' || p[i] == '\n') { + continue; + } + + q = &p[i]; + for (; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + type = ssh_key_type_from_name(q); + + i++; + if (i >= AUTH_KEYS_MAX_LINE_SIZE) { + fprintf(stderr, + "warning: The line %d in %s too long! Skipping.\n", + lineno, + authorizedkeys); + continue; + } + + q = &p[i]; + for (; i < AUTH_KEYS_MAX_LINE_SIZE; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + result = ssh_pki_import_pubkey_base64(q, type, &key); + if (result != SSH_OK) { + fprintf(stderr, + "Warning: Cannot import key on line no. %d in authorized keys file: %s\n", + lineno, + authorizedkeys); + continue; + } + + result = ssh_key_cmp(key, pubkey, SSH_KEY_CMP_PUBLIC); + ssh_key_free(key); + if (result == 0) { + sdata->authenticated = 1; + fclose(fp); + return SSH_AUTH_SUCCESS; + } + } + if (ferror(fp) != 0) { + fprintf(stderr, + "Error: Reading from authorized keys file %s failed, reason: %s\n", + authorizedkeys, strerror(errno)); + } + fclose(fp); + + /* no matches */ + return SSH_AUTH_DENIED; +} + +static int kbdint_check_response(ssh_session session) +{ + int count, cmp; + const char *answer = NULL; + + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + cmp = strcasecmp("omnitrix", answer); + if (cmp != 0) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 1); + cmp = strcmp("000", answer); + if (cmp != 0) { + return 0; + } + + return 1; +} + +static int +auth_kbdint(ssh_message message, ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + const char *name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + const char *instruction = "Most powerful weapon in the galaxy"; + const char *prompts[2] = {"Name of the weapon: ", "Destruct Code: "}; + char echo[] = {1, 0}; + if (!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, + name, + instruction, + 2, + prompts, + echo); + return SSH_AUTH_INFO; + } else { + if (kbdint_check_response(session)) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + return SSH_AUTH_DENIED; + } +} + +static ssh_channel +channel_open(ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = (struct session_data_struct *)userdata; + + sdata->channel = ssh_channel_new(session); + return sdata->channel; +} + +static int +process_stdout(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel)userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int +process_stderr(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel)userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +static void +handle_session(ssh_event event, ssh_session session) +{ + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_struct cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_struct sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0 + }; + + struct ssh_channel_callbacks_struct channel_cb = { + .userdata = &cdata, + .channel_pty_request_function = pty_request, + .channel_pty_window_change_function = pty_resize, + .channel_shell_request_function = shell_request, + .channel_exec_request_function = exec_request, + .channel_data_function = data_function, + .channel_subsystem_request_function = subsystem_request + }; + + struct ssh_server_callbacks_struct server_cb = { + .userdata = &sdata, + .auth_password_function = auth_password, + .auth_kbdint_function = auth_kbdint, + .channel_open_request_session_function = channel_open, + }; + + if (authorizedkeys[0]) { + server_cb.auth_pubkey_function = auth_publickey; + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_INTERACTIVE); + } else + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_INTERACTIVE); + + ssh_callbacks_init(&server_cb); + ssh_callbacks_init(&channel_cb); + + ssh_set_server_callbacks(session, &server_cb); + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= 3 || n >= 100) { + return; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + return; + } + n++; + } + + ssh_set_channel_callbacks(sdata.channel, &channel_cb); + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, -1) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while (ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } +} + +#ifdef WITH_FORK +/* SIGCHLD handler for cleaning up dead children. */ +static void sigchld_handler(int signo) +{ + (void)signo; + while (waitpid(-1, NULL, WNOHANG) > 0); +} +#else +static void *session_thread(void *arg) +{ + ssh_session session = arg; + ssh_event event; + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child thread exiting, or client disconnecting. */ + handle_session(event, session); + ssh_event_free(event); + } else { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + return NULL; +} +#endif + +int main(int argc, char **argv) +{ + ssh_bind sshbind = NULL; + ssh_session session = NULL; + int rc; +#ifdef WITH_FORK + struct sigaction sa; + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGCHLD handler\n"); + return 1; + } +#endif + + rc = ssh_init(); + if (rc < 0) { + fprintf(stderr, "ssh_init failed\n"); + return 1; + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + fprintf(stderr, "ssh_bind_new failed\n"); + ssh_finalize(); + return 1; + } + +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, sshbind); +#else + if (parse_opt(argc, argv, sshbind) < 0) { + ssh_bind_free(sshbind); + ssh_finalize(); + return 1; + } +#endif /* HAVE_ARGP_H */ + + rc = ssh_bind_listen(sshbind); + if (rc < 0) { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + ssh_bind_free(sshbind); + ssh_finalize(); + return 1; + } + + while (1) { + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Failed to allocate session\n"); + continue; + } + + /* Blocks until there is a new incoming connection. */ + rc = ssh_bind_accept(sshbind, session); + if (rc != SSH_ERROR) { +#ifdef WITH_FORK + ssh_event event; + + pid_t pid = fork(); + switch (pid) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + handle_session(event, session); + ssh_event_free(event); + } else { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + + exit(0); + case -1: + fprintf(stderr, "Failed to fork\n"); + } +#else + pthread_t tid; + + rc = pthread_create(&tid, NULL, session_thread, session); + if (rc == 0) { + pthread_detach(tid); + continue; + } + fprintf(stderr, "Failed to pthread_create\n"); +#endif + } else { + fprintf(stderr, "%s\n", ssh_get_error(sshbind)); + } + /* Since the session has been passed to a child fork, do some cleaning + * up at the parent process. */ + ssh_disconnect(session); + ssh_free(session); + } + + ssh_bind_free(sshbind); + ssh_finalize(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/examples/sshd_direct-tcpip.c b/src/libs/libssh-0.12.2/examples/sshd_direct-tcpip.c new file mode 100644 index 000000000000..9bb09111f805 --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/sshd_direct-tcpip.c @@ -0,0 +1,746 @@ +/* This is a sample implementation of a libssh based SSH server */ +/* +Copyright 2003-2009 Aris Adamantiadis +Copyright 2018 T. Wimmer + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +/* + Example: + ./sshd_direct-tcpip -v -p 2022 -r serverkey.rsa 127.0.0.1 +*/ + +#include "config.h" + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif +#ifndef _WIN32 +#include +#endif +#include +#include +#include +#include +#include +#include +#include + +#ifndef BUF_SIZE +#define BUF_SIZE 16384 +#endif + +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) + +#ifndef __unused__ +# ifdef HAVE_UNUSED_ATTRIBUTE +# define __unused__ __attribute__((unused)) +# else /* HAVE_UNUSED_ATTRIBUTE */ +# define __unused__ +# endif /* HAVE_UNUSED_ATTRIBUTE */ +#endif /* __unused__ */ + +#ifndef UNUSED_PARAM +#define UNUSED_PARAM(param) param __unused__ +#endif /* UNUSED_PARAM */ + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define USER "user" +#define PASSWORD "pwd" + +struct event_fd_data_struct { + int *p_fd; + ssh_channel channel; + struct ssh_channel_callbacks_struct *cb_chan; + int stacked; +}; + +struct cleanup_node_struct { + struct event_fd_data_struct *data; + struct cleanup_node_struct *next; +}; + +static bool authenticated = false; +static int tries = 0; +static bool error_set = false; +static int sockets_cnt = 0; +static ssh_event mainloop = NULL; +static struct cleanup_node_struct *cleanup_stack = NULL; + +static void _close_socket(struct event_fd_data_struct event_fd_data); + +static void +cleanup_push(struct cleanup_node_struct** head_ref, + struct event_fd_data_struct *new_data) +{ + // Allocate memory for node + struct cleanup_node_struct *new_node = malloc(sizeof *new_node); + if (new_node == NULL) { + return; + } + + if (*head_ref != NULL) { + new_node->next = *head_ref; + } else { + new_node->next = NULL; + } + + // Copy new_data + new_node->data = new_data; + + // Change head pointer as new node is added at the beginning + (*head_ref) = new_node; +} + +static void +do_cleanup(struct cleanup_node_struct **head_ref) +{ + struct cleanup_node_struct *current = (*head_ref); + struct cleanup_node_struct *previous = NULL, *gone = NULL; + + while (current != NULL) { + if (ssh_channel_is_closed(current->data->channel)) { + if (current == (*head_ref)) { + (*head_ref) = current->next; + } + if (previous != NULL) { + previous->next = current->next; + } + gone = current; + current = current->next; + + if (gone->data->channel) { + _close_socket(*gone->data); + ssh_remove_channel_callbacks(gone->data->channel, gone->data->cb_chan); + ssh_channel_free(gone->data->channel); + gone->data->channel = NULL; + + SAFE_FREE(gone->data->p_fd); + SAFE_FREE(gone->data->cb_chan); + SAFE_FREE(gone->data); + SAFE_FREE(gone); + } + else { + fprintf(stderr, "channel already freed!\n"); + } + _ssh_log(SSH_LOG_FUNCTIONS, "=== do_cleanup", "Freed."); + } + else { + ssh_channel_close(current->data->channel); + previous = current; + current = current->next; + } + } +} + +static int +auth_password(ssh_session session, + const char *user, + const char *password, + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, + "=== auth_password", "Authenticating user %s pwd %s", + user, + password); + if (strcmp(user, USER) == 0 && strcmp(password, PASSWORD) == 0) { + authenticated = true; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + if (tries >= 3) { + printf("Too many authentication tries\n"); + ssh_disconnect(session); + error_set = true; + return SSH_AUTH_DENIED; + } + tries++; + return SSH_AUTH_DENIED; +} + +static int +auth_gssapi_mic(ssh_session session, + const char *user, + const char *principal, + UNUSED_PARAM(void *userdata)) +{ + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + printf("Authenticating user %s with gssapi principal %s\n", + user, principal); + if (creds != NULL) { + printf("Received some gssapi credentials\n"); + } else { + printf("Not received any forwardable creds\n"); + } + printf("authenticated\n"); + authenticated = true; + return SSH_AUTH_SUCCESS; +} + +static int +subsystem_request(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + const char *subsystem, + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, + "=== subsystem_request", "Channel subsystem request: %s", + subsystem); + return 0; +} + +struct ssh_channel_callbacks_struct channel_cb = { + .channel_subsystem_request_function = subsystem_request +}; + +static ssh_channel +new_session_channel(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, "=== subsystem_request", "Session channel request"); + /* For TCP forward only there seems to be no need for a session channel */ + /*if(chan != NULL) + return NULL; + printf("Session channel request\n"); + chan = ssh_channel_new(session); + ssh_callbacks_init(&channel_cb); + ssh_set_channel_callbacks(chan, &channel_cb); + return chan;*/ + return NULL; +} + +static void +stack_socket_close(UNUSED_PARAM(ssh_session session), + struct event_fd_data_struct *event_fd_data) +{ + if (event_fd_data->stacked != 1) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== stack_socket_close", + "Closing fd = %d sockets_cnt = %d", *event_fd_data->p_fd, + sockets_cnt); + event_fd_data->stacked = 1; + cleanup_push(&cleanup_stack, event_fd_data); + } +} + +static void +_close_socket(struct event_fd_data_struct event_fd_data) +{ + _ssh_log(SSH_LOG_FUNCTIONS, "=== close_socket", + "Closing fd = %d sockets_cnt = %d", *event_fd_data.p_fd, + sockets_cnt); + ssh_event_remove_fd(mainloop, *event_fd_data.p_fd); + sockets_cnt--; +#ifdef _WIN32 + closesocket(*event_fd_data.p_fd); +#else + close(*event_fd_data.p_fd); +#endif // _WIN32 + (*event_fd_data.p_fd) = SSH_INVALID_SOCKET; +} + +static int +service_request(UNUSED_PARAM(ssh_session session), + const char *service, + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, "=== service_request", "Service request: %s", service); + return 0; +} + +static void +global_request(UNUSED_PARAM(ssh_session session), + ssh_message message, + UNUSED_PARAM(void *userdata)) +{ + _ssh_log(SSH_LOG_PROTOCOL, + "=== global_request", "Global request, message type: %d", + ssh_message_type(message)); +} + +static void +my_channel_close_function(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_close_function", + "Channel closed by remote."); + + stack_socket_close(session, event_fd_data); +} + +static void +my_channel_eof_function(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_eof_function", + "Got EOF on channel. Shutting down write on socket (fd = %d).", + *event_fd_data->p_fd); + + stack_socket_close(session, event_fd_data); +} + +static void +my_channel_exit_status_function(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + int exit_status, + void *userdata) +{ + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_exit_status_function", + "Got exit status %d on channel fd = %d.", + exit_status, *event_fd_data->p_fd); +} + +static int +my_channel_data_function(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + int i = 0; + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + + if (event_fd_data->channel == NULL) { + fprintf(stderr, "Why we're here? Stacked = %d\n", event_fd_data->stacked); + } + + _ssh_log(SSH_LOG_PROTOCOL, + "=== my_channel_data_function", + "%d bytes waiting on channel for reading. Fd = %d", + len, + *event_fd_data->p_fd); + if (len > 0) { + i = send(*event_fd_data->p_fd, data, len, 0); + } + if (i < 0) { + _ssh_log(SSH_LOG_WARNING, "=== my_channel_data_function", + "Writing to tcp socket %d: %s", *event_fd_data->p_fd, + strerror(errno)); + stack_socket_close(session, event_fd_data); + } + else { + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_channel_data_function", "Sent %d bytes", i); + } + return i; +} + +static int +my_fd_data_function(UNUSED_PARAM(socket_t fd), + int revents, + void *userdata) +{ + struct event_fd_data_struct *event_fd_data = (struct event_fd_data_struct *)userdata; + ssh_channel channel = event_fd_data->channel; + ssh_session session = NULL; + int len, i, wr; + char buf[BUF_SIZE]; + int blocking; + + if (channel == NULL) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "channel == NULL!"); + return 0; + } + + session = ssh_channel_get_session(channel); + + if (ssh_channel_is_closed(channel)) { + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "channel is closed!"); + stack_socket_close(session, event_fd_data); + return 0; + } + + if (!(revents & POLLIN)) { + if (revents & POLLPRI) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLPRI"); + } + if (revents & POLLOUT) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLOUT"); + } + if (revents & POLLHUP) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLHUP"); + } + if (revents & POLLNVAL) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLNVAL"); + } + if (revents & POLLERR) { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "poll revents & POLLERR"); + } + return 0; + } + + blocking = ssh_is_blocking(session); + ssh_set_blocking(session, 0); + + _ssh_log(SSH_LOG_FUNCTIONS, + "=== my_fd_data_function", + "Trying to read from tcp socket fd = %d", + *event_fd_data->p_fd); +#ifdef _WIN32 + struct sockaddr from; + int fromlen = sizeof(from); + len = recvfrom(*event_fd_data->p_fd, buf, sizeof(buf), 0, &from, &fromlen); +#else + len = recv(*event_fd_data->p_fd, buf, sizeof(buf), 0); +#endif // _WIN32 + if (len < 0) { + _ssh_log(SSH_LOG_WARNING, "=== my_fd_data_function", "Reading from tcp socket: %s", strerror(errno)); + + ssh_channel_send_eof(channel); + } + else if (len > 0) { + if (ssh_channel_is_open(channel)) { + wr = 0; + do { + i = ssh_channel_write(channel, buf, len); + if (i < 0) { + _ssh_log(SSH_LOG_WARNING, "=== my_fd_data_function", "Error writing on the direct-tcpip channel: %d", i); + len = wr; + break; + } + wr += i; + _ssh_log(SSH_LOG_FUNCTIONS, "=== my_fd_data_function", "ssh_channel_write (%d from %d)", wr, len); + } while (i > 0 && wr < len); + } + else { + _ssh_log(SSH_LOG_WARNING, "=== my_fd_data_function", "Can't write on closed channel!"); + } + } + else { + _ssh_log(SSH_LOG_PROTOCOL, "=== my_fd_data_function", "The destination host has disconnected!"); + + ssh_channel_close(channel); +#ifdef _WIN32 + shutdown(*event_fd_data->p_fd, SD_RECEIVE); +#else + shutdown(*event_fd_data->p_fd, SHUT_RD); +#endif // _WIN32 + } + ssh_set_blocking(session, blocking); + + return len; +} + +static int +open_tcp_socket(ssh_message msg) +{ + struct sockaddr_in sin; + int forwardsock = -1; + struct hostent *host = NULL; + const char *dest_hostname = NULL; + int dest_port; + + forwardsock = socket(AF_INET, SOCK_STREAM, 0); + if (forwardsock < 0) { + _ssh_log(SSH_LOG_WARNING, "=== open_tcp_socket", "ERROR opening socket: %s", strerror(errno)); + return -1; + } + + dest_hostname = ssh_message_channel_request_open_destination(msg); + dest_port = ssh_message_channel_request_open_destination_port(msg); + + _ssh_log(SSH_LOG_PROTOCOL, "=== open_tcp_socket", "Connecting to %s on port %d", dest_hostname, dest_port); + + host = gethostbyname(dest_hostname); + if (host == NULL) { + close(forwardsock); + _ssh_log(SSH_LOG_WARNING, "=== open_tcp_socket", "ERROR, no such host: %s", dest_hostname); + return -1; + } + + memset((char *)&sin, '\0', sizeof(sin)); + sin.sin_family = AF_INET; + memcpy((char *)&sin.sin_addr.s_addr, (char *)host->h_addr, host->h_length); + sin.sin_port = htons(dest_port); + + if (connect(forwardsock, (struct sockaddr *)&sin, sizeof(sin)) < 0) { + close(forwardsock); + _ssh_log(SSH_LOG_WARNING, "=== open_tcp_socket", "ERROR connecting: %s", strerror(errno)); + return -1; + } + + sockets_cnt++; + _ssh_log(SSH_LOG_FUNCTIONS, "=== open_tcp_socket", "Connected. sockets_cnt = %d", sockets_cnt); + return forwardsock; +} + +static int +message_callback(UNUSED_PARAM(ssh_session session), + ssh_message message, + UNUSED_PARAM(void *userdata)) +{ + ssh_channel channel; + int socket_fd, *pFd = NULL; + struct ssh_channel_callbacks_struct *cb_chan = NULL; + struct event_fd_data_struct *event_fd_data; + + _ssh_log(SSH_LOG_PACKET, "=== message_callback", "Message type: %d", + ssh_message_type(message)); + _ssh_log(SSH_LOG_PACKET, "=== message_callback", "Message Subtype: %d", + ssh_message_subtype(message)); + if (ssh_message_type(message) == SSH_REQUEST_CHANNEL_OPEN) { + _ssh_log(SSH_LOG_PROTOCOL, "=== message_callback", "channel_request_open"); + + if (ssh_message_subtype(message) == SSH_CHANNEL_DIRECT_TCPIP) { + channel = ssh_message_channel_request_open_reply_accept(message); + + if (channel == NULL) { + _ssh_log(SSH_LOG_WARNING, "=== message_callback", "Accepting direct-tcpip channel failed!"); + return 1; + } + else { + _ssh_log(SSH_LOG_PROTOCOL, "=== message_callback", "Connected to channel!"); + + socket_fd = open_tcp_socket(message); + if (-1 == socket_fd) { + return 1; + } + + pFd = malloc(sizeof *pFd); + cb_chan = calloc(1, sizeof *cb_chan); + event_fd_data = malloc(sizeof *event_fd_data); + if (pFd == NULL || cb_chan == NULL || event_fd_data == NULL) { + SAFE_FREE(pFd); + SAFE_FREE(cb_chan); + SAFE_FREE(event_fd_data); + close(socket_fd); + return 1; + } + + (*pFd) = socket_fd; + event_fd_data->channel = channel; + event_fd_data->p_fd = pFd; + event_fd_data->stacked = 0; + event_fd_data->cb_chan = cb_chan; + + cb_chan->userdata = event_fd_data; + cb_chan->channel_eof_function = my_channel_eof_function; + cb_chan->channel_close_function = my_channel_close_function; + cb_chan->channel_data_function = my_channel_data_function; + cb_chan->channel_exit_status_function = my_channel_exit_status_function; + + ssh_callbacks_init(cb_chan); + ssh_set_channel_callbacks(channel, cb_chan); + + ssh_event_add_fd(mainloop, (socket_t)*pFd, POLLIN, my_fd_data_function, event_fd_data); + + return 0; + } + } + } + return 1; +} + +#ifdef HAVE_ARGP_H +const char *argp_program_version = "libssh server example " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the rsa key (deprecated alias for 'k').", + .group = 0 + }, + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Get verbose output.", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t +parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + ssh_bind sshbind = state->input; + + switch (key) { + case 'p': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT_STR, arg); + break; + case 'r': + case 'k': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, arg); + break; + case 'v': + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "1"); + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + argp_usage (state); + } + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, arg); + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + /* Not enough arguments. */ + argp_usage (state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +int +main(int argc, char **argv) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + struct ssh_server_callbacks_struct cb = { + .userdata = NULL, + .auth_password_function = auth_password, + .auth_gssapi_mic_function = auth_gssapi_mic, + .channel_open_request_session_function = new_session_channel, + .service_request_function = service_request + }; + struct ssh_callbacks_struct cb_gen = { + .userdata = NULL, + .global_request_function = global_request + }; + + int ret = 1; + + sshbind = ssh_bind_new(); + session = ssh_new(); + mainloop = ssh_event_new(); + + ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HOSTKEY, KEYS_FOLDER "ssh_host_rsa_key"); + +#ifdef HAVE_ARGP_H + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ + argp_parse (&argp, argc, argv, 0, 0, sshbind); +#else + (void)argc; + (void)argv; +#endif + + if (ssh_bind_listen(sshbind) < 0) { + printf("Error listening to socket: %s\n", ssh_get_error(sshbind)); + return 1; + } + + if (ssh_bind_accept(sshbind, session) == SSH_ERROR) { + printf("error accepting a connection : %s\n", ssh_get_error(sshbind)); + ret = 1; + goto shutdown; + } + + ssh_callbacks_init(&cb); + ssh_callbacks_init(&cb_gen); + ssh_set_server_callbacks(session, &cb); + ssh_set_callbacks(session, &cb_gen); + ssh_set_message_callback(session, message_callback, (void *)NULL); + + if (ssh_handle_key_exchange(session)) { + printf("ssh_handle_key_exchange: %s\n", ssh_get_error(session)); + ret = 1; + goto shutdown; + } + ssh_set_auth_methods(session, SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_GSSAPI_MIC); + ssh_event_add_session(mainloop, session); + + while (!authenticated) { + if (error_set) { + break; + } + if (ssh_event_dopoll(mainloop, -1) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + ret = 1; + goto shutdown; + } + } + if (error_set) { + printf("Error, exiting loop\n"); + } else { + printf("Authenticated and got a channel\n"); + + while (!error_set) { + if (ssh_event_dopoll(mainloop, 100) == SSH_ERROR) { + printf("Error : %s\n", ssh_get_error(session)); + ret = 1; + goto shutdown; + } + do_cleanup(&cleanup_stack); + } + } + +shutdown: + ssh_disconnect(session); + ssh_bind_free(sshbind); + ssh_finalize(); + return ret; +} diff --git a/src/libs/libssh-0.12.2/examples/sshnetcat.c b/src/libs/libssh-0.12.2/examples/sshnetcat.c new file mode 100644 index 000000000000..3dba51e8a9dd --- /dev/null +++ b/src/libs/libssh-0.12.2/examples/sshnetcat.c @@ -0,0 +1,287 @@ +/* +Copyright 2010 Aris Adamantiadis + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" +#include +#include +#include +#ifdef HAVE_TERMIOS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include "examples_common.h" + +#ifndef BUF_SIZE +#define BUF_SIZE 4096 +#endif + +char *host = NULL; +const char *desthost = "localhost"; +const char *port = "22"; + +#ifdef WITH_PCAP +#include +char *pcap_file = NULL; +#endif + +static void usage(void) +{ + fprintf(stderr, + "Usage : sshnetcat [user@]host forwarded_host forwarded_port\n"); + exit(1); +} + +static int opts(int argc, char **argv) +{ + int i; + while ((i = getopt(argc, argv, "P:")) != -1) { + switch (i) { +#ifdef WITH_PCAP + case 'P': + pcap_file = optarg; + break; +#endif + default: + fprintf(stderr, "unknown option %c\n", optopt); + usage(); + } + } + if (optind < argc) + host = argv[optind++]; + if (optind < argc) + desthost = argv[optind++]; + if (optind < argc) + port = argv[optind++]; + if (host == NULL) + usage(); + return 0; +} + +static void select_loop(ssh_session session, ssh_channel channel) +{ + fd_set fds; + struct timeval timeout; + char buffer[BUF_SIZE]; + /* channels will be set to the channels to poll. + * outchannels will contain the result of the poll + */ + ssh_channel channels[2], outchannels[2]; + int lus; + int eof = 0; + int maxfd; + int ret; + while (channel) { + do { + int fd; + + ZERO_STRUCT(fds); + FD_ZERO(&fds); + if (!eof) + FD_SET(0, &fds); + timeout.tv_sec = 30; + timeout.tv_usec = 0; + + fd = ssh_get_fd(session); + if (fd == -1) { + fprintf(stderr, + "Error getting the session file descriptor: %s\n", + ssh_get_error(session)); + return; + } + FD_SET(fd, &fds); + maxfd = fd + 1; + + channels[0] = channel; // set the first channel we want to read from + channels[1] = NULL; + ret = ssh_select(channels, outchannels, maxfd, &fds, &timeout); + if (ret == EINTR) + continue; + if (FD_ISSET(0, &fds)) { + lus = read(0, buffer, sizeof(buffer)); + if (lus) + ssh_channel_write(channel, buffer, lus); + else { + eof = 1; + ssh_channel_send_eof(channel); + } + } + if (channel && ssh_channel_is_closed(channel)) { + ssh_channel_free(channel); + channel = NULL; + channels[0] = NULL; + } + if (outchannels[0]) { + while (channel && ssh_channel_is_open(channel) && + ssh_channel_poll(channel, 0)) { + lus = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + if (lus == -1) { + fprintf(stderr, + "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if (lus == 0) { + ssh_channel_free(channel); + channel = channels[0] = NULL; + } else { + ret = write(1, buffer, lus); + if (ret < 0) { + fprintf(stderr, + "Error writing to stdin: %s", + strerror(errno)); + return; + } + } + } + while (channel && ssh_channel_is_open(channel) && + ssh_channel_poll(channel, 1)) { /* stderr */ + lus = ssh_channel_read(channel, buffer, sizeof(buffer), 1); + if (lus == -1) { + fprintf(stderr, + "Error reading channel: %s\n", + ssh_get_error(session)); + return; + } + if (lus == 0) { + ssh_channel_free(channel); + channel = channels[0] = NULL; + } else { + ret = write(2, buffer, lus); + if (ret < 0) { + fprintf(stderr, + "Error writing to stderr: %s", + strerror(errno)); + return; + } + } + } + } + if (channel && ssh_channel_is_closed(channel)) { + ssh_channel_free(channel); + channel = NULL; + } + } while (ret == EINTR || ret == SSH_EINTR); + } +} + +static void forwarding(ssh_session session) +{ + ssh_channel channel; + int r; + channel = ssh_channel_new(session); + r = ssh_channel_open_forward(channel, desthost, atoi(port), "localhost", 22); + if (r < 0) { + printf("error forwarding port : %s\n", ssh_get_error(session)); + return; + } + select_loop(session, channel); +} + +static int client(ssh_session session) +{ + int auth = 0; + char *banner = NULL; + int state; + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) + return -1; + ssh_options_parse_config(session, NULL); + + if (ssh_connect(session)) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + return -1; + } + state = verify_knownhost(session); + if (state != 0) + return -1; + ssh_userauth_none(session, NULL); + banner = ssh_get_issue_banner(session); + if (banner) { + printf("%s\n", banner); + free(banner); + } + auth = authenticate_console(session); + if (auth != SSH_AUTH_SUCCESS) { + return -1; + } + forwarding(session); + return 0; +} + +#ifdef WITH_PCAP +ssh_pcap_file pcap; +void set_pcap(ssh_session session); +void set_pcap(ssh_session session) +{ + if (!pcap_file) + return; + pcap = ssh_pcap_file_new(); + if (ssh_pcap_file_open(pcap, pcap_file) == SSH_ERROR) { + printf("Error opening pcap file\n"); + ssh_pcap_file_free(pcap); + pcap = NULL; + return; + } + ssh_set_pcap_file(session, pcap); +} + +void cleanup_pcap(void); +void cleanup_pcap(void) +{ + ssh_pcap_file_free(pcap); + pcap = NULL; +} +#endif + +int main(int argc, char **argv) +{ + ssh_session session = NULL; + + session = ssh_new(); + + if (ssh_options_getopt(session, &argc, argv)) { + fprintf(stderr, + "error parsing command line :%s\n", + ssh_get_error(session)); + usage(); + } + opts(argc, argv); +#ifdef WITH_PCAP + set_pcap(session); +#endif + client(session); + + ssh_disconnect(session); + ssh_free(session); +#ifdef WITH_PCAP + cleanup_pcap(); +#endif + + ssh_finalize(); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/include/CMakeLists.txt b/src/libs/libssh-0.12.2/include/CMakeLists.txt new file mode 100644 index 000000000000..a01b5298e92f --- /dev/null +++ b/src/libs/libssh-0.12.2/include/CMakeLists.txt @@ -0,0 +1,3 @@ +project(libssh-headers-x C) + +add_subdirectory(libssh) diff --git a/src/libs/libssh-0.12.2/include/libssh/CMakeLists.txt b/src/libs/libssh-0.12.2/include/libssh/CMakeLists.txt new file mode 100644 index 000000000000..109e6837a1c8 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/CMakeLists.txt @@ -0,0 +1,53 @@ +project(libssh-headers C) + +set(libssh_HDRS + callbacks.h + libssh.h + ssh2.h + legacy.h + libsshpp.hpp +) + +if (WITH_SFTP) + set(libssh_HDRS + ${libssh_HDRS} + sftp.h + ) +endif (WITH_SFTP) + +if (WITH_SERVER) + set(libssh_HDRS + ${libssh_HDRS} + server.h + ) + + if (WITH_SFTP) + set(libssh_HDRS + ${libssh_HDRS} + sftpserver.h + ) + endif (WITH_SFTP) +endif (WITH_SERVER) + +if (WITH_FIDO2) + set(libssh_HDRS + ${libssh_HDRS} + sk_api.h + ) +endif (WITH_FIDO2) + +install( + FILES + ${libssh_HDRS} + DESTINATION + ${CMAKE_INSTALL_INCLUDEDIR}/${APPLICATION_NAME} + COMPONENT + headers +) + +configure_file(${CMAKE_CURRENT_SOURCE_DIR}/libssh_version.h.cmake + ${libssh_BINARY_DIR}/include/libssh/libssh_version.h + @ONLY) +install(FILES ${libssh_BINARY_DIR}/include/libssh/libssh_version.h + DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/${APPLICATION_NAME} + COMPONENT headers) diff --git a/src/libs/libssh-0.12.2/include/libssh/agent.h b/src/libs/libssh-0.12.2/include/libssh/agent.h new file mode 100644 index 000000000000..caf8d3e263d4 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/agent.h @@ -0,0 +1,126 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef __AGENT_H +#define __AGENT_H + +#include "libssh/libssh.h" + +/* Messages for the authentication agent connection. */ +#define SSH_AGENTC_REQUEST_RSA_IDENTITIES 1 +#define SSH_AGENT_RSA_IDENTITIES_ANSWER 2 +#define SSH_AGENTC_RSA_CHALLENGE 3 +#define SSH_AGENT_RSA_RESPONSE 4 +#define SSH_AGENT_FAILURE 5 +#define SSH_AGENT_SUCCESS 6 +#define SSH_AGENTC_ADD_RSA_IDENTITY 7 +#define SSH_AGENTC_REMOVE_RSA_IDENTITY 8 +#define SSH_AGENTC_REMOVE_ALL_RSA_IDENTITIES 9 + +/* private OpenSSH extensions for SSH2 */ +#define SSH2_AGENTC_REQUEST_IDENTITIES 11 +#define SSH2_AGENT_IDENTITIES_ANSWER 12 +#define SSH2_AGENTC_SIGN_REQUEST 13 +#define SSH2_AGENT_SIGN_RESPONSE 14 +#define SSH2_AGENTC_ADD_IDENTITY 17 +#define SSH2_AGENTC_REMOVE_IDENTITY 18 +#define SSH2_AGENTC_REMOVE_ALL_IDENTITIES 19 + +/* smartcard */ +#define SSH_AGENTC_ADD_SMARTCARD_KEY 20 +#define SSH_AGENTC_REMOVE_SMARTCARD_KEY 21 + +/* lock/unlock the agent */ +#define SSH_AGENTC_LOCK 22 +#define SSH_AGENTC_UNLOCK 23 + +/* add key with constraints */ +#define SSH_AGENTC_ADD_RSA_ID_CONSTRAINED 24 +#define SSH2_AGENTC_ADD_ID_CONSTRAINED 25 +#define SSH_AGENTC_ADD_SMARTCARD_KEY_CONSTRAINED 26 + +#define SSH_AGENT_CONSTRAIN_LIFETIME 1 +#define SSH_AGENT_CONSTRAIN_CONFIRM 2 + +/* extended failure messages */ +#define SSH2_AGENT_FAILURE 30 + +/* additional error code for ssh.com's ssh-agent2 */ +#define SSH_COM_AGENT2_FAILURE 102 + +#define SSH_AGENT_OLD_SIGNATURE 0x01 +/* Signature flags from draft-miller-ssh-agent-02 */ +#define SSH_AGENT_RSA_SHA2_256 0x02 +#define SSH_AGENT_RSA_SHA2_512 0x04 + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_agent_struct { + struct ssh_socket_struct *sock; + ssh_buffer ident; + unsigned int count; + ssh_channel channel; +}; + +/* agent.c */ +/** + * @brief Create a new ssh agent structure. + * + * @return An allocated ssh agent structure or NULL on error. + */ +struct ssh_agent_struct *ssh_agent_new(struct ssh_session_struct *session); + +void ssh_agent_close(struct ssh_agent_struct *agent); + +/** + * @brief Free an allocated ssh agent structure. + * + * @param agent The ssh agent structure to free. + */ +void ssh_agent_free(struct ssh_agent_struct *agent); + +/** + * @brief Check if the ssh agent is running. + * + * @param session The ssh session to check for the agent. + * + * @return 1 if it is running, 0 if not. + */ +int ssh_agent_is_running(struct ssh_session_struct *session); + +uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session); + +ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, + char **comment); + +ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, + char **comment); + +ssh_string ssh_agent_sign_data(ssh_session session, + const ssh_key pubkey, + struct ssh_buffer_struct *data); + +#ifdef __cplusplus +} +#endif + +#endif /* __AGENT_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/auth.h b/src/libs/libssh-0.12.2/include/libssh/auth.h new file mode 100644 index 000000000000..6c630292fc2d --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/auth.h @@ -0,0 +1,113 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef AUTH_H_ +#define AUTH_H_ +#include "config.h" +#include "libssh/callbacks.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner); +SSH_PACKET_CALLBACK(ssh_packet_userauth_failure); +SSH_PACKET_CALLBACK(ssh_packet_userauth_success); +SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response); + +/** @internal + * kdbint structure must be shared with message.c + * and server.c + */ +struct ssh_kbdint_struct { + uint32_t nprompts; + uint32_t nanswers; + char *name; + char *instruction; + char **prompts; + unsigned char *echo; /* bool array */ + char **answers; +}; +typedef struct ssh_kbdint_struct* ssh_kbdint; + +ssh_kbdint ssh_kbdint_new(void); +void ssh_kbdint_clean(ssh_kbdint kbd); +void ssh_kbdint_free(ssh_kbdint kbd); + +/** @internal + * States of authentication in the client-side. They describe + * what was the last response from the server + */ +enum ssh_auth_state_e { + /** No authentication asked */ + SSH_AUTH_STATE_NONE = 0, + /** Last authentication response was a partial success */ + SSH_AUTH_STATE_PARTIAL, + /** Last authentication response was a success */ + SSH_AUTH_STATE_SUCCESS, + /** Last authentication response was failed */ + SSH_AUTH_STATE_FAILED, + /** Last authentication was erroneous */ + SSH_AUTH_STATE_ERROR, + /** Last state was a keyboard-interactive ask for info */ + SSH_AUTH_STATE_INFO, + /** Last state was a public key accepted for authentication */ + SSH_AUTH_STATE_PK_OK, + /** We asked for a keyboard-interactive authentication */ + SSH_AUTH_STATE_KBDINT_SENT, + /** We have sent an userauth request with gssapi-with-mic */ + SSH_AUTH_STATE_GSSAPI_REQUEST_SENT, + /** We are exchanging tokens until authentication */ + SSH_AUTH_STATE_GSSAPI_TOKEN, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_MIC_SENT, + /** We have offered a pubkey to check if it is supported */ + SSH_AUTH_STATE_PUBKEY_OFFER_SENT, + /** We have sent pubkey and signature expecting to be authenticated */ + SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + /** We have sent a password expecting to be authenticated */ + SSH_AUTH_STATE_PASSWORD_AUTH_SENT, + /** We have sent a request without auth information (method 'none') */ + SSH_AUTH_STATE_AUTH_NONE_SENT, + /** We have sent the MIC and expecting to be authenticated */ + SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT, +}; + +/** @internal + * @brief states of the authentication service request + */ +enum ssh_auth_service_state_e { + /** initial state */ + SSH_AUTH_SERVICE_NONE=0, + /** Authentication service request packet sent */ + SSH_AUTH_SERVICE_SENT, + /** Service accepted */ + SSH_AUTH_SERVICE_ACCEPTED, + /** Access to service denied (fatal) */ + SSH_AUTH_SERVICE_DENIED, +}; + +#ifdef __cplusplus +} +#endif + +#endif /* AUTH_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/bignum.h b/src/libs/libssh-0.12.2/include/libssh/bignum.h new file mode 100644 index 000000000000..37e87bc05125 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/bignum.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BIGNUM_H_ +#define BIGNUM_H_ + +#include "libssh/libcrypto.h" +#include "libssh/libgcrypt.h" +#include "libssh/libmbedcrypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +bignum ssh_make_string_bn(ssh_string string); +ssh_string ssh_make_bignum_string(bignum num); +ssh_string ssh_make_padded_bignum_string(bignum num, size_t pad_len); +void ssh_print_bignum(const char *which, const_bignum num); + +#ifdef __cplusplus +} +#endif + +#endif /* BIGNUM_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/bind.h b/src/libs/libssh-0.12.2/include/libssh/bind.h new file mode 100644 index 000000000000..a848003e4072 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/bind.h @@ -0,0 +1,68 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BIND_H_ +#define BIND_H_ + +#include "libssh/priv.h" +#include "libssh/kex.h" +#include "libssh/session.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_bind_struct { + struct ssh_common_struct common; /* stuff common to ssh_bind and ssh_session */ + struct ssh_bind_callbacks_struct *bind_callbacks; + void *bind_callbacks_userdata; + + struct ssh_poll_handle_struct *poll; + /* options */ + char *wanted_methods[SSH_KEX_METHODS]; + char *banner; + char *ecdsakey; + char *rsakey; + char *ed25519key; + ssh_key ecdsa; + ssh_key rsa; + ssh_key ed25519; + char *bindaddr; + socket_t bindfd; + unsigned int bindport; + int blocking; + int toaccept; + bool config_processed; + char *config_dir; + char *pubkey_accepted_key_types; + char* moduli_file; + int rsa_min_size; + bool gssapi_key_exchange; + char *gssapi_key_exchange_algs; +}; + +struct ssh_poll_handle_struct *ssh_bind_get_poll(struct ssh_bind_struct + *sshbind); + +#ifdef __cplusplus +} +#endif + +#endif /* BIND_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/bind_config.h b/src/libs/libssh-0.12.2/include/libssh/bind_config.h new file mode 100644 index 000000000000..54346d8eb16c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/bind_config.h @@ -0,0 +1,83 @@ +/* + * bind_config.h - Parse the SSH server configuration file + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef BIND_CONFIG_H_ +#define BIND_CONFIG_H_ + +#include "libssh/server.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_bind_config_opcode_e { + /* Known but not allowed in Match block */ + BIND_CFG_NOT_ALLOWED_IN_MATCH = -4, + /* Unknown opcode */ + BIND_CFG_UNKNOWN = -3, + /* Known and not applicable to libssh */ + BIND_CFG_NA = -2, + /* Known but not supported by current libssh version */ + BIND_CFG_UNSUPPORTED = -1, + BIND_CFG_INCLUDE, + BIND_CFG_HOSTKEY, + BIND_CFG_LISTENADDRESS, + BIND_CFG_PORT, + BIND_CFG_LOGLEVEL, + BIND_CFG_CIPHERS, + BIND_CFG_MACS, + BIND_CFG_KEXALGORITHMS, + BIND_CFG_MATCH, + BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES, + BIND_CFG_HOSTKEY_ALGORITHMS, + BIND_CFG_REQUIRED_RSA_SIZE, + + BIND_CFG_MAX /* Keep this one last in the list */ +}; + +/* @brief Parse configuration file and set the options to the given ssh_bind + * + * @params[in] sshbind The ssh_bind context to be configured + * @params[in] filename The path to the configuration file + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_bind_config_parse_file(ssh_bind sshbind, const char *filename); + +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input); + +#ifdef __cplusplus +} +#endif + +#endif /* BIND_CONFIG_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/blf.h b/src/libs/libssh-0.12.2/include/libssh/blf.h new file mode 100644 index 000000000000..71928a7d261c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/blf.h @@ -0,0 +1,93 @@ +/* $OpenBSD: blf.h,v 1.8 2021/11/29 01:04:45 djm Exp $ */ +/* + * Blowfish - a fast block cipher designed by Bruce Schneier + * + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#ifndef _BLF_H_ +#define _BLF_H_ + +//#include "includes.h" + +#if !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) + +/* Schneier specifies a maximum key length of 56 bytes. + * This ensures that every key bit affects every cipher + * bit. However, the subkeys can hold up to 72 bytes. + * Warning: For normal blowfish encryption only 56 bytes + * of the key affect all cipherbits. + */ + +#define BLF_N 16 /* Number of Subkeys */ +#define BLF_MAXKEYLEN ((BLF_N-2)*4) /* 448 bits */ +#define BLF_MAXUTILIZED ((BLF_N+2)*4) /* 576 bits */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Blowfish context */ +typedef struct BlowfishContext { + uint32_t S[4][256]; /* S-Boxes */ + uint32_t P[BLF_N + 2]; /* Subkeys */ +} ssh_blf_ctx; + +/* Raw access to customized Blowfish + * blf_key is just: + * Blowfish_initstate( state ) + * Blowfish_expand0state( state, key, keylen ) + */ + +void Blowfish_encipher(ssh_blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_decipher(ssh_blf_ctx *, uint32_t *, uint32_t *); +void Blowfish_initstate(ssh_blf_ctx *); +void Blowfish_expand0state(ssh_blf_ctx *, const uint8_t *, uint16_t); +void Blowfish_expandstate +(ssh_blf_ctx *, const uint8_t *, uint16_t, const uint8_t *, uint16_t); + +/* Standard Blowfish */ + +void ssh_blf_key(ssh_blf_ctx *, const uint8_t *, uint16_t); +void ssh_blf_enc(ssh_blf_ctx *, uint32_t *, uint16_t); +void ssh_blf_dec(ssh_blf_ctx *, uint32_t *, uint16_t); + +void ssh_blf_ecb_encrypt(ssh_blf_ctx *, uint8_t *, uint32_t); +void ssh_blf_ecb_decrypt(ssh_blf_ctx *, uint8_t *, uint32_t); + +void ssh_blf_cbc_encrypt(ssh_blf_ctx *, uint8_t *, uint8_t *, uint32_t); +void ssh_blf_cbc_decrypt(ssh_blf_ctx *, uint8_t *, uint8_t *, uint32_t); + +/* Converts uint8_t to uint32_t */ +uint32_t Blowfish_stream2word(const uint8_t *, uint16_t , uint16_t *); + +#endif /* !defined(HAVE_BCRYPT_PBKDF) && !defined(HAVE_BLH_H) */ + +#ifdef __cplusplus +} +#endif + +#endif /* _BLF_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/buffer.h b/src/libs/libssh-0.12.2/include/libssh/buffer.h new file mode 100644 index 000000000000..108225f13504 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/buffer.h @@ -0,0 +1,83 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef BUFFER_H_ +#define BUFFER_H_ + +#include + +#include "libssh/libssh.h" + +#define SSH_BUFFER_PACK_END ((uint32_t) 0x4f65feb3) + +#ifdef __cplusplus +extern "C" { +#endif + +void ssh_buffer_set_secure(ssh_buffer buffer); +int ssh_buffer_add_ssh_string(ssh_buffer buffer, ssh_string string); +int ssh_buffer_add_u8(ssh_buffer buffer, uint8_t data); +int ssh_buffer_add_u16(ssh_buffer buffer, uint16_t data); +int ssh_buffer_add_u32(ssh_buffer buffer, uint32_t data); +int ssh_buffer_add_u64(ssh_buffer buffer, uint64_t data); + +int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len); + +void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len); +int ssh_buffer_allocate_size(struct ssh_buffer_struct *buffer, uint32_t len); +int _ssh_buffer_pack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...); +#define ssh_buffer_pack(buffer, format, ...) \ + _ssh_buffer_pack((buffer), (format), __VA_NARG__(__VA_ARGS__), __VA_ARGS__, SSH_BUFFER_PACK_END) + +int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, + const char *format, size_t argc, + va_list ap); +int _ssh_buffer_unpack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...); +#define ssh_buffer_unpack(buffer, format, ...) \ + _ssh_buffer_unpack((buffer), (format), __VA_NARG__(__VA_ARGS__), __VA_ARGS__, SSH_BUFFER_PACK_END) + +int ssh_buffer_prepend_data(ssh_buffer buffer, const void *data, uint32_t len); +int ssh_buffer_add_buffer(ssh_buffer buffer, ssh_buffer source); + +/* buffer_read_*() returns the number of bytes read, except for ssh strings */ +uint32_t ssh_buffer_get_u8(ssh_buffer buffer, uint8_t *data); +uint32_t ssh_buffer_get_u32(ssh_buffer buffer, uint32_t *data); +uint32_t ssh_buffer_get_u64(ssh_buffer buffer, uint64_t *data); + +/* ssh_buffer_get_ssh_string() is an exception. if the String read is too large or invalid, it will answer NULL. */ +ssh_string ssh_buffer_get_ssh_string(ssh_buffer buffer); + +/* ssh_buffer_pass_bytes acts as if len bytes have been read (used for padding) */ +uint32_t ssh_buffer_pass_bytes_end(ssh_buffer buffer, uint32_t len); +uint32_t ssh_buffer_pass_bytes(ssh_buffer buffer, uint32_t len); + +ssh_buffer ssh_buffer_dup(const ssh_buffer buffer); + +#ifdef __cplusplus +} +#endif + +#endif /* BUFFER_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/bytearray.h b/src/libs/libssh-0.12.2/include/libssh/bytearray.h new file mode 100644 index 000000000000..0c0690d67cb9 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/bytearray.h @@ -0,0 +1,90 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ +#ifndef _BYTEARRAY_H +#define _BYTEARRAY_H + +#define _DATA_BYTE_CONST(data, pos) \ + ((uint8_t)(((const uint8_t *)(data))[(pos)])) + +#define _DATA_BYTE(data, pos) \ + (((uint8_t *)(data))[(pos)]) + +/* + * These macros pull or push integer values from byte arrays stored in + * little-endian byte order. + */ +#define PULL_LE_U8(data, pos) \ + (_DATA_BYTE_CONST(data, pos)) + +#define PULL_LE_U16(data, pos) \ + ((uint16_t)PULL_LE_U8(data, pos) | ((uint16_t)(PULL_LE_U8(data, (pos) + 1))) << 8) + +#define PULL_LE_U32(data, pos) \ + ((uint32_t)(PULL_LE_U16(data, pos) | ((uint32_t)PULL_LE_U16(data, (pos) + 2)) << 16)) + +#define PULL_LE_U64(data, pos) \ + ((uint64_t)(PULL_LE_U32(data, pos) | ((uint64_t)PULL_LE_U32(data, (pos) + 4)) << 32)) + + +#define PUSH_LE_U8(data, pos, val) \ + (_DATA_BYTE(data, pos) = ((uint8_t)(val))) + +#define PUSH_LE_U16(data, pos, val) \ + (PUSH_LE_U8((data), (pos), (uint8_t)((uint16_t)(val) & 0xff)), PUSH_LE_U8((data), (pos) + 1, (uint8_t)((uint16_t)(val) >> 8))) + +#define PUSH_LE_U32(data, pos, val) \ + (PUSH_LE_U16((data), (pos), (uint16_t)((uint32_t)(val) & 0xffff)), PUSH_LE_U16((data), (pos) + 2, (uint16_t)((uint32_t)(val) >> 16))) + +#define PUSH_LE_U64(data, pos, val) \ + (PUSH_LE_U32((data), (pos), (uint32_t)((uint64_t)(val) & 0xffffffff)), PUSH_LE_U32((data), (pos) + 4, (uint32_t)((uint64_t)(val) >> 32))) + + + +/* + * These macros pull or push integer values from byte arrays stored in + * big-endian byte order (network byte order). + */ +#define PULL_BE_U8(data, pos) \ + (_DATA_BYTE_CONST(data, pos)) + +#define PULL_BE_U16(data, pos) \ + ((((uint16_t)(PULL_BE_U8(data, pos))) << 8) | (uint16_t)PULL_BE_U8(data, (pos) + 1)) + +#define PULL_BE_U32(data, pos) \ + ((((uint32_t)PULL_BE_U16(data, pos)) << 16) | (uint32_t)(PULL_BE_U16(data, (pos) + 2))) + +#define PULL_BE_U64(data, pos) \ + ((((uint64_t)PULL_BE_U32(data, pos)) << 32) | (uint64_t)(PULL_BE_U32(data, (pos) + 4))) + + + +#define PUSH_BE_U8(data, pos, val) \ + (_DATA_BYTE(data, pos) = ((uint8_t)(val))) + +#define PUSH_BE_U16(data, pos, val) \ + (PUSH_BE_U8((data), (pos), (uint8_t)(((uint16_t)(val)) >> 8)), PUSH_BE_U8((data), (pos) + 1, (uint8_t)((val) & 0xff))) + +#define PUSH_BE_U32(data, pos, val) \ + (PUSH_BE_U16((data), (pos), (uint16_t)(((uint32_t)(val)) >> 16)), PUSH_BE_U16((data), (pos) + 2, (uint16_t)((val) & 0xffff))) + +#define PUSH_BE_U64(data, pos, val) \ + (PUSH_BE_U32((data), (pos), (uint32_t)(((uint64_t)(val)) >> 32)), PUSH_BE_U32((data), (pos) + 4, (uint32_t)((val) & 0xffffffff))) + +#endif /* _BYTEARRAY_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/callbacks.h b/src/libs/libssh-0.12.2/include/libssh/callbacks.h new file mode 100644 index 000000000000..b719532a5449 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/callbacks.h @@ -0,0 +1,1360 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* callback.h + * This file includes the public declarations for the libssh callback mechanism + */ + +#ifndef _SSH_CALLBACK_H +#define _SSH_CALLBACK_H + +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * @defgroup libssh_callbacks The libssh callbacks + * @ingroup libssh + * + * Callback which can be replaced in libssh. + * + * @{ + */ + +/** @internal + * @brief callback to process simple codes + * @param code value to transmit + * @param user Userdata to pass in callback + */ +typedef void (*ssh_callback_int) (int code, void *user); + +/** @internal + * @brief callback for data received messages. + * @param data data retrieved from the socket or stream + * @param len number of bytes available from this stream + * @param user user-supplied pointer sent along with all callback messages + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef size_t (*ssh_callback_data) (const void *data, size_t len, void *user); + +typedef void (*ssh_callback_int_int) (int code, int errno_code, void *user); + +typedef int (*ssh_message_callback) (ssh_session, ssh_message message, void *user); +typedef int (*ssh_channel_callback_int) (ssh_channel channel, int code, void *user); +typedef int (*ssh_channel_callback_data) (ssh_channel channel, int code, void *data, size_t len, void *user); + +/** + * @brief SSH log callback. All logging messages will go through this callback + * @param session Current session handler + * @param priority Priority of the log, the smaller being the more important + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_log_callback) (ssh_session session, int priority, + const char *message, void *userdata); + +/** + * @brief SSH log callback. + * + * All logging messages will go through this callback. + * + * @param priority Priority of the log, the smaller being the more important. + * + * @param function The function name calling the logging functions. + * + * @param buffer The actual message + * + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_logging_callback) (int priority, + const char *function, + const char *buffer, + void *userdata); + +/** + * @brief SSH Connection status callback. + * @param session Current session handler + * @param status Percentage of connection status, going from 0.0 to 1.0 + * once connection is done. + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_status_callback) (ssh_session session, float status, + void *userdata); + +/** + * @brief SSH global request callback. All global request will go through this + * callback. + * @param session Current session handler + * @param message the actual message + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_global_request_callback) (ssh_session session, + ssh_message message, void *userdata); + +/** + * @brief SSH connect status callback. These are functions that report the + * status of the connection i,e. a function indicating the completed percentage + * of the connection + * steps. + * @param userdata Userdata to be passed to the callback function. + * @param status Percentage of connection status, going from 0.0 to 1.0 + * once connection is done. + */ +typedef void (*ssh_connect_status_callback)(void *userdata, float status); + +/** + * @brief Handles an SSH new channel open X11 request. This happens when the server + * sends back an X11 connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @param originator_address IP address of the machine who sent the request + * @param originator_port port number of the machine who sent the request + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_x11_callback) (ssh_session session, + const char * originator_address, int originator_port, void *userdata); + +/** + * @brief Handles an SSH new channel open "auth-agent" request. This happens when the server + * sends back an "auth-agent" connection attempt. This is a client-side API + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_auth_agent_callback) (ssh_session session, + void *userdata); + +/** + * @brief Handles an SSH new channel open "forwarded-tcpip" request. This + * happens when the server forwards an incoming TCP connection on a port it was + * previously requested to listen on. This is a client-side API + * @param session current session handler + * @param destination_address the address that the TCP connection connected to + * @param destination_port the port that the TCP connection connected to + * @param originator_address the originator IP address + * @param originator_port the originator port + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the + * application. + */ +typedef ssh_channel (*ssh_channel_open_request_forwarded_tcpip_callback) (ssh_session session, + const char *destination_address, int destination_port, + const char *originator_address, int originator_port, + void *userdata); + +/** + * The structure to replace libssh functions with appropriate callbacks. + */ +struct ssh_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called if e.g. a keyphrase is needed. + */ + ssh_auth_callback auth_function; + /** + * This function will be called each time a loggable event happens. + */ + ssh_log_callback log_function; + /** + * This function gets called during connection time to indicate the + * percentage of connection steps completed. + */ + ssh_connect_status_callback connect_status_function; + /** + * This function will be called each time a global request is received. + */ + ssh_global_request_callback global_request_function; + /** This function will be called when an incoming X11 request is received. + */ + ssh_channel_open_request_x11_callback channel_open_request_x11_function; + /** This function will be called when an incoming "auth-agent" request is received. + */ + ssh_channel_open_request_auth_agent_callback channel_open_request_auth_agent_function; + /** + * This function will be called when an incoming "forwarded-tcpip" + * request is received. + */ + ssh_channel_open_request_forwarded_tcpip_callback channel_open_request_forwarded_tcpip_function; +}; +typedef struct ssh_callbacks_struct *ssh_callbacks; + +/** These are callbacks used specifically in SSH servers. + */ + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param password Password used for authentication + * @param userdata Userdata to be passed to the callback function. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. + */ +typedef int (*ssh_auth_password_callback) (ssh_session session, const char *user, const char *password, + void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the + * "none" method which is anonymous or passwordless. + * @param session Current session handler + * @param user User that wants to authenticate + * @param userdata Userdata to be passed to the callback function. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. + */ +typedef int (*ssh_auth_none_callback) (ssh_session session, const char *user, void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticate user with the + * "gssapi-with-mic" or "gssapi-keyex" method. This callback is dispatched + * after the server has already verified the authenticity of the principal. + * @param session Current session handler + * @param user Username of the user (can be spoofed) + * @param principal Authenticated principal of the user, including realm. + * @param userdata Userdata to be passed to the callback function. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. + * @warning Using this callback, implementations should verify that the principal + * is allowed to log in as the local user, e.g. by checking that the username + * matches the principal in some way. + */ +typedef int (*ssh_auth_gssapi_mic_callback) (ssh_session session, const char *user, const char *principal, + void *userdata); + +/** + * @brief SSH authentication callback. + * @param session Current session handler + * @param user User that wants to authenticate + * @param pubkey public key used for authentication + * @param signature_state `SSH_PUBLICKEY_STATE_NONE` if the key is not signed + * (simple public key probe), `SSH_PUBLICKEY_STATE_VALID` if the signature is + * valid. Others values should be replied with a `SSH_AUTH_DENIED`. + * @param userdata Userdata to be passed to the callback function. + * @returns `SSH_AUTH_SUCCESS` Authentication is accepted. + * @returns `SSH_AUTH_PARTIAL` Partial authentication, more authentication means + * are needed. + * @returns `SSH_AUTH_DENIED` Authentication failed. + */ +typedef int (*ssh_auth_pubkey_callback) (ssh_session session, const char *user, struct ssh_key_struct *pubkey, + char signature_state, void *userdata); + +/** + * @brief SSH authentication callback. Tries to authenticates user with the "keyboard-interactive" method + * @param message Current message + * @param session Current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns SSH_AUTH_SUCCESS Authentication is accepted. + * @returns SSH_AUTH_INFO More info required for authentication. + * @returns SSH_AUTH_PARTIAL Partial authentication, more authentication means are needed. + * @returns SSH_AUTH_DENIED Authentication failed. +*/ +typedef int (*ssh_auth_kbdint_callback) (ssh_message message, ssh_session session, void *userdata); + +/** + * @brief Handles an SSH service request + * @param session current session handler + * @param service name of the service (e.g. "ssh-userauth") requested + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the request is to be allowed + * @returns -1 if the request should not be allowed + */ +typedef int (*ssh_service_request_callback) (ssh_session session, const char *service, void *userdata); + +/** + * @brief Handles an SSH new channel open session request + * @param session current session handler + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the application. + */ +typedef ssh_channel (*ssh_channel_open_request_session_callback) (ssh_session session, void *userdata); + +/** + * @brief handle the beginning of a GSSAPI authentication, server side. + * Callback should select the oid and also acquire the server credential. + * @param session current session handler + * @param user the username of the client + * @param n_oid number of available oids + * @param oids OIDs provided by the client + * @returns an ssh_string containing the chosen OID, that's supported by both + * client and server. + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef ssh_string (*ssh_gssapi_select_oid_callback) (ssh_session session, const char *user, + int n_oid, ssh_string *oids, void *userdata); + +/** + * @brief handle the negotiation of a security context, server side. + * @param session current session handler + * @param[in] input_token input token provided by client + * @param[out] output_token output of the gssapi accept_sec_context method, + * NULL after completion. + * @returns `SSH_OK` if the token was generated correctly or accept_sec_context + * returned GSS_S_COMPLETE + * @returns `SSH_ERROR` in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_accept_sec_ctx_callback) (ssh_session session, + ssh_string input_token, ssh_string *output_token, void *userdata); + +/** + * @brief Verify and authenticates a MIC, server side. + * @param session current session handler + * @param[in] mic input mic to be verified provided by client + * @param[in] mic_buffer buffer of data to be signed. + * @param[in] mic_buffer_size size of mic_buffer + * @returns `SSH_OK` if the MIC was authenticated correctly + * @returns `SSH_ERROR` in case of error + * @warning It is not necessary to fill this callback in if libssh is linked + * with libgssapi. + */ +typedef int (*ssh_gssapi_verify_mic_callback) (ssh_session session, + ssh_string mic, void *mic_buffer, size_t mic_buffer_size, void *userdata); + +/** + * @brief Handles an SSH new channel open "direct-tcpip" request. This + * happens when the client forwards an incoming TCP connection on a port it + * wants to forward to the destination. This is a server-side API + * @param session current session handler + * @param destination_address the address that the TCP connection connected to + * @param destination_port the port that the TCP connection connected to + * @param originator_address the originator IP address + * @param originator_port the originator port + * @param userdata Userdata to be passed to the callback function. + * @returns a valid ssh_channel handle if the request is to be allowed + * @returns NULL if the request should not be allowed + * @warning The channel pointer returned by this callback must be closed by the + * application. + */ +typedef ssh_channel (*ssh_channel_open_request_direct_tcpip_callback)( + ssh_session session, + const char *destination_address, + int destination_port, + const char *originator_address, + int originator_port, + void *userdata); + +/** + * This structure can be used to implement a libssh server, with appropriate callbacks. + */ + +struct ssh_server_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** This function gets called when a client tries to authenticate through + * password method. + */ + ssh_auth_password_callback auth_password_function; + + /** This function gets called when a client tries to authenticate through + * none method. + */ + ssh_auth_none_callback auth_none_function; + + /** This function gets called when a client tries to authenticate through + * gssapi-mic method. + */ + ssh_auth_gssapi_mic_callback auth_gssapi_mic_function; + + /** this function gets called when a client tries to authenticate or offer + * a public key. + */ + ssh_auth_pubkey_callback auth_pubkey_function; + + /** This functions gets called when a service request is issued by the + * client + */ + ssh_service_request_callback service_request_function; + /** This functions gets called when a new channel request is issued by + * the client + */ + ssh_channel_open_request_session_callback channel_open_request_session_function; + /** This function will be called when a new gssapi authentication is attempted. + * This should select the oid and acquire credential for the server. + */ + ssh_gssapi_select_oid_callback gssapi_select_oid_function; + /** This function will be called when a gssapi token comes in. + */ + ssh_gssapi_accept_sec_ctx_callback gssapi_accept_sec_ctx_function; + /** This function will be called when a MIC needs to be verified. + */ + ssh_gssapi_verify_mic_callback gssapi_verify_mic_function; + /** + * This function will be called when an incoming "direct-tcpip" + * request is received. + */ + ssh_channel_open_request_direct_tcpip_callback + channel_open_request_direct_tcpip_function; + + /** This function gets called when a client tries to authenticate through + * keyboard interactive method. + */ + ssh_auth_kbdint_callback auth_kbdint_function; + +}; +typedef struct ssh_server_callbacks_struct *ssh_server_callbacks; + +/** + * @brief Set the session server callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for user authentication, new channels and requests. + * + * Note, that the structure is not copied to the session structure so it needs + * to be valid for the whole session lifetime. + * + * @code + * struct ssh_server_callbacks_struct cb = { + * .userdata = data, + * .auth_password_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_server_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + */ +LIBSSH_API int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb); + +/** + * These are the callbacks exported by the socket structure + * They are called by the socket module when a socket event appears + */ +struct ssh_socket_callbacks_struct { + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This function will be called each time data appears on socket. The data + * not consumed will appear on the next data event. + */ + ssh_callback_data data; + /** This function will be called each time a controlflow state changes, i.e. + * the socket is available for reading or writing. + */ + ssh_callback_int controlflow; + /** This function will be called each time an exception appears on socket. An + * exception can be a socket problem (timeout, ...) or an end-of-file. + */ + ssh_callback_int_int exception; + /** This function is called when the ssh_socket_connect was used on the socket + * on nonblocking state, and the connection succeeded. + */ + ssh_callback_int_int connected; +}; +typedef struct ssh_socket_callbacks_struct *ssh_socket_callbacks; + +#define SSH_SOCKET_FLOW_WRITEWILLBLOCK 1 +#define SSH_SOCKET_FLOW_WRITEWONTBLOCK 2 + +#define SSH_SOCKET_EXCEPTION_EOF 1 +#define SSH_SOCKET_EXCEPTION_ERROR 2 + +#define SSH_SOCKET_CONNECTED_OK 1 +#define SSH_SOCKET_CONNECTED_ERROR 2 +#define SSH_SOCKET_CONNECTED_TIMEOUT 3 + +/** + * @brief Initializes an ssh_callbacks_struct + * A call to this macro is mandatory when you have set a new + * ssh_callback_struct structure. Its goal is to maintain the binary + * compatibility with future versions of libssh as the structure + * evolves with time. + */ +#define ssh_callbacks_init(p) do {\ + (p)->size=sizeof(*(p)); \ +} while(0); + +/** + * @internal + * @brief tests if a callback can be called without crash + * verifies that the struct size if big enough + * verifies that the callback pointer exists + * @param p callback pointer + * @param c callback name + * @returns nonzero if callback can be called + */ +#define ssh_callbacks_exists(p,c) (\ + (p != NULL) && ( (char *)&((p)-> c) < (char *)(p) + (p)->size ) && \ + ((p)-> c != NULL) \ + ) + +/** + * @internal + * + * @brief Iterate through a list of callback structures + * + * This tests for their validity and executes them. The userdata argument is + * automatically passed through. + * + * @param list list of callbacks + * + * @param cbtype type of the callback + * + * @param c callback name + * + * @param va_args parameters to be passed + */ +#define ssh_callbacks_execute_list(list, cbtype, c, ...) \ + do { \ + struct ssh_iterator *i = ssh_list_get_iterator(list); \ + cbtype cb; \ + while (i != NULL){ \ + cb = ssh_iterator_value(cbtype, i); \ + if (ssh_callbacks_exists(cb, c)) \ + cb-> c (__VA_ARGS__, cb->userdata); \ + i = i->next; \ + } \ + } while(0) + +/** + * @internal + * + * @brief iterate through a list of callback structures. + * + * This tests for their validity and give control back to the calling code to + * execute them. Caller can decide to break the loop or continue executing the + * callbacks with different parameters + * + * @code + * ssh_callbacks_iterate(channel->callbacks, ssh_channel_callbacks, + * channel_eof_function){ + * rc = ssh_callbacks_iterate_exec(session, channel); + * if (rc != SSH_OK){ + * break; + * } + * } + * ssh_callbacks_iterate_end(); + * @endcode + */ +#define ssh_callbacks_iterate(_cb_list, _cb_type, _cb_name) \ + do { \ + struct ssh_iterator *_cb_i = ssh_list_get_iterator(_cb_list); \ + _cb_type _cb; \ + for (; _cb_i != NULL; _cb_i = _cb_i->next) { \ + _cb = ssh_iterator_value(_cb_type, _cb_i); \ + if (ssh_callbacks_exists(_cb, _cb_name)) + +#define ssh_callbacks_iterate_exec(_cb_name, ...) \ + _cb->_cb_name(__VA_ARGS__, _cb->userdata) + +#define ssh_callbacks_iterate_end() \ + } \ + } while(0) + +/** @brief Prototype for a packet callback, to be called when a new packet + * arrives + * @param session The current session of the packet + * @param type packet type (see ssh2.h) + * @param packet buffer containing the packet, excluding size, type and padding + * fields + * @param user user argument to the callback + * and are called each time a packet shows up + * @returns `SSH_PACKET_USED` Packet was parsed and used + * @returns `SSH_PACKET_NOT_USED` Packet was not used or understood, processing + * must continue + */ +typedef int (*ssh_packet_callback) (ssh_session session, uint8_t type, ssh_buffer packet, void *user); + +/** return values for a ssh_packet_callback */ +/** Packet was used and should not be parsed by another callback */ +#define SSH_PACKET_USED 1 +/** Packet was not used and should be passed to any other callback + * available */ +#define SSH_PACKET_NOT_USED 2 + + +/** @brief This macro declares a packet callback handler + * @code + * SSH_PACKET_CALLBACK(mycallback){ + * ... + * } + * @endcode + */ +#define SSH_PACKET_CALLBACK(name) \ + int name (ssh_session session, uint8_t type, ssh_buffer packet, void *user) + +struct ssh_packet_callbacks_struct { + /** Index of the first packet type being handled */ + uint8_t start; + /** Number of packets being handled by this callback struct */ + uint8_t n_callbacks; + /** A pointer to n_callbacks packet callbacks */ + ssh_packet_callback *callbacks; + /** + * User-provided data. User is free to set anything he wants here + */ + void *user; +}; + +typedef struct ssh_packet_callbacks_struct *ssh_packet_callbacks; + +/** + * @brief Set the session callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for auth, logging and status. + * + * Note, that the callback structure is not copied into the session so it needs + * to be valid for the whole session lifetime. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_callbacks(session, &cb); + * @endcode + * + * @param session The session to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + */ +LIBSSH_API int ssh_set_callbacks(ssh_session session, ssh_callbacks cb); + +/** + * @brief SSH channel data callback. Called when data is available on a channel + * @param session Current session handler + * @param channel the actual channel + * @param data the data that has been read on the channel + * @param len the length of the data + * @param is_stderr is 0 for stdout or 1 for stderr + * @param userdata Userdata to be passed to the callback function. + * @returns number of bytes processed by the callee. The remaining bytes will + * be sent in the next callback message, when more data is available. + */ +typedef int (*ssh_channel_data_callback) (ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); + +/** + * @brief SSH channel eof callback. Called when a channel receives EOF + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_eof_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel close callback. Called when a channel is closed by remote peer + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_close_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH channel signal callback. Called when a channel has received a signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +/** + * @brief SSH channel exit status callback. Called when a channel has received an exit status + * @param session Current session handler + * @param channel the actual channel + * @param exit_status Exit status of the ran command + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_status_callback) (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +/** + * @brief SSH channel exit signal callback. Called when a channel has received an exit signal + * @param session Current session handler + * @param channel the actual channel + * @param signal the signal name (without the SIG prefix) + * @param core a boolean telling whether a core has been dumped or not + * @param errmsg the description of the exception + * @param lang the language of the description (format: RFC 3066) + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_exit_signal_callback) (ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +/** + * @brief SSH channel PTY request from a client. + * @param session the session + * @param channel the channel + * @param term The type of terminal emulation + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pwheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_request_callback) (ssh_session session, + ssh_channel channel, + const char *term, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Shell request from a client. + * @param session the session + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the shell request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_shell_request_callback) (ssh_session session, + ssh_channel channel, + void *userdata); +/** + * @brief SSH auth-agent-request from the client. This request is + * sent by a client when agent forwarding is available. + * Server is free to ignore this callback, no answer is expected. + * @param session the session + * @param channel the channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_auth_agent_req_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +/** + * @brief SSH X11 request from the client. This request is + * sent by a client when X11 forwarding is requested(and available). + * Server is free to ignore this callback, no answer is expected. + * @param session the session + * @param channel the channel + * @param single_connection If true, only one channel should be forwarded + * @param auth_protocol The X11 authentication method to be used + * @param auth_cookie Authentication cookie encoded hexadecimal + * @param screen_number Screen number + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_x11_req_callback) (ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata); +/** + * @brief SSH channel PTY windows change (terminal size) from a client. + * @param session the session + * @param channel the channel + * @param width width of the terminal, in characters + * @param height height of the terminal, in characters + * @param pxwidth width of the terminal, in pixels + * @param pwheight height of the terminal, in pixels + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the pty request is accepted + * @returns -1 if the request is denied + */ +typedef int (*ssh_channel_pty_window_change_callback) (ssh_session session, + ssh_channel channel, + int width, int height, + int pxwidth, int pwheight, + void *userdata); + +/** + * @brief SSH channel Exec request from a client. + * @param session the session + * @param channel the channel + * @param command the shell command to be executed + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the exec request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_exec_request_callback) (ssh_session session, + ssh_channel channel, + const char *command, + void *userdata); + +/** + * @brief SSH channel environment request from a client. + * @param session the session + * @param channel the channel + * @param env_name name of the environment value to be set + * @param env_value value of the environment value to be set + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the env request is accepted + * @returns 1 if the request is denied + * @warning some environment variables can be dangerous if changed (e.g. + * LD_PRELOAD) and should not be fulfilled. + */ +typedef int (*ssh_channel_env_request_callback) (ssh_session session, + ssh_channel channel, + const char *env_name, + const char *env_value, + void *userdata); +/** + * @brief SSH channel subsystem request from a client. + * @param session the session + * @param channel the channel + * @param subsystem the subsystem required + * @param userdata Userdata to be passed to the callback function. + * @returns 0 if the subsystem request is accepted + * @returns 1 if the request is denied + */ +typedef int (*ssh_channel_subsystem_request_callback) (ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata); + +/** + * @brief SSH channel write will not block (flow control). + * + * @param session the session + * + * @param channel the channel + * + * @param[in] bytes size of the remote window in bytes. Writing as much data + * will not block. + * + * @param[in] userdata Userdata to be passed to the callback function. + * + * @returns 0 default return value (other return codes may be added in future). + */ +typedef int (*ssh_channel_write_wontblock_callback) (ssh_session session, + ssh_channel channel, + uint32_t bytes, + void *userdata); + +/** + * @brief SSH channel open callback. Called when a channel open succeeds or fails. + * @param session Current session handler + * @param channel the actual channel + * @param is_success is 1 when the open succeeds, and 0 otherwise. + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_open_resp_callback) (ssh_session session, + ssh_channel channel, + bool is_success, + void *userdata); + +/** + * @brief SSH channel request response callback. Called when a response to the pending request is received. + * @param session Current session handler + * @param channel the actual channel + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_channel_request_resp_callback) (ssh_session session, + ssh_channel channel, + void *userdata); + +struct ssh_channel_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** + * User-provided data. User is free to set anything he wants here + */ + void *userdata; + /** + * This functions will be called when there is data available. + */ + ssh_channel_data_callback channel_data_function; + /** + * This functions will be called when the channel has received an EOF. + */ + ssh_channel_eof_callback channel_eof_function; + /** + * This functions will be called when the channel has been closed by remote + */ + ssh_channel_close_callback channel_close_function; + /** + * This functions will be called when a signal has been received + */ + ssh_channel_signal_callback channel_signal_function; + /** + * This functions will be called when an exit status has been received + */ + ssh_channel_exit_status_callback channel_exit_status_function; + /** + * This functions will be called when an exit signal has been received + */ + ssh_channel_exit_signal_callback channel_exit_signal_function; + /** + * This function will be called when a client requests a PTY + */ + ssh_channel_pty_request_callback channel_pty_request_function; + /** + * This function will be called when a client requests a shell + */ + ssh_channel_shell_request_callback channel_shell_request_function; + /** This function will be called when a client requests agent + * authentication forwarding. + */ + ssh_channel_auth_agent_req_callback channel_auth_agent_req_function; + /** This function will be called when a client requests X11 + * forwarding. + */ + ssh_channel_x11_req_callback channel_x11_req_function; + /** This function will be called when a client requests a + * window change. + */ + ssh_channel_pty_window_change_callback channel_pty_window_change_function; + /** This function will be called when a client requests a + * command execution. + */ + ssh_channel_exec_request_callback channel_exec_request_function; + /** This function will be called when a client requests an environment + * variable to be set. + */ + ssh_channel_env_request_callback channel_env_request_function; + /** This function will be called when a client requests a subsystem + * (like sftp). + */ + ssh_channel_subsystem_request_callback channel_subsystem_request_function; + /** This function will be called when the channel write is guaranteed + * not to block. + */ + ssh_channel_write_wontblock_callback channel_write_wontblock_function; + /** + * This functions will be called when the channel has received a channel open confirmation or failure. + */ + ssh_channel_open_resp_callback channel_open_response_function; + /** + * This functions will be called when the channel has received the response to the pending request. + */ + ssh_channel_request_resp_callback channel_request_response_function; +}; + +typedef struct ssh_channel_callbacks_struct *ssh_channel_callbacks; + +/** + * @brief Set the channel callback functions. + * + * This functions sets the callback structure to use your own callback + * functions for channel data and exceptions. + * + * Note, that the structure is not copied to the channel structure so it needs + * to be valid as for the whole life of the channel or until it is removed with + * ssh_remove_channel_callbacks(). + * + * @code + * struct ssh_channel_callbacks_struct cb = { + * .userdata = data, + * .channel_data_function = my_channel_data_function + * }; + * ssh_callbacks_init(&cb); + * ssh_set_channel_callbacks(channel, &cb); + * @endcode + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * @warning this function will not replace existing callbacks but set the + * new one atop of them. + */ +LIBSSH_API int ssh_set_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Add channel callback functions + * + * This function will add channel callback functions to the channel callback + * list. + * Callbacks missing from a callback structure will be probed in the next + * on the list. + * + * @param channel The channel to set the callback structure. + * + * @param cb The callback structure itself. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * + * @see ssh_set_channel_callbacks + */ +LIBSSH_API int ssh_add_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** + * @brief Remove a channel callback. + * + * The channel has been added with ssh_add_channel_callbacks or + * ssh_set_channel_callbacks in this case. + * + * @param channel The channel to remove the callback structure from. + * + * @param cb The callback structure to remove + * + * @returns `SSH_OK` on success, `SSH_ERROR` on error. + */ +LIBSSH_API int ssh_remove_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb); + +/** @} */ + +/** @addtogroup libssh_threads + * @{ + */ + +typedef int (*ssh_thread_callback) (void **lock); + +typedef unsigned long (*ssh_thread_id_callback) (void); +struct ssh_threads_callbacks_struct { + const char *type; + ssh_thread_callback mutex_init; + ssh_thread_callback mutex_destroy; + ssh_thread_callback mutex_lock; + ssh_thread_callback mutex_unlock; + ssh_thread_id_callback thread_id; +}; + +/** + * @brief Set the thread callbacks structure. + * + * This is necessary if your program is using libssh in a multithreaded fashion. + * This function must be called first, outside of any threading context (in your + * main() function for instance), before you call ssh_init(). + * + * @param[in] cb A pointer to a ssh_threads_callbacks_struct structure, which + * contains the different callbacks to be set. + * + * @returns Always returns `SSH_OK`. + * + * @see ssh_threads_callbacks_struct + * @see SSH_THREADS_PTHREAD + * @bug libgcrypt 1.6 and bigger backend does not support custom callback. + * Using anything else than pthreads here will fail. + */ +LIBSSH_API int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct + *cb); + +/** + * @brief Returns a pointer to the appropriate callbacks structure for the + * environment, to be used with ssh_threads_set_callbacks. + * + * @returns A pointer to a ssh_threads_callbacks_struct to be used with + * ssh_threads_set_callbacks. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_default(void); + +/** + * @brief Returns a pointer on the pthread threads callbacks, to be used with + * ssh_threads_set_callbacks. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void); + +/** + * @brief Get the noop threads callbacks structure + * + * This can be used with ssh_threads_set_callbacks. These callbacks do nothing + * and are being used by default. + * + * @return Always returns a valid pointer to the noop callbacks structure. + * + * @see ssh_threads_set_callbacks + */ +LIBSSH_API struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void); +/** @} */ + +/** + * @brief Set the logging callback function. + * + * @param[in] cb The callback to set. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int ssh_set_log_callback(ssh_logging_callback cb); + +/** + * @brief Get the pointer to the logging callback function. + * + * @return The pointer the the callback or NULL if none set. + */ +LIBSSH_API ssh_logging_callback ssh_get_log_callback(void); + +/** + * @brief SSH proxyjump before connection callback. Called before calling + * ssh_connect() + * @param session Jump session handler + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_jump_before_connection_callback)(ssh_session session, + void *userdata); + +/** + * @brief SSH proxyjump verify knownhost callback. Verify the host. + * If not specified default function will be used. + * @param session Jump session handler + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_jump_verify_knownhost_callback)(ssh_session session, + void *userdata); + +/** + * @brief SSH proxyjump user authentication callback. Authenticate the user. + * @param session Jump session handler + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_jump_authenticate_callback)(ssh_session session, + void *userdata); + +struct ssh_jump_callbacks_struct { + void *userdata; + ssh_jump_before_connection_callback before_connection; + ssh_jump_verify_knownhost_callback verify_knownhost; + ssh_jump_authenticate_callback authenticate; +}; + +/* Security key callbacks */ + +/* + * Forward declarations for structs that have been defined in sk_api.h. + * If you need to work with the fields inside them, please include + * libssh/sk_api.h + */ +struct sk_enroll_response; +struct sk_sign_response; +struct sk_resident_key; +struct sk_option; + +#define LIBSSH_SK_API_VERSION_MAJOR 0x000a0000 + +/** + * @brief FIDO2/U2F SK API version callback. + * + * Returns the version of the FIDO2/U2F API that the callbacks implement. + * This callback allows custom callback implementations to specify their + * SK API version for compatibility checking with libssh's security key + * interface. + * + * @details Version compatibility is determined by comparing the major version + * portion (upper 16 bits) of the returned value with SSH_SK_VERSION_MAJOR. + * + * For compatibility, implementations should return a version where: + * (returned_version & SSH_SK_VERSION_MAJOR_MASK) == SSH_SK_VERSION_MAJOR + * + * This ensures that the callbacks' SK API matches the major version expected + * by libssh, while allowing minor version differences for backward + * compatibility. + * + * @see LIBSSH_SK_API_VERSION_MAJOR Current expected major API version + * @see SSH_SK_VERSION_MAJOR_MASK Mask for extracting major version (0xffff0000) + */ +typedef uint32_t (*sk_api_version_callback)(void); + +/** + * @brief FIDO2/U2F key enrollment callback. + * + * Enrolls a new FIDO2/U2F security key credential (private key generation). + * This callback handles the creation of new FIDO2/U2F credentials, including + * both resident and non-resident keys. + * + * @param[in] alg The cryptographic algorithm to use + * @param[in] challenge Random challenge data for enrollment + * @param[in] challenge_len Length of the challenge data + * @param[in] application Application identifier (relying party ID) + * @param[in] flags Enrollment flags + * @param[in] pin PIN for user verification (may be NULL) + * @param[in] options Array of enrollment options (device path, user ID, etc.) + * @param[out] enroll_response Enrollment response containing public key, + * key handle, signature, and attestation data + * + * @returns SSH_OK on success, SSH_SK_ERR_* codes on failure. + */ +typedef int (*sk_enroll_callback)(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response); + +/** + * @brief FIDO2/U2F security key signing callback. + * + * Signs data using a FIDO2 security key credential. This callback performs + * cryptographic signing operations using previously enrolled FIDO2/U2F + * credentials. + * + * @param[in] alg The cryptographic algorithm used by the key + * @param[in] data Data to be signed + * @param[in] data_len Length of the data to sign + * @param[in] application Application identifier (relying party ID) + * @param[in] key_handle Key handle identifying the credential + * @param[in] key_handle_len Length of the key handle + * @param[in] flags Signing flags + * @param[in] pin PIN for user verification (may be NULL) + * @param[in] options Array of signing options (device path, etc.) + * @param[out] sign_response Signature response containing signature data, + * flags, and counter information + * + * @returns SSH_OK on success, SSH_SK_ERR_* codes on failure. + */ +typedef int (*sk_sign_callback)(uint32_t alg, + const uint8_t *data, + size_t data_len, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_sign_response **sign_response); + +/** + * @brief FIDO2 security key resident keys loading callback. + * + * Enumerates and loads all resident keys (discoverable credentials) stored + * on FIDO2 devices. Resident keys are credentials stored directly on + * the device itself and can be discovered without prior knowledge + * of key handles. + * + * @param[in] pin PIN for accessing resident keys (required for most operations) + * @param[in] options Array of options (device path, etc.) + * @param[out] resident_keys Array of resident key structures containing key + * data, application IDs, user information, and metadata + * @param[out] num_keys_found Number of resident keys found and loaded + * + * @returns SSH_OK on success, SSH_SK_ERR_* codes on failure. + */ +typedef int (*sk_load_resident_keys_callback)( + const char *pin, + struct sk_option **options, + struct sk_resident_key ***resident_keys, + size_t *num_keys_found); + +/** + * @brief FIDO2/U2F security key callbacks structure. + * + * This structure contains callbacks for FIDO2/U2F operations. + * It allows applications to provide custom implementations of FIDO2/U2F + * operations to override the default libfido2-based implementation. + * + * @warning These callbacks will only be called if libssh was built with + * FIDO2/U2F support enabled. (WITH_FIDO2 = ON). + */ +struct ssh_sk_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + + /** + * This callback returns the SK API version used by the callback + * implementation. + * + * @see sk_api_version_callback for detailed documentation + */ + sk_api_version_callback api_version; + + /** + * This callback enrolls a new FIDO2/U2F credential, generating + * a new key pair and optionally storing it on the device itself + * (resident keys). + * + * @see sk_enroll_callback for detailed documentation + */ + sk_enroll_callback enroll; + + /** + * This callback performs cryptographic signing operations using a + * previously enrolled FIDO2/U2F credential. + * + * @see sk_sign_callback for detailed documentation + */ + sk_sign_callback sign; + + /** + * This callback enumerates and loads all resident keys (discoverable + * credentials) stored on the FIDO2 device. + * + * @see sk_load_resident_keys_callback for detailed documentation + */ + sk_load_resident_keys_callback load_resident_keys; +}; + +typedef struct ssh_sk_callbacks_struct *ssh_sk_callbacks; + +const struct ssh_sk_callbacks_struct *ssh_sk_get_default_callbacks(void); + +#ifdef __cplusplus +} +#endif + +#endif /*_SSH_CALLBACK_H */ + +/* @} */ diff --git a/src/libs/libssh-0.12.2/include/libssh/chacha.h b/src/libs/libssh-0.12.2/include/libssh/chacha.h new file mode 100644 index 000000000000..ab3fe492846c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/chacha.h @@ -0,0 +1,48 @@ +/* $OpenBSD: chacha.h,v 1.3 2014/05/02 03:27:54 djm Exp $ */ + +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. +*/ + +#ifndef CHACHA_H +#define CHACHA_H + +struct chacha_ctx { + uint32_t input[16]; +}; + +#define CHACHA_MINKEYLEN 16 +#define CHACHA_NONCELEN 8 +#define CHACHA_CTRLEN 8 +#define CHACHA_STATELEN (CHACHA_NONCELEN+CHACHA_CTRLEN) + +#ifdef __cplusplus +extern "C" { +#endif + +void chacha_keysetup(struct chacha_ctx *x, const uint8_t *k, uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif + ; +void chacha_ivsetup(struct chacha_ctx *x, const uint8_t *iv, const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif + ; +void chacha_encrypt_bytes(struct chacha_ctx *x, const uint8_t *m, + uint8_t *c, uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* CHACHA_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/chacha20-poly1305-common.h b/src/libs/libssh-0.12.2/include/libssh/chacha20-poly1305-common.h new file mode 100644 index 000000000000..b2f0231b7292 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/chacha20-poly1305-common.h @@ -0,0 +1,54 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 Red Hat, Inc. + * + * Author: Jakub Jelen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * chacha20-poly1305.h file + * This file includes definitions needed for Chacha20-poly1305 AEAD cipher + * using different crypto backends. + */ + +#ifndef CHACHA20_POLY1305_H +#define CHACHA20_POLY1305_H + +#define CHACHA20_BLOCKSIZE 64 +#define CHACHA20_KEYLEN 32 + +#define POLY1305_TAGLEN 16 +/* size of the keys k1 and k2 as defined in specs */ +#define POLY1305_KEYLEN 32 + +#ifdef _MSC_VER +#pragma pack(push, 1) +#endif +struct ssh_packet_header { + uint32_t length; + uint8_t payload[]; +} +#if defined(__GNUC__) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack(pop) +#endif +; + +#endif /* CHACHA20_POLY1305_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/channels.h b/src/libs/libssh-0.12.2/include/libssh/channels.h new file mode 100644 index 000000000000..7a3535eca589 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/channels.h @@ -0,0 +1,125 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CHANNELS_H_ +#define CHANNELS_H_ +#include "libssh/priv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** @internal + * Describes the different possible states in a + * outgoing (client) channel request + */ +enum ssh_channel_request_state_e { + /** No request has been made */ + SSH_CHANNEL_REQ_STATE_NONE = 0, + /** A request has been made and answer is pending */ + SSH_CHANNEL_REQ_STATE_PENDING, + /** A request has been replied and accepted */ + SSH_CHANNEL_REQ_STATE_ACCEPTED, + /** A request has been replied and refused */ + SSH_CHANNEL_REQ_STATE_DENIED, + /** A request has been replied and an error happened */ + SSH_CHANNEL_REQ_STATE_ERROR +}; + +enum ssh_channel_state_e { + SSH_CHANNEL_STATE_NOT_OPEN = 0, + SSH_CHANNEL_STATE_OPENING, + SSH_CHANNEL_STATE_OPEN_DENIED, + SSH_CHANNEL_STATE_OPEN, + SSH_CHANNEL_STATE_CLOSED +}; + +/* The channel has been closed by the remote side */ +#define SSH_CHANNEL_FLAG_CLOSED_REMOTE 0x0001 + +/* The channel has been closed locally */ +#define SSH_CHANNEL_FLAG_CLOSED_LOCAL 0x0002 + +/* The channel has been freed by the calling program */ +#define SSH_CHANNEL_FLAG_FREED_LOCAL 0x0004 + +/* the channel has not yet been bound to a remote one */ +#define SSH_CHANNEL_FLAG_NOT_BOUND 0x0008 + +struct ssh_channel_struct { + ssh_session session; /* SSH_SESSION pointer */ + uint32_t local_channel; + uint32_t local_window; + int local_eof; + uint32_t local_maxpacket; + + uint32_t remote_channel; + uint32_t remote_window; + int remote_eof; /* end of file received */ + uint32_t remote_maxpacket; + enum ssh_channel_state_e state; + int delayed_close; + int flags; + ssh_buffer stdout_buffer; + ssh_buffer stderr_buffer; + void *userarg; + struct { + bool status; + uint32_t code; + char *signal; + bool core_dumped; + } exit; + enum ssh_channel_request_state_e request_state; + struct ssh_list *callbacks; /* list of ssh_channel_callbacks */ + + /* counters */ + ssh_counter counter; +}; + +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf); +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail); +SSH_PACKET_CALLBACK(ssh_packet_channel_success); +SSH_PACKET_CALLBACK(ssh_packet_channel_failure); +SSH_PACKET_CALLBACK(ssh_request_success); +SSH_PACKET_CALLBACK(ssh_request_denied); + +SSH_PACKET_CALLBACK(channel_rcv_change_window); +SSH_PACKET_CALLBACK(channel_rcv_eof); +SSH_PACKET_CALLBACK(channel_rcv_close); +SSH_PACKET_CALLBACK(channel_rcv_request); +SSH_PACKET_CALLBACK(channel_rcv_data); + +int channel_default_bufferize(ssh_channel channel, + void *data, uint32_t len, + bool is_stderr); +int ssh_channel_flush(ssh_channel channel); +uint32_t ssh_channel_new_id(ssh_session session); +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id); +void ssh_channel_do_free(ssh_channel channel); +int ssh_global_request(ssh_session session, + const char *request, + ssh_buffer buffer, + int reply); + +#ifdef __cplusplus +} +#endif + +#endif /* CHANNELS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/config.h b/src/libs/libssh-0.12.2/include/libssh/config.h new file mode 100644 index 000000000000..87cc25be06b5 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/config.h @@ -0,0 +1,80 @@ +/* + * config.h - parse the ssh config file + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef LIBSSH_CONFIG_H_ +#define LIBSSH_CONFIG_H_ + +#include "libssh/libssh.h" + +enum ssh_config_opcode_e { + /* Unknown opcode */ + SOC_UNKNOWN = -3, + /* Known and not applicable to libssh */ + SOC_NA = -2, + /* Known but not supported by current libssh version */ + SOC_UNSUPPORTED = -1, + SOC_HOST, + SOC_MATCH, + SOC_HOSTNAME, + SOC_PORT, + SOC_USERNAME, + SOC_IDENTITY, + SOC_CIPHERS, + SOC_MACS, + SOC_COMPRESSION, + SOC_TIMEOUT, + SOC_STRICTHOSTKEYCHECK, + SOC_KNOWNHOSTS, + SOC_PROXYCOMMAND, + SOC_PROXYJUMP, + SOC_GSSAPISERVERIDENTITY, + SOC_GSSAPICLIENTIDENTITY, + SOC_GSSAPIDELEGATECREDENTIALS, + SOC_INCLUDE, + SOC_BINDADDRESS, + SOC_GLOBALKNOWNHOSTSFILE, + SOC_LOGLEVEL, + SOC_HOSTKEYALGORITHMS, + SOC_KEXALGORITHMS, + SOC_GSSAPIAUTHENTICATION, + SOC_KBDINTERACTIVEAUTHENTICATION, + SOC_PASSWORDAUTHENTICATION, + SOC_PUBKEYAUTHENTICATION, + SOC_PUBKEYACCEPTEDKEYTYPES, + SOC_REKEYLIMIT, + SOC_IDENTITYAGENT, + SOC_IDENTITIESONLY, + SOC_CONTROLMASTER, + SOC_CONTROLPATH, + SOC_CERTIFICATE, + SOC_REQUIRED_RSA_SIZE, + SOC_ADDRESSFAMILY, + SOC_GSSAPIKEYEXCHANGE, + SOC_GSSAPIKEXALGORITHMS, + + SOC_MAX /* Keep this one last in the list */ +}; +enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword); +int ssh_config_parse_line_cli(ssh_session session, const char *line); + +#endif /* LIBSSH_CONFIG_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/config_parser.h b/src/libs/libssh-0.12.2/include/libssh/config_parser.h new file mode 100644 index 000000000000..f5d1fee83b22 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/config_parser.h @@ -0,0 +1,86 @@ +/* + * config_parser.h - Common configuration file parser functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef CONFIG_PARSER_H_ +#define CONFIG_PARSER_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +#include "libssh/libssh.h" +#include + +char *ssh_config_get_cmd(char **str); + +char *ssh_config_get_token(char **str); + +long ssh_config_get_long(char **str, long notfound); + +const char *ssh_config_get_str_tok(char **str, const char *def); + +int ssh_config_get_yesno(char **str, int notfound); + +/* @brief Parse SSH URI in format [user@]host[:port] from the given string + * + * @param[in] tok String to parse + * @param[out] username Pointer to the location, where the new username will + * be stored or NULL if we do not care about the result. + * @param[out] hostname Pointer to the location, where the new hostname will + * be stored or NULL if we do not care about the result. + * @param[out] port Pointer to the location, where the new port will + * be stored or NULL if we do not care about the result. + * @param[in] ignore_port Set to true if we should not attempt to parse + * port number. + * + * @returns SSH_OK if the provided string is in format of SSH URI, + * SSH_ERROR on failure + */ +int ssh_config_parse_uri(const char *tok, + char **username, + char **hostname, + char **port, + bool ignore_port); + +/** + * @brief: Parse the ProxyJump configuration line and if parsing, + * stores the result in the configuration option + * + * @param[in] session The ssh session + * @param[in] s The string to be parsed. + * @param[in] do_parsing Whether to parse or not. + * + * @returns SSH_OK if the provided string is formatted and parsed correctly + * SSH_ERROR on failure + */ +int ssh_config_parse_proxy_jump(ssh_session session, + const char *s, + bool do_parsing); + +#ifdef __cplusplus +} +#endif + +#endif /* LIBSSH_CONFIG_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/crypto.h b/src/libs/libssh-0.12.2/include/libssh/crypto.h new file mode 100644 index 000000000000..dd7fa2e8e2b4 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/crypto.h @@ -0,0 +1,276 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * crypto.h is an include file for internal cryptographic structures of libssh + */ + +#ifndef _CRYPTO_H_ +#define _CRYPTO_H_ + +#include +#include "config.h" + +#ifdef HAVE_LIBGCRYPT +#include +#elif defined(HAVE_LIBMBEDCRYPTO) +#include +#endif +#include "libssh/wrapper.h" + +#ifdef cbc_encrypt +#undef cbc_encrypt +#endif +#ifdef cbc_decrypt +#undef cbc_decrypt +#endif + +#ifdef HAVE_OPENSSL_ECDH_H +#include +#endif +#include "libssh/curve25519.h" +#include "libssh/dh.h" +#include "libssh/ecdh.h" +#include "libssh/kex.h" +#include "libssh/sntrup761.h" + +#define DIGEST_MAX_LEN 64 + +#define AES_GCM_TAGLEN 16 +#define AES_GCM_IVLEN 12 + +enum ssh_key_exchange_e { + /* diffie-hellman-group1-sha1 */ + SSH_KEX_DH_GROUP1_SHA1 = 1, + /* diffie-hellman-group14-sha1 */ + SSH_KEX_DH_GROUP14_SHA1, +#ifdef WITH_GEX + /* diffie-hellman-group-exchange-sha1 */ + SSH_KEX_DH_GEX_SHA1, + /* diffie-hellman-group-exchange-sha256 */ + SSH_KEX_DH_GEX_SHA256, +#endif /* WITH_GEX */ + /* ecdh-sha2-nistp256 */ + SSH_KEX_ECDH_SHA2_NISTP256, + /* ecdh-sha2-nistp384 */ + SSH_KEX_ECDH_SHA2_NISTP384, + /* ecdh-sha2-nistp521 */ + SSH_KEX_ECDH_SHA2_NISTP521, + /* curve25519-sha256@libssh.org */ + SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG, + /* curve25519-sha256 */ + SSH_KEX_CURVE25519_SHA256, + /* diffie-hellman-group16-sha512 */ + SSH_KEX_DH_GROUP16_SHA512, + /* diffie-hellman-group18-sha512 */ + SSH_KEX_DH_GROUP18_SHA512, + /* diffie-hellman-group14-sha256 */ + SSH_KEX_DH_GROUP14_SHA256, + /* sntrup761x25519-sha512@openssh.com */ + SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM, + /* sntrup761x25519-sha512 */ + SSH_KEX_SNTRUP761X25519_SHA512, + /* mlkem768x25519-sha256 */ + SSH_KEX_MLKEM768X25519_SHA256, + /* mlkem768nistp256-sha256 */ + SSH_KEX_MLKEM768NISTP256_SHA256, +#ifdef HAVE_MLKEM1024 + /* mlkem1024nistp384-sha384 */ + SSH_KEX_MLKEM1024NISTP384_SHA384, +#endif /* HAVE_MLKEM1024 */ + /* gss-group14-sha256-* */ + SSH_GSS_KEX_DH_GROUP14_SHA256, + /* gss-group16-sha512-* */ + SSH_GSS_KEX_DH_GROUP16_SHA512, + /* gss-nistp256-sha256-* */ + SSH_GSS_KEX_ECDH_NISTP256_SHA256, + /* gss-curve25519-sha256-* */ + SSH_GSS_KEX_CURVE25519_SHA256, +}; + +enum ssh_cipher_e { + SSH_NO_CIPHER=0, +#ifdef HAVE_BLOWFISH + SSH_BLOWFISH_CBC, +#endif /* HAVE_BLOWFISH */ + SSH_3DES_CBC, + SSH_AES128_CBC, + SSH_AES192_CBC, + SSH_AES256_CBC, + SSH_AES128_CTR, + SSH_AES192_CTR, + SSH_AES256_CTR, + SSH_AEAD_AES128_GCM, + SSH_AEAD_AES256_GCM, + SSH_AEAD_CHACHA20_POLY1305 +}; + +struct dh_ctx; + +struct ssh_crypto_struct { + bignum shared_secret; + ssh_string hybrid_client_init; + ssh_string hybrid_server_reply; + ssh_string hybrid_shared_secret; + struct dh_ctx *dh_ctx; +#ifdef WITH_GEX + size_t dh_pmin; size_t dh_pn; size_t dh_pmax; /* preferred group parameters */ +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdh_privkey; +#else + EVP_PKEY *ecdh_privkey; +#endif /* OPENSSL_VERSION_NUMBER */ +#elif defined HAVE_GCRYPT_ECC + gcry_sexp_t ecdh_privkey; +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_ecp_keypair *ecdh_privkey; +#endif + ssh_string ecdh_client_pubkey; + ssh_string ecdh_server_pubkey; +#endif +#ifdef HAVE_CURVE25519 +#ifdef HAVE_LIBCRYPTO + EVP_PKEY *curve25519_privkey; +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_sexp_t curve25519_privkey; +#else + ssh_curve25519_privkey curve25519_privkey; +#endif + ssh_curve25519_pubkey curve25519_client_pubkey; + ssh_curve25519_pubkey curve25519_server_pubkey; +#endif +#ifdef HAVE_OPENSSL_MLKEM + EVP_PKEY *mlkem_privkey; +#else + unsigned char *mlkem_privkey; + size_t mlkem_privkey_len; +#endif + ssh_string mlkem_client_pubkey; + ssh_string mlkem_ciphertext; +#ifdef HAVE_SNTRUP761 + ssh_sntrup761_privkey sntrup761_privkey; + ssh_sntrup761_pubkey sntrup761_client_pubkey; + ssh_sntrup761_ciphertext sntrup761_ciphertext; +#endif + ssh_string dh_server_signature; /* information used by dh_handshake. */ + size_t session_id_len; + unsigned char *session_id; + size_t digest_len; /* len of the secret hash */ + unsigned char *secret_hash; /* Secret hash is same as session id until re-kex */ + unsigned char *encryptIV; + unsigned char *decryptIV; + unsigned char *decryptkey; + unsigned char *encryptkey; + unsigned char *encryptMAC; + unsigned char *decryptMAC; + unsigned char hmacbuf[DIGEST_MAX_LEN]; + struct ssh_cipher_struct *in_cipher, *out_cipher; /* the cipher structures/objects */ + enum ssh_hmac_e in_hmac, out_hmac; /* the MAC algorithms used */ + bool in_hmac_etm, out_hmac_etm; /* Whether EtM mode is used or not */ + + ssh_key server_pubkey; + int do_compress_out; /* idem */ + int do_compress_in; /* don't set them, set the option instead */ + int delayed_compress_in; /* Use of zlib@openssh.org */ + int delayed_compress_out; + void *compress_out_ctx; /* don't touch it */ + void *compress_in_ctx; /* really, don't */ + /* kex sent by server, client, and mutually elected methods */ + struct ssh_kex_struct server_kex; + struct ssh_kex_struct client_kex; + char *kex_methods[SSH_KEX_METHODS]; + enum ssh_key_exchange_e kex_type; + enum ssh_kdf_digest digest_type; /* Digest type for session keys derivation */ + enum ssh_crypto_direction_e used; /* Is this crypto still used for either of directions? */ +}; + +struct ssh_cipher_struct { + const char *name; /* ssh name of the algorithm */ + unsigned int blocksize; /* blocksize of the algo */ + enum ssh_cipher_e ciphertype; + uint32_t lenfield_blocksize; /* blocksize of the packet length field */ + size_t keylen; /* length of the key structure */ +#ifdef HAVE_LIBGCRYPT + gcry_cipher_hd_t *key; + unsigned char last_iv[AES_GCM_IVLEN]; +#elif defined HAVE_LIBCRYPTO + struct ssh_3des_key_schedule *des3_key; + struct ssh_aes_key_schedule *aes_key; + const EVP_CIPHER *cipher; + EVP_CIPHER_CTX *ctx; +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_cipher_context_t encrypt_ctx; + mbedtls_cipher_context_t decrypt_ctx; + mbedtls_cipher_type_t type; +#ifdef MBEDTLS_GCM_C + mbedtls_gcm_context gcm_ctx; + unsigned char last_iv[AES_GCM_IVLEN]; +#endif /* MBEDTLS_GCM_C */ +#endif + struct chacha20_poly1305_keysched *chacha20_schedule; + unsigned int keysize; /* bytes of key used. != keylen */ + size_t tag_size; /* overhead required for tag */ + /* Counters for rekeying initialization */ + uint32_t packets; + uint64_t blocks; + /* Rekeying limit for the cipher or manually enforced */ + uint64_t max_blocks; + /* sets the new key for immediate use */ + int (*set_encrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + int (*set_decrypt_key)(struct ssh_cipher_struct *cipher, void *key, void *IV); + void (*encrypt)(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len); + void (*decrypt)(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len); + void (*aead_encrypt)(struct ssh_cipher_struct *cipher, void *in, void *out, + size_t len, uint8_t *mac, uint64_t seq); + int (*aead_decrypt_length)(struct ssh_cipher_struct *cipher, void *in, + uint8_t *out, size_t len, uint64_t seq); + int (*aead_decrypt)(struct ssh_cipher_struct *cipher, void *complete_packet, uint8_t *out, + size_t encrypted_size, uint64_t seq); + void (*cleanup)(struct ssh_cipher_struct *cipher); +}; + +#ifdef __cplusplus +extern "C" { +#endif + +const struct ssh_cipher_struct *ssh_get_chacha20poly1305_cipher(void); +int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len); + +int secure_memcmp(const void *s1, const void *s2, size_t n); + +void compress_cleanup(struct ssh_crypto_struct *crypto); + +#ifdef __cplusplus +} +#endif + +#endif /* _CRYPTO_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/curve25519.h b/src/libs/libssh-0.12.2/include/libssh/curve25519.h new file mode 100644 index 000000000000..e5691157b29d --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/curve25519.h @@ -0,0 +1,69 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef CURVE25519_H_ +#define CURVE25519_H_ + +#include "config.h" +#include "libssh.h" + +#ifdef WITH_NACL + +#include +#define CURVE25519_PUBKEY_SIZE crypto_scalarmult_curve25519_BYTES +#define CURVE25519_PRIVKEY_SIZE crypto_scalarmult_curve25519_SCALARBYTES +#define crypto_scalarmult_base crypto_scalarmult_curve25519_base +#define crypto_scalarmult crypto_scalarmult_curve25519 +#else + +#ifdef __cplusplus +extern "C" { +#endif + +#define CURVE25519_PUBKEY_SIZE 32 +#define CURVE25519_PRIVKEY_SIZE 32 +int crypto_scalarmult_base(unsigned char *q, const unsigned char *n); +int crypto_scalarmult(unsigned char *q, const unsigned char *n, const unsigned char *p); +#endif /* WITH_NACL */ + +#ifdef HAVE_ECC +#define HAVE_CURVE25519 1 +#endif + +typedef unsigned char ssh_curve25519_pubkey[CURVE25519_PUBKEY_SIZE]; +typedef unsigned char ssh_curve25519_privkey[CURVE25519_PRIVKEY_SIZE]; + +int ssh_curve25519_init(ssh_session session); +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k); +int ssh_curve25519_create_k(ssh_session session, ssh_curve25519_pubkey k); +int ssh_curve25519_build_k(ssh_session session); + +int ssh_client_curve25519_init(ssh_session session); +void ssh_client_curve25519_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_curve25519_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* CURVE25519_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/dh-gex.h b/src/libs/libssh-0.12.2/include/libssh/dh-gex.h new file mode 100644 index 000000000000..0f547e37a047 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/dh-gex.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2016 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + + +#ifndef SRC_DH_GEX_H_ +#define SRC_DH_GEX_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_client_dhgex_init(ssh_session session); +void ssh_client_dhgex_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_dhgex_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* SRC_DH_GEX_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/dh.h b/src/libs/libssh-0.12.2/include/libssh/dh.h new file mode 100644 index 000000000000..34c4a7ed9e35 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/dh.h @@ -0,0 +1,95 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef DH_H_ +#define DH_H_ + +#include "config.h" + +#include "libssh/crypto.h" + +struct dh_ctx; + +#define DH_CLIENT_KEYPAIR 0 +#define DH_SERVER_KEYPAIR 1 + +#ifdef __cplusplus +extern "C" { +#endif + +/* functions implemented by crypto backends */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto); +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto); + +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator); +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator); +#endif /* OPENSSL_VERSION_NUMBER */ +int ssh_dh_set_parameters(struct dh_ctx *ctx, + const bignum modulus, const bignum generator); + +int ssh_dh_keypair_gen_keys(struct dh_ctx *ctx, int peer); +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub); +#else +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub); +#endif /* OPENSSL_VERSION_NUMBER */ +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + bignum priv, bignum pub); + +int ssh_dh_compute_shared_secret(struct dh_ctx *ctx, int local, int remote, + bignum *dest); + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c); + +/* common functions */ +int ssh_dh_init(void); +void ssh_dh_finalize(void); + +int ssh_dh_import_next_pubkey_blob(ssh_session session, + ssh_string pubkey_blob); + +ssh_key ssh_dh_get_current_server_publickey(ssh_session session); +int ssh_dh_get_current_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); +ssh_key ssh_dh_get_next_server_publickey(ssh_session session); +int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob); +int dh_handshake(ssh_session session); + +int ssh_client_dh_init(ssh_session session); +void ssh_client_dh_remove_callbacks(ssh_session session); +#ifdef WITH_SERVER +void ssh_server_dh_init(ssh_session session); +#endif /* WITH_SERVER */ +int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet); +int ssh_fallback_group(uint32_t pmax, bignum *p, bignum *g); +bool ssh_dh_is_known_group(bignum modulus, bignum generator); + +#ifdef __cplusplus +} +#endif + +#endif /* DH_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/ecdh.h b/src/libs/libssh-0.12.2/include/libssh/ecdh.h new file mode 100644 index 000000000000..2f763528ae99 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/ecdh.h @@ -0,0 +1,66 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ECDH_H_ +#define ECDH_H_ + +#include "config.h" +#include "libssh/callbacks.h" + +#ifdef HAVE_LIBCRYPTO +#ifdef HAVE_OPENSSL_ECDH_H + +#ifdef HAVE_ECC +#define HAVE_ECDH 1 +#endif + +#endif /* HAVE_OPENSSL_ECDH_H */ +#endif /* HAVE_LIBCRYPTO */ + +#ifdef HAVE_GCRYPT_ECC +#define HAVE_ECDH 1 +#endif + +#ifdef HAVE_LIBMBEDCRYPTO +#define HAVE_ECDH 1 +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +extern struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks; +/* Backend-specific functions. */ +int ssh_ecdh_init(ssh_session session); +int ssh_client_ecdh_init(ssh_session session); +void ssh_client_ecdh_remove_callbacks(ssh_session session); +int ecdh_build_k(ssh_session session); + +#ifdef WITH_SERVER +extern struct ssh_packet_callbacks_struct ssh_ecdh_server_callbacks; +void ssh_server_ecdh_init(ssh_session session); +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* ECDH_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/ed25519.h b/src/libs/libssh-0.12.2/include/libssh/ed25519.h new file mode 100644 index 000000000000..a6bcdaf3d4d3 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/ed25519.h @@ -0,0 +1,116 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef ED25519_H_ +#define ED25519_H_ +#include "libssh/priv.h" + +/** + * @defgroup ed25519 ed25519 API + * @brief API for DJB's ed25519 + * + * @{ + */ + +/** @internal + * @brief ED25519 public key. + * Ed25519 public key consist of 32 bytes. + */ +#define ED25519_PK_LEN 32 + +/** @internal + * @brief ED25519 secret key. + * Ed25519 secret key consist of 64 bytes. + */ +#define ED25519_SK_LEN 64 + +/** @internal + * @brief ED25519 signature. + * Ed25519 signatures consist of 64 bytes. + */ +#define ED25519_SIG_LEN 64 + +/** @internal + * @brief ED25519 public key. + * The public key consists of 32 bytes and can be used for signature + * verification. + */ +typedef uint8_t ed25519_pubkey[ED25519_PK_LEN]; + +/** @internal + * @brief ED25519 private key. + * The private key consists of 64 bytes and should be kept secret. + */ +typedef uint8_t ed25519_privkey[ED25519_SK_LEN]; + +/** @internal + * @brief ED25519 signature. + * Ed25519 signatures consists of 64 bytes. + */ +typedef uint8_t ed25519_signature[ED25519_SIG_LEN]; + +#ifdef __cplusplus +extern "C" { +#endif + +/** @internal + * @brief generate an ed25519 key pair + * @param[out] pk generated public key + * @param[out] sk generated secret key + * @return 0 on success, -1 on error. + */ +int crypto_sign_ed25519_keypair(ed25519_pubkey pk, ed25519_privkey sk); + +/** @internal + * @brief sign a message with ed25519 + * @param[out] sm location to store the signed message. + * Its length should be mlen + 64. + * @param[out] smlen pointer to the size of the signed message + * @param[in] m message to be signed + * @param[in] mlen length of the message to be signed + * @param[in] sk secret key to sign the message with + * @return 0 on success. + */ +int crypto_sign_ed25519( + unsigned char *sm, uint64_t *smlen, + const unsigned char *m, uint64_t mlen, + const ed25519_privkey sk); + +/** @internal + * @brief "open" and verify the signature of a signed message + * @param[out] m location to store the verified message. + * Its length should be equal to smlen. + * @param[out] mlen pointer to the size of the verified message + * @param[in] sm signed message to verify + * @param[in] smlen length of the signed message to verify + * @param[in] pk public key used to sign the message + * @returns 0 on success (supposedly). + */ +int crypto_sign_ed25519_open( + unsigned char *m, uint64_t *mlen, + const unsigned char *sm, uint64_t smlen, + const ed25519_pubkey pk); + +/** @} */ +#ifdef __cplusplus +} +#endif + +#endif /* ED25519_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/fe25519.h b/src/libs/libssh-0.12.2/include/libssh/fe25519.h new file mode 100644 index 000000000000..0dfb0613fbe7 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/fe25519.h @@ -0,0 +1,76 @@ +/* $OpenBSD: fe25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/fe25519.h + */ + +#ifndef FE25519_H +#define FE25519_H + +#include "libssh/priv.h" + +#define fe25519 crypto_sign_ed25519_ref_fe25519 +#define fe25519_freeze crypto_sign_ed25519_ref_fe25519_freeze +#define fe25519_unpack crypto_sign_ed25519_ref_fe25519_unpack +#define fe25519_pack crypto_sign_ed25519_ref_fe25519_pack +#define fe25519_iszero crypto_sign_ed25519_ref_fe25519_iszero +#define fe25519_iseq_vartime crypto_sign_ed25519_ref_fe25519_iseq_vartime +#define fe25519_cmov crypto_sign_ed25519_ref_fe25519_cmov +#define fe25519_setone crypto_sign_ed25519_ref_fe25519_setone +#define fe25519_setzero crypto_sign_ed25519_ref_fe25519_setzero +#define fe25519_neg crypto_sign_ed25519_ref_fe25519_neg +#define fe25519_getparity crypto_sign_ed25519_ref_fe25519_getparity +#define fe25519_add crypto_sign_ed25519_ref_fe25519_add +#define fe25519_sub crypto_sign_ed25519_ref_fe25519_sub +#define fe25519_mul crypto_sign_ed25519_ref_fe25519_mul +#define fe25519_square crypto_sign_ed25519_ref_fe25519_square +#define fe25519_invert crypto_sign_ed25519_ref_fe25519_invert +#define fe25519_pow2523 crypto_sign_ed25519_ref_fe25519_pow2523 + +typedef struct { + uint32_t v[32]; +} fe25519; + +#ifdef __cplusplus +extern "C" { +#endif + +void fe25519_freeze(fe25519 *r); + +void fe25519_unpack(fe25519 *r, const unsigned char x[32]); + +void fe25519_pack(unsigned char r[32], const fe25519 *x); + +uint32_t fe25519_iszero(const fe25519 *x); + +int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y); + +void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b); + +void fe25519_setone(fe25519 *r); + +void fe25519_setzero(fe25519 *r); + +void fe25519_neg(fe25519 *r, const fe25519 *x); + +unsigned char fe25519_getparity(const fe25519 *x); + +void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y); + +void fe25519_square(fe25519 *r, const fe25519 *x); + +void fe25519_invert(fe25519 *r, const fe25519 *x); + +void fe25519_pow2523(fe25519 *r, const fe25519 *x); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/ge25519.h b/src/libs/libssh-0.12.2/include/libssh/ge25519.h new file mode 100644 index 000000000000..480f29dc4050 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/ge25519.h @@ -0,0 +1,51 @@ +/* $OpenBSD: ge25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519.h + */ + +#ifndef GE25519_H +#define GE25519_H + +#include "fe25519.h" +#include "sc25519.h" + +#define ge25519 crypto_sign_ed25519_ref_ge25519 +#define ge25519_base crypto_sign_ed25519_ref_ge25519_base +#define ge25519_unpackneg_vartime crypto_sign_ed25519_ref_unpackneg_vartime +#define ge25519_pack crypto_sign_ed25519_ref_pack +#define ge25519_isneutral_vartime crypto_sign_ed25519_ref_isneutral_vartime +#define ge25519_double_scalarmult_vartime crypto_sign_ed25519_ref_double_scalarmult_vartime +#define ge25519_scalarmult_base crypto_sign_ed25519_ref_scalarmult_base + +typedef struct +{ + fe25519 x; + fe25519 y; + fe25519 z; + fe25519 t; +} ge25519; + +#ifdef __cplusplus +extern "C" { +#endif + +extern const ge25519 ge25519_base; + +int ge25519_unpackneg_vartime(ge25519 *r, const unsigned char p[32]); + +void ge25519_pack(unsigned char r[32], const ge25519 *p); + +int ge25519_isneutral_vartime(const ge25519 *p); + +void ge25519_double_scalarmult_vartime(ge25519 *r, const ge25519 *p1, const sc25519 *s1, const ge25519 *p2, const sc25519 *s2); + +void ge25519_scalarmult_base(ge25519 *r, const sc25519 *s); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/gssapi.h b/src/libs/libssh-0.12.2/include/libssh/gssapi.h new file mode 100644 index 000000000000..fd1216f2eeb1 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/gssapi.h @@ -0,0 +1,103 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef GSSAPI_H_ +#define GSSAPI_H_ + +#include "config.h" +#ifdef WITH_GSSAPI +#include "session.h" +#include + +/* all OID begin with the tag identifier + length */ +#define SSH_OID_TAG 06 + +#define GSSAPI_KEY_EXCHANGE_SUPPORTED "gss-group14-sha256-," \ + "gss-group16-sha512-," \ + "gss-nistp256-sha256-," \ + "gss-curve25519-sha256-" + +typedef struct ssh_gssapi_struct *ssh_gssapi; + +#ifdef __cplusplus +extern "C" { +#endif + +/** current state of an GSSAPI authentication */ +enum ssh_gssapi_state_e { + SSH_GSSAPI_STATE_NONE, /* no status */ + SSH_GSSAPI_STATE_RCV_TOKEN, /* Expecting a token */ + SSH_GSSAPI_STATE_RCV_MIC, /* Expecting a MIC */ +}; + +struct ssh_gssapi_struct{ + enum ssh_gssapi_state_e state; /* current state */ + gss_cred_id_t server_creds; /* credentials of server */ + gss_cred_id_t client_creds; /* creds delegated by the client */ + gss_ctx_id_t ctx; /* the authentication context */ + gss_name_t client_name; /* Identity of the client */ + char *user; /* username of client */ + char *canonic_user; /* canonic form of the client's username */ + struct { + gss_name_t server_name; /* identity of server */ + OM_uint32 flags; /* flags used for init context */ + gss_OID oid; /* mech being used for authentication */ + gss_cred_id_t creds; /* creds used to initialize context */ + gss_cred_id_t client_deleg_creds; /* delegated creds (const, not freeable) */ + } client; +}; + +#ifdef WITH_SERVER +int ssh_gssapi_handle_userauth(ssh_session session, const char *user, uint32_t n_oid, ssh_string *oids); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic); +int ssh_gssapi_server_oids(gss_OID_set *selected); +#endif /* WITH_SERVER */ + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client); +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response); + + +int ssh_gssapi_init(ssh_session session); +void ssh_gssapi_log_error(int verb, const char *msg_a, int maj_stat, int min_stat); +int ssh_gssapi_auth_mic(ssh_session session); +void ssh_gssapi_free(ssh_session session); +int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids); +char *ssh_gssapi_name_to_char(gss_name_t name); +int ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host); +OM_uint32 ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi, + gss_buffer_desc *input_token, + gss_buffer_desc *output_token, + OM_uint32 *ret_flags); + +char *ssh_gssapi_oid_hash(ssh_string oid); +char *ssh_gssapi_kex_mechs(ssh_session session); +int ssh_gssapi_check_client_config(ssh_session session); +ssh_buffer ssh_gssapi_build_mic(ssh_session session, const char *context); +int ssh_gssapi_auth_keyex_mic(ssh_session session, + gss_buffer_desc *mic_token_buf); + +#ifdef __cplusplus +} +#endif + +#endif /* WITH_GSSAPI */ +#endif /* GSSAPI_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/hybrid_mlkem.h b/src/libs/libssh-0.12.2/include/libssh/hybrid_mlkem.h new file mode 100644 index 000000000000..ca9b6a20150c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/hybrid_mlkem.h @@ -0,0 +1,51 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Sahana Prasad + * Author: Pavol Žáčik + * Author: Claude (Anthropic) + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef HYBRID_MLKEM_H_ +#define HYBRID_MLKEM_H_ + +#include "libssh/mlkem.h" +#include "libssh/wrapper.h" + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#define NISTP256_SHARED_SECRET_SIZE 32 +#define NISTP384_SHARED_SECRET_SIZE 48 + +int ssh_client_hybrid_mlkem_init(ssh_session session); +void ssh_client_hybrid_mlkem_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_hybrid_mlkem_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* HYBRID_MLKEM_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/kex-gss.h b/src/libs/libssh-0.12.2/include/libssh/kex-gss.h new file mode 100644 index 000000000000..65ae2fe4f9ad --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/kex-gss.h @@ -0,0 +1,36 @@ +/* + * kex-gss.h - GSSAPI key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Gauravsingh Sisodia + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ +#ifndef KEX_GSS_H_ +#define KEX_GSS_H_ + +#include "config.h" +#ifdef WITH_GSSAPI + +int ssh_client_gss_kex_init(ssh_session session); +void ssh_server_gss_kex_init(ssh_session session); +int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet); +void ssh_client_gss_kex_remove_callbacks(ssh_session session); +void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session); + +#endif /* WITH_GSSAPI */ +#endif /* KEX_GSS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/kex.h b/src/libs/libssh-0.12.2/include/libssh/kex.h new file mode 100644 index 000000000000..435ecc88bdf3 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/kex.h @@ -0,0 +1,76 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KEX_H_ +#define KEX_H_ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" + +#define SSH_KEX_METHODS 10 + +struct ssh_kex_struct { + unsigned char cookie[16]; + char *methods[SSH_KEX_METHODS]; +}; + +/* crypto.h needs ssh_kex_struct so it is included below the struct definition */ +#include "libssh/crypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_kexinit); + +int ssh_send_kex(ssh_session session); +void ssh_list_kex(struct ssh_kex_struct *kex); +int ssh_set_client_kex(ssh_session session); +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex); +int ssh_kex_select_methods(ssh_session session); +int ssh_verify_existing_algo(enum ssh_kex_types_e algo, const char *name); +char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_add_to_default_algos(enum ssh_kex_types_e algo, const char *list); +char *ssh_remove_from_default_algos(enum ssh_kex_types_e algo, + const char *list); +char *ssh_prefix_default_algos(enum ssh_kex_types_e algo, const char *list); +char **ssh_space_tokenize(const char *chain); +int ssh_get_kex1(ssh_session session); +char *ssh_find_matching(const char *in_d, const char *what_d); +const char *ssh_kex_get_supported_method(enum ssh_kex_types_e type); +const char *ssh_kex_get_default_methods(enum ssh_kex_types_e type); +const char *ssh_kex_get_fips_methods(enum ssh_kex_types_e type); +const char *ssh_kex_get_description(enum ssh_kex_types_e type); +char *ssh_client_select_hostkeys(ssh_session session); +int ssh_send_rekex(ssh_session session); +int server_set_kex(ssh_session session); +int ssh_make_sessionid(ssh_session session); +/* add data for the final cookie */ +int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie); +int ssh_hashbufout_add_cookie(ssh_session session); +int ssh_generate_session_keys(ssh_session session); +bool ssh_kex_is_gss(struct ssh_crypto_struct *crypto); + +#ifdef __cplusplus +} +#endif + +#endif /* KEX_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/keys.h b/src/libs/libssh-0.12.2/include/libssh/keys.h new file mode 100644 index 000000000000..1379539e0c22 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/keys.h @@ -0,0 +1,64 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef KEYS_H_ +#define KEYS_H_ + +#include "config.h" +#include "libssh/libssh.h" +#include "libssh/wrapper.h" + +struct ssh_public_key_struct { + int type; + const char *type_c; /* Don't free it ! it is static */ +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t rsa_pub; +#elif defined(HAVE_LIBCRYPTO) + EVP_PKEY *key_pub; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa_pub; +#endif +}; + +struct ssh_private_key_struct { + int type; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t rsa_priv; +#elif defined(HAVE_LIBCRYPTO) + EVP_PKEY *key_priv; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *rsa_priv; +#endif +}; + +#ifdef __cplusplus +extern "C" { +#endif + +const char *ssh_type_to_char(int type); +int ssh_type_from_name(const char *name); + +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s); + +#ifdef __cplusplus +} +#endif + +#endif /* KEYS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/knownhosts.h b/src/libs/libssh-0.12.2/include/libssh/knownhosts.h new file mode 100644 index 000000000000..b50018cdb0b2 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/knownhosts.h @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 20014 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + + +#ifndef SSH_KNOWNHOSTS_H_ +#define SSH_KNOWNHOSTS_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session); +char *ssh_known_hosts_get_algorithms_names(ssh_session session); +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry_file(ssh_session session, + const char *filename, + struct ssh_knownhosts_entry **pentry); + +#ifdef __cplusplus +} +#endif + +#endif /* SSH_KNOWNHOSTS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/legacy.h b/src/libs/libssh-0.12.2/include/libssh/legacy.h new file mode 100644 index 000000000000..38bef4dac79f --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/legacy.h @@ -0,0 +1,128 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* Since libssh.h includes legacy.h, it's important that libssh.h is included + * first. we don't define LEGACY_H now because we want it to be defined when + * included from libssh.h + * All function calls declared in this header are deprecated and meant to be + * removed in future. + */ + +#ifndef LEGACY_H_ +#define LEGACY_H_ + +typedef struct ssh_private_key_struct* ssh_private_key; +typedef struct ssh_public_key_struct* ssh_public_key; + +#ifdef __cplusplus +extern "C" { +#endif + +LIBSSH_API int ssh_auth_list(ssh_session session); +LIBSSH_API int ssh_userauth_offer_pubkey(ssh_session session, const char *username, int type, ssh_string publickey); +LIBSSH_API int ssh_userauth_pubkey(ssh_session session, const char *username, ssh_string publickey, ssh_private_key privatekey); +#ifndef _WIN32 +LIBSSH_API int ssh_userauth_agent_pubkey(ssh_session session, const char *username, + ssh_public_key publickey); +#endif +LIBSSH_API int ssh_userauth_autopubkey(ssh_session session, const char *passphrase); +LIBSSH_API int ssh_userauth_privatekey_file(ssh_session session, const char *username, + const char *filename, const char *passphrase); + +SSH_DEPRECATED LIBSSH_API void buffer_free(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API void *buffer_get(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API uint32_t buffer_get_len(ssh_buffer buffer); +SSH_DEPRECATED LIBSSH_API ssh_buffer buffer_new(void); + +SSH_DEPRECATED LIBSSH_API ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_change_pty_size(ssh_channel channel,int cols,int rows); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int channel_close(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int channel_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API void channel_free(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_get_exit_status(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_session channel_get_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_closed(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_is_open(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API ssh_channel channel_new(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +SSH_DEPRECATED LIBSSH_API int channel_open_session(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_poll(ssh_channel channel, int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr); + +SSH_DEPRECATED LIBSSH_API int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +SSH_DEPRECATED LIBSSH_API int channel_request_env(ssh_channel channel, const char *name, const char *value); +SSH_DEPRECATED LIBSSH_API int channel_request_exec(ssh_channel channel, const char *cmd); +SSH_DEPRECATED LIBSSH_API int channel_request_pty(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +SSH_DEPRECATED LIBSSH_API int channel_request_shell(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_send_signal(ssh_channel channel, const char *signum); +SSH_DEPRECATED LIBSSH_API int channel_request_sftp(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_request_subsystem(ssh_channel channel, const char *subsystem); +SSH_DEPRECATED LIBSSH_API int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +SSH_DEPRECATED LIBSSH_API int channel_send_eof(ssh_channel channel); +SSH_DEPRECATED LIBSSH_API int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); +SSH_DEPRECATED LIBSSH_API void channel_set_blocking(ssh_channel channel, int blocking); +SSH_DEPRECATED LIBSSH_API int channel_write(ssh_channel channel, const void *data, uint32_t len); + +SSH_DEPRECATED LIBSSH_API void privatekey_free(ssh_private_key prv); +SSH_DEPRECATED LIBSSH_API ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +SSH_DEPRECATED LIBSSH_API void publickey_free(ssh_public_key key); +SSH_DEPRECATED LIBSSH_API int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +SSH_DEPRECATED LIBSSH_API ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type); +SSH_DEPRECATED LIBSSH_API ssh_public_key publickey_from_privatekey(ssh_private_key prv); +SSH_DEPRECATED LIBSSH_API ssh_string publickey_to_string(ssh_public_key key); +SSH_DEPRECATED LIBSSH_API int ssh_try_publickey_from_file(ssh_session session, const char *keyfile, + ssh_string *publickey, int *type); +SSH_DEPRECATED LIBSSH_API enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey); + +LIBSSH_API ssh_string ssh_get_pubkey(ssh_session session); + +LIBSSH_API ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype); +LIBSSH_API ssh_public_key ssh_message_auth_publickey(ssh_message msg); + +SSH_DEPRECATED LIBSSH_API void string_burn(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_copy(ssh_string str); +SSH_DEPRECATED LIBSSH_API void *string_data(ssh_string str); +SSH_DEPRECATED LIBSSH_API int string_fill(ssh_string str, const void *data, size_t len); +SSH_DEPRECATED LIBSSH_API void string_free(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_from_char(const char *what); +SSH_DEPRECATED LIBSSH_API size_t string_len(ssh_string str); +SSH_DEPRECATED LIBSSH_API ssh_string string_new(size_t size); +SSH_DEPRECATED LIBSSH_API char *string_to_char(ssh_string str); + +#ifdef __cplusplus +} +#endif + +#endif /* LEGACY_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libcrypto.h b/src/libs/libssh-0.12.2/include/libssh/libcrypto.h new file mode 100644 index 000000000000..b33665144d1e --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libcrypto.h @@ -0,0 +1,137 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBCRYPTO_H_ +#define LIBCRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBCRYPTO + +#include "libssh/libssh.h" +#include +#include +#include +#include +#include +#include +#include + +typedef EVP_MD_CTX* SHACTX; +typedef EVP_MD_CTX* SHA256CTX; +typedef EVP_MD_CTX* SHA384CTX; +typedef EVP_MD_CTX* SHA512CTX; +typedef EVP_MD_CTX* MD5CTX; +typedef EVP_MD_CTX* HMACCTX; + +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH +#ifdef MD5_DIGEST_LEN + #undef MD5_DIGEST_LEN +#endif +#define MD5_DIGEST_LEN MD5_DIGEST_LENGTH + +#ifdef HAVE_OPENSSL_ECC +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE +#endif + +/* Use ssh_crypto_free() to release memory allocated by bignum_bn2dec(), + bignum_bn2hex() and other functions that use crypto-library functions that + are documented to allocate memory that needs to be de-allocate with + OPENSSL_free. */ +#define ssh_crypto_free(x) OPENSSL_free(x) + +#include +#include + +typedef BIGNUM* bignum; +typedef const BIGNUM* const_bignum; +typedef BN_CTX* bignum_CTX; + +#define bignum_new() BN_new() +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + BN_clear_free((num)); \ + (num)=NULL; \ + } \ + } while(0) +#define bignum_set_word(bn,n) BN_set_word(bn,n) +#define bignum_bin2bn(data, datalen, dest) \ + do { \ + (*dest) = BN_new(); \ + if ((*dest) != NULL) { \ + BN_bin2bn(data,datalen,(*dest)); \ + } \ + } while(0) +#define bignum_bn2dec(num) BN_bn2dec(num) +#define bignum_dec2bn(data, bn) BN_dec2bn(bn, data) +#define bignum_hex2bn(data, bn) BN_hex2bn(bn, data) +#define bignum_bn2hex(num, dest) (*dest)=(unsigned char *)BN_bn2hex(num) +#define bignum_rand(rnd, bits) BN_rand(rnd, bits, 0, 1) +#define bignum_rand_range(rnd, max) BN_rand_range(rnd, max) +#define bignum_ctx_new() BN_CTX_new() +#define bignum_ctx_free(num) BN_CTX_free(num) +#define bignum_ctx_invalid(ctx) ((ctx) == NULL) +#define bignum_mod_exp(dest,generator,exp,modulo,ctx) BN_mod_exp(dest,generator,exp,modulo,ctx) +#define bignum_add(dest, a, b) BN_add(dest, a, b) +#define bignum_sub(dest, a, b) BN_sub(dest, a, b) +#define bignum_mod(dest, a, b, ctx) BN_mod(dest, a, b, ctx) +#define bignum_num_bytes(num) (size_t)BN_num_bytes(num) +#define bignum_num_bits(num) (size_t)BN_num_bits(num) +#define bignum_is_bit_set(num,bit) BN_is_bit_set(num, (int)bit) +#define bignum_bn2bin(num,len, ptr) BN_bn2bin(num, ptr) +#define bignum_cmp(num1,num2) BN_cmp(num1,num2) +#define bignum_rshift1(dest, src) BN_rshift1(dest, src) +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = BN_dup(orig); \ + } else { \ + BN_copy(*(dest), orig); \ + } \ + } while(0) + + +/* Returns true if the OpenSSL is operating in FIPS mode */ +#ifdef HAVE_OPENSSL_FIPS_MODE +#define ssh_fips_mode() (FIPS_mode() != 0) +#elif OPENSSL_VERSION_NUMBER >= 0x30000000L +#define ssh_fips_mode() EVP_default_properties_is_fips_enabled(NULL) +#else +#define ssh_fips_mode() false +#endif + +ssh_string pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p); +int pki_key_ecgroup_name_to_nid(const char *group); + +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) +int pki_load_pkcs11_provider(void); +#else +ENGINE *pki_get_engine(void); +#endif +#endif /* WITH_PKCS11_PROVIDER */ + +#define FIPS_FALLBACK_PROPQ "provider=default,-fips" + +#endif /* HAVE_LIBCRYPTO */ + +#endif /* LIBCRYPTO_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libgcrypt.h b/src/libs/libssh-0.12.2/include/libssh/libgcrypt.h new file mode 100644 index 000000000000..ce0beefdb5b3 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libgcrypt.h @@ -0,0 +1,128 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBGCRYPT_H_ +#define LIBGCRYPT_H_ + +#include "config.h" + +#ifdef HAVE_LIBGCRYPT + +#include +typedef gcry_md_hd_t SHACTX; +typedef gcry_md_hd_t SHA256CTX; +typedef gcry_md_hd_t SHA384CTX; +typedef gcry_md_hd_t SHA512CTX; +typedef gcry_md_hd_t MD5CTX; +typedef gcry_md_hd_t HMACCTX; +#define SHA_DIGEST_LENGTH 20 +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define MD5_DIGEST_LEN 16 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH + +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 64 +#endif + +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE + +#define ssh_crypto_free(x) gcry_free(x) + +typedef gcry_mpi_t bignum; +typedef const struct gcry_mpi *const_bignum; +typedef void* bignum_CTX; + +/* Constants for curves. */ +#define NID_gcrypt_nistp256 0 +#define NID_gcrypt_nistp384 1 +#define NID_gcrypt_nistp521 2 + +/* missing gcrypt functions */ +int ssh_gcry_dec2bn(bignum *bn, const char *data); +char *ssh_gcry_bn2dec(bignum bn); +int ssh_gcry_rand_range(bignum rnd, bignum max); + +#define bignum_new() gcry_mpi_new(0) +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + gcry_mpi_release((num)); \ + (num)=NULL; \ + } \ + } while (0) +#define bignum_free(num) gcry_mpi_release(num) +#define bignum_ctx_new() NULL +#define bignum_ctx_free(ctx) do {(ctx) = NULL;} while(0) +#define bignum_ctx_invalid(ctx) (ctx != NULL) +#define bignum_set_word(bn,n) (gcry_mpi_set_ui(bn,n)!=NULL ? 1 : 0) +#define bignum_bin2bn(data,datalen,dest) gcry_mpi_scan(dest,GCRYMPI_FMT_USG,data,datalen,NULL) +#define bignum_bn2dec(num) ssh_gcry_bn2dec(num) +#define bignum_dec2bn(num, data) ssh_gcry_dec2bn(data, num) + +#define bignum_bn2hex(num, data) \ + gcry_mpi_aprint(GCRYMPI_FMT_HEX, data, NULL, (const gcry_mpi_t)num) + +#define bignum_hex2bn(data, num) (gcry_mpi_scan(num,GCRYMPI_FMT_HEX,data,0,NULL)==0?1:0) +#define bignum_rand(num,bits) 1,gcry_mpi_randomize(num,bits,GCRY_STRONG_RANDOM),gcry_mpi_set_bit(num,bits-1),gcry_mpi_set_bit(num,0) +#define bignum_mod_exp(dest,generator,exp,modulo, ctx) 1,gcry_mpi_powm(dest,generator,exp,modulo) +#define bignum_num_bits(num) gcry_mpi_get_nbits(num) +#define bignum_num_bytes(num) ((gcry_mpi_get_nbits(num)+7)/8) +#define bignum_is_bit_set(num,bit) gcry_mpi_test_bit(num,bit) +#define bignum_bn2bin(num,datalen,data) gcry_mpi_print(GCRYMPI_FMT_USG,data,datalen,NULL,num) +#define bignum_cmp(num1,num2) gcry_mpi_cmp(num1,num2) +#define bignum_rshift1(dest, src) gcry_mpi_rshift (dest, src, 1) +#define bignum_add(dst, a, b) gcry_mpi_add(dst, a, b) +#define bignum_sub(dst, a, b) gcry_mpi_sub(dst, a, b) +#define bignum_mod(dst, a, b, ctx) 1,gcry_mpi_mod(dst, a, b) +#define bignum_rand_range(rnd, max) ssh_gcry_rand_range(rnd, max); +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = gcry_mpi_copy((const gcry_mpi_t)orig); \ + } else { \ + gcry_mpi_set(*(dest), (const gcry_mpi_t)orig); \ + } \ + } while(0) +/* Helper functions for data conversions. */ + +#ifdef __cplusplus +extern "C" { +#endif + +/* Extract an MPI from the given s-expression SEXP named NAME which is + encoded using INFORMAT and store it in a newly allocated ssh_string + encoded using OUTFORMAT. */ +ssh_string ssh_sexp_extract_mpi(const gcry_sexp_t sexp, + const char *name, + enum gcry_mpi_format informat, + enum gcry_mpi_format outformat); + +#define ssh_fips_mode() false + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_LIBGCRYPT */ + +#endif /* LIBGCRYPT_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libmbedcrypto.h b/src/libs/libssh-0.12.2/include/libssh/libmbedcrypto.h new file mode 100644 index 000000000000..71ebcccd0f70 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libmbedcrypto.h @@ -0,0 +1,150 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef LIBMBEDCRYPTO_H_ +#define LIBMBEDCRYPTO_H_ + +#include "config.h" + +#ifdef HAVE_LIBMBEDCRYPTO + +#include +#include +#include +#include +#include +#include +#include + +typedef mbedtls_md_context_t *SHACTX; +typedef mbedtls_md_context_t *SHA256CTX; +typedef mbedtls_md_context_t *SHA384CTX; +typedef mbedtls_md_context_t *SHA512CTX; +typedef mbedtls_md_context_t *MD5CTX; +typedef mbedtls_md_context_t *HMACCTX; + +#define SHA_DIGEST_LENGTH 20 +#define SHA_DIGEST_LEN SHA_DIGEST_LENGTH +#define MD5_DIGEST_LEN 16 +#define SHA256_DIGEST_LENGTH 32 +#define SHA256_DIGEST_LEN SHA256_DIGEST_LENGTH +#define SHA384_DIGEST_LENGTH 48 +#define SHA384_DIGEST_LEN SHA384_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH 64 +#define SHA512_DIGEST_LEN SHA512_DIGEST_LENGTH + +#ifndef EVP_MAX_MD_SIZE +#define EVP_MAX_MD_SIZE 64 +#endif + +#define EVP_DIGEST_LEN EVP_MAX_MD_SIZE + +#define ssh_crypto_free(x) mbedtls_free(x) + +typedef mbedtls_mpi *bignum; +typedef const mbedtls_mpi *const_bignum; +typedef void* bignum_CTX; + +/* Constants for curves */ +#define NID_mbedtls_nistp256 0 +#define NID_mbedtls_nistp384 1 +#define NID_mbedtls_nistp521 2 + +struct mbedtls_ecdsa_sig { + bignum r; + bignum s; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +bignum ssh_mbedcry_bn_new(void); +void ssh_mbedcry_bn_free(bignum num); +char *ssh_mbedcry_bn2num(const_bignum num, int radix); +int ssh_mbedcry_rand(bignum rnd, int bits, int top, int bottom); +int ssh_mbedcry_is_bit_set(bignum num, size_t pos); +int ssh_mbedcry_rand_range(bignum dest, bignum max); +int ssh_mbedcry_hex2bn(bignum *dest, char *data); + +#define bignum_new() ssh_mbedcry_bn_new() +#define bignum_safe_free(num) do { \ + if ((num) != NULL) { \ + ssh_mbedcry_bn_free(num); \ + (num)=NULL; \ + } \ + } while(0) +#define bignum_ctx_new() NULL +#define bignum_ctx_free(num) do {(num) = NULL;} while(0) +#define bignum_ctx_invalid(ctx) (ctx == NULL?0:1) +#define bignum_set_word(bn, n) (mbedtls_mpi_lset(bn, n)==0?1:0) /* TODO fix + overflow/underflow */ +#define bignum_bin2bn(data, datalen, bn) do { \ + *(bn) = bignum_new(); \ + if (*(bn) != NULL) { \ + mbedtls_mpi_read_binary(*(bn), data, datalen); \ + } \ + } while(0) +#define bignum_bn2dec(num) ssh_mbedcry_bn2num(num, 10) +#define bignum_dec2bn(data, bn) mbedtls_mpi_read_string(bn, 10, data) +#define bignum_bn2hex(num, dest) (*dest)=(unsigned char *)ssh_mbedcry_bn2num(num, 16) +#define bignum_hex2bn(data, dest) ssh_mbedcry_hex2bn(dest, data) +#define bignum_rand(rnd, bits) ssh_mbedcry_rand((rnd), (bits), 0, 1) +#define bignum_rand_range(rnd, max) ssh_mbedcry_rand_range(rnd, max) +#define bignum_mod_exp(dest, generator, exp, modulo, ctx) \ + (mbedtls_mpi_exp_mod(dest, generator, exp, modulo, NULL)==0?1:0) +#define bignum_add(dest, a, b) mbedtls_mpi_add_mpi(dest, a, b) +#define bignum_sub(dest, a, b) mbedtls_mpi_sub_mpi(dest, a, b) +#define bignum_mod(dest, a, b, ctx) \ + (mbedtls_mpi_mod_mpi(dest, a, b) == 0 ? 1 : 0) +#define bignum_num_bytes(num) mbedtls_mpi_size(num) +#define bignum_num_bits(num) mbedtls_mpi_bitlen(num) +#define bignum_is_bit_set(num, bit) ssh_mbedcry_is_bit_set(num, bit) +#define bignum_bn2bin(num, len, ptr) mbedtls_mpi_write_binary(num, ptr, \ + mbedtls_mpi_size(num)) +#define bignum_cmp(num1, num2) mbedtls_mpi_cmp_mpi(num1, num2) +#define bignum_rshift1(dest, src) mbedtls_mpi_copy(dest, src), mbedtls_mpi_shift_r(dest, 1) +#define bignum_dup(orig, dest) do { \ + if (*(dest) == NULL) { \ + *(dest) = bignum_new(); \ + } \ + if (*(dest) != NULL) { \ + mbedtls_mpi_copy(*(dest), orig); \ + } \ + } while(0) + +mbedtls_ctr_drbg_context *ssh_get_mbedtls_ctr_drbg_context(void); + +int ssh_mbedtls_random(void *where, int len, int strong); + +ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const + mbedtls_ecp_point *p); + +#define ssh_fips_mode() false + +#ifdef __cplusplus +} +#endif + +#endif /* HAVE_LIBMBEDCRYPTO */ +#endif /* LIBMBEDCRYPTO_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libssh.h b/src/libs/libssh-0.12.2/include/libssh/libssh.h new file mode 100644 index 000000000000..0c9e774654a9 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libssh.h @@ -0,0 +1,1023 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2026 by Aris Adamantiadis and the libssh team + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_H +#define _LIBSSH_H + +#include + +#if defined _WIN32 || defined __CYGWIN__ + #ifdef LIBSSH_STATIC + #define LIBSSH_API + #else + #ifdef LIBSSH_EXPORTS + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllexport)) + #else + #define LIBSSH_API __declspec(dllexport) + #endif + #else + #ifdef __GNUC__ + #define LIBSSH_API __attribute__((dllimport)) + #else + #define LIBSSH_API __declspec(dllimport) + #endif + #endif + #endif +#else + #if __GNUC__ >= 4 && !defined(__OS2__) + #define LIBSSH_API __attribute__((visibility("default"))) + #else + #define LIBSSH_API + #endif +#endif + +#include +#include +#include +#include + +#ifdef _MSC_VER + typedef int mode_t; +#else /* _MSC_VER */ + #include + #include +#endif /* _MSC_VER */ + +#ifdef _WIN32 + #include +#else /* _WIN32 */ + #include /* for fd_set * */ + #include +#endif /* _WIN32 */ + +#define SSH_STRINGIFY(s) SSH_TOSTRING(s) +#define SSH_TOSTRING(s) #s + +/* GCC have printf type attribute check. */ +#ifdef __GNUC__ +#define PRINTF_ATTRIBUTE(a,b) __attribute__ ((__format__ (__printf__, a, b))) +#else +#define PRINTF_ATTRIBUTE(a,b) +#endif /* __GNUC__ */ + +#if !defined(SSH_SUPPRESS_DEPRECATED) && defined(__GNUC__) +#define SSH_DEPRECATED __attribute__ ((deprecated)) +#else +#define SSH_DEPRECATED +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_counter_struct { + uint64_t in_bytes; + uint64_t out_bytes; + uint64_t in_packets; + uint64_t out_packets; +}; +typedef struct ssh_counter_struct *ssh_counter; + +typedef struct ssh_agent_struct* ssh_agent; +typedef struct ssh_buffer_struct* ssh_buffer; +typedef struct ssh_channel_struct* ssh_channel; +typedef struct ssh_message_struct* ssh_message; +typedef struct ssh_pcap_file_struct* ssh_pcap_file; +typedef struct ssh_key_struct* ssh_key; +typedef struct ssh_scp_struct* ssh_scp; +typedef struct ssh_session_struct* ssh_session; +typedef struct ssh_string_struct* ssh_string; +typedef struct ssh_event_struct* ssh_event; +typedef struct ssh_connector_struct * ssh_connector; +typedef struct ssh_pki_ctx_struct *ssh_pki_ctx; +typedef void* ssh_gssapi_creds; + +/* Socket type */ +#ifdef _WIN32 +#ifndef socket_t +typedef SOCKET socket_t; +#endif /* socket_t */ +#else /* _WIN32 */ +#ifndef socket_t +typedef int socket_t; +#endif +#endif /* _WIN32 */ + +#define SSH_INVALID_SOCKET ((socket_t) -1) + +/* the offsets of methods */ +enum ssh_kex_types_e { + SSH_KEX=0, + SSH_HOSTKEYS, + SSH_CRYPT_C_S, + SSH_CRYPT_S_C, + SSH_MAC_C_S, + SSH_MAC_S_C, + SSH_COMP_C_S, + SSH_COMP_S_C, + SSH_LANG_C_S, + SSH_LANG_S_C +}; + +#define SSH_CRYPT 2 +#define SSH_MAC 3 +#define SSH_COMP 4 +#define SSH_LANG 5 + +enum ssh_auth_e { + SSH_AUTH_SUCCESS=0, + SSH_AUTH_DENIED, + SSH_AUTH_PARTIAL, + SSH_AUTH_INFO, + SSH_AUTH_AGAIN, + SSH_AUTH_ERROR=-1 +}; + +/* auth flags */ +#define SSH_AUTH_METHOD_UNKNOWN 0x0000u +#define SSH_AUTH_METHOD_NONE 0x0001u +#define SSH_AUTH_METHOD_PASSWORD 0x0002u +#define SSH_AUTH_METHOD_PUBLICKEY 0x0004u +#define SSH_AUTH_METHOD_HOSTBASED 0x0008u +#define SSH_AUTH_METHOD_INTERACTIVE 0x0010u +#define SSH_AUTH_METHOD_GSSAPI_MIC 0x0020u +#define SSH_AUTH_METHOD_GSSAPI_KEYEX 0x0040u + +/* messages */ +enum ssh_requests_e { + SSH_REQUEST_AUTH=1, + SSH_REQUEST_CHANNEL_OPEN, + SSH_REQUEST_CHANNEL, + SSH_REQUEST_SERVICE, + SSH_REQUEST_GLOBAL +}; + +enum ssh_channel_type_e { + SSH_CHANNEL_UNKNOWN=0, + SSH_CHANNEL_SESSION, + SSH_CHANNEL_DIRECT_TCPIP, + SSH_CHANNEL_FORWARDED_TCPIP, + SSH_CHANNEL_X11, + SSH_CHANNEL_AUTH_AGENT +}; + +enum ssh_channel_requests_e { + SSH_CHANNEL_REQUEST_UNKNOWN=0, + SSH_CHANNEL_REQUEST_PTY, + SSH_CHANNEL_REQUEST_EXEC, + SSH_CHANNEL_REQUEST_SHELL, + SSH_CHANNEL_REQUEST_ENV, + SSH_CHANNEL_REQUEST_SUBSYSTEM, + SSH_CHANNEL_REQUEST_WINDOW_CHANGE, + SSH_CHANNEL_REQUEST_X11 +}; + +enum ssh_global_requests_e { + SSH_GLOBAL_REQUEST_UNKNOWN=0, + SSH_GLOBAL_REQUEST_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD, + SSH_GLOBAL_REQUEST_KEEPALIVE, + SSH_GLOBAL_REQUEST_NO_MORE_SESSIONS +}; + +enum ssh_publickey_state_e { + SSH_PUBLICKEY_STATE_ERROR=-1, + SSH_PUBLICKEY_STATE_NONE=0, + SSH_PUBLICKEY_STATE_VALID=1, + SSH_PUBLICKEY_STATE_WRONG=2 +}; + +/* Status flags */ +/** Socket is closed */ +#define SSH_CLOSED 0x01 +/** Reading to socket won't block */ +#define SSH_READ_PENDING 0x02 +/** Session was closed due to an error */ +#define SSH_CLOSED_ERROR 0x04 +/** Output buffer not empty */ +#define SSH_WRITE_PENDING 0x08 + +enum ssh_server_known_e { + SSH_SERVER_ERROR=-1, + SSH_SERVER_NOT_KNOWN=0, + SSH_SERVER_KNOWN_OK, + SSH_SERVER_KNOWN_CHANGED, + SSH_SERVER_FOUND_OTHER, + SSH_SERVER_FILE_NOT_FOUND +}; + +enum ssh_known_hosts_e { + /** + * There had been an error checking the host. + */ + SSH_KNOWN_HOSTS_ERROR = -2, + + /** + * The known host file does not exist. The host is thus unknown. File will + * be created if host key is accepted. + */ + SSH_KNOWN_HOSTS_NOT_FOUND = -1, + + /** + * The server is unknown. User should confirm the public key hash is + * correct. + */ + SSH_KNOWN_HOSTS_UNKNOWN = 0, + + /** + * The server is known and has not changed. + */ + SSH_KNOWN_HOSTS_OK, + + /** + * The server key has changed. Either you are under attack or the + * administrator changed the key. You HAVE to warn the user about a + * possible attack. + */ + SSH_KNOWN_HOSTS_CHANGED, + + /** + * The server gave use a key of a type while we had an other type recorded. + * It is a possible attack. + */ + SSH_KNOWN_HOSTS_OTHER, +}; + +#ifndef MD5_DIGEST_LEN + #define MD5_DIGEST_LEN 16 +#endif +/* errors */ + +enum ssh_error_types_e { + SSH_NO_ERROR=0, + SSH_REQUEST_DENIED, + SSH_FATAL, + SSH_EINTR +}; + +/* some types for keys */ +enum ssh_keytypes_e{ + SSH_KEYTYPE_UNKNOWN=0, + SSH_KEYTYPE_DSS=1, /* deprecated */ + SSH_KEYTYPE_RSA, + SSH_KEYTYPE_RSA1, + SSH_KEYTYPE_ECDSA, /* deprecated */ + SSH_KEYTYPE_ED25519, + SSH_KEYTYPE_DSS_CERT01, /* deprecated */ + SSH_KEYTYPE_RSA_CERT01, + SSH_KEYTYPE_ECDSA_P256, + SSH_KEYTYPE_ECDSA_P384, + SSH_KEYTYPE_ECDSA_P521, + SSH_KEYTYPE_ECDSA_P256_CERT01, + SSH_KEYTYPE_ECDSA_P384_CERT01, + SSH_KEYTYPE_ECDSA_P521_CERT01, + SSH_KEYTYPE_ED25519_CERT01, + SSH_KEYTYPE_SK_ECDSA, + SSH_KEYTYPE_SK_ECDSA_CERT01, + SSH_KEYTYPE_SK_ED25519, + SSH_KEYTYPE_SK_ED25519_CERT01, +}; + +enum ssh_keycmp_e { + SSH_KEY_CMP_PUBLIC = 0, + SSH_KEY_CMP_PRIVATE = 1, + SSH_KEY_CMP_CERTIFICATE = 2, +}; + +#define SSH_ADDRSTRLEN 46 + +struct ssh_knownhosts_entry { + char *hostname; + char *unparsed; + ssh_key publickey; + char *comment; +}; + + +/* Error return codes */ +#define SSH_OK 0 /* No error */ +#define SSH_ERROR -1 /* Error of some kind */ +#define SSH_AGAIN -2 /* The nonblocking call must be repeated */ +#define SSH_EOF -127 /* We have already a eof */ + +/** + * @addtogroup libssh_log + * + * @{ + */ + +enum { + /** No logging at all + */ + SSH_LOG_NOLOG=0, + /** Only unrecoverable errors + */ + SSH_LOG_WARNING, + /** Information for the users + */ + SSH_LOG_PROTOCOL, + /** Debug information, to see what is going on + */ + SSH_LOG_PACKET, + /** Trace information and recoverable error messages + */ + SSH_LOG_FUNCTIONS +}; +/** @} */ +#define SSH_LOG_RARE SSH_LOG_WARNING + +/** + * @name Logging levels + * + * @brief Debug levels for logging. + * @{ + */ + +/** No logging at all */ +#define SSH_LOG_NONE 0 +/** Show only fatal warnings */ +#define SSH_LOG_WARN 1 +/** Get some information what's going on */ +#define SSH_LOG_INFO 2 +/** Get detailed debugging information **/ +#define SSH_LOG_DEBUG 3 +/** Get trace output, packet information, ... */ +#define SSH_LOG_TRACE 4 + +/** @} */ + +enum ssh_control_master_options_e { + SSH_CONTROL_MASTER_NO, + SSH_CONTROL_MASTER_AUTO, + SSH_CONTROL_MASTER_YES, + SSH_CONTROL_MASTER_ASK, + SSH_CONTROL_MASTER_AUTOASK +}; + +enum ssh_address_family_options_e { + SSH_ADDRESS_FAMILY_ANY, + SSH_ADDRESS_FAMILY_INET, + SSH_ADDRESS_FAMILY_INET6 +}; + +enum ssh_options_e { + SSH_OPTIONS_HOST, + SSH_OPTIONS_PORT, + SSH_OPTIONS_PORT_STR, + SSH_OPTIONS_FD, + SSH_OPTIONS_USER, + SSH_OPTIONS_SSH_DIR, + SSH_OPTIONS_IDENTITY, + SSH_OPTIONS_ADD_IDENTITY, + SSH_OPTIONS_KNOWNHOSTS, + SSH_OPTIONS_TIMEOUT, + SSH_OPTIONS_TIMEOUT_USEC, + SSH_OPTIONS_SSH1, + SSH_OPTIONS_SSH2, + SSH_OPTIONS_LOG_VERBOSITY, + SSH_OPTIONS_LOG_VERBOSITY_STR, + SSH_OPTIONS_CIPHERS_C_S, + SSH_OPTIONS_CIPHERS_S_C, + SSH_OPTIONS_COMPRESSION_C_S, + SSH_OPTIONS_COMPRESSION_S_C, + SSH_OPTIONS_PROXYCOMMAND, + SSH_OPTIONS_BINDADDR, + SSH_OPTIONS_STRICTHOSTKEYCHECK, + SSH_OPTIONS_COMPRESSION, + SSH_OPTIONS_COMPRESSION_LEVEL, + SSH_OPTIONS_KEY_EXCHANGE, + SSH_OPTIONS_HOSTKEYS, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, + SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, + SSH_OPTIONS_HMAC_C_S, + SSH_OPTIONS_HMAC_S_C, + SSH_OPTIONS_PASSWORD_AUTH, + SSH_OPTIONS_PUBKEY_AUTH, + SSH_OPTIONS_KBDINT_AUTH, + SSH_OPTIONS_GSSAPI_AUTH, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + SSH_OPTIONS_NODELAY, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + SSH_OPTIONS_PROCESS_CONFIG, + SSH_OPTIONS_REKEY_DATA, + SSH_OPTIONS_REKEY_TIME, + SSH_OPTIONS_RSA_MIN_SIZE, + SSH_OPTIONS_IDENTITY_AGENT, + SSH_OPTIONS_IDENTITIES_ONLY, + SSH_OPTIONS_CONTROL_MASTER, + SSH_OPTIONS_CONTROL_PATH, + SSH_OPTIONS_CERTIFICATE, + SSH_OPTIONS_PROXYJUMP, + SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, + SSH_OPTIONS_PKI_CONTEXT, + SSH_OPTIONS_ADDRESS_FAMILY, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + SSH_OPTIONS_NEXT_IDENTITY, +}; + +enum { + /** Code is going to write/create remote files */ + SSH_SCP_WRITE, + /** Code is going to read remote files */ + SSH_SCP_READ, + SSH_SCP_RECURSIVE=0x10 +}; + +enum ssh_scp_request_types { + /** A new directory is going to be pulled */ + SSH_SCP_REQUEST_NEWDIR=1, + /** A new file is going to be pulled */ + SSH_SCP_REQUEST_NEWFILE, + /** End of requests */ + SSH_SCP_REQUEST_EOF, + /** End of directory */ + SSH_SCP_REQUEST_ENDDIR, + /** Warning received */ + SSH_SCP_REQUEST_WARNING +}; + +enum ssh_connector_flags_e { + /** Only the standard stream of the channel */ + SSH_CONNECTOR_STDOUT = 1, + SSH_CONNECTOR_STDINOUT = 1, + /** Only the exception stream of the channel */ + SSH_CONNECTOR_STDERR = 2, + /** Merge both standard and exception streams */ + SSH_CONNECTOR_BOTH = 3 +}; + +LIBSSH_API int ssh_blocking_flush(ssh_session session, int timeout); +LIBSSH_API ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms); +LIBSSH_API int ssh_channel_change_pty_size(ssh_channel channel,int cols,int rows); +LIBSSH_API int ssh_channel_close(ssh_channel channel); +#define SSH_CHANNEL_FREE(x) \ + do { \ + if ((x) != NULL) { \ + ssh_channel_free(x); \ + (x) = NULL; \ + } \ + } while (0) +LIBSSH_API void ssh_channel_free(ssh_channel channel); +LIBSSH_API int ssh_channel_get_exit_state(ssh_channel channel, + uint32_t *pexit_code, + char **pexit_signal, + int *pcore_dumped); +SSH_DEPRECATED LIBSSH_API int ssh_channel_get_exit_status(ssh_channel channel); +LIBSSH_API ssh_session ssh_channel_get_session(ssh_channel channel); +LIBSSH_API int ssh_channel_is_closed(ssh_channel channel); +LIBSSH_API int ssh_channel_is_eof(ssh_channel channel); +LIBSSH_API int ssh_channel_is_open(ssh_channel channel); +LIBSSH_API ssh_channel ssh_channel_new(ssh_session session); +LIBSSH_API int ssh_channel_open_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_forward_unix(ssh_channel channel, const char *remotepath, + const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_session(ssh_channel channel); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, const char *orig_addr, int orig_port); +LIBSSH_API int ssh_channel_open_tunnel(ssh_channel channel, int remote_unit); +LIBSSH_API int ssh_channel_poll(ssh_channel channel, int is_stderr); +LIBSSH_API int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr); +LIBSSH_API int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr); +LIBSSH_API int ssh_channel_read_timeout(ssh_channel channel, void *dest, uint32_t count, int is_stderr, int timeout_ms); +LIBSSH_API int ssh_channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr); +LIBSSH_API int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value); +LIBSSH_API int ssh_channel_request_exec(ssh_channel channel, const char *cmd); +LIBSSH_API int ssh_channel_request_pty(ssh_channel channel); +LIBSSH_API int ssh_channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows); +LIBSSH_API int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *term, + int cols, int rows, const unsigned char* modes, size_t modes_len); +LIBSSH_API int ssh_channel_request_shell(ssh_channel channel); +LIBSSH_API int ssh_channel_request_send_signal(ssh_channel channel, const char *signum); +LIBSSH_API int ssh_channel_request_send_break(ssh_channel channel, uint32_t length); +LIBSSH_API int ssh_channel_request_sftp(ssh_channel channel); +LIBSSH_API int ssh_channel_request_subsystem(ssh_channel channel, const char *subsystem); +LIBSSH_API int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number); +LIBSSH_API int ssh_channel_request_auth_agent(ssh_channel channel); +LIBSSH_API int ssh_channel_send_eof(ssh_channel channel); +LIBSSH_API void ssh_channel_set_blocking(ssh_channel channel, int blocking); +LIBSSH_API void ssh_channel_set_counter(ssh_channel channel, + ssh_counter counter); +LIBSSH_API int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len); +LIBSSH_API int ssh_channel_write_stderr(ssh_channel channel, + const void *data, + uint32_t len); +LIBSSH_API uint32_t ssh_channel_window_size(ssh_channel channel); + +LIBSSH_API char *ssh_basename (const char *path); +LIBSSH_API void ssh_clean_pubkey_hash(unsigned char **hash); +LIBSSH_API int ssh_connect(ssh_session session); + +LIBSSH_API ssh_connector ssh_connector_new(ssh_session session); +LIBSSH_API void ssh_connector_free(ssh_connector connector); +LIBSSH_API int ssh_connector_set_in_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API int ssh_connector_set_out_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags); +LIBSSH_API void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd); +LIBSSH_API void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd); + +LIBSSH_API const char *ssh_copyright(void); +LIBSSH_API void ssh_disconnect(ssh_session session); +LIBSSH_API char *ssh_dirname (const char *path); +LIBSSH_API int ssh_finalize(void); + +/* REVERSE PORT FORWARDING */ +LIBSSH_API ssh_channel ssh_channel_open_forward_port(ssh_session session, + int timeout_ms, + int *destination_port, + char **originator, + int *originator_port); +SSH_DEPRECATED LIBSSH_API ssh_channel ssh_channel_accept_forward(ssh_session session, + int timeout_ms, + int *destination_port); +LIBSSH_API int ssh_channel_cancel_forward(ssh_session session, + const char *address, + int port); +LIBSSH_API int ssh_channel_listen_forward(ssh_session session, + const char *address, + int port, + int *bound_port); + +LIBSSH_API void ssh_free(ssh_session session); +LIBSSH_API const char *ssh_get_disconnect_message(ssh_session session); +LIBSSH_API const char *ssh_get_error(void *error); +LIBSSH_API int ssh_get_error_code(void *error); +LIBSSH_API socket_t ssh_get_fd(ssh_session session); +LIBSSH_API char *ssh_get_hexa(const unsigned char *what, size_t len); +LIBSSH_API char *ssh_get_issue_banner(ssh_session session); +LIBSSH_API int ssh_get_openssh_version(ssh_session session); +LIBSSH_API int ssh_request_no_more_sessions(ssh_session session); + +LIBSSH_API int ssh_get_server_publickey(ssh_session session, ssh_key *key); + +enum ssh_publickey_hash_type { + SSH_PUBLICKEY_HASH_SHA1, + SSH_PUBLICKEY_HASH_MD5, + SSH_PUBLICKEY_HASH_SHA256 +}; +LIBSSH_API int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, + unsigned char **hash, + size_t *hlen); + +/* DEPRECATED FUNCTIONS */ +SSH_DEPRECATED LIBSSH_API int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash); +SSH_DEPRECATED LIBSSH_API ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms); +SSH_DEPRECATED LIBSSH_API int ssh_forward_cancel(ssh_session session, const char *address, int port); +SSH_DEPRECATED LIBSSH_API int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port); +SSH_DEPRECATED LIBSSH_API int ssh_get_publickey(ssh_session session, ssh_key *key); +SSH_DEPRECATED LIBSSH_API int ssh_write_knownhost(ssh_session session); +SSH_DEPRECATED LIBSSH_API char *ssh_dump_knownhost(ssh_session session); +SSH_DEPRECATED LIBSSH_API int ssh_is_server_known(ssh_session session); +SSH_DEPRECATED LIBSSH_API void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len); +SSH_DEPRECATED LIBSSH_API int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout); + +SSH_DEPRECATED LIBSSH_API int ssh_scp_accept_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_close(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_deny_request(ssh_scp scp, const char *reason); +SSH_DEPRECATED LIBSSH_API void ssh_scp_free(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_init(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_leave_directory(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location); +SSH_DEPRECATED LIBSSH_API int ssh_scp_pull_request(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, int perms); +SSH_DEPRECATED LIBSSH_API int ssh_scp_read(ssh_scp scp, void *buffer, size_t size); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_filename(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_request_get_permissions(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API size_t ssh_scp_request_get_size(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API uint64_t ssh_scp_request_get_size64(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API const char *ssh_scp_request_get_warning(ssh_scp scp); +SSH_DEPRECATED LIBSSH_API int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len); + + +LIBSSH_API int ssh_get_random(void *where,int len,int strong); +LIBSSH_API int ssh_get_version(ssh_session session); +LIBSSH_API int ssh_get_status(ssh_session session); +LIBSSH_API int ssh_get_poll_flags(ssh_session session); +LIBSSH_API int ssh_init(void); +LIBSSH_API int ssh_is_blocking(ssh_session session); +LIBSSH_API int ssh_is_connected(ssh_session session); + +/* KNOWN HOSTS */ +LIBSSH_API void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry); +#define SSH_KNOWNHOSTS_ENTRY_FREE(e) do { \ + if ((e) != NULL) { \ + ssh_knownhosts_entry_free(e); \ + e = NULL; \ + } \ +} while(0) + +LIBSSH_API int ssh_known_hosts_parse_line(const char *host, + const char *line, + struct ssh_knownhosts_entry **entry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session); + +LIBSSH_API int ssh_session_export_known_hosts_entry(ssh_session session, + char **pentry_string); +LIBSSH_API int ssh_session_update_known_hosts(ssh_session session); + +LIBSSH_API enum ssh_known_hosts_e ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry); +LIBSSH_API enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session); + +/* LOGGING */ +LIBSSH_API int ssh_set_log_level(int level); +LIBSSH_API int ssh_get_log_level(void); +LIBSSH_API void *ssh_get_log_userdata(void); +LIBSSH_API int ssh_set_log_userdata(void *data); +LIBSSH_API void ssh_vlog(int verbosity, + const char *function, + const char *format, + va_list *va) PRINTF_ATTRIBUTE(3, 0); +LIBSSH_API void _ssh_log(int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +/* legacy */ +SSH_DEPRECATED LIBSSH_API void ssh_log(ssh_session session, + int prioriry, + const char *format, ...) PRINTF_ATTRIBUTE(3, 4); + +LIBSSH_API ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, ssh_channel chan); +LIBSSH_API int ssh_message_channel_request_reply_success(ssh_message msg); +#define SSH_MESSAGE_FREE(x) \ + do { if ((x) != NULL) { ssh_message_free(x); (x) = NULL; } } while(0) +LIBSSH_API void ssh_message_free(ssh_message msg); +LIBSSH_API ssh_message ssh_message_get(ssh_session session); +LIBSSH_API int ssh_message_subtype(ssh_message msg); +LIBSSH_API int ssh_message_type(ssh_message msg); +LIBSSH_API int ssh_mkdir (const char *pathname, mode_t mode); +LIBSSH_API ssh_session ssh_new(void); + +LIBSSH_API int ssh_options_copy(ssh_session src, ssh_session *dest); +LIBSSH_API int ssh_options_getopt(ssh_session session, int *argcptr, char **argv); +LIBSSH_API int ssh_options_parse_config(ssh_session session, const char *filename); +LIBSSH_API int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value); +LIBSSH_API int ssh_options_get(ssh_session session, enum ssh_options_e type, + char **value); +LIBSSH_API int ssh_options_get_port(ssh_session session, unsigned int * port_target); +LIBSSH_API int ssh_pcap_file_close(ssh_pcap_file pcap); +LIBSSH_API void ssh_pcap_file_free(ssh_pcap_file pcap); +LIBSSH_API ssh_pcap_file ssh_pcap_file_new(void); +LIBSSH_API int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename); + +/** + * @addtogroup libssh_auth + * + * @{ + */ + +/** + * @brief SSH authentication callback for password and publickey auth. + * + * @param prompt Prompt to be displayed. + * @param buf Buffer to save the password. You should null-terminate it. + * @param len Length of the buffer. + * @param echo Enable or disable the echo of what you type. + * @param verify Should the password be verified? + * @param userdata Userdata to be passed to the callback function. Useful + * for GUI applications. + * + * @return 0 on success, < 0 on error. + */ +typedef int (*ssh_auth_callback) (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata); + +/** @} */ + +enum ssh_file_format_e { + SSH_FILE_FORMAT_DEFAULT = 0, + SSH_FILE_FORMAT_OPENSSH, + SSH_FILE_FORMAT_PEM, +}; + +LIBSSH_API ssh_key ssh_key_new(void); +#define SSH_KEY_FREE(x) \ + do { if ((x) != NULL) { ssh_key_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_key_free (ssh_key key); +LIBSSH_API enum ssh_keytypes_e ssh_key_type(const ssh_key key); +LIBSSH_API const char *ssh_key_type_to_char(enum ssh_keytypes_e type); +LIBSSH_API enum ssh_keytypes_e ssh_key_type_from_name(const char *name); +LIBSSH_API int ssh_key_is_public(const ssh_key k); +LIBSSH_API int ssh_key_is_private(const ssh_key k); +LIBSSH_API int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); +LIBSSH_API ssh_key ssh_key_dup(const ssh_key key); +LIBSSH_API uint32_t ssh_key_get_sk_flags(const ssh_key key); +LIBSSH_API ssh_string ssh_key_get_sk_application(const ssh_key key); +LIBSSH_API ssh_string ssh_key_get_sk_user_id(const ssh_key key); + +SSH_DEPRECATED LIBSSH_API int +ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey); + +LIBSSH_API int ssh_pki_generate_key(enum ssh_keytypes_e type, + ssh_pki_ctx pki_context, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key); +LIBSSH_API int +ssh_pki_export_privkey_base64_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key, + enum ssh_file_format_e format); +LIBSSH_API int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename); +LIBSSH_API int +ssh_pki_export_privkey_file_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename, + enum ssh_file_format_e format); + +LIBSSH_API int ssh_pki_copy_cert_to_privkey(const ssh_key cert_key, + ssh_key privkey); + +LIBSSH_API int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_pubkey_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_import_cert_base64(const char *b64_cert, + enum ssh_keytypes_e type, + ssh_key *pkey); +LIBSSH_API int ssh_pki_import_cert_file(const char *filename, + ssh_key *pkey); + +LIBSSH_API int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey); +LIBSSH_API int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key); +LIBSSH_API int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename); + +LIBSSH_API const char *ssh_pki_key_ecdsa_name(const ssh_key key); + +LIBSSH_API char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len); +LIBSSH_API void ssh_print_hash(enum ssh_publickey_hash_type type, unsigned char *hash, size_t len); +LIBSSH_API int ssh_send_ignore (ssh_session session, const char *data); +LIBSSH_API int ssh_send_debug (ssh_session session, const char *message, int always_display); +LIBSSH_API void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds); +LIBSSH_API int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout); +LIBSSH_API int ssh_service_request(ssh_session session, const char *service); +LIBSSH_API int ssh_set_agent_channel(ssh_session session, ssh_channel channel); +LIBSSH_API int ssh_set_agent_socket(ssh_session session, socket_t fd); +LIBSSH_API void ssh_set_blocking(ssh_session session, int blocking); +LIBSSH_API void ssh_set_counters(ssh_session session, ssh_counter scounter, + ssh_counter rcounter); +LIBSSH_API void ssh_set_fd_except(ssh_session session); +LIBSSH_API void ssh_set_fd_toread(ssh_session session); +LIBSSH_API void ssh_set_fd_towrite(ssh_session session); +LIBSSH_API void ssh_silent_disconnect(ssh_session session); +LIBSSH_API int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile); + +/* USERAUTH */ +LIBSSH_API int ssh_userauth_none(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_list(ssh_session session, const char *username); +LIBSSH_API int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey); +LIBSSH_API int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey); +LIBSSH_API int ssh_userauth_agent(ssh_session session, + const char *username); +LIBSSH_API int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, + char** value); +LIBSSH_API int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase); +LIBSSH_API int ssh_userauth_password(ssh_session session, + const char *username, + const char *password); + +LIBSSH_API int ssh_userauth_kbdint(ssh_session session, const char *user, const char *submethods); +LIBSSH_API const char *ssh_userauth_kbdint_getinstruction(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getname(ssh_session session); +LIBSSH_API int ssh_userauth_kbdint_getnprompts(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo); +LIBSSH_API int ssh_userauth_kbdint_getnanswers(ssh_session session); +LIBSSH_API const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i); +LIBSSH_API int ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer); +LIBSSH_API int ssh_userauth_gssapi(ssh_session session); +LIBSSH_API int ssh_userauth_gssapi_keyex(ssh_session session); +LIBSSH_API const char *ssh_version(int req_version); + +LIBSSH_API void ssh_string_burn(ssh_string str); +LIBSSH_API ssh_string ssh_string_copy(ssh_string str); +LIBSSH_API void *ssh_string_data(ssh_string str); +LIBSSH_API int ssh_string_fill(ssh_string str, const void *data, size_t len); +#define SSH_STRING_FREE(x) \ + do { if ((x) != NULL) { ssh_string_free(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free(ssh_string str); +LIBSSH_API ssh_string ssh_string_from_char(const char *what); +LIBSSH_API ssh_string ssh_string_from_data(const void *data, size_t len); +LIBSSH_API size_t ssh_string_len(ssh_string str); +LIBSSH_API ssh_string ssh_string_new(size_t size); +LIBSSH_API const char *ssh_string_get_char(ssh_string str); +LIBSSH_API char *ssh_string_to_char(ssh_string str); +#define SSH_STRING_FREE_CHAR(x) \ + do { if ((x) != NULL) { ssh_string_free_char(x); x = NULL; } } while(0) +LIBSSH_API void ssh_string_free_char(char *s); +LIBSSH_API int ssh_string_cmp(ssh_string s1, ssh_string s2); + +LIBSSH_API int ssh_getpass(const char *prompt, char *buf, size_t len, int echo, + int verify); + + +typedef int (*ssh_event_callback)(socket_t fd, int revents, void *userdata); + +LIBSSH_API ssh_event ssh_event_new(void); +LIBSSH_API int ssh_event_add_fd(ssh_event event, socket_t fd, short events, + ssh_event_callback cb, void *userdata); +LIBSSH_API int ssh_event_add_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_add_connector(ssh_event event, ssh_connector connector); +LIBSSH_API int ssh_event_dopoll(ssh_event event, int timeout); +LIBSSH_API int ssh_event_remove_fd(ssh_event event, socket_t fd); +LIBSSH_API int ssh_event_remove_session(ssh_event event, ssh_session session); +LIBSSH_API int ssh_event_remove_connector(ssh_event event, ssh_connector connector); +LIBSSH_API void ssh_event_free(ssh_event event); +LIBSSH_API const char* ssh_get_clientbanner(ssh_session session); +LIBSSH_API const char* ssh_get_serverbanner(ssh_session session); +LIBSSH_API const char* ssh_get_kex_algo(ssh_session session); +LIBSSH_API bool ssh_session_kex_is_gss(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_in(ssh_session session); +LIBSSH_API const char* ssh_get_cipher_out(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_in(ssh_session session); +LIBSSH_API const char* ssh_get_hmac_out(ssh_session session); +LIBSSH_API const char *ssh_get_supported_methods(enum ssh_kex_types_e type); + +LIBSSH_API ssh_buffer ssh_buffer_new(void); +LIBSSH_API void ssh_buffer_free(ssh_buffer buffer); +#define SSH_BUFFER_FREE(x) \ + do { if ((x) != NULL) { ssh_buffer_free(x); x = NULL; } } while(0) +LIBSSH_API int ssh_buffer_reinit(ssh_buffer buffer); +LIBSSH_API int ssh_buffer_add_data(ssh_buffer buffer, const void *data, uint32_t len); +LIBSSH_API uint32_t ssh_buffer_get_data(ssh_buffer buffer, void *data, uint32_t requestedlen); +LIBSSH_API void *ssh_buffer_get(ssh_buffer buffer); +LIBSSH_API uint32_t ssh_buffer_get_len(ssh_buffer buffer); +LIBSSH_API int ssh_session_set_disconnect_message(ssh_session session, const char *message); + +/* SSHSIG hashes data independently from the key used, so we use a new enum + to avoid confusion. See + https://gitlab.com/jas/ietf-sshsig-format/-/blob/cc70a225cbd695d5a6f20aaebdb4b92b0818e43a/ietf-sshsig-format.md#L137 + */ +enum sshsig_digest_e { + SSHSIG_DIGEST_SHA2_256 = 0, + SSHSIG_DIGEST_SHA2_512 = 1, +}; + +LIBSSH_API int sshsig_sign(const void *data, + size_t data_length, + ssh_key privkey, + ssh_pki_ctx pki_context, + const char *sig_namespace, + enum sshsig_digest_e hash_alg, + char **signature); +LIBSSH_API int sshsig_verify(const void *data, + size_t data_length, + const char *signature, + const char *sig_namespace, + ssh_key *sign_key); + +/* PKI context API */ + +enum ssh_pki_options_e { + SSH_PKI_OPTION_RSA_KEY_SIZE, + + /* Security Key options */ + SSH_PKI_OPTION_SK_APPLICATION, + SSH_PKI_OPTION_SK_FLAGS, + SSH_PKI_OPTION_SK_USER_ID, + SSH_PKI_OPTION_SK_CHALLENGE, + SSH_PKI_OPTION_SK_CALLBACKS, +}; + +/* FIDO2/U2F Operation Flags */ + +/** Requires user presence confirmation (tap/touch) */ +#ifndef SSH_SK_USER_PRESENCE_REQD +#define SSH_SK_USER_PRESENCE_REQD 0x01 +#endif + +/** Requires user verification (PIN/biometric) - FIDO2 only */ +#ifndef SSH_SK_USER_VERIFICATION_REQD +#define SSH_SK_USER_VERIFICATION_REQD 0x04 +#endif + +/** Force resident key enrollment even if a resident key with given user ID + * already exists - FIDO2 only */ +#ifndef SSH_SK_FORCE_OPERATION +#define SSH_SK_FORCE_OPERATION 0x10 +#endif + +/** Create/use resident key stored on authenticator - FIDO2 only */ +#ifndef SSH_SK_RESIDENT_KEY +#define SSH_SK_RESIDENT_KEY 0x20 +#endif + +LIBSSH_API ssh_pki_ctx ssh_pki_ctx_new(void); + +LIBSSH_API int ssh_pki_ctx_options_set(ssh_pki_ctx context, + enum ssh_pki_options_e option, + const void *value); + +LIBSSH_API int ssh_pki_ctx_set_sk_pin_callback(ssh_pki_ctx context, + ssh_auth_callback pin_callback, + void *userdata); + +#define SSH_SK_OPTION_NAME_DEVICE_PATH "device" +#define SSH_SK_OPTION_NAME_USER_ID "user" + +LIBSSH_API int ssh_pki_ctx_sk_callbacks_option_set(ssh_pki_ctx context, + const char *name, + const char *value, + bool required); + +LIBSSH_API int ssh_pki_ctx_sk_callbacks_options_clear(ssh_pki_ctx context); + +LIBSSH_API int +ssh_pki_ctx_get_sk_attestation_buffer(const struct ssh_pki_ctx_struct *context, + ssh_buffer *attestation_buffer); + +LIBSSH_API void ssh_pki_ctx_free(ssh_pki_ctx context); + +#define SSH_PKI_CTX_FREE(x) \ + do { \ + if ((x) != NULL) { \ + ssh_pki_ctx_free(x); \ + x = NULL; \ + } \ + } while (0) + +/* Security key resident keys API */ + +LIBSSH_API int +ssh_sk_resident_keys_load(const struct ssh_pki_ctx_struct *pki_context, + ssh_key **resident_keys_result, + size_t *num_keys_found_result); + +#ifndef LIBSSH_LEGACY_0_4 +#include "libssh/legacy.h" +#endif + +#ifdef __cplusplus +} +#endif +#endif /* _LIBSSH_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libssh_version.h b/src/libs/libssh-0.12.2/include/libssh/libssh_version.h new file mode 100644 index 000000000000..453b38cd65ac --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libssh_version.h @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Heiko Thiery + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_VERSION_H +#define _LIBSSH_VERSION_H + +/* libssh version macros */ +#define SSH_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define SSH_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define SSH_VERSION(a, b, c) SSH_VERSION_DOT(a, b, c) + +/* libssh version */ +#define LIBSSH_VERSION_MAJOR 0 +#define LIBSSH_VERSION_MINOR 12 +#define LIBSSH_VERSION_MICRO 2 + +#define LIBSSH_VERSION_INT SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) +#define LIBSSH_VERSION SSH_VERSION(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) + +#endif /* _LIBSSH_VERSION_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libssh_version.h.cmake b/src/libs/libssh-0.12.2/include/libssh/libssh_version.h.cmake new file mode 100644 index 000000000000..464fa14d0b6c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libssh_version.h.cmake @@ -0,0 +1,41 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Heiko Thiery + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _LIBSSH_VERSION_H +#define _LIBSSH_VERSION_H + +/* libssh version macros */ +#define SSH_VERSION_INT(a, b, c) ((a) << 16 | (b) << 8 | (c)) +#define SSH_VERSION_DOT(a, b, c) a ##.## b ##.## c +#define SSH_VERSION(a, b, c) SSH_VERSION_DOT(a, b, c) + +/* libssh version */ +#define LIBSSH_VERSION_MAJOR @libssh_VERSION_MAJOR@ +#define LIBSSH_VERSION_MINOR @libssh_VERSION_MINOR@ +#define LIBSSH_VERSION_MICRO @libssh_VERSION_PATCH@ + +#define LIBSSH_VERSION_INT SSH_VERSION_INT(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) +#define LIBSSH_VERSION SSH_VERSION(LIBSSH_VERSION_MAJOR, \ + LIBSSH_VERSION_MINOR, \ + LIBSSH_VERSION_MICRO) + +#endif /* _LIBSSH_VERSION_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/libsshpp.hpp b/src/libs/libssh-0.12.2/include/libssh/libsshpp.hpp new file mode 100644 index 000000000000..553a77771ca2 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/libsshpp.hpp @@ -0,0 +1,698 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef LIBSSHPP_HPP_ +#define LIBSSHPP_HPP_ + +/** + * @defgroup ssh_cpp The libssh C++ wrapper + * + * The C++ bindings for libssh are completely embedded in a single .hpp file, and + * this for two reasons: + * - C++ is hard to keep binary compatible, C is easy. We try to keep libssh C version + * as much as possible binary compatible between releases, while this would be hard for + * C++. If you compile your program with these headers, you will only link to the C version + * of libssh which will be kept ABI compatible. No need to recompile your C++ program + * each time a new binary-compatible version of libssh is out + * - Most of the functions in this file are really short and are probably worth the "inline" + * linking mode, which the compiler can decide to do in some case. There would be nearly no + * performance penalty of using the wrapper rather than native calls. + * + * Please visit the documentation of ssh::Session and ssh::Channel + * @see ssh::Session + * @see ssh::Channel + * + * If you wish not to use C++ exceptions, please define SSH_NO_CPP_EXCEPTIONS: + * @code + * #define SSH_NO_CPP_EXCEPTIONS + * #include + * @endcode + * All functions will then return SSH_ERROR in case of error. + * @{ + */ + +/* do not use deprecated functions */ +#define LIBSSH_LEGACY_0_4 + +#include +#include +#include +#include +#include +#include + +namespace ssh { + +class Channel; +/** Some people do not like C++ exceptions. With this define, we give + * the choice to use or not exceptions. + * @brief if defined, disable C++ exceptions for libssh c++ wrapper + */ +#ifndef SSH_NO_CPP_EXCEPTIONS + +/** @brief This class describes a SSH Exception object. This object can be thrown + * by several SSH functions that interact with the network, and may fail because of + * socket, protocol or memory errors. + */ +class SshException{ +public: + SshException(ssh_session csession){ + code=ssh_get_error_code(csession); + description=std::string(ssh_get_error(csession)); + } + SshException(const SshException &e){ + code=e.code; + description=e.description; + } + /** @brief returns the Error code + * @returns SSH_FATAL Fatal error happened (not recoverable) + * @returns SSH_REQUEST_DENIED Request was denied by remote host + * @see ssh_get_error_code + */ + int getCode(){ + return code; + } + /** @brief returns the error message of the last exception + * @returns pointer to a c string containing the description of error + * @see ssh_get_error + */ + std::string getError(){ + return description; + } +private: + int code; + std::string description; +}; + +/** @internal + * @brief Macro to throw exception if there was an error + */ +#define ssh_throw(x) if((x)==SSH_ERROR) throw SshException(getCSession()) +#define ssh_throw_null(CSession,x) if((x)==NULL) throw SshException(CSession) +#define void_throwable void +#define return_throwable return + +#else + +/* No exception at all. All functions will return an error code instead + * of an exception + */ +#define ssh_throw(x) if((x)==SSH_ERROR) return SSH_ERROR +#define ssh_throw_null(CSession,x) if((x)==NULL) return NULL +#define void_throwable int +#define return_throwable return SSH_OK +#endif + +/** + * The ssh::Session class contains the state of a SSH connection. + */ +class Session { + friend class Channel; +public: + Session(){ + c_session=ssh_new(); + } + ~Session(){ + ssh_free(c_session); + c_session=NULL; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option cstring containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, const char *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option long integer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, long int option){ + ssh_throw(ssh_options_set(c_session,type,&option)); + return_throwable; + } + /** @brief sets an SSH session options + * @param type Type of option + * @param option void pointer containing the value of option + * @throws SshException on error + * @see ssh_options_set + */ + void_throwable setOption(enum ssh_options_e type, void *option){ + ssh_throw(ssh_options_set(c_session,type,option)); + return_throwable; + } + /** @brief connects to the remote host + * @throws SshException on error + * @see ssh_connect + */ + void_throwable connect(){ + int ret=ssh_connect(c_session); + ssh_throw(ret); + return_throwable; + } + /** @brief Authenticates automatically using public key + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_autopubkey + */ + int userauthPublickeyAuto(void){ + int ret=ssh_userauth_publickey_auto(c_session, NULL, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the "none" method. Prefer using autopubkey if + * possible. + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_none + * @see Session::userauthAutoPubkey + */ + int userauthNone(){ + int ret=ssh_userauth_none(c_session,NULL); + ssh_throw(ret); + return ret; + } + + /** + * @brief Authenticate through the "keyboard-interactive" method. + * + * @param[in] username The username to authenticate. You can specify NULL if + * ssh_option_set_username() has been used. You cannot + * try two different logins in a row. + * + * @param[in] submethods Undocumented. Set it to NULL. + * + * @throws SshException on error + * + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED, + * SSH_AUTH_ERROR, SSH_AUTH_INFO, SSH_AUTH_AGAIN + * + * @see ssh_userauth_kbdint + */ + int userauthKbdint(const char* username, const char* submethods){ + int ret = ssh_userauth_kbdint(c_session, username, submethods); + ssh_throw(ret); + return ret; + } + + /** @brief Get the number of prompts (questions) the server has given. + * @returns The number of prompts. + * @see ssh_userauth_kbdint_getnprompts + */ + int userauthKbdintGetNPrompts(){ + return ssh_userauth_kbdint_getnprompts(c_session); + } + + /** + * @brief Set the answer for a question from a message block. + * + * @param[in] index The index number of the prompt. + * @param[in] answer The answer to give to the server. The answer MUST be + * encoded UTF-8. It is up to the server how to interpret + * the value and validate it. However, if you read the + * answer in some other encoding, you MUST convert it to + * UTF-8. + * + * @throws SshException on error + * + * @returns 0 on success, < 0 on error + * + * @see ssh_userauth_kbdint_setanswer + */ + int userauthKbdintSetAnswer(unsigned int index, const char *answer) + { + int ret = ssh_userauth_kbdint_setanswer(c_session, index, answer); + ssh_throw(ret); + return ret; + } + + + + /** @brief Authenticates using the password method. + * @param[in] password password to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_password + */ + int userauthPassword(const char *password){ + int ret=ssh_userauth_password(c_session,NULL,password); + ssh_throw(ret); + return ret; + } + /** @brief Try to authenticate using the publickey method. + * @param[in] pubkey public key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS if the pubkey is accepted, + * @returns SSH_AUTH_DENIED if the pubkey is denied + * @see ssh_userauth_try_pubkey + */ + int userauthTryPublickey(ssh_key pubkey){ + int ret=ssh_userauth_try_publickey(c_session, NULL, pubkey); + ssh_throw(ret); + return ret; + } + /** @brief Authenticates using the publickey method. + * @param[in] privkey private key to use for authentication + * @throws SshException on error + * @returns SSH_AUTH_SUCCESS, SSH_AUTH_PARTIAL, SSH_AUTH_DENIED + * @see ssh_userauth_pubkey + */ + int userauthPublickey(ssh_key privkey){ + int ret=ssh_userauth_publickey(c_session, NULL, privkey); + ssh_throw(ret); + return ret; + } + + /** @brief Returns the available authentication methods from the server + * @throws SshException on error + * @returns Bitfield of available methods. + * @see ssh_userauth_list + */ + int getAuthList(){ + int ret=ssh_userauth_list(c_session, NULL); + ssh_throw(ret); + return ret; + } + /** @brief Disconnects from the SSH server and closes connection + * @see ssh_disconnect + */ + void disconnect(){ + ssh_disconnect(c_session); + } + /** @brief Returns the disconnect message from the server, if any + * @returns pointer to the message, or NULL. Do not attempt to free + * the pointer. + */ + const char *getDisconnectMessage(){ + const char *msg=ssh_get_disconnect_message(c_session); + return msg; + } + /** @internal + * @brief gets error message + */ + const char *getError(){ + return ssh_get_error(c_session); + } + /** @internal + * @brief returns error code + */ + int getErrorCode(){ + return ssh_get_error_code(c_session); + } + /** @brief returns the file descriptor used for the communication + * @returns the file descriptor + * @warning if a proxycommand is used, this function will only return + * one of the two file descriptors being used + * @see ssh_get_fd + */ + socket_t getSocket(){ + return ssh_get_fd(c_session); + } + /** @brief gets the Issue banner from the ssh server + * @returns the issue banner. This is generally a MOTD from server + * @see ssh_get_issue_banner + */ + std::string getIssueBanner(){ + char *banner = ssh_get_issue_banner(c_session); + std::string ret = ""; + if (banner != NULL) { + ret = std::string(banner); + ::free(banner); + } + return ret; + } + /** @brief returns the OpenSSH version (server) if possible + * @returns openssh version code + * @see ssh_get_openssh_version + */ + int getOpensshVersion(){ + return ssh_get_openssh_version(c_session); + } + /** @brief returns the version of the SSH protocol being used + * @returns the SSH protocol version + * @see ssh_get_version + */ + int getVersion(){ + return ssh_get_version(c_session); + } + /** @brief verifies that the server is known + * @throws SshException on error + * @returns Integer value depending on the knowledge of the + * server key + * @see ssh_session_update_known_hosts + */ + int isServerKnown(){ + int state = ssh_session_is_known_server(c_session); + ssh_throw(state); + return state; + } + void log(int priority, const char *format, ...){ + va_list va; + + va_start(va, format); + ssh_vlog(priority, "libsshpp", format, &va); + va_end(va); + } + + /** @brief copies options from a session to another + * @throws SshException on error + * @see ssh_options_copy + */ + void_throwable optionsCopy(const Session &source){ + ssh_throw(ssh_options_copy(source.c_session,&c_session)); + return_throwable; + } + /** @brief parses a configuration file for options + * @throws SshException on error + * @param[in] file configuration file name + * @see ssh_options_parse_config + */ + void_throwable optionsParseConfig(const char *file){ + ssh_throw(ssh_options_parse_config(c_session,file)); + return_throwable; + } + /** @brief silently disconnect from remote host + * @see ssh_silent_disconnect + */ + void silentDisconnect(){ + ssh_silent_disconnect(c_session); + } + /** @brief Writes the known host file with current + * host key + * @throws SshException on error + * @see ssh_write_knownhost + */ + int writeKnownhost(){ + int ret = ssh_session_update_known_hosts(c_session); + ssh_throw(ret); + return ret; + } + + /** @brief accept an incoming forward connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the forward connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_forward_accept + * @see Session::listenForward + */ + inline Channel *acceptForward(int timeout_ms); + /* implemented outside the class due Channel references */ + + void_throwable cancelForward(const char *address, int port){ + int err=ssh_channel_cancel_forward(c_session, address, port); + ssh_throw(err); + return_throwable; + } + + void_throwable listenForward(const char *address, int port, + int &boundport){ + int err=ssh_channel_listen_forward(c_session, address, port, &boundport); + ssh_throw(err); + return_throwable; + } + + ssh_session getCSession(){ + return c_session; + } + +protected: + ssh_session c_session; + +private: + /* No copy constructor, no = operator */ + Session(const Session &); + Session& operator=(const Session &); +}; + +/** @brief the ssh::Channel class describes the state of an SSH + * channel. + * @see ssh_channel + */ +class Channel { + friend class Session; +public: + Channel(Session &ssh_session){ + channel = ssh_channel_new(ssh_session.getCSession()); + this->session = &ssh_session; + } + ~Channel(){ + ssh_channel_free(channel); + channel=NULL; + } + + /** @brief accept an incoming X11 connection + * @param[in] timeout_ms timeout for waiting, in ms + * @returns new Channel pointer on the X11 connection + * @returns NULL in case of error + * @warning you have to delete this pointer after use + * @see ssh_channel_accept_x11 + * @see Channel::requestX11 + */ + Channel *acceptX11(int timeout_ms){ + ssh_channel x11chan = ssh_channel_accept_x11(channel,timeout_ms); + ssh_throw_null(getCSession(),x11chan); + Channel *newchan = new Channel(getSession(),x11chan); + return newchan; + } + /** @brief change the size of a pseudoterminal + * @param[in] cols number of columns + * @param[in] rows number of rows + * @throws SshException on error + * @see ssh_channel_change_pty_size + */ + void_throwable changePtySize(int cols, int rows){ + int err=ssh_channel_change_pty_size(channel,cols,rows); + ssh_throw(err); + return_throwable; + } + + /** @brief closes a channel + * @throws SshException on error + * @see ssh_channel_close + */ + void_throwable close(){ + ssh_throw(ssh_channel_close(channel)); + return_throwable; + } + + /* + * @deprecated Please use getExitState() + */ + int getExitStatus() { + uint32_t exit_status = (uint32_t)-1; + ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + return exit_status; + } + void_throwable getExitState(uint32_t & pexit_code, + char **pexit_signal, + int & pcore_dumped) { + ssh_throw(ssh_channel_get_exit_state(channel, + &pexit_code, + pexit_signal, + &pcore_dumped)); + return_throwable; + } + Session &getSession(){ + return *session; + } + /** @brief returns true if channel is in closed state + * @see ssh_channel_is_closed + */ + bool isClosed(){ + return ssh_channel_is_closed(channel) != 0; + } + /** @brief returns true if channel is in EOF state + * @see ssh_channel_is_eof + */ + bool isEof(){ + return ssh_channel_is_eof(channel) != 0; + } + /** @brief returns true if channel is in open state + * @see ssh_channel_is_open + */ + bool isOpen(){ + return ssh_channel_is_open(channel) != 0; + } + int openForward(const char *remotehost, int remoteport, + const char *sourcehost, int localport=0){ + int err=ssh_channel_open_forward(channel,remotehost,remoteport, + sourcehost, localport); + ssh_throw(err); + return err; + } + /* TODO: completely remove this ? */ + void_throwable openSession(){ + int err=ssh_channel_open_session(channel); + ssh_throw(err); + return_throwable; + } + int poll(bool is_stderr=false){ + int err=ssh_channel_poll(channel,is_stderr); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,false,-1); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count, int timeout){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,false,timeout); + ssh_throw(err); + return err; + } + int read(void *dest, size_t count, bool is_stderr=false, int timeout=-1){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_timeout(channel,dest,count,is_stderr,timeout); + ssh_throw(err); + return err; + } + int readNonblocking(void *dest, size_t count, bool is_stderr=false){ + int err; + /* handle int overflow */ + if(count > 0x7fffffff) + count = 0x7fffffff; + err=ssh_channel_read_nonblocking(channel,dest,count,is_stderr); + ssh_throw(err); + return err; + } + void_throwable requestEnv(const char *name, const char *value){ + int err=ssh_channel_request_env(channel,name,value); + ssh_throw(err); + return_throwable; + } + + void_throwable requestExec(const char *cmd){ + int err=ssh_channel_request_exec(channel,cmd); + ssh_throw(err); + return_throwable; + } + void_throwable requestPty(const char *term=NULL, int cols=0, int rows=0, + const unsigned char* modes=NULL, size_t modes_len=0){ + int err; + if(term != NULL && cols != 0 && rows != 0 && modes != NULL) + err=ssh_channel_request_pty_size_modes(channel,term,cols,rows,modes,modes_len); + else if(term != NULL && cols != 0 && rows != 0) + err=ssh_channel_request_pty_size(channel,term,cols,rows); + else + err=ssh_channel_request_pty(channel); + ssh_throw(err); + return_throwable; + } + + void_throwable requestShell(){ + int err=ssh_channel_request_shell(channel); + ssh_throw(err); + return_throwable; + } + void_throwable requestSendSignal(const char *signum){ + int err=ssh_channel_request_send_signal(channel, signum); + ssh_throw(err); + return_throwable; + } + void_throwable requestSubsystem(const char *subsystem){ + int err=ssh_channel_request_subsystem(channel,subsystem); + ssh_throw(err); + return_throwable; + } + int requestX11(bool single_connection, + const char *protocol, const char *cookie, int screen_number){ + int err=ssh_channel_request_x11(channel,single_connection, + protocol, cookie, screen_number); + ssh_throw(err); + return err; + } + void_throwable sendEof(){ + int err=ssh_channel_send_eof(channel); + ssh_throw(err); + return_throwable; + } + /** @brief Writes on a channel + * @param data data to write. + * @param len number of bytes to write. + * @param is_stderr write should be done on the stderr channel (server only) + * @returns number of bytes written + * @throws SshException in case of error + * @see ssh_channel_write + * @see ssh_channel_write_stderr + */ + int write(const void *data, size_t len, bool is_stderr=false){ + int ret; + if(is_stderr){ + ret=ssh_channel_write_stderr(channel,data,len); + } else { + ret=ssh_channel_write(channel,data,len); + } + ssh_throw(ret); + return ret; + } + + ssh_session getCSession(){ + return session->getCSession(); + } + + ssh_channel getCChannel() { + return channel; + } + +protected: + Session *session; + ssh_channel channel; + +private: + Channel (Session &ssh_session, ssh_channel c_channel){ + this->channel=c_channel; + this->session = &ssh_session; + } + /* No copy and no = operator */ + Channel(const Channel &); + Channel &operator=(const Channel &); +}; + + +inline Channel *Session::acceptForward(int timeout_ms){ + ssh_channel forward = + ssh_channel_open_forward_port(c_session, timeout_ms, NULL, NULL, NULL); + ssh_throw_null(c_session,forward); + Channel *newchan = new Channel(*this,forward); + return newchan; + } + +} // namespace ssh + +/** @} */ +#endif /* LIBSSHPP_HPP_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/messages.h b/src/libs/libssh-0.12.2/include/libssh/messages.h new file mode 100644 index 000000000000..9dd6b06cae22 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/messages.h @@ -0,0 +1,116 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MESSAGES_H_ +#define MESSAGES_H_ + +#include "config.h" + +struct ssh_auth_request { + char *username; + int method; + char *password; + struct ssh_key_struct *pubkey; + struct ssh_key_struct *server_pubkey; + char *sigtype; + enum ssh_publickey_state_e signature_state; + char kbdint_response; +}; + +struct ssh_channel_request_open { + int type; + uint32_t sender; + uint32_t window; + uint32_t packet_size; + char *originator; + uint16_t originator_port; + char *destination; + uint16_t destination_port; +}; + +struct ssh_service_request { + char *service; +}; + +struct ssh_global_request { + int type; + uint8_t want_reply; + char *bind_address; + uint16_t bind_port; +}; + +struct ssh_channel_request { + int type; + ssh_channel channel; + uint8_t want_reply; + /* pty-req type specifics */ + char *TERM; + uint32_t width; + uint32_t height; + uint32_t pxwidth; + uint32_t pxheight; + ssh_string modes; + + /* env type request */ + char *var_name; + char *var_value; + /* exec type request */ + char *command; + /* subsystem */ + char *subsystem; + + /* X11 */ + uint8_t x11_single_connection; + char *x11_auth_protocol; + char *x11_auth_cookie; + uint32_t x11_screen_number; +}; + +struct ssh_message_struct { + ssh_session session; + int type; + struct ssh_auth_request auth_request; + struct ssh_channel_request_open channel_request_open; + struct ssh_channel_request channel_request; + struct ssh_service_request service_request; + struct ssh_global_request global_request; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_channel_open); +SSH_PACKET_CALLBACK(ssh_packet_global_request); + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_service_request); +SSH_PACKET_CALLBACK(ssh_packet_userauth_request); +#endif /* WITH_SERVER */ + +int ssh_message_handle_channel_request(ssh_session session, ssh_channel channel, ssh_buffer packet, + const char *request, uint8_t want_reply); +ssh_message ssh_message_pop_head(ssh_session session); + +#ifdef __cplusplus +} +#endif + +#endif /* MESSAGES_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/misc.h b/src/libs/libssh-0.12.2/include/libssh/misc.h new file mode 100644 index 000000000000..781a540fd1a5 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/misc.h @@ -0,0 +1,152 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef MISC_H_ +#define MISC_H_ + +#ifdef _WIN32 + +# ifdef _MSC_VER +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ +# endif /* _MSC_VER */ + +#else +#include +#include +#endif /* _WIN32 */ +#include + +#ifdef __cplusplus +extern "C" { +#endif + +/* in misc.c */ +/* gets the user home dir. */ +char *ssh_get_user_home_dir(ssh_session session); +char *ssh_get_local_username(void); +char *ssh_get_local_hostname(void); +int ssh_file_readaccess_ok(const char *file); +int ssh_dir_writeable(const char *path); + +char *ssh_path_expand_tilde(const char *d); +char *ssh_path_expand_escape(ssh_session session, const char *s); +int ssh_analyze_banner(ssh_session session, int server); +int ssh_is_ipaddr_v4(const char *str); +int ssh_is_ipaddr(const char *str); + +/* list processing */ + +struct ssh_list { + struct ssh_iterator *root; + struct ssh_iterator *end; +}; + +struct ssh_iterator { + struct ssh_iterator *next; + const void *data; +}; + +struct ssh_jump_info_struct { + char *hostname; + char *username; + /** + * Port number of the jump host, in the range 1-65535. Zero means the + * ProxyJump specification did not give a port, in which case the jump + * host's own configuration (or the connection default) supplies it. + */ + int port; +}; + +struct ssh_timestamp { + long seconds; + long useconds; +}; + +enum ssh_quote_state_e { + NO_QUOTE, + SINGLE_QUOTE, + DOUBLE_QUOTE +}; + +struct ssh_list *ssh_list_new(void); +void ssh_list_free(struct ssh_list *list); +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list); +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value); +size_t ssh_list_count(const struct ssh_list *list); +int ssh_list_append(struct ssh_list *list, const void *data); +int ssh_list_prepend(struct ssh_list *list, const void *data); +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator); +char *ssh_lowercase(const char* str); +char *ssh_hostport(const char *host, int port); + +const void *_ssh_list_pop_head(struct ssh_list *list); + +#define ssh_iterator_value(type, iterator)\ + ((type)((iterator)->data)) + +/** @brief fetch the head element of a list and remove it from list + * @param type type of the element to return + * @param ssh_list the ssh_list to use + * @return the first element of the list, or NULL if the list is empty + */ +#define ssh_list_pop_head(type, ssh_list)\ + ((type)_ssh_list_pop_head(ssh_list)) + +#define SSH_LIST_FREE(x) \ + do { if ((x) != NULL) { ssh_list_free(x); (x) = NULL; } } while(0) + +int ssh_make_milliseconds(unsigned long sec, unsigned long usec); +void ssh_timestamp_init(struct ssh_timestamp *ts); +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout); +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout); + +void uint64_inc(unsigned char *counter); + +void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len); + +int ssh_mkdirs(const char *pathname, mode_t mode); + +int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len); +int ssh_newline_vis(const char *string, char *buf, size_t buf_len); +int ssh_tmpname(char *name); + +char *ssh_strreplace(const char *src, const char *pattern, const char *repl); + +ssize_t ssh_readn(int fd, void *buf, size_t nbytes); +ssize_t ssh_writen(int fd, const void *buf, size_t nbytes); + +int ssh_check_hostname_syntax(const char *hostname); +int ssh_check_username_syntax(const char *username); + +void ssh_proxyjumps_free(struct ssh_list *proxy_jump_list); +bool ssh_libssh_proxy_jumps(void); + +FILE *ssh_strict_fopen(const char *filename, size_t max_file_size); + +#ifdef __cplusplus +} +#endif + +#endif /* MISC_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/mlkem.h b/src/libs/libssh-0.12.2/include/libssh/mlkem.h new file mode 100644 index 000000000000..2b76a760a744 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/mlkem.h @@ -0,0 +1,73 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Pavol Žáčik + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef MLKEM_H_ +#define MLKEM_H_ + +#include "libssh/crypto.h" +#include "libssh/libssh.h" +#include "libssh/session.h" + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +struct mlkem_type_info { + size_t pubkey_size; + size_t ciphertext_size; +#ifdef HAVE_GCRYPT_MLKEM + size_t privkey_size; + enum gcry_kem_algos alg; +#elif defined(HAVE_OPENSSL_MLKEM) + const char *name; +#else + size_t privkey_size; +#endif +}; + +extern const struct mlkem_type_info MLKEM768_INFO; +#ifdef HAVE_MLKEM1024 +extern const struct mlkem_type_info MLKEM1024_INFO; +#endif + +#define MLKEM_SHARED_SECRET_SIZE 32 + +typedef unsigned char ssh_mlkem_shared_secret[MLKEM_SHARED_SECRET_SIZE]; + +const struct mlkem_type_info * +kex_type_to_mlkem_info(enum ssh_key_exchange_e kex_type); + +int ssh_mlkem_init(ssh_session session); + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret); + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret); + +#ifdef __cplusplus +} +#endif + +#endif /* MLKEM_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/mlkem_native.h b/src/libs/libssh-0.12.2/include/libssh/mlkem_native.h new file mode 100644 index 000000000000..d5fd8334f672 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/mlkem_native.h @@ -0,0 +1,127 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef MLKEM_NATIVE_H_ +#define MLKEM_NATIVE_H_ + +#include +#include +#include +#include +#include + +#include "config.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/** +A monomorphic instance of libcrux_ml_kem.types.MlKemPrivateKey +with const generics +- $2400size_t +*/ +typedef struct libcrux_ml_kem_types_MlKemPrivateKey_d9_s { + uint8_t value[2400U]; +} libcrux_ml_kem_types_MlKemPrivateKey_d9; + +/** +A monomorphic instance of libcrux_ml_kem.types.MlKemPublicKey +with const generics +- $1184size_t +*/ +typedef struct libcrux_ml_kem_types_MlKemPublicKey_30_s { + uint8_t value[1184U]; +} libcrux_ml_kem_types_MlKemPublicKey_30; + +typedef struct libcrux_ml_kem_mlkem768_MlKem768KeyPair_s { + libcrux_ml_kem_types_MlKemPrivateKey_d9 sk; + libcrux_ml_kem_types_MlKemPublicKey_30 pk; +} libcrux_ml_kem_mlkem768_MlKem768KeyPair; + +typedef struct libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s { + uint8_t value[1088U]; +} libcrux_ml_kem_mlkem768_MlKem768Ciphertext; + +/** +A monomorphic instance of K. +with types libcrux_ml_kem_types_MlKemCiphertext[[$1088size_t]], +uint8_t[32size_t] + +*/ +typedef struct tuple_c2_s { + libcrux_ml_kem_mlkem768_MlKem768Ciphertext fst; + uint8_t snd[32U]; +} tuple_c2; + +/** + Generate ML-KEM 768 Key Pair +*/ +libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_mlkem768_portable_generate_key_pair(uint8_t randomness[64U]); + +/** + Validate a public key. + + Returns `true` if valid, and `false` otherwise. +*/ +bool libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key); + +/** + Encapsulate ML-KEM 768 + + Generates an ([`MlKem768Ciphertext`], [`MlKemSharedSecret`]) tuple. + The input is a reference to an [`MlKem768PublicKey`] and [`SHARED_SECRET_SIZE`] + bytes of `randomness`. +*/ +tuple_c2 libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]); + +/** + Decapsulate ML-KEM 768 + + Generates an [`MlKemSharedSecret`]. + The input is a reference to an [`MlKem768PrivateKey`] and an + [`MlKem768Ciphertext`]. +*/ +void libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, + uint8_t ret[32U]); + +/* rename some types to be a bit more ergonomic */ +#define libcrux_mlkem768_keypair libcrux_ml_kem_mlkem768_MlKem768KeyPair_s +#define libcrux_mlkem768_pk libcrux_ml_kem_types_MlKemPublicKey_30_s +#define libcrux_mlkem768_sk libcrux_ml_kem_types_MlKemPrivateKey_d9_s +#define libcrux_mlkem768_ciphertext libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s +#define libcrux_mlkem768_enc_result tuple_c2_s +/* defines for PRNG inputs */ +#define LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN 64U +#define LIBCRUX_ML_KEM_ENC_PRNG_LEN 32 + +#ifdef __cplusplus +} +#endif + +#endif /* MLKEM_NATIVE_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/options.h b/src/libs/libssh-0.12.2/include/libssh/options.h new file mode 100644 index 000000000000..63b207fa9f4a --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/options.h @@ -0,0 +1,43 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011 Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _OPTIONS_H +#define _OPTIONS_H + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_config_parse(ssh_session session, FILE *fp, bool global); +int ssh_config_parse_file(ssh_session session, const char *filename); +int ssh_config_parse_string(ssh_session session, const char *input); +int ssh_options_set_algo(ssh_session session, + enum ssh_kex_types_e algo, + const char *list, + char **place); +int ssh_options_apply(ssh_session session); + +char *ssh_options_get_algo(ssh_session session, enum ssh_kex_types_e algo); + +#ifdef __cplusplus +} +#endif + +#endif /* _OPTIONS_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/packet.h b/src/libs/libssh-0.12.2/include/libssh/packet.h new file mode 100644 index 000000000000..531d7e4bd40e --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/packet.h @@ -0,0 +1,101 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PACKET_H_ +#define PACKET_H_ + +#include "libssh/wrapper.h" + +struct ssh_socket_struct; + +/* this structure should go someday */ +typedef struct packet_struct { + int valid; + uint32_t len; + uint8_t type; +} PACKET; + +/** different state of packet reading. */ +enum ssh_packet_state_e { + /** Packet not initialized, must read the size of packet */ + PACKET_STATE_INIT, + /** Size was read, waiting for the rest of data */ + PACKET_STATE_SIZEREAD, + /** Full packet was read and callbacks are being called. Future packets + * should wait for the end of the callback. */ + PACKET_STATE_PROCESSING +}; + +enum ssh_packet_filter_result_e { + SSH_PACKET_UNKNOWN, + SSH_PACKET_ALLOWED, + SSH_PACKET_DENIED +}; + +int ssh_packet_send(ssh_session session); + +#ifdef __cplusplus +extern "C" { +#endif + +SSH_PACKET_CALLBACK(ssh_packet_unimplemented); +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback); +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback); +SSH_PACKET_CALLBACK(ssh_packet_debug_callback); +SSH_PACKET_CALLBACK(ssh_packet_dh_reply); +SSH_PACKET_CALLBACK(ssh_packet_newkeys); +SSH_PACKET_CALLBACK(ssh_packet_service_accept); +SSH_PACKET_CALLBACK(ssh_packet_ext_info); + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_kexdh_init); +#endif + +int ssh_packet_send_newkeys(ssh_session session); +int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum); +int ssh_packet_parse_type(ssh_session session); +//int packet_flush(ssh_session session, int enforce_blocking); + +size_t ssh_packet_socket_callback(const void *data, size_t len, void *user); +void ssh_packet_register_socket_callback(ssh_session session, struct ssh_socket_struct *s); +void ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks); +void ssh_packet_set_default_callbacks(ssh_session session); +void ssh_packet_process(ssh_session session, uint8_t type); + +/* PACKET CRYPT */ +uint32_t ssh_packet_decrypt_len(ssh_session session, uint8_t *destination, uint8_t *source); +int ssh_packet_decrypt(ssh_session session, uint8_t *destination, uint8_t *source, + size_t start, size_t encrypted_size); +unsigned char *ssh_packet_encrypt(ssh_session session, + void *packet, + size_t len); +int ssh_packet_hmac_verify(ssh_session session, const void *data, size_t len, + unsigned char *mac, enum ssh_hmac_e type); +int ssh_packet_set_newkeys(ssh_session session, + enum ssh_crypto_direction_e direction); +struct ssh_crypto_struct *ssh_packet_get_current_crypto(ssh_session session, + enum ssh_crypto_direction_e direction); + +#ifdef __cplusplus +} +#endif + +#endif /* PACKET_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pcap.h b/src/libs/libssh-0.12.2/include/libssh/pcap.h new file mode 100644 index 000000000000..2a6c3c279c96 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pcap.h @@ -0,0 +1,53 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PCAP_H_ +#define PCAP_H_ + +#include "config.h" +#include "libssh/libssh.h" + +#ifdef WITH_PCAP +typedef struct ssh_pcap_context_struct* ssh_pcap_context; + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len); + +ssh_pcap_context ssh_pcap_context_new(ssh_session session); +void ssh_pcap_context_free(ssh_pcap_context ctx); + +enum ssh_pcap_direction{ + SSH_PCAP_DIR_IN, + SSH_PCAP_DIR_OUT +}; +void ssh_pcap_context_set_file(ssh_pcap_context, ssh_pcap_file); +int ssh_pcap_context_write(ssh_pcap_context,enum ssh_pcap_direction direction, void *data, + uint32_t len, uint32_t origlen); + + +#ifdef __cplusplus +} +#endif + +#endif /* WITH_PCAP */ +#endif /* PCAP_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pki.h b/src/libs/libssh-0.12.2/include/libssh/pki.h new file mode 100644 index 000000000000..e22c05f84446 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pki.h @@ -0,0 +1,218 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PKI_H_ +#define PKI_H_ + +#include +#include "libssh/priv.h" +#ifdef HAVE_OPENSSL_EC_H +#include +#endif +#ifdef HAVE_OPENSSL_ECDSA_H +#include +#endif +#ifdef HAVE_LIBCRYPTO +#include +#endif +#include "libssh/crypto.h" +#ifdef HAVE_LIBCRYPTO +/* If using OpenSSL implementation, define the signature length which would be + * defined in libssh/ed25519.h otherwise */ +#define ED25519_SIG_LEN 64 +#else +#include "libssh/ed25519.h" +#endif +/* This definition is used for both OpenSSL and internal implementations */ +#define ED25519_KEY_LEN 32 + +#define MAX_PUBKEY_SIZE 0x100000 /* 1M */ +#define MAX_PRIVKEY_SIZE 0x400000 /* 4M */ + +#define RSA_MIN_KEY_SIZE 1024 +#define RSA_MIN_FIPS_KEY_SIZE 2048 +#define RSA_DEFAULT_KEY_SIZE 3072 + +#define SSH_KEY_FLAG_EMPTY 0x0 +#define SSH_KEY_FLAG_PUBLIC 0x0001 +#define SSH_KEY_FLAG_PRIVATE 0x0002 +#define SSH_KEY_FLAG_PKCS11_URI 0x0004 + +/* Constants matching the Lightweight Secure Shell Signature Format */ +/* https://datatracker.ietf.org/doc/draft-josefsson-sshsig-format */ +#define SSHSIG_VERSION 0x01 +#define SSHSIG_MAGIC_PREAMBLE "SSHSIG" +#define SSHSIG_MAGIC_PREAMBLE_LEN (sizeof(SSHSIG_MAGIC_PREAMBLE) - 1) +#define SSHSIG_BEGIN_SIGNATURE "-----BEGIN SSH SIGNATURE-----" +#define SSHSIG_END_SIGNATURE "-----END SSH SIGNATURE-----" +#define SSHSIG_LINE_LENGTH 76 + +struct ssh_key_struct { + enum ssh_keytypes_e type; + int flags; + const char *type_c; /* Don't free it ! it is static */ + int ecdsa_nid; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t rsa; + gcry_sexp_t ecdsa; +#elif defined(HAVE_LIBMBEDCRYPTO) + mbedtls_pk_context *pk; + mbedtls_ecdsa_context *ecdsa; +#elif defined(HAVE_LIBCRYPTO) + /* This holds either ENGINE/PROVIDER key for PKCS#11 support + * or just key in high-level format */ + EVP_PKEY *key; + /* keep this around for FIPS mode so we can parse the public keys. We won't + * be able to use them nor use the private keys though */ + uint8_t *ed25519_pubkey; +#endif /* HAVE_LIBGCRYPT */ +#ifndef HAVE_LIBCRYPTO + ed25519_pubkey *ed25519_pubkey; + ed25519_privkey *ed25519_privkey; +#endif /* HAVE_LIBCRYPTO */ + ssh_string sk_application; + ssh_buffer cert; + enum ssh_keytypes_e cert_type; + + /* Security Key specific private data */ + uint8_t sk_flags; + ssh_string sk_key_handle; + ssh_string sk_reserved; + + /* Resident key specific metadata */ + ssh_string sk_user_id; +}; + +struct ssh_signature_struct { + enum ssh_keytypes_e type; + enum ssh_digest_e hash_type; + const char *type_c; +#if defined(HAVE_LIBGCRYPT) + gcry_sexp_t rsa_sig; + gcry_sexp_t ecdsa_sig; +#elif defined(HAVE_LIBMBEDCRYPTO) + ssh_string rsa_sig; + struct mbedtls_ecdsa_sig ecdsa_sig; +#endif /* HAVE_LIBGCRYPT */ +#ifndef HAVE_LIBCRYPTO + ed25519_signature *ed25519_sig; +#endif /* HAVE_LIBGCRYPT */ + ssh_string raw_sig; + + /* Security Key specific additions */ + uint8_t sk_flags; + uint32_t sk_counter; +}; + +typedef struct ssh_signature_struct *ssh_signature; + +#ifdef __cplusplus +extern "C" { +#endif + +/* SSH Key Functions */ +void ssh_key_clean (ssh_key key); + +const char * +ssh_key_get_signature_algorithm(ssh_session session, + enum ssh_keytypes_e type); +enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name); +enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type); +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type); +enum ssh_digest_e ssh_key_hash_from_name(const char *name); + +#define is_ecdsa_key_type(t) \ + ((t) >= SSH_KEYTYPE_ECDSA_P256 && (t) <= SSH_KEYTYPE_ECDSA_P521) + +#define is_cert_type(kt)\ + ((kt) == SSH_KEYTYPE_RSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ECDSA_CERT01 ||\ + (kt) == SSH_KEYTYPE_SK_ED25519_CERT01 ||\ + ((kt) >= SSH_KEYTYPE_ECDSA_P256_CERT01 &&\ + (kt) <= SSH_KEYTYPE_ED25519_CERT01)) + +#define is_sk_key_type(kt) \ + ((kt) == SSH_KEYTYPE_SK_ECDSA || (kt) == SSH_KEYTYPE_SK_ED25519 || \ + (kt) == SSH_KEYTYPE_SK_ECDSA_CERT01 || \ + (kt) == SSH_KEYTYPE_SK_ED25519_CERT01) + +/* SSH Signature Functions */ +ssh_signature ssh_signature_new(void); +void ssh_signature_free(ssh_signature sign); +#define SSH_SIGNATURE_FREE(x) \ + do { ssh_signature_free(x); x = NULL; } while(0) + +int ssh_pki_export_signature_blob(const ssh_signature sign, + ssh_string *sign_blob); +int ssh_pki_import_signature_blob(const ssh_string sig_blob, + const ssh_key pubkey, + ssh_signature *psig); +int ssh_pki_signature_verify(ssh_session session, + ssh_signature sig, + const ssh_key key, + const unsigned char *digest, + size_t dlen); + +/* SSH Public Key Functions */ +int ssh_pki_export_pubkey_blob(const ssh_key key, + ssh_string *pblob); +int ssh_pki_import_pubkey_blob(const ssh_string key_blob, + ssh_key *pkey); + +int ssh_pki_import_cert_blob(const ssh_string cert_blob, + ssh_key *pkey); + +/* SSH Private Key Functions */ +int ssh_pki_export_privkey_blob(const ssh_key key, + ssh_string *pblob); + + +/* SSH Signing Functions */ +ssh_string ssh_pki_do_sign(ssh_session session, ssh_buffer sigbuf, + const ssh_key privatekey, enum ssh_digest_e hash_type); +ssh_string ssh_pki_do_sign_agent(ssh_session session, + struct ssh_buffer_struct *buf, + const ssh_key pubkey); +ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, + const ssh_key privkey, + const enum ssh_digest_e digest); + +/* Temporary functions, to be removed after migration to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key); +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key); + +int ssh_key_algorithm_allowed(ssh_session session, const char *type); +bool ssh_key_size_allowed(ssh_session session, ssh_key key); + +/* Return the key size in bits */ +int ssh_key_size(ssh_key key); + +/* PKCS11 URI function to check if filename is a path or a PKCS11 URI */ +#ifdef WITH_PKCS11_URI +bool ssh_pki_is_uri(const char *filename); +char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri); +#endif /* WITH_PKCS11_URI */ + +#ifdef __cplusplus +} +#endif + +#endif /* PKI_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pki_context.h b/src/libs/libssh-0.12.2/include/libssh/pki_context.h new file mode 100644 index 000000000000..6fb48bcfbbbd --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pki_context.h @@ -0,0 +1,103 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef PKI_CONTEXT_H +#define PKI_CONTEXT_H + +#include "libssh/callbacks.h" +#include "libssh/libssh.h" + +/** + * @brief Security key context structure + * + * Context structure containing all parameters and callbacks + * needed for FIDO2/U2F security key operations. + */ +struct ssh_pki_ctx_struct { + /** @brief Desired RSA modulus size in bits + * + * Specified size of RSA keys to generate. If set to 0, defaults to 3072 + * bits. Must be greater than or equal to 1024, as anything below is + * considered insecure. + */ + int rsa_key_size; + + /** @brief Security key callbacks + * + * Provides enroll/sign/load_resident_keys operations. + */ + const struct ssh_sk_callbacks_struct *sk_callbacks; + + /** @brief Application identifier string for the security key credential + * + * FIDO2 relying party identifier, typically "ssh:user@hostname" format. + * This is required for all security key operations. + */ + char *sk_application; + + /** @brief FIDO2 operation flags + * + * Bitfield controlling authenticator behavior. Combine with bitwise OR: + * - SSH_SK_USER_PRESENCE_REQD (0x01): Require user touch + * - SSH_SK_USER_VERIFICATION_REQD (0x04): Require PIN/biometric + * - SSH_SK_FORCE_OPERATION (0x10): Override duplicate detection + * - SSH_SK_RESIDENT_KEY (0x20): Create discoverable credential + */ + uint8_t sk_flags; + + /** @brief PIN callback for authenticator user verification (optional) + * + * Callback invoked to obtain a PIN or perform user verification when + * SSH_SK_USER_VERIFICATION_REQD is set or the authenticator requires it. + * If NULL, no interactive PIN retrieval is performed. + */ + ssh_auth_callback sk_pin_callback; + + /** @brief User supplied pointer passed to callbacks (optional) + * + * Generic pointer set by the application and forwarded to + * interactive callbacks (e.g. PIN callback) to allow applications to + * carry state context. + */ + void *sk_userdata; + + /** @brief Custom challenge data for enrollment (optional) + * + * Buffer containing challenge data signed by the authenticator. + * If NULL, a random 32-byte challenge is automatically generated. + */ + ssh_buffer sk_challenge_buffer; + + /** @brief Options to be passed to the sk_callbacks (optional) + * + * NULL-terminated array of sk_option pointers owned by this context. + */ + struct sk_option **sk_callbacks_options; + + /** @brief The buffer used to store attestation information returned in a + * key enrollment operation + */ + ssh_buffer sk_attestation_buffer; +}; + +/* Internal PKI context functions */ +ssh_pki_ctx ssh_pki_ctx_dup(const ssh_pki_ctx context); + +#endif /* PKI_CONTEXT_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pki_priv.h b/src/libs/libssh-0.12.2/include/libssh/pki_priv.h new file mode 100644 index 000000000000..2a6f8966b36a --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pki_priv.h @@ -0,0 +1,181 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef PKI_PRIV_H_ +#define PKI_PRIV_H_ + +#include "libssh/pki.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* defined in bcrypt_pbkdf.c */ +int bcrypt_pbkdf(const char *pass, + size_t passlen, + const uint8_t *salt, + size_t saltlen, + uint8_t *key, + size_t keylen, + unsigned int rounds); + +#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" +#define ECDSA_HEADER_BEGIN "-----BEGIN EC PRIVATE KEY-----" +#define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" +#define OPENSSH_HEADER_BEGIN "-----BEGIN OPENSSH PRIVATE KEY-----" +#define OPENSSH_HEADER_END "-----END OPENSSH PRIVATE KEY-----" +/* Magic defined in OpenSSH/PROTOCOL.key */ +#define OPENSSH_AUTH_MAGIC "openssh-key-v1" + +/* Determine type of ssh key. */ +enum ssh_key_e { + SSH_KEY_PUBLIC = 0, + SSH_KEY_PRIVATE +}; + +void pki_key_clean(ssh_key key); + +int pki_key_ecdsa_nid_from_name(const char *name); +const char *pki_key_ecdsa_nid_to_name(int nid); +const char *ssh_key_signature_to_char(enum ssh_keytypes_e type, + enum ssh_digest_e hash_type); +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type); + +/* SSH Key Functions */ +ssh_key pki_key_dup_common_init(const ssh_key key, int demote); +ssh_key pki_key_dup(const ssh_key key, int demote); +int pki_key_generate_rsa(ssh_key key, int parameter); +int pki_key_generate_ecdsa(ssh_key key, int parameter); +int pki_key_generate_ed25519(ssh_key key); + +int pki_key_compare(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); + +int pki_key_check_hash_compatible(ssh_key key, + enum ssh_digest_e hash_type); +/* SSH Private Key Functions */ +enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey); +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data); + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data); +int pki_import_privkey_buffer(enum ssh_keytypes_e type, + ssh_buffer buffer, + ssh_key *pkey); + +/* SSH Public Key Functions */ +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n); +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e); +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type); + +/* SSH Private Key Functions */ +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + ssh_string iqmp, + ssh_string p, + ssh_string q); +int pki_privkey_build_ecdsa(ssh_key key, + int nid, + ssh_string e, + ssh_string exp); + +/* SSH Signature Functions */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len); +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len); +ssh_string pki_signature_to_blob(const ssh_signature sign); +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type); + +/* SSH Signing Functions */ +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *input, + size_t input_len, + enum ssh_digest_e hash_type); +ssh_signature pki_do_sign_hash(const ssh_key privkey, + const unsigned char *hash, + size_t hlen, + enum ssh_digest_e hash_type); +#ifndef HAVE_LIBCRYPTO +int pki_ed25519_sign(const ssh_key privkey, ssh_signature sig, + const unsigned char *hash, size_t hlen); +int pki_ed25519_verify(const ssh_key pubkey, ssh_signature sig, + const unsigned char *hash, size_t hlen); +#endif /* HAVE_LIBCRYPTO */ +int pki_ed25519_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what); +int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key); +int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key); +int pki_ed25519_private_key_to_blob(ssh_buffer buffer, const ssh_key privkey); +ssh_string pki_ed25519_signature_to_blob(ssh_signature sig); +int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob); +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey); +int pki_pubkey_build_ed25519(ssh_key key, ssh_string pubkey); + +/* PKI Container OpenSSH */ +ssh_key ssh_pki_openssh_pubkey_import(const char *text_key); +ssh_key ssh_pki_openssh_privkey_import(const char *text_key, + const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); +ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, + const char *passphrase, ssh_auth_callback auth_fn, void *auth_data); + +#ifdef WITH_PKCS11_URI +/* URI Function */ +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type); +#endif /* WITH_PKCS11_URI */ + +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key); + +/* Security Key Helper Functions */ +int pki_buffer_pack_sk_priv_data(ssh_buffer buffer, const ssh_key key); +int pki_buffer_unpack_sk_priv_data(ssh_buffer buffer, ssh_key key); +int pki_sk_signature_buffer_prepare(const ssh_key key, + const ssh_signature sig, + const unsigned char *input, + size_t input_len, + ssh_buffer *sk_buffer_out); + +#ifdef __cplusplus +} +#endif + +#endif /* PKI_PRIV_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/pki_sk.h b/src/libs/libssh-0.12.2/include/libssh/pki_sk.h new file mode 100644 index 000000000000..f6c33300b5ad --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/pki_sk.h @@ -0,0 +1,90 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef PKI_SK_H +#define PKI_SK_H + +#include "libssh/libssh.h" +#include "libssh/pki.h" + +#include + +#define SSH_SK_MAX_USER_ID_LEN 64 + +/** + * @brief Enroll a new security key using a U2F/FIDO2 authenticator + * + * Creates a new security key credential configured according to the parameters + * in the PKI context. This function handles key enrollment for both ECDSA and + * Ed25519 algorithms, generates appropriate challenges, and returns the + * enrolled key with optional attestation data. + * + * The PKI context must be configured with appropriate security key parameters + * using ssh_pki_ctx_options_set() before calling this function. Required + * options include SSH_PKI_OPTION_SK_APPLICATION, SSH_PKI_OPTION_SK_USER_ID, and + * SSH_PKI_OPTION_SK_CALLBACKS. + * + * @param[in] context The PKI context containing security key configuration and + * parameters + * @param[in] key_type The type of key to enroll (SSH_KEYTYPE_SK_ECDSA or + * SSH_KEYTYPE_SK_ED25519) + * @param[out] enrolled_key_result Pointer to store the enrolled ssh_key + * + * @return SSH_OK on success, SSH_ERROR on failure + * + * @see ssh_pki_ctx_new() + * @see ssh_pki_ctx_options_set() + * @see ssh_pki_ctx_get_sk_attestation_buffer() + */ +int pki_sk_enroll_key(ssh_pki_ctx context, + enum ssh_keytypes_e key_type, + ssh_key *enrolled_key_result); + +/** + * @brief Sign arbitrary data using a security key and a PKI context + * + * This function performs signing operations configured according to the + * parameters in the PKI context and returns a properly formatted + * ssh_signature. The caller must free the signature when it is no longer + * needed. + * + * The PKI context should be configured with appropriate security key parameters + * using ssh_pki_ctx_options_set() before calling this function. The security + * key must have been previously enrolled or loaded. + * + * @param[in] context The PKI context containing security key configuration and + * parameters + * @param[in] key The security key to use for signing + * @param[in] data The data to sign + * @param[in] data_len Length of data to sign + * + * @return A valid ssh_signature on success, NULL on failure + * + * @see ssh_pki_ctx_new() + * @see ssh_pki_ctx_options_set() + * @see pki_sk_enroll_key() + * @see ssh_signature_free() + */ +ssh_signature pki_sk_do_sign(ssh_pki_ctx context, + const ssh_key key, + const uint8_t *data, + size_t data_len); + +#endif /* PKI_SK_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/poll.h b/src/libs/libssh-0.12.2/include/libssh/poll.h new file mode 100644 index 000000000000..478764b6aa29 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/poll.h @@ -0,0 +1,170 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef POLL_H_ +#define POLL_H_ + +#include "config.h" + +#ifdef HAVE_POLL + +#include +typedef struct pollfd ssh_pollfd_t; + +#else /* HAVE_POLL */ + +/* poll emulation support */ + +typedef struct ssh_pollfd_struct { + socket_t fd; /* file descriptor */ + short events; /* requested events */ + short revents; /* returned events */ +} ssh_pollfd_t; + +typedef unsigned long int nfds_t; + +#ifdef _WIN32 + +#ifndef POLLRDNORM +#define POLLRDNORM 0x0100 +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x0200 +#endif +#ifndef POLLIN +#define POLLIN (POLLRDNORM | POLLRDBAND) +#endif +#ifndef POLLPRI +#define POLLPRI 0x0400 +#endif + +#ifndef POLLWRNORM +#define POLLWRNORM 0x0010 +#endif +#ifndef POLLOUT +#define POLLOUT (POLLWRNORM) +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x0020 +#endif + +#ifndef POLLERR +#define POLLERR 0x0001 +#endif +#ifndef POLLHUP +#define POLLHUP 0x0002 +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x0004 +#endif + +#else /* _WIN32 */ + +/* poll.c */ +#ifndef POLLIN +#define POLLIN 0x001 /* There is data to read. */ +#endif +#ifndef POLLPRI +#define POLLPRI 0x002 /* There is urgent data to read. */ +#endif +#ifndef POLLOUT +#define POLLOUT 0x004 /* Writing now will not block. */ +#endif + +#ifndef POLLERR +#define POLLERR 0x008 /* Error condition. */ +#endif +#ifndef POLLHUP +#define POLLHUP 0x010 /* Hung up. */ +#endif +#ifndef POLLNVAL +#define POLLNVAL 0x020 /* Invalid polling request. */ +#endif + +#ifndef POLLRDNORM +#define POLLRDNORM 0x040 /* mapped to read fds_set */ +#endif +#ifndef POLLRDBAND +#define POLLRDBAND 0x080 /* mapped to exception fds_set */ +#endif +#ifndef POLLWRNORM +#define POLLWRNORM 0x100 /* mapped to write fds_set */ +#endif +#ifndef POLLWRBAND +#define POLLWRBAND 0x200 /* mapped to write fds_set */ +#endif + +#endif /* WIN32 */ +#endif /* HAVE_POLL */ + +#ifdef __cplusplus +extern "C" { +#endif + +void ssh_poll_init(void); +void ssh_poll_cleanup(void); +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout); +typedef struct ssh_poll_ctx_struct *ssh_poll_ctx; +typedef struct ssh_poll_handle_struct *ssh_poll_handle; + +/** + * @brief SSH poll callback. This callback will be used when an event + * caught on the socket. + * + * @param p Poll object this callback belongs to. + * @param fd The raw socket. + * @param revents The current poll events on the socket. + * @param userdata Userdata to be passed to the callback function. + * + * @return 0 on success, < 0 if you removed the poll object from + * its poll context. + */ +typedef int (*ssh_poll_callback)(ssh_poll_handle p, socket_t fd, int revents, + void *userdata); + +struct ssh_socket_struct; + +ssh_poll_handle ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, + void *userdata); +void ssh_poll_free(ssh_poll_handle p); +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p); +short ssh_poll_get_events(ssh_poll_handle p); +void ssh_poll_set_events(ssh_poll_handle p, short events); +void ssh_poll_add_events(ssh_poll_handle p, short events); +void ssh_poll_remove_events(ssh_poll_handle p, short events); +socket_t ssh_poll_get_fd(ssh_poll_handle p); +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd); +void ssh_poll_set_callback(ssh_poll_handle p, ssh_poll_callback cb, void *userdata); +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size); +void ssh_poll_ctx_free(ssh_poll_ctx ctx); +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p); +int ssh_poll_ctx_add_socket (ssh_poll_ctx ctx, struct ssh_socket_struct *s); +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p); +bool ssh_poll_is_locked(ssh_poll_handle p); +int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout); +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session); +int ssh_event_add_poll(ssh_event event, ssh_poll_handle p); +void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p); + +#ifdef __cplusplus +} +#endif + +#endif /* POLL_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/poly1305.h b/src/libs/libssh-0.12.2/include/libssh/poly1305.h new file mode 100644 index 000000000000..a22fea878969 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/poly1305.h @@ -0,0 +1,27 @@ +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#ifndef POLY1305_H +#define POLY1305_H +#include "libssh/chacha20-poly1305-common.h" + +#ifdef __cplusplus +extern "C" { +#endif + +void poly1305_auth(uint8_t out[POLY1305_TAGLEN], const uint8_t *m, size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif + ; + +#ifdef __cplusplus +} +#endif + +#endif /* POLY1305_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/priv.h b/src/libs/libssh-0.12.2/include/libssh/priv.h new file mode 100644 index 000000000000..375436af4f9c --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/priv.h @@ -0,0 +1,520 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* + * priv.h file + * This include file contains everything you shouldn't deal with in + * user programs. Consider that anything in this file might change + * without notice; libssh.h file will keep backward compatibility + * on binary & source + */ + +#ifndef _LIBSSH_PRIV_H +#define _LIBSSH_PRIV_H + +#include +#include +#include +#include +#include +#include + +#if !defined(HAVE_STRTOULL) +# if defined(HAVE___STRTOULL) +# define strtoull __strtoull +# elif defined(HAVE__STRTOUI64) +# define strtoull _strtoui64 +# elif defined(__hpux) && defined(__LP64__) +# define strtoull strtoul +# else +# error "no strtoull function found" +# endif +#endif /* !defined(HAVE_STRTOULL) */ + +#ifdef HAVE_TERMIOS_H +#include +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +#if !defined(HAVE_STRNDUP) +char *strndup(const char *s, size_t n); +#endif /* ! HAVE_STRNDUP */ + +#ifdef HAVE_BYTESWAP_H +#include +#endif + +#ifdef HAVE_ARPA_INET_H +#include +#endif + +#ifndef bswap_32 +#define bswap_32(x) \ + ((((x) & 0xff000000) >> 24) | (((x) & 0x00ff0000) >> 8) | \ + (((x) & 0x0000ff00) << 8) | (((x) & 0x000000ff) << 24)) +#endif + +#ifdef _WIN32 + +# ifndef PRIu64 +# if __WORDSIZE == 64 +# define PRIu64 "lu" +# else +# define PRIu64 "llu" +# endif /* __WORDSIZE */ +# endif /* PRIu64 */ + +# ifndef PRIu32 +# define PRIu32 "u" +# endif /* PRIu32 */ + +# ifndef PRIx64 +# if __WORDSIZE == 64 +# define PRIx64 "lx" +# else +# define PRIx64 "llx" +# endif /* __WORDSIZE */ +# endif /* PRIx64 */ + +# ifndef PRIx32 +# define PRIx32 "x" +# endif /* PRIx32 */ + +# ifdef _MSC_VER +# include +# include /* va_copy define check */ + +/* On Microsoft compilers define inline to __inline on all others use inline */ +# undef inline +# define inline __inline + +# ifndef va_copy +# define va_copy(dest, src) (dest = src) +# endif + +# define strcasecmp _stricmp +# define strncasecmp _strnicmp +# if ! defined(HAVE_ISBLANK) +# define isblank(ch) ((ch) == ' ' || (ch) == '\t' || (ch) == '\n' || (ch) == '\r') +# endif + +# define usleep(X) Sleep(((X)+1000)/1000) + +# undef strtok_r +# define strtok_r strtok_s + +# if defined(HAVE__SNPRINTF_S) +# undef snprintf +# define snprintf(d, n, ...) _snprintf_s((d), (n), _TRUNCATE, __VA_ARGS__) +# else /* HAVE__SNPRINTF_S */ +# if defined(HAVE__SNPRINTF) +# undef snprintf +# define snprintf _snprintf +# else /* HAVE__SNPRINTF */ +# if !defined(HAVE_SNPRINTF) +# error "no snprintf compatible function found" +# endif /* HAVE_SNPRINTF */ +# endif /* HAVE__SNPRINTF */ +# endif /* HAVE__SNPRINTF_S */ + +# if defined(HAVE__VSNPRINTF_S) +# undef vsnprintf +# define vsnprintf(s, n, f, v) _vsnprintf_s((s), (n), _TRUNCATE, (f), (v)) +# else /* HAVE__VSNPRINTF_S */ +# if defined(HAVE__VSNPRINTF) +# undef vsnprintf +# define vsnprintf _vsnprintf +# else +# if !defined(HAVE_VSNPRINTF) +# error "No vsnprintf compatible function found" +# endif /* HAVE_VSNPRINTF */ +# endif /* HAVE__VSNPRINTF */ +# endif /* HAVE__VSNPRINTF_S */ + +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ + +# endif /* _MSC_VER */ + +struct timeval; +int ssh_gettimeofday(struct timeval *__p, void *__t); + +#define gettimeofday ssh_gettimeofday + +struct tm *ssh_localtime(const time_t *timer, struct tm *result); +# define localtime_r ssh_localtime + +#define _XCLOSESOCKET closesocket + +# ifdef HAVE_IO_H +# include +# undef open +# define open _open +# undef close +# define close _close +# undef read +# define read _read +# undef write +# define write _write +# undef unlink +# define unlink _unlink +# endif /* HAVE_IO_H */ + +#else /* _WIN32 */ + +#include + +#define _XCLOSESOCKET close + +#endif /* _WIN32 */ + +#include "libssh/libssh.h" +#include "libssh/callbacks.h" + +/* some constants */ +#ifndef PATH_MAX +#ifdef MAX_PATH +#define PATH_MAX MAX_PATH +#else +#define PATH_MAX 4096 +#endif +#endif + +#ifndef MAX_PACKET_LEN +#define MAX_PACKET_LEN 262144 +#endif +#ifndef ERROR_BUFFERLEN +#define ERROR_BUFFERLEN 1024 +#endif + +#ifndef CLIENT_BANNER_SSH2 +#define CLIENT_BANNER_SSH2 "SSH-2.0-libssh_" SSH_STRINGIFY(LIBSSH_VERSION) +#endif /* CLIENT_BANNER_SSH2 */ + +#ifndef KBDINT_MAX_PROMPT +#define KBDINT_MAX_PROMPT 256 /* more than openssh's :) */ +#endif +#ifndef MAX_BUF_SIZE +#define MAX_BUF_SIZE 4096 +#endif + +#ifndef HAVE_COMPILER__FUNC__ +# ifdef HAVE_COMPILER__FUNCTION__ +# define __func__ __FUNCTION__ +# else +# error "Your system must provide a __func__ macro" +# endif +#endif + +#if defined(HAVE_GCC_THREAD_LOCAL_STORAGE) +# define LIBSSH_THREAD __thread +#elif defined(HAVE_MSC_THREAD_LOCAL_STORAGE) +# define LIBSSH_THREAD __declspec(thread) +#else +# define LIBSSH_THREAD +#endif + +/* + * This makes sure that the compiler doesn't optimize out the code + * + * Use it in a macro where the provided variable is 'x'. + */ +#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION) +# define LIBSSH_MEM_PROTECTION __asm__ volatile("" : : "r"(&(x)) : "memory") +#else +# define LIBSSH_MEM_PROTECTION +#endif + +#define SSH_DANGEROUS_SHELL_CHARS "'`\";&<>|(){}$\\," + +/* forward declarations */ +struct ssh_common_struct; +struct ssh_kex_struct; + +enum ssh_digest_e { + SSH_DIGEST_AUTO=0, + SSH_DIGEST_SHA1=1, + SSH_DIGEST_SHA256, + SSH_DIGEST_SHA384, + SSH_DIGEST_SHA512, +}; + +int ssh_get_key_params(ssh_session session, + ssh_key *privkey, + enum ssh_digest_e *digest); + +/* LOGGING */ +void ssh_log_function(int verbosity, + const char *function, + const char *buffer); +#define SSH_LOG(priority, ...) \ + _ssh_log(priority, __func__, __VA_ARGS__) + +/* LEGACY */ +void ssh_log_common(struct ssh_common_struct *common, + int verbosity, + const char *function, + const char *format, ...) PRINTF_ATTRIBUTE(4, 5); + +void _ssh_remove_legacy_log_cb(void); + +/* log.c */ +void _ssh_reset_log_cb(void); + +/* ERROR HANDLING */ + +/* error handling structure */ +struct error_struct { + int error_code; + char error_buffer[ERROR_BUFFERLEN]; +}; + +#define ssh_set_error(error, code, ...) \ + _ssh_set_error(error, code, __func__, __VA_ARGS__) +void _ssh_set_error(void *error, + int code, + const char *function, + const char *descr, ...) PRINTF_ATTRIBUTE(4, 5); + +#define ssh_set_error_oom(error) \ + _ssh_set_error_oom(error, __func__) +void _ssh_set_error_oom(void *error, const char *function); + +#define ssh_set_error_invalid(error) \ + _ssh_set_error_invalid(error, __func__) +void _ssh_set_error_invalid(void *error, const char *function); + +void ssh_reset_error(void *error); + +/* server.c */ +#ifdef WITH_SERVER +int ssh_auth_reply_default(ssh_session session,int partial); +int ssh_auth_reply_success(ssh_session session, int partial); +#endif +/* client.c */ + +int ssh_send_banner(ssh_session session, int is_server); +void ssh_session_socket_close(ssh_session session); + +/* connect.c */ +socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + const char *bind_addr, int port); + +/* in base64.c */ +ssh_buffer base64_to_bin(const char *source); +uint8_t *bin_to_base64(const uint8_t *source, size_t len); + +/* gzip.c */ +int compress_buffer(ssh_session session,ssh_buffer buf); +int decompress_buffer(ssh_session session,ssh_buffer buf, size_t maxlen); + +/* match.c */ +int match_pattern_list(const char *string, const char *pattern, + size_t len, int dolower); +int match_hostname(const char *host, const char *pattern, size_t len); +#ifndef _WIN32 +int match_cidr_address_list(const char *address, + const char *addrlist, + int sa_family); +#endif +int match_group(const char *group, const char *object); + +/* connector.c */ +int ssh_connector_set_event(ssh_connector connector, ssh_event event); +int ssh_connector_remove_event(ssh_connector connector); + +#ifndef MIN +#define MIN(a,b) ((a) < (b) ? (a) : (b)) +#endif + +#ifndef MAX +#define MAX(a,b) ((a) > (b) ? (a) : (b)) +#endif + +/** Free memory space */ +#define SAFE_FREE(x) do { if ((x) != NULL) {free(x); x=NULL;} } while(0) + +/** Zero a structure */ +#define ZERO_STRUCT(x) memset(&(x), 0, sizeof(x)) + +/** Zero a structure given a pointer to the structure */ +#define ZERO_STRUCTP(x) do { if ((x) != NULL) memset((x), 0, sizeof(*(x))); } while(0) + +/** Get the size of an array */ +#define ARRAY_SIZE(a) (sizeof(a)/sizeof(a[0])) + +/** Securely zero memory in a way that won't be optimized away */ +#if defined(HAVE_MEMSET_EXPLICIT) +#define ssh_burn(ptr, len) memset_explicit((ptr), '\0', (len)) +#elif defined(HAVE_EXPLICIT_BZERO) +#define ssh_burn(ptr, len) explicit_bzero((ptr), (len)) +#elif defined(HAVE_MEMSET_S) +#define ssh_burn(ptr, len) memset_s((ptr), (len), '\0', (len)) +#elif defined(HAVE_SECURE_ZERO_MEMORY) +#define ssh_burn(ptr, len) SecureZeroMemory((ptr), (len)) +#else +#if defined(HAVE_GCC_VOLATILE_MEMORY_PROTECTION) +#define ssh_burn(ptr, len) \ + do { \ + memset((ptr), '\0', (len)); \ + __asm__ volatile("" : : "g"(ptr) : "memory"); \ + } while (0) +#else +#define ssh_burn(ptr, len) \ + do { \ + memset((ptr), '\0', (len)); \ + } while (0) +#endif +#endif + +void burn_free(void *ptr, size_t len); + +/** Free memory space after zeroing it */ +#define BURN_FREE(x, len) \ + do { \ + if ((x) != NULL) { \ + burn_free((x), (len)); \ + (x) = NULL; \ + } \ + } while (0) + +/** + * This is a hack to fix warnings. The idea is to use this everywhere that we + * get the "discarding const" warning by the compiler. That doesn't actually + * fix the real issue, but marks the place and you can search the code for + * discard_const. + * + * Please use this macro only when there is no other way to fix the warning. + * We should use this function in only in a very few places. + * + * Also, please call this via the discard_const_p() macro interface, as that + * makes the return type safe. + */ +#define discard_const(ptr) ((void *)((uintptr_t)(ptr))) + +/** + * Type-safe version of discard_const + */ +#define discard_const_p(type, ptr) ((type *)discard_const(ptr)) + +#ifndef __VA_NARG__ +/** + * Get the argument count of variadic arguments + */ +/* + * Since MSVC 2010 there is a bug in passing __VA_ARGS__ to subsequent + * macros as a single token, which results in: + * warning C4003: not enough actual parameters for macro '_VA_ARG_N' + * and incorrect behavior. This fixes issue. + */ +#define VA_APPLY_VARIADIC_MACRO(macro, tuple) macro tuple + +#define __VA_NARG__(...) \ + (__VA_NARG_(__VA_ARGS__, __RSEQ_N())) +#define __VA_NARG_(...) \ + VA_APPLY_VARIADIC_MACRO(__VA_ARG_N, (__VA_ARGS__)) +#define __VA_ARG_N( \ + _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,N,...) N +#define __RSEQ_N() \ + 63, 62, 61, 60, \ + 59, 58, 57, 56, 55, 54, 53, 52, 51, 50, \ + 49, 48, 47, 46, 45, 44, 43, 42, 41, 40, \ + 39, 38, 37, 36, 35, 34, 33, 32, 31, 30, \ + 29, 28, 27, 26, 25, 24, 23, 22, 21, 20, \ + 19, 18, 17, 16, 15, 14, 13, 12, 11, 10, \ + 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 +#endif + +#define CLOSE_SOCKET(s) do { if ((s) != SSH_INVALID_SOCKET) { _XCLOSESOCKET(s); (s) = SSH_INVALID_SOCKET;} } while(0) + +#ifndef HAVE_HTONLL +# ifdef WORDS_BIGENDIAN +# define htonll(x) (x) +# else +# define htonll(x) \ + (((uint64_t)htonl((x) & 0xFFFFFFFF) << 32) | htonl((x) >> 32)) +# endif +#endif + +#ifndef HAVE_NTOHLL +# ifdef WORDS_BIGENDIAN +# define ntohll(x) (x) +# else +# define ntohll(x) \ + (((uint64_t)ntohl((x) & 0xFFFFFFFF) << 32) | ntohl((x) >> 32)) +# endif +#endif + +#ifndef FALL_THROUGH +# ifdef HAVE_FALLTHROUGH_ATTRIBUTE +# define FALL_THROUGH __attribute__ ((fallthrough)) +# else /* HAVE_FALLTHROUGH_ATTRIBUTE */ +# define FALL_THROUGH +# endif /* HAVE_FALLTHROUGH_ATTRIBUTE */ +#endif /* FALL_THROUGH */ + +#ifndef __attr_unused__ +# ifdef HAVE_UNUSED_ATTRIBUTE +# define __attr_unused__ __attribute__((unused)) +# else /* HAVE_UNUSED_ATTRIBUTE */ +# define __attr_unused__ +# endif /* HAVE_UNUSED_ATTRIBUTE */ +#endif /* __attr_unused__ */ + +#ifndef UNUSED_PARAM +#define UNUSED_PARAM(param) param __attr_unused__ +#endif /* UNUSED_PARAM */ + +#ifndef UNUSED_VAR +#define UNUSED_VAR(var) __attr_unused__ var +#endif /* UNUSED_VAR */ + +void ssh_agent_state_free(void *data); + +bool is_ssh_initialized(void); + +#define SSH_ERRNO_MSG_MAX 1024 +char *ssh_strerror(int err_num, char *buf, size_t buflen); + +/** 55 defined options (5 bytes each) + terminator */ +#define SSH_TTY_MODES_MAX_BUFSIZE (55 * 5 + 1) +int encode_current_tty_opts(unsigned char *buf, size_t buflen); + +/** The default maximum file size for a configuration file */ +#define SSH_MAX_CONFIG_FILE_SIZE 16 * 1024 * 1024 + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSSH_PRIV_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sc25519.h b/src/libs/libssh-0.12.2/include/libssh/sc25519.h new file mode 100644 index 000000000000..43b09a0560fb --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sc25519.h @@ -0,0 +1,82 @@ +/* $OpenBSD: sc25519.h,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/sc25519.h + */ + +#ifndef SC25519_H +#define SC25519_H + +#define sc25519 crypto_sign_ed25519_ref_sc25519 +#define shortsc25519 crypto_sign_ed25519_ref_shortsc25519 +#define sc25519_from32bytes crypto_sign_ed25519_ref_sc25519_from32bytes +#define shortsc25519_from16bytes crypto_sign_ed25519_ref_shortsc25519_from16bytes +#define sc25519_from64bytes crypto_sign_ed25519_ref_sc25519_from64bytes +#define sc25519_from_shortsc crypto_sign_ed25519_ref_sc25519_from_shortsc +#define sc25519_to32bytes crypto_sign_ed25519_ref_sc25519_to32bytes +#define sc25519_iszero_vartime crypto_sign_ed25519_ref_sc25519_iszero_vartime +#define sc25519_isshort_vartime crypto_sign_ed25519_ref_sc25519_isshort_vartime +#define sc25519_lt_vartime crypto_sign_ed25519_ref_sc25519_lt_vartime +#define sc25519_add crypto_sign_ed25519_ref_sc25519_add +#define sc25519_sub_nored crypto_sign_ed25519_ref_sc25519_sub_nored +#define sc25519_mul crypto_sign_ed25519_ref_sc25519_mul +#define sc25519_mul_shortsc crypto_sign_ed25519_ref_sc25519_mul_shortsc +#define sc25519_window3 crypto_sign_ed25519_ref_sc25519_window3 +#define sc25519_window5 crypto_sign_ed25519_ref_sc25519_window5 +#define sc25519_2interleave2 crypto_sign_ed25519_ref_sc25519_2interleave2 + +typedef struct { + uint32_t v[32]; +} sc25519; + +typedef struct { + uint32_t v[16]; +} shortsc25519; + +#ifdef __cplusplus +extern "C" { +#endif + +void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]); + +void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]); + +void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]); + +void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x); + +void sc25519_to32bytes(unsigned char r[32], const sc25519 *x); + +int sc25519_iszero_vartime(const sc25519 *x); + +int sc25519_isshort_vartime(const sc25519 *x); + +int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y); + +void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y); + +void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y); + +/* Convert s into a representation of the form \sum_{i=0}^{84}r[i]2^3 + * with r[i] in {-4,...,3} + */ +void sc25519_window3(signed char r[85], const sc25519 *s); + +/* Convert s into a representation of the form \sum_{i=0}^{50}r[i]2^5 + * with r[i] in {-16,...,15} + */ +void sc25519_window5(signed char r[51], const sc25519 *s); + +void sc25519_2interleave2(unsigned char r[127], const sc25519 *s1, const sc25519 *s2); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/scp.h b/src/libs/libssh-0.12.2/include/libssh/scp.h new file mode 100644 index 000000000000..089fcfc94a68 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/scp.h @@ -0,0 +1,63 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef _SCP_H +#define _SCP_H + +enum ssh_scp_states { + SSH_SCP_NEW, //Data structure just created + SSH_SCP_WRITE_INITED, //Gave our intention to write + SSH_SCP_WRITE_WRITING,//File was opened and currently writing + SSH_SCP_READ_INITED, //Gave our intention to read + SSH_SCP_READ_REQUESTED, //We got a read request + SSH_SCP_READ_READING, //File is opened and reading + SSH_SCP_ERROR, //Something bad happened + SSH_SCP_TERMINATED //Transfer finished +}; + +struct ssh_scp_struct { + ssh_session session; + int mode; + int recursive; + ssh_channel channel; + char *location; + enum ssh_scp_states state; + uint64_t filelen; + uint64_t processed; + enum ssh_scp_request_types request_type; + char *request_name; + char *warning; + int request_mode; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len); +int ssh_scp_integer_mode(const char *mode); +char *ssh_scp_string_mode(int mode); +int ssh_scp_response(ssh_scp scp, char **response); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/server.h b/src/libs/libssh-0.12.2/include/libssh/server.h new file mode 100644 index 000000000000..ee800567b7ab --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/server.h @@ -0,0 +1,415 @@ +/* Public include file for server support */ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_server The libssh server API + * + * @{ + */ + +#ifndef SERVER_H +#define SERVER_H + +#include "libssh/libssh.h" +#define SERVERBANNER CLIENTBANNER + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_bind_options_e { + SSH_BIND_OPTIONS_BINDADDR, + SSH_BIND_OPTIONS_BINDPORT, + SSH_BIND_OPTIONS_BINDPORT_STR, + SSH_BIND_OPTIONS_HOSTKEY, + SSH_BIND_OPTIONS_DSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_RSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_BANNER, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + SSH_BIND_OPTIONS_ECDSAKEY, /* deprecated */ + SSH_BIND_OPTIONS_IMPORT_KEY, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + SSH_BIND_OPTIONS_CIPHERS_C_S, + SSH_BIND_OPTIONS_CIPHERS_S_C, + SSH_BIND_OPTIONS_HMAC_C_S, + SSH_BIND_OPTIONS_HMAC_S_C, + SSH_BIND_OPTIONS_CONFIG_DIR, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + SSH_BIND_OPTIONS_PROCESS_CONFIG, + SSH_BIND_OPTIONS_MODULI, + SSH_BIND_OPTIONS_RSA_MIN_SIZE, + SSH_BIND_OPTIONS_IMPORT_KEY_STR, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, +}; + +typedef struct ssh_bind_struct* ssh_bind; + +/* Callback functions */ + +/** + * @brief Incoming connection callback. This callback is called when a ssh_bind + * has a new incoming connection. + * @param sshbind Current sshbind session handler + * @param userdata Userdata to be passed to the callback function. + */ +typedef void (*ssh_bind_incoming_connection_callback) (ssh_bind sshbind, + void *userdata); + +/** + * @brief These are the callbacks exported by the ssh_bind structure. + * + * They are called by the server module when events appear on the network. + */ +struct ssh_bind_callbacks_struct { + /** DON'T SET THIS use ssh_callbacks_init() instead. */ + size_t size; + /** A new connection is available. */ + ssh_bind_incoming_connection_callback incoming_connection; +}; +typedef struct ssh_bind_callbacks_struct *ssh_bind_callbacks; + +/** + * @brief Creates a new SSH server bind. + * + * @return A newly allocated ssh_bind session pointer. + */ +LIBSSH_API ssh_bind ssh_bind_new(void); + +LIBSSH_API int ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, const void *value); + +LIBSSH_API int ssh_bind_options_parse_config(ssh_bind sshbind, + const char *filename); + +/** + * @brief Start listening to the socket. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int ssh_bind_listen(ssh_bind ssh_bind_o); + +/** + * @brief Set the callback for this bind. + * + * @param[in] sshbind The bind to set the callback on. + * + * @param[in] callbacks An already set up ssh_bind_callbacks instance. + * + * @param[in] userdata A pointer to private data to pass to the callbacks. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + * + * @code + * struct ssh_callbacks_struct cb = { + * .userdata = data, + * .auth_function = my_auth_function + * }; + * ssh_callbacks_init(&cb); + * ssh_bind_set_callbacks(session, &cb); + * @endcode + */ +LIBSSH_API int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, + void *userdata); + +/** + * @brief Set the session to blocking/nonblocking mode. + * + * @param ssh_bind_o The ssh server bind to use. + * + * @param blocking Zero for nonblocking mode. + */ +LIBSSH_API void ssh_bind_set_blocking(ssh_bind ssh_bind_o, int blocking); + +/** + * @brief Recover the file descriptor from the session. + * + * @param ssh_bind_o The ssh server bind to get the fd from. + * + * @return The file descriptor. + */ +LIBSSH_API socket_t ssh_bind_get_fd(ssh_bind ssh_bind_o); + +/** + * @brief Set the file descriptor for a session. + * + * @param ssh_bind_o The ssh server bind to set the fd. + * + * @param fd The file descriptssh_bind B + */ +LIBSSH_API void ssh_bind_set_fd(ssh_bind ssh_bind_o, socket_t fd); + +/** + * @brief Allow the file descriptor to accept new sessions. + * + * @param ssh_bind_o The ssh server bind to use. + */ +LIBSSH_API void ssh_bind_fd_toaccept(ssh_bind ssh_bind_o); + +/** + * @brief Accept an incoming ssh connection and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @see ssh_new + * @return `SSH_OK` when a connection is established + */ +LIBSSH_API int ssh_bind_accept(ssh_bind ssh_bind_o, ssh_session session); + +/** + * @brief Accept an incoming ssh connection on the given file descriptor + * and initialize the session. + * + * @param ssh_bind_o The ssh server bind to accept a connection. + * @param session A preallocated ssh session + * @param fd A file descriptor of an already established TCP + * inbound connection + * @see ssh_new + * @see ssh_bind_accept + * @return `SSH_OK` when a connection is established + */ +LIBSSH_API int ssh_bind_accept_fd(ssh_bind ssh_bind_o, ssh_session session, + socket_t fd); + +LIBSSH_API ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session); + +/** + * @brief Handles the key exchange and set up encryption + * + * @param session A connected ssh session + * @see ssh_bind_accept + * @return `SSH_OK` if the key exchange was successful + */ +LIBSSH_API int ssh_handle_key_exchange(ssh_session session); + +/** + * @brief Initialize the set of key exchange, hostkey, ciphers, MACs, and + * compression algorithms for the given ssh_session. + * + * The selection of algorithms and keys used are determined by the + * options that are currently set in the given ssh_session structure. + * May only be called before the initial key exchange has begun. + * + * @param session The session structure to initialize. + * + * @see ssh_handle_key_exchange + * @see ssh_options_set + * + * @return `SSH_OK` if initialization succeeds. + */ +LIBSSH_API int ssh_server_init_kex(ssh_session session); + +/** + * @brief Free a ssh servers bind. + * + * Note that this will also free options that have been set on the bind, + * including keys set with SSH_BIND_OPTIONS_IMPORT_KEY. + * + * @param ssh_bind_o The ssh server bind to free. + */ +LIBSSH_API void ssh_bind_free(ssh_bind ssh_bind_o); + +/** + * @brief Set the acceptable authentication methods to be sent to the client. + * + * + * @param[in] session The server session + * + * @param[in] auth_methods The authentication methods we will support, which + * can be bitwise-or'd. + * + * Supported methods are: + * + * SSH_AUTH_METHOD_PASSWORD + * SSH_AUTH_METHOD_PUBLICKEY + * SSH_AUTH_METHOD_HOSTBASED + * SSH_AUTH_METHOD_INTERACTIVE + * SSH_AUTH_METHOD_GSSAPI_MIC + * SSH_AUTH_METHOD_GSSAPI_KEYEX + */ +LIBSSH_API void ssh_set_auth_methods(ssh_session session, int auth_methods); + +/** + * @brief Send the server's issue-banner to client. + * + * + * @param[in] session The server session. + * + * @param[in] banner The server's banner. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + */ +LIBSSH_API int ssh_send_issue_banner(ssh_session session, const ssh_string banner); + +/********************************************************** + * SERVER MESSAGING + **********************************************************/ + +/** + * @brief Reply with a standard reject message. + * + * Use this function if you don't know what to respond or if you want to reject + * a request. + * + * @param[in] msg The message to use for the reply. + * + * @return 0 on success, -1 on error. + * + * @see ssh_message_get() + */ +LIBSSH_API int ssh_message_reply_default(ssh_message msg); + +/** + * @brief Get the name of the authenticated user. + * + * @param[in] msg The message to get the username from. + * + * @return The username or NULL if an error occurred. + * + * @see ssh_message_get() + * @see ssh_message_type() + */ +LIBSSH_API const char *ssh_message_auth_user(ssh_message msg); + +/** + * @brief Get the password of the authenticated user. + * + * @param[in] msg The message to get the password from. + * + * @return The password or NULL if an error occurred. + * + * @see ssh_message_get() + * @see ssh_message_type() + * @deprecated This function should not be used anymore as there is a + * callback based server implementation now auth_password_function. + */ +SSH_DEPRECATED LIBSSH_API const char *ssh_message_auth_password(ssh_message msg); + +/** + * @brief Get the publickey of the authenticated user. + * + * If you need the key for later user you should duplicate it. + * + * @param[in] msg The message to get the public key from. + * + * @return The public key or NULL. + * + * @see ssh_key_dup() + * @see ssh_key_cmp() + * @see ssh_message_get() + * @see ssh_message_type() + * @deprecated This function should not be used anymore as there is a + * callback based server implementation auth_pubkey_function. + */ +SSH_DEPRECATED LIBSSH_API ssh_key ssh_message_auth_pubkey(ssh_message msg); + +LIBSSH_API int ssh_message_auth_kbdint_is_response(ssh_message msg); + +/** + * @param[in] msg The message to get the public key state from. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation auth_pubkey_function + */ +SSH_DEPRECATED LIBSSH_API enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg); + +LIBSSH_API int ssh_message_auth_reply_success(ssh_message msg,int partial); +LIBSSH_API int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey); +LIBSSH_API int ssh_message_auth_reply_pk_ok_simple(ssh_message msg); + +LIBSSH_API int ssh_message_auth_set_methods(ssh_message msg, int methods); + +LIBSSH_API int ssh_message_auth_interactive_request(ssh_message msg, + const char *name, const char *instruction, + unsigned int num_prompts, const char **prompts, char *echo); + +LIBSSH_API int ssh_message_service_reply_success(ssh_message msg); +LIBSSH_API const char *ssh_message_service_service(ssh_message msg); + +LIBSSH_API int ssh_message_global_request_reply_success(ssh_message msg, + uint16_t bound_port); + +LIBSSH_API void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data); +LIBSSH_API int ssh_execute_message_callbacks(ssh_session session); + +LIBSSH_API const char *ssh_message_channel_request_open_originator(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_originator_port(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_open_destination(ssh_message msg); +LIBSSH_API int ssh_message_channel_request_open_destination_port(ssh_message msg); + +LIBSSH_API ssh_channel ssh_message_channel_request_channel(ssh_message msg); + +/* Replaced by callback based server implementation function channel_pty_request_function*/ +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_pty_term(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_width(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_height(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_pxwidth(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_pty_pxheight(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_env_name(ssh_message msg); +LIBSSH_API const char *ssh_message_channel_request_env_value(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_command(ssh_message msg); + +LIBSSH_API const char *ssh_message_channel_request_subsystem(ssh_message msg); + +/* Replaced by callback based server implementation function channel_open_request_x11_function*/ +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_x11_single_connection(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg); +SSH_DEPRECATED LIBSSH_API const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg); +SSH_DEPRECATED LIBSSH_API int ssh_message_channel_request_x11_screen_number(ssh_message msg); + +LIBSSH_API const char *ssh_message_global_request_address(ssh_message msg); +LIBSSH_API int ssh_message_global_request_port(ssh_message msg); + +LIBSSH_API int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport); +LIBSSH_API int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port); + +LIBSSH_API int ssh_channel_request_send_exit_status(ssh_channel channel, + int exit_status); +LIBSSH_API int ssh_channel_request_send_exit_signal(ssh_channel channel, + const char *signum, + int core, + const char *errmsg, + const char *lang); + +LIBSSH_API int ssh_send_keepalive(ssh_session session); + +/* deprecated functions */ +SSH_DEPRECATED LIBSSH_API int ssh_accept(ssh_session session); +SSH_DEPRECATED LIBSSH_API int channel_write_stderr(ssh_channel channel, + const void *data, uint32_t len); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SERVER_H */ + +/** @} */ diff --git a/src/libs/libssh-0.12.2/include/libssh/session.h b/src/libs/libssh-0.12.2/include/libssh/session.h new file mode 100644 index 000000000000..da39df2a7f08 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/session.h @@ -0,0 +1,319 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SESSION_H_ +#define SESSION_H_ +#include + +#include "libssh/priv.h" +#include "libssh/callbacks.h" +#include "libssh/kex.h" +#include "libssh/packet.h" +#include "libssh/pcap.h" +#include "libssh/auth.h" +#include "libssh/channels.h" +#include "libssh/poll.h" +#include "libssh/config.h" +#include "libssh/misc.h" + +/* These are the different states a SSH session can be into its life */ +enum ssh_session_state_e { + SSH_SESSION_STATE_NONE=0, + SSH_SESSION_STATE_CONNECTING, + SSH_SESSION_STATE_SOCKET_CONNECTED, + SSH_SESSION_STATE_BANNER_RECEIVED, + SSH_SESSION_STATE_INITIAL_KEX, + SSH_SESSION_STATE_KEXINIT_RECEIVED, + SSH_SESSION_STATE_DH, + SSH_SESSION_STATE_AUTHENTICATING, + SSH_SESSION_STATE_AUTHENTICATED, + SSH_SESSION_STATE_ERROR, + SSH_SESSION_STATE_DISCONNECTED +}; + +enum ssh_dh_state_e { + DH_STATE_INIT=0, + DH_STATE_GROUP_SENT, + DH_STATE_REQUEST_SENT, + DH_STATE_INIT_SENT, + DH_STATE_NEWKEYS_SENT, + DH_STATE_FINISHED +}; + +enum ssh_pending_call_e { + SSH_PENDING_CALL_NONE = 0, + SSH_PENDING_CALL_CONNECT, + SSH_PENDING_CALL_AUTH_NONE, + SSH_PENDING_CALL_AUTH_PASSWORD, + SSH_PENDING_CALL_AUTH_OFFER_PUBKEY, + SSH_PENDING_CALL_AUTH_PUBKEY, + SSH_PENDING_CALL_AUTH_AGENT, + SSH_PENDING_CALL_AUTH_KBDINT_INIT, + SSH_PENDING_CALL_AUTH_KBDINT_SEND, + SSH_PENDING_CALL_AUTH_GSSAPI_MIC, + SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX, +}; + +/* libssh calls may block an undefined amount of time */ +#define SSH_SESSION_FLAG_BLOCKING 0x0001 + +/* Client successfully authenticated */ +#define SSH_SESSION_FLAG_AUTHENTICATED 0x0002 + +/* Do not accept new session channels (no-more-sessions@openssh.com) */ +#define SSH_SESSION_FLAG_NO_MORE_SESSIONS 0x0004 + +/* The KEXINIT message can be sent first by either of the parties so this flag + * indicates that the message was already sent to make sure it is sent and avoid + * sending it twice during key exchange to simplify the state machine. */ +#define SSH_SESSION_FLAG_KEXINIT_SENT 0x0008 + +/* The current SSH2 session implements the "strict KEX" feature and should behave + * differently on SSH2_MSG_NEWKEYS. */ +#define SSH_SESSION_FLAG_KEX_STRICT 0x0010 +/* Unexpected packets have been sent while the session was still unencrypted */ +#define SSH_SESSION_FLAG_KEX_TAINTED 0x0020 +/* The scp on server can not handle quoted paths. Skip the mitigation for + * CVE-2019-14889 when using scp */ +#define SSH_SESSION_FLAG_SCP_QUOTING_BROKEN 0x0040 + +/* codes to use with ssh_handle_packets*() */ +/* Infinite timeout */ +#define SSH_TIMEOUT_INFINITE -1 +/* Use the timeout defined by user if any. Mostly used with new connections */ +#define SSH_TIMEOUT_USER -2 +/* Use the default timeout, depending on ssh_is_blocking() */ +#define SSH_TIMEOUT_DEFAULT -3 +/* Don't block at all */ +#define SSH_TIMEOUT_NONBLOCKING 0 + +/* options flags */ +/* Authentication with *** allowed */ +#define SSH_OPT_FLAG_PASSWORD_AUTH 0x1 +#define SSH_OPT_FLAG_PUBKEY_AUTH 0x2 +#define SSH_OPT_FLAG_KBDINT_AUTH 0x4 +#define SSH_OPT_FLAG_GSSAPI_AUTH 0x8 + +/* Escape expansion of different variables */ +#define SSH_OPT_EXP_FLAG_KNOWNHOSTS 0x1 +#define SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS 0x2 +#define SSH_OPT_EXP_FLAG_PROXYCOMMAND 0x4 +#define SSH_OPT_EXP_FLAG_IDENTITY 0x8 +#define SSH_OPT_EXP_FLAG_CONTROL_PATH 0x10 + +/* extensions flags */ +/* negotiation enabled */ +#define SSH_EXT_NEGOTIATION 0x01 +/* server-sig-algs extension */ +#define SSH_EXT_SIG_RSA_SHA256 0x02 +#define SSH_EXT_SIG_RSA_SHA512 0x04 +/* Host-bound public key authentication extension */ +#define SSH_EXT_PUBLICKEY_HOSTBOUND 0x08 + +/* members that are common to ssh_session and ssh_bind */ +struct ssh_common_struct { + struct error_struct error; + ssh_callbacks callbacks; /* Callbacks to user functions */ + int log_verbosity; /* verbosity of the log functions */ +}; + +struct ssh_session_struct { + struct ssh_common_struct common; + struct ssh_socket_struct *socket; + char *serverbanner; + char *clientbanner; + int protoversion; + int server; + int client; + int openssh; + uint32_t send_seq; + uint32_t recv_seq; + struct ssh_timestamp last_rekey_time; + bool proxy_root; + + int connected; + /* !=0 when the user got a session handle */ + int alive; + /* two previous are deprecated */ + /* int auth_service_asked; */ + + /* session flags (SSH_SESSION_FLAG_*) */ + int flags; + + /* Extensions negotiated using RFC 8308 */ + uint32_t extensions; + + ssh_string banner; /* that's the issue banner from the server */ + char *peer_discon_msg; /* disconnect message from the remote host */ + char *disconnect_message; /* disconnect message to be set */ + ssh_buffer in_buffer; + PACKET in_packet; + ssh_buffer out_buffer; + struct ssh_list *out_queue; /* This list is used for delaying packets + when rekeying is required */ + + /* the states are used by the nonblocking stuff to remember */ + /* where it was before being interrupted */ + enum ssh_pending_call_e pending_call_state; + enum ssh_session_state_e session_state; + enum ssh_packet_state_e packet_state; + enum ssh_dh_state_e dh_handshake_state; + enum ssh_channel_request_state_e global_req_state; + struct ssh_agent_state_struct *agent_state; + + struct { + struct ssh_auth_auto_state_struct *auto_state; + enum ssh_auth_service_state_e service_state; + enum ssh_auth_state_e state; + uint32_t supported_methods; + uint32_t current_method; + } auth; + + /* Sending this flag before key exchange to save one round trip during the + * key exchange. This might make sense on high-latency connections. + * So far internal only for testing. Usable only on the client side -- + * there is no key exchange method that would start with server message */ + bool send_first_kex_follows; + /* + * RFC 4253, 7.1: if the first_kex_packet_follows flag was set in + * the received SSH_MSG_KEXINIT, but the guess was wrong, this + * field will be set such that the following guessed packet will + * be ignored on the receiving side. Once that packet has been received and + * ignored, this field is cleared. + * On the sending side, this is set after we got peer KEXINIT message and we + * need to resend the initial message of the negotiated KEX algorithm. + */ + bool first_kex_follows_guess_wrong; + + ssh_string gssapi_key_exchange_mic; + + ssh_buffer in_hashbuf; + ssh_buffer out_hashbuf; + struct ssh_crypto_struct *current_crypto; + /* next_crypto is going to be used after a SSH2_MSG_NEWKEYS */ + struct ssh_crypto_struct *next_crypto; + + struct ssh_list *channels; /* linked list of channels */ + uint32_t maxchannel; + ssh_agent agent; /* ssh agent */ + + /* keyboard interactive data */ + struct ssh_kbdint_struct *kbdint; + struct ssh_gssapi_struct *gssapi; + + /* server host keys */ + struct { + ssh_key rsa_key; + ssh_key ecdsa_key; + ssh_key ed25519_key; + /* The type of host key wanted by client */ + enum ssh_keytypes_e hostkey; + enum ssh_digest_e hostkey_digest; + } srv; + + /* auths accepted by server */ + struct ssh_list *ssh_message_list; /* list of delayed SSH messages */ + int (*ssh_message_callback)(struct ssh_session_struct *session, + ssh_message msg, void *userdata); + void *ssh_message_callback_data; + ssh_server_callbacks server_callbacks; + void (*ssh_connection_callback)( struct ssh_session_struct *session); + struct ssh_packet_callbacks_struct default_packet_callbacks; + struct ssh_list *packet_callbacks; + struct ssh_socket_callbacks_struct socket_callbacks; + ssh_poll_ctx default_poll_ctx; + /* options */ +#ifdef WITH_PCAP + ssh_pcap_context pcap_ctx; /* pcap debugging context */ +#endif + struct { + struct ssh_list *identity; + struct ssh_list *identity_non_exp; + struct ssh_iterator *identity_it; + struct ssh_list *certificate; + struct ssh_list *certificate_non_exp; + struct ssh_list *proxy_jumps; + struct ssh_list *proxy_jumps_user_cb; + char *proxy_jumps_str; + char *username; + char *host; + char *bindaddr; /* bind the client to an ip addr */ + char *homedir; + char *sshdir; + char *knownhosts; + char *global_knownhosts; + char *wanted_methods[SSH_KEX_METHODS]; + char *pubkey_accepted_types; + char *ProxyCommand; + char *agent_socket; + unsigned long timeout; /* seconds */ + unsigned long timeout_usec; + uint16_t port; + socket_t fd; + int StrictHostKeyChecking; + char compressionlevel; + char *gss_server_identity; + char *gss_client_identity; + bool gssapi_key_exchange; + char *gssapi_key_exchange_algs; + int gss_delegate_creds; + int flags; + int exp_flags; + int nodelay; + bool config_processed; + uint8_t options_seen[SOC_MAX]; + uint64_t rekey_data; + uint32_t rekey_time; + int rsa_min_size; + bool identities_only; + int control_master; + char *control_path; + int address_family; + } opts; + + /* server options */ + struct { + char *custombanner; + char *moduli_file; + } server_opts; + + /* counters */ + ssh_counter socket_counter; + ssh_counter raw_counter; + + /* PKI context structure containing various parameters to configure PKI + * operations */ + struct ssh_pki_ctx_struct *pki_context; +}; + +/** @internal + * @brief a termination function evaluates the status of an object + * @param user[in] object to evaluate + * @returns 1 if the polling routine should terminate, 0 instead + */ +typedef int (*ssh_termination_function)(void *user); +int ssh_handle_packets(ssh_session session, int timeout); +int ssh_handle_packets_termination(ssh_session session, + int timeout, + ssh_termination_function fct, + void *user); +void ssh_socket_exception_callback(int code, int errno_code, void *user); + +#endif /* SESSION_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sftp.h b/src/libs/libssh-0.12.2/include/libssh/sftp.h new file mode 100644 index 000000000000..f401ec035c82 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sftp.h @@ -0,0 +1,1482 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/** + * @defgroup libssh_sftp The libssh SFTP API + * + * @brief SFTP handling functions + * + * SFTP commands are channeled by the ssh sftp subsystem. Every packet is + * sent/read using a sftp_packet type structure. Related to these packets, + * most of the server answers are messages having an ID and a message + * specific part. It is described by sftp_message when reading a message, + * the sftp system puts it into the queue, so the process having asked for + * it can fetch it, while continuing to read for other messages (it is + * unspecified in which order messages may be sent back to the client + * + * @{ + */ + +#ifndef SFTP_H +#define SFTP_H + +#include + +#include "libssh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef _WIN32 +#ifndef uid_t + typedef uint32_t uid_t; +#endif /* uid_t */ +#ifndef gid_t + typedef uint32_t gid_t; +#endif /* gid_t */ +#ifdef _MSC_VER + +# ifndef _SSIZE_T_DEFINED +# undef ssize_t +# include + typedef _W64 SSIZE_T ssize_t; +# define _SSIZE_T_DEFINED +# endif /* _SSIZE_T_DEFINED */ + +#endif /* _MSC_VER */ +#endif /* _WIN32 */ + +#define LIBSFTP_VERSION 3 + +typedef struct sftp_attributes_struct* sftp_attributes; +typedef struct sftp_client_message_struct* sftp_client_message; +typedef struct sftp_dir_struct* sftp_dir; +typedef struct sftp_ext_struct *sftp_ext; +typedef struct sftp_file_struct* sftp_file; +typedef struct sftp_message_struct* sftp_message; +typedef struct sftp_packet_struct* sftp_packet; +typedef struct sftp_request_queue_struct* sftp_request_queue; + +/** + * @brief SFTP session handle. + * + * This type represents an active SFTP session associated with an SSH channel. + * It is created and destroyed via the libssh SFTP API and is internally + * managed by libssh. It is used by applications to perform SFTP operations + * such as file access and directory management. + * + * The internal structure of this type is opaque and must not be accessed + * directly by applications. + * + * @see sftp_new + * @see sftp_free + */ +typedef struct sftp_session_struct* sftp_session; +typedef struct sftp_status_message_struct* sftp_status_message; +typedef struct sftp_statvfs_struct* sftp_statvfs_t; +typedef struct sftp_limits_struct* sftp_limits_t; +typedef struct sftp_aio_struct* sftp_aio; +typedef struct sftp_name_id_map_struct *sftp_name_id_map; + +struct sftp_session_struct { + ssh_session session; + ssh_channel channel; + int server_version; + int client_version; + int version; + sftp_request_queue queue; + uint32_t id_counter; + int errnum; + void **handles; + sftp_ext ext; + sftp_packet read_packet; + sftp_limits_t limits; + struct ssh_list *outstanding_ids; +}; + +struct sftp_packet_struct { + sftp_session sftp; + uint8_t type; + ssh_buffer payload; +}; + +/* file handler */ +struct sftp_file_struct { + sftp_session sftp; + char *name; + uint64_t offset; + ssh_string handle; + int eof; + int nonblocking; +}; + +struct sftp_dir_struct { + sftp_session sftp; + char *name; + ssh_string handle; /* handle to directory */ + ssh_buffer buffer; /* contains raw attributes from server which haven't been parsed */ + uint32_t count; /* counts the number of following attributes structures into buffer */ + int eof; /* end of directory listing */ +}; + +struct sftp_message_struct { + sftp_session sftp; + uint8_t packet_type; + ssh_buffer payload; + uint32_t id; +}; + +/* this is a bunch of all data that could be into a message */ +struct sftp_client_message_struct { + sftp_session sftp; + uint8_t type; + uint32_t id; + char *filename; /* can be "path" */ + uint32_t flags; + sftp_attributes attr; + ssh_string handle; + uint64_t offset; + uint32_t len; + int attr_num; + ssh_buffer attrbuf; /* used by sftp_reply_attrs */ + ssh_string data; /* can be newpath of rename() */ + ssh_buffer complete_message; /* complete message in case of retransmission*/ + char *str_data; /* cstring version of data */ + char *submessage; /* for extended messages */ +}; + +struct sftp_request_queue_struct { + sftp_request_queue next; + sftp_message message; +}; + +/* SSH_FXP_MESSAGE described into .7 page 26 */ +struct sftp_status_message_struct { + uint32_t id; + uint32_t status; + ssh_string error_unused; /* not used anymore */ + ssh_string lang_unused; /* not used anymore */ + char *errormsg; + char *langmsg; +}; + +struct sftp_attributes_struct { + char *name; + char *longname; /* ls -l output on openssh, not reliable else */ + uint32_t flags; + uint8_t type; + uint64_t size; + uint32_t uid; + uint32_t gid; + char *owner; /* set if openssh and version 4 */ + char *group; /* set if openssh and version 4 */ + uint32_t permissions; + uint64_t atime64; + uint32_t atime; + uint32_t atime_nseconds; + uint64_t createtime; + uint32_t createtime_nseconds; + uint64_t mtime64; + uint32_t mtime; + uint32_t mtime_nseconds; + ssh_string acl; + uint32_t extended_count; + ssh_string extended_type; + ssh_string extended_data; +}; + +/** + * @brief SFTP statvfs structure. + */ +struct sftp_statvfs_struct { + uint64_t f_bsize; /** file system block size */ + uint64_t f_frsize; /** fundamental fs block size */ + uint64_t f_blocks; /** number of blocks (unit f_frsize) */ + uint64_t f_bfree; /** free blocks in file system */ + uint64_t f_bavail; /** free blocks for non-root */ + uint64_t f_files; /** total file inodes */ + uint64_t f_ffree; /** free file inodes */ + uint64_t f_favail; /** free file inodes for to non-root */ + uint64_t f_fsid; /** file system id */ + uint64_t f_flag; /** bit mask of f_flag values */ + uint64_t f_namemax; /** maximum filename length */ +}; + +/** + * @brief SFTP limits structure. + */ +struct sftp_limits_struct { + uint64_t max_packet_length; /** maximum number of bytes in a single sftp packet */ + uint64_t max_read_length; /** maximum length in a SSH_FXP_READ packet */ + uint64_t max_write_length; /** maximum length in a SSH_FXP_WRITE packet */ + uint64_t max_open_handles; /** maximum number of active handles allowed by server */ +}; + +/** + * @brief SFTP names map structure to store the mapping between ids and names. + * + * This is mainly for the use of sftp_get_users_groups_by_id() function. + */ +struct sftp_name_id_map_struct { + /** @brief Count of name-id pairs in the map */ + uint32_t count; + + /** @brief Array of ids, ids[i] mapped to names[i] */ + uint32_t *ids; + + /** @brief Array of names, names[i] mapped to ids[i] */ + char **names; +}; + +/** + * @brief Creates a new sftp session. + * + * This function creates a new sftp session and allocates a new sftp channel + * with the server inside of the provided ssh session. This function call is + * usually followed by the sftp_init(), which initializes SFTP protocol itself. + * + * @param session The ssh session to use. The session *must* be in + * blocking mode since most `sftp_*` functions do not + * support the non-blocking API. + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + * @see sftp_init() + * @see ssh_set_blocking() + */ +LIBSSH_API sftp_session sftp_new(ssh_session session); + +/** + * @brief Start a new sftp session with an existing channel. + * + * @param session The ssh session to use. The session *must* be in + * blocking mode since most `sftp_*` functions do not + * support the non-blocking API. + * @param channel An open session channel with subsystem already allocated + * + * @return A new sftp session or NULL on error. + * + * @see sftp_free() + * @see ssh_set_blocking() + */ +LIBSSH_API sftp_session sftp_new_channel(ssh_session session, ssh_channel channel); + + +/** + * @brief Close and deallocate a sftp session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_free(sftp_session sftp); + +/** + * @brief Initialize the sftp protocol with the server. + * + * This function involves the SFTP protocol initialization (as described + * in the SFTP specification), including the version and extensions negotiation. + * + * @param sftp The sftp session to initialize. + * + * @return 0 on success, < 0 on error with ssh error set. + * + * @see sftp_new() + */ +LIBSSH_API int sftp_init(sftp_session sftp); + +/** + * @brief Get the last sftp error. + * + * Use this function to get the latest error set by a posix like sftp function. + * + * @param sftp The sftp session where the error is saved. + * + * @return The saved error (see server responses), < 0 if an error + * in the function occurred. + * + * @see Server responses + */ +LIBSSH_API int sftp_get_error(sftp_session sftp); + +/** + * @brief Get the count of extensions provided by the server. + * + * @param sftp The sftp session to use. + * + * @return The count of extensions provided by the server, 0 on error or + * not available. + */ +LIBSSH_API unsigned int sftp_extensions_get_count(sftp_session sftp); + +/** + * @brief Get the name of the extension provided by the server. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension name you want. + * + * @return The name of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_name(sftp_session sftp, unsigned int indexn); + +/** + * @brief Get the data of the extension provided by the server. + * + * This is normally the version number of the extension. + * + * @param sftp The sftp session to use. + * + * @param indexn The index number of the extension data you want. + * + * @return The data of the extension. + */ +LIBSSH_API const char *sftp_extensions_get_data(sftp_session sftp, unsigned int indexn); + +/** + * @brief Check if the given extension is supported. + * + * @param sftp The sftp session to use. + * + * @param name The name of the extension. + * + * @param data The data of the extension. + * + * @return 1 if supported, 0 if not. + * + * Example: + * + * @code + * sftp_extension_supported(sftp, "statvfs@openssh.com", "2"); + * @endcode + */ +LIBSSH_API int sftp_extension_supported(sftp_session sftp, const char *name, + const char *data); + +/** + * @brief Open a directory used to obtain directory entries. + * + * @param session The sftp session handle to open the directory. + * @param path The path of the directory to open. + * + * @return A sftp directory handle or NULL on error with ssh and + * sftp error set. + * + * @see sftp_readdir + * @see sftp_closedir + */ +LIBSSH_API sftp_dir sftp_opendir(sftp_session session, const char *path); + +/** + * @brief Get a single file attributes structure of a directory. + * + * @param session The sftp session handle to read the directory entry. + * @param dir The opened sftp directory handle to read from. + * + * @return A file attribute structure or NULL at the end of the + * directory. + * + * @see sftp_opendir() + * @see sftp_attribute_free() + * @see sftp_closedir() + */ +LIBSSH_API sftp_attributes sftp_readdir(sftp_session session, sftp_dir dir); + +/** + * @brief Tell if the directory has reached EOF (End Of File). + * + * @param dir The sftp directory handle. + * + * @return 1 if the directory is EOF, 0 if not. + * + * @see sftp_readdir() + */ +LIBSSH_API int sftp_dir_eof(sftp_dir dir); + +/** + * @brief Get information about a file or directory. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_stat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory. + * + * Identical to sftp_stat, but if the file or directory is a symbolic link, + * then the link itself is stated, not the file that it refers to. + * + * @param session The sftp session handle. + * @param path The path to the file or directory to obtain the + * information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_lstat(sftp_session session, const char *path); + +/** + * @brief Get information about a file or directory from a file handle. + * + * @param file The sftp file handle to get the stat information. + * + * @return The sftp attributes structure of the file or directory, + * NULL on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_attributes sftp_fstat(sftp_file file); + +/** + * @brief Free a sftp attribute structure. + * + * @param file The sftp attribute structure to free. + */ +LIBSSH_API void sftp_attributes_free(sftp_attributes file); + +/** + * @brief Close a directory handle opened by sftp_opendir(). + * + * @param dir The sftp directory handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occurred. + */ +LIBSSH_API int sftp_closedir(sftp_dir dir); + +/** + * @brief Close an open file handle. + * + * @param file The open sftp file handle to close. + * + * @return Returns SSH_NO_ERROR or SSH_ERROR if an error occurred. + * + * @see sftp_open() + */ +LIBSSH_API int sftp_close(sftp_file file); + +/** + * @brief Open a file on the server. + * + * @param session The sftp session handle. + * + * @param file The file to be opened. + * + * @param accesstype Is one of O_RDONLY, O_WRONLY or O_RDWR which request + * opening the file read-only,write-only or read/write. + * Acesss may also be bitwise-or'd with one or more of + * the following: + * O_CREAT - If the file does not exist it will be + * created. + * O_EXCL - When used with O_CREAT, if the file already + * exists it is an error and the open will fail. + * O_TRUNC - If the file already exists it will be + * truncated. + * + * @param mode Mode specifies the permissions to use if a new file is + * created. It is modified by the process's umask in + * the usual way: The permissions of the created file are + * (mode & ~umask) + * + * @return A sftp file handle, NULL on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_file sftp_open(sftp_session session, const char *file, int accesstype, + mode_t mode); + +/** + * @brief Make the sftp communication for this file handle non blocking. + * + * @param[in] handle The file handle to set non blocking. + */ +LIBSSH_API void sftp_file_set_nonblocking(sftp_file handle); + +/** + * @brief Make the sftp communication for this file handle blocking. + * + * @param[in] handle The file handle to set blocking. + */ +LIBSSH_API void sftp_file_set_blocking(sftp_file handle); + +/** + * @brief Read from a file using an opened sftp file handle. + * + * This function caps the length a user is allowed to read from an sftp file. + * + * The value used for the cap is same as the value of the max_read_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file The opened sftp file handle to be read from. + * + * @param buf Pointer to buffer to receive read data. + * + * @param count Size of the buffer in bytes. + * + * @return Number of bytes read, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_get_error() + */ +LIBSSH_API ssize_t sftp_read(sftp_file file, void *buf, size_t count); + +/** + * @brief Start an asynchronous read from a file using an opened sftp file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous read. To do so, you must call 2 functions: + * + * sftp_async_read_begin() and sftp_async_read(). + * + * The first step is to call sftp_async_read_begin(). This function returns a + * request identifier. The second step is to call sftp_async_read() using the + * returned identifier. + * + * @param file The opened sftp file handle to be read from. + * + * @param len Size to read in bytes. + * + * @return An identifier corresponding to the sent request, < 0 on + * error. + * + * @warning When calling this function, the internal offset is + * updated corresponding to the len parameter. + * + * @warning A call to sftp_async_read_begin() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_async_read() is called. + * Not calling sftp_async_read() will lead to memory + * leaks. + * + * @see sftp_async_read() + * @see sftp_open() + */ +SSH_DEPRECATED LIBSSH_API int sftp_async_read_begin(sftp_file file, + uint32_t len); + +/** + * @brief Wait for an asynchronous read to complete and save the data. + * + * @param file The opened sftp file handle to be read from. + * + * @param data Pointer to buffer to receive read data. + * + * @param len Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_async_read_begin() call. + * + * @param id The identifier returned by the sftp_async_read_begin() + * function. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occurred, SSH_AGAIN if the file is opened in nonblocking + * mode and the request hasn't been executed yet. + * + * @warning A call to this function with an invalid identifier + * will never return. + * + * @see sftp_async_read_begin() + */ +SSH_DEPRECATED LIBSSH_API int sftp_async_read(sftp_file file, + void *data, + uint32_t len, + uint32_t id); + +/** + * @brief Write to a file using an opened sftp file handle. + * + * This function caps the length a user is allowed to write to an sftp file. + * + * The value used for the cap is same as the value of the max_write_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file Open sftp file handle to write to. + * + * @param buf Pointer to buffer to write data. + * + * @param count Size of buffer in bytes. + * + * @return Number of bytes written, < 0 on error with ssh and sftp + * error set. + * + * @see sftp_open() + * @see sftp_read() + * @see sftp_close() + */ +LIBSSH_API ssize_t sftp_write(sftp_file file, const void *buf, size_t count); + +/** + * @brief Deallocate memory corresponding to a sftp aio handle. + * + * This function deallocates memory corresponding to the aio handle returned + * by the sftp_aio_begin_*() functions. Users can use this function to free + * memory corresponding to an aio handle for an outstanding async i/o request + * on encountering some error. + * + * @param aio sftp aio handle corresponding to which memory has + * to be deallocated. + * + * @see sftp_aio_begin_read() + * @see sftp_aio_wait_read() + * @see sftp_aio_begin_write() + * @see sftp_aio_wait_write() + */ +LIBSSH_API void sftp_aio_free(sftp_aio aio); +#define SFTP_AIO_FREE(x) \ + do { if(x != NULL) {sftp_aio_free(x); x = NULL;} } while(0) + +/** + * @brief Start an asynchronous read from a file using an opened sftp + * file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous read. To do so, you must call 2 functions : + * + * sftp_aio_begin_read() and sftp_aio_wait_read(). + * + * - The first step is to call sftp_aio_begin_read(). This function sends a + * read request to the sftp server, dynamically allocates memory to store + * information about the sent request and provides the caller an sftp aio + * handle to that memory. + * + * - The second step is to call sftp_aio_wait_read() and pass it the address + * of a location storing the sftp aio handle provided by + * sftp_aio_begin_read(). + * + * These two functions do not close the open sftp file handle passed to + * sftp_aio_begin_read() irrespective of whether they fail or not. + * + * It is the responsibility of the caller to ensure that the open sftp file + * handle passed to sftp_aio_begin_read() must not be closed before the + * corresponding call to sftp_aio_wait_read(). After sftp_aio_wait_read() + * returns, it is caller's decision whether to immediately close the file by + * calling sftp_close() or to keep it open and perform some more operations + * on it. + * + * This function caps the length a user is allowed to read from an sftp file, + * the value of len parameter after capping is returned on success. + * + * The value used for the cap is same as the value of the max_read_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file The opened sftp file handle to be read from. + * + * @param len Number of bytes to read. + * + * @param aio Pointer to a location where the sftp aio handle + * (corresponding to the sent request) should be stored. + * + * @returns On success, the number of bytes the server is + * requested to read (value of len parameter after + * capping). On error, SSH_ERROR with sftp and ssh + * errors set. + * + * @warning When calling this function, the internal file offset is + * updated corresponding to the number of bytes requested + * to read. + * + * @warning A call to sftp_aio_begin_read() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_aio_wait_read() is called. + * Not calling sftp_aio_wait_read() will lead to memory + * leaks. + * + * @see sftp_aio_wait_read() + * @see sftp_aio_free() + * @see sftp_open() + * @see sftp_close() + * @see sftp_get_error() + * @see ssh_get_error() + */ +LIBSSH_API ssize_t sftp_aio_begin_read(sftp_file file, + size_t len, + sftp_aio *aio); + +/** + * @brief Wait for an asynchronous read to complete and store the read data + * in the supplied buffer. + * + * A pointer to an sftp aio handle should be passed while calling + * this function. Except when the return value is SSH_AGAIN, + * this function releases the memory corresponding to the supplied + * aio handle and assigns NULL to that aio handle using the passed + * pointer to that handle. + * + * If the file is opened in non-blocking mode and the request hasn't been + * executed yet, this function returns SSH_AGAIN and must be called again + * using the same sftp aio handle. + * + * @param aio Pointer to the sftp aio handle returned by + * sftp_aio_begin_read(). + * + * @param buf Pointer to the buffer in which read data will be stored. + * + * @param buf_size Size of the buffer in bytes. It should be bigger or + * equal to the length parameter of the + * sftp_aio_begin_read() call. + * + * @return Number of bytes read, 0 on EOF, SSH_ERROR if an error + * occurred, SSH_AGAIN if the file is opened in nonblocking + * mode and the request hasn't been executed yet. + * + * @warning A call to this function with an invalid sftp aio handle + * may never return. + * + * @see sftp_aio_begin_read() + * @see sftp_aio_free() + */ +LIBSSH_API ssize_t sftp_aio_wait_read(sftp_aio *aio, + void *buf, + size_t buf_size); + +/** + * @brief Start an asynchronous write to a file using an opened sftp + * file handle. + * + * Its goal is to avoid the slowdowns related to the request/response pattern + * of a synchronous write. To do so, you must call 2 functions : + * + * sftp_aio_begin_write() and sftp_aio_wait_write(). + * + * - The first step is to call sftp_aio_begin_write(). This function sends a + * write request to the sftp server, dynamically allocates memory to store + * information about the sent request and provides the caller an sftp aio + * handle to that memory. + * + * - The second step is to call sftp_aio_wait_write() and pass it the address + * of a location storing the sftp aio handle provided by + * sftp_aio_begin_write(). + * + * These two functions do not close the open sftp file handle passed to + * sftp_aio_begin_write() irrespective of whether they fail or not. + * + * It is the responsibility of the caller to ensure that the open sftp file + * handle passed to sftp_aio_begin_write() must not be closed before the + * corresponding call to sftp_aio_wait_write(). After sftp_aio_wait_write() + * returns, it is caller's decision whether to immediately close the file by + * calling sftp_close() or to keep it open and perform some more operations + * on it. + * + * This function caps the length a user is allowed to write to an sftp file, + * the value of len parameter after capping is returned on success. + * + * The value used for the cap is same as the value of the max_write_length + * field of the sftp_limits_t returned by sftp_limits(). + * + * @param file The opened sftp file handle to write to. + * + * @param buf Pointer to the buffer containing data to write. + * + * @param len Number of bytes to write. + * + * @param aio Pointer to a location where the sftp aio handle + * (corresponding to the sent request) should be stored. + * + * @returns On success, the number of bytes the server is + * requested to write (value of len parameter after + * capping). On error, SSH_ERROR with sftp and ssh errors + * set. + * + * @warning When calling this function, the internal file offset is + * updated corresponding to the number of bytes requested + * to write. + * + * @warning A call to sftp_aio_begin_write() sends a request to + * the server. When the server answers, libssh allocates + * memory to store it until sftp_aio_wait_write() is + * called. Not calling sftp_aio_wait_write() will lead to + * memory leaks. + * + * @see sftp_aio_wait_write() + * @see sftp_aio_free() + * @see sftp_open() + * @see sftp_close() + * @see sftp_get_error() + * @see ssh_get_error() + */ +LIBSSH_API ssize_t sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio); + +/** + * @brief Wait for an asynchronous write to complete. + * + * A pointer to an sftp aio handle should be passed while calling + * this function. Except when the return value is SSH_AGAIN, + * this function releases the memory corresponding to the supplied + * aio handle and assigns NULL to that aio handle using the passed + * pointer to that handle. + * + * If the file is opened in non-blocking mode and the request hasn't + * been executed yet, this function returns SSH_AGAIN and must be called + * again using the same sftp aio handle. + * + * @param aio Pointer to the sftp aio handle returned by + * sftp_aio_begin_write(). + * + * @return Number of bytes written on success, SSH_ERROR + * if an error occurred, SSH_AGAIN if the file is + * opened in nonblocking mode and the request hasn't + * been executed yet. + * + * @warning A call to this function with an invalid sftp aio handle + * may never return. + * + * @see sftp_aio_begin_write() + * @see sftp_aio_free() + */ +LIBSSH_API ssize_t sftp_aio_wait_write(sftp_aio *aio); + +/** + * @brief Seek to a specific location in a file. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek(sftp_file file, uint32_t new_offset); + +/** + * @brief Seek to a specific location in a file. This is the + * 64bit version. + * + * @param file Open sftp file handle to seek in. + * + * @param new_offset Offset in bytes to seek. + * + * @return 0 on success, < 0 on error. + */ +LIBSSH_API int sftp_seek64(sftp_file file, uint64_t new_offset); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. < 0 on + * error. + */ +LIBSSH_API unsigned long sftp_tell(sftp_file file); + +/** + * @brief Report current byte position in file. + * + * @param file Open sftp file handle. + * + * @return The offset of the current byte relative to the beginning + * of the file associated with the file descriptor. + */ +LIBSSH_API uint64_t sftp_tell64(sftp_file file); + +/** + * @brief Rewinds the position of the file pointer to the beginning of the + * file. + * + * @param file Open sftp file handle. + */ +LIBSSH_API void sftp_rewind(sftp_file file); + +/** + * @brief Unlink (delete) a file. + * + * @param sftp The sftp session handle. + * + * @param file The file to unlink/delete. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_unlink(sftp_session sftp, const char *file); + +/** + * @brief Remove a directory. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to remove. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rmdir(sftp_session sftp, const char *directory); + +/** + * @brief Create a directory. + * + * @param sftp The sftp session handle. + * + * @param directory The directory to create. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode); + +/** + * @brief Rename or move a file or directory. + * + * @param sftp The sftp session handle. + * + * @param original The original url (source url) of file or directory to + * be moved. + * + * @param newname The new url (destination url) of the file or directory + * after the move. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_rename(sftp_session sftp, const char *original, const char *newname); + +/** + * @brief Set file attributes on a file, directory or symbolic link. + * + * Note, that this function can only set time values using 32 bit values due to + * the restrictions in the SFTP protocol version 3 implemented by libssh. + * The support for 64 bit time values was introduced in SFTP version 5, which is + * not implemented by libssh nor any major SFTP servers. + * + * @param sftp The sftp session handle. + * + * @param file The file which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr); + +/** + * @brief This request is like setstat (excluding mode and size) but sets file + * attributes on symlinks themselves. + * + * Note, that this function can only set time values using 32 bit values due to + * the restrictions in the SFTP protocol version 3 implemented by libssh. + * The support for 64 bit time values was introduced in SFTP version 5, which is + * not implemented by libssh nor any major SFTP servers. + * + * @param sftp The sftp session handle. + * + * @param file The symbolic link which attributes should be changed. + * + * @param attr The file attributes structure with the attributes set + * which should be changed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int +sftp_lsetstat(sftp_session sftp, const char *file, sftp_attributes attr); + +/** + * @brief Change the file owner and group + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param owner The new owner which should be set. + * + * @param group The new group which should be set. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group); + +/** + * @brief Change permissions of a file + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param mode Specifies the permissions to use. It is modified by the + * process's umask in the usual way: + * The permissions of the created file are (mode & ~umask) + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_chmod(sftp_session sftp, const char *file, mode_t mode); + +/** + * @brief Change the last modification and access time of a file. + * + * @param sftp The sftp session handle. + * + * @param file The file which owner and group should be changed. + * + * @param times A timeval structure which contains the desired access + * and modification time. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_utimes(sftp_session sftp, const char *file, const struct timeval *times); + +/** + * @brief Create a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param target Specifies the target of the symlink. + * + * @param dest Specifies the path name of the symlink to be created. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_symlink(sftp_session sftp, const char *target, const char *dest); + +/** + * @brief Read the value of a symbolic link. + * + * @param sftp The sftp session handle. + * + * @param path Specifies the path name of the symlink to be read. + * + * @return The target of the link, NULL on error. + * The caller needs to free the memory + * using ssh_string_free_char(). + * + * @see sftp_get_error() + */ +LIBSSH_API char *sftp_readlink(sftp_session sftp, const char *path); + +/** + * @brief Create a hard link. + * + * @param sftp The sftp session handle. + * + * @param oldpath Specifies the pathname of the file for + * which the new hardlink is to be created. + * + * @param newpath Specifies the pathname of the hardlink to be created. + * + * @return 0 on success, -1 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath); + +/** + * @brief Get information about a mounted file system. + * + * @param sftp The sftp session handle. + * + * @param path The pathname of any file within the mounted file system. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path); + +/** + * @brief Get information about a mounted file system. + * + * @param file An opened file. + * + * @return A statvfs structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_statvfs_t sftp_fstatvfs(sftp_file file); + +/** + * @brief Free the memory of an allocated statvfs. + * + * @param statvfs_o The statvfs to free. + */ +LIBSSH_API void sftp_statvfs_free(sftp_statvfs_t statvfs_o); + +/** + * @brief Synchronize a file's in-core state with storage device + * + * This calls the "fsync@openssh.com" extension. You should check if the + * extensions is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, "fsync@openssh.com", "1"); + * @endcode + * + * @param file The opened sftp file handle to sync + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + */ +LIBSSH_API int sftp_fsync(sftp_file file); + +/** + * @brief Get information about the various limits the server might impose. + * + * @param sftp The sftp session handle. + * + * @return A limits structure or NULL on error. + * + * @see sftp_get_error() + */ +LIBSSH_API sftp_limits_t sftp_limits(sftp_session sftp); + +/** + * @brief Free the memory of an allocated limits. + * + * @param limits The limits to free. + */ +LIBSSH_API void sftp_limits_free(sftp_limits_t limits); + +/** + * @brief Canonicalize a sftp path. + * + * @param sftp The sftp session handle. + * + * @param path The path to be canonicalized. + * + * @return A pointer to the newly allocated canonicalized path, + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_canonicalize_path(sftp_session sftp, const char *path); + +/** + * @brief Get the version of the SFTP protocol supported by the server + * + * @param sftp The sftp session handle. + * + * @return The server version. + */ +LIBSSH_API int sftp_server_version(sftp_session sftp); + +/** + * @brief Canonicalize path using expand-path@openssh.com extension + * + * @param sftp The sftp session handle. + * + * @param path The path to be canonicalized. + * + * @return A pointer to the newly allocated canonicalized path, + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_expand_path(sftp_session sftp, const char *path); + +/** + * @brief Get the specified user's home directory + * + * This calls the "home-directory" extension. You should check if the extension + * is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, "home-directory", "1"); + * @endcode + * + * @param sftp The sftp session handle. + * + * @param username username of the user whose home directory is requested. + * + * @return On success, a newly allocated string containing the + * absolute real-path of the home directory of the user. + * NULL on error. The caller needs to free the memory + * using ssh_string_free_char(). + */ +LIBSSH_API char *sftp_home_directory(sftp_session sftp, const char *username); + +/** + * @brief Create a new sftp_name_id_map struct. + * + * @param count The number of ids/names to store in the map. + * + * @return A pointer to the newly allocated sftp_name_id_map + * struct. + */ +LIBSSH_API sftp_name_id_map sftp_name_id_map_new(uint32_t count); + +/** + * @brief Free the memory of an allocated `sftp_name_id_map` struct. + * + * @param map A pointer to the `sftp_name_id_map` struct to free. + */ +LIBSSH_API void sftp_name_id_map_free(sftp_name_id_map map); + +/** + * @brief Retrieves usernames and group names based on provided user and group + * IDs. + * + * The retrieved names are stored in the `names` field of the + * `sftp_name_id_map` structure. In case a uid or gid is not found, an empty + * string is stored. + * + * This calls the "users-groups-by-id@openssh.com" extension. + * You should check if the extension is supported using: + * + * @code + * int supported = sftp_extension_supported(sftp, + * "users-groups-by-id@openssh.com", "1"); + * @endcode + * + * @param sftp The SFTP session handle. + * + * @param users_map A pointer to a `sftp_name_id_map` struct with the user + * IDs. Can be NULL if only group names are needed. + * + * @param groups_map A pointer to a `sftp_name_id_map` struct with the group + * IDs. Can be NULL if only user names are needed. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @note The caller needs to free the memory used for + * the maps later using `sftp_name_id_map_free()`. + * + * @see sftp_get_error() + */ +LIBSSH_API int sftp_get_users_groups_by_id(sftp_session sftp, + sftp_name_id_map users_map, + sftp_name_id_map groups_map); + +#ifdef WITH_SERVER +/** + * @brief Create a new sftp server session. + * + * @param session The ssh session to use. + * + * @param chan The ssh channel to use. + * + * @return A new sftp server session. + */ +LIBSSH_API sftp_session sftp_server_new(ssh_session session, ssh_channel chan); + +/** + * @brief Initialize the sftp server. + * + * @param sftp The sftp session to init. + * + * @return 0 on success, < 0 on error. + */ +SSH_DEPRECATED LIBSSH_API int sftp_server_init(sftp_session sftp); + +/** + * @brief Close and deallocate a sftp server session. + * + * @param sftp The sftp session handle to free. + */ +LIBSSH_API void sftp_server_free(sftp_session sftp); +#endif /* WITH_SERVER */ + +/* sftpserver.c */ + +LIBSSH_API sftp_client_message sftp_get_client_message(sftp_session sftp); +LIBSSH_API void sftp_client_message_free(sftp_client_message msg); +LIBSSH_API uint8_t sftp_client_message_get_type(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_filename(sftp_client_message msg); +LIBSSH_API void sftp_client_message_set_filename(sftp_client_message msg, const char *newname); +LIBSSH_API const char *sftp_client_message_get_data(sftp_client_message msg); +LIBSSH_API uint32_t sftp_client_message_get_flags(sftp_client_message msg); +LIBSSH_API const char *sftp_client_message_get_submessage(sftp_client_message msg); +LIBSSH_API int sftp_send_client_message(sftp_session sftp, sftp_client_message msg); +LIBSSH_API int sftp_reply_name(sftp_client_message msg, const char *name, + sftp_attributes attr); +LIBSSH_API int sftp_reply_handle(sftp_client_message msg, ssh_string handle); +LIBSSH_API ssh_string sftp_handle_alloc(sftp_session sftp, void *info); +LIBSSH_API int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr); +LIBSSH_API void *sftp_handle(sftp_session sftp, ssh_string handle); +LIBSSH_API int sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message); +LIBSSH_API int sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr); +LIBSSH_API int sftp_reply_names(sftp_client_message msg); +LIBSSH_API int sftp_reply_data(sftp_client_message msg, const void *data, int len); +LIBSSH_API void sftp_handle_remove(sftp_session sftp, void *handle); + +/* SFTP commands and constants */ +#define SSH_FXP_INIT 1 +#define SSH_FXP_VERSION 2 +#define SSH_FXP_OPEN 3 +#define SSH_FXP_CLOSE 4 +#define SSH_FXP_READ 5 +#define SSH_FXP_WRITE 6 +#define SSH_FXP_LSTAT 7 +#define SSH_FXP_FSTAT 8 +#define SSH_FXP_SETSTAT 9 +#define SSH_FXP_FSETSTAT 10 +#define SSH_FXP_OPENDIR 11 +#define SSH_FXP_READDIR 12 +#define SSH_FXP_REMOVE 13 +#define SSH_FXP_MKDIR 14 +#define SSH_FXP_RMDIR 15 +#define SSH_FXP_REALPATH 16 +#define SSH_FXP_STAT 17 +#define SSH_FXP_RENAME 18 +#define SSH_FXP_READLINK 19 +#define SSH_FXP_SYMLINK 20 + +#define SSH_FXP_STATUS 101 +#define SSH_FXP_HANDLE 102 +#define SSH_FXP_DATA 103 +#define SSH_FXP_NAME 104 +#define SSH_FXP_ATTRS 105 + +#define SSH_FXP_EXTENDED 200 +#define SSH_FXP_EXTENDED_REPLY 201 + +/* attributes */ +/* sftp draft is completely braindead : version 3 and 4 have different flags for same constants */ +/* and even worst, version 4 has same flag for 2 different constants */ +/* follow up : i won't develop any sftp4 compliant library before having a clarification */ + +#define SSH_FILEXFER_ATTR_SIZE 0x00000001 +#define SSH_FILEXFER_ATTR_PERMISSIONS 0x00000004 +#define SSH_FILEXFER_ATTR_ACCESSTIME 0x00000008 +#define SSH_FILEXFER_ATTR_ACMODTIME 0x00000008 +#define SSH_FILEXFER_ATTR_CREATETIME 0x00000010 +#define SSH_FILEXFER_ATTR_MODIFYTIME 0x00000020 +#define SSH_FILEXFER_ATTR_ACL 0x00000040 +#define SSH_FILEXFER_ATTR_OWNERGROUP 0x00000080 +#define SSH_FILEXFER_ATTR_SUBSECOND_TIMES 0x00000100 +#define SSH_FILEXFER_ATTR_EXTENDED 0x80000000 +#define SSH_FILEXFER_ATTR_UIDGID 0x00000002 + +/* types */ +#define SSH_FILEXFER_TYPE_REGULAR 1 +#define SSH_FILEXFER_TYPE_DIRECTORY 2 +#define SSH_FILEXFER_TYPE_SYMLINK 3 +#define SSH_FILEXFER_TYPE_SPECIAL 4 +#define SSH_FILEXFER_TYPE_UNKNOWN 5 + +/** + * @name Server responses + * + * @brief Responses returned by the sftp server. + * @{ + */ + +/** No error */ +#define SSH_FX_OK 0 +/** End-of-file encountered */ +#define SSH_FX_EOF 1 +/** File doesn't exist */ +#define SSH_FX_NO_SUCH_FILE 2 +/** Permission denied */ +#define SSH_FX_PERMISSION_DENIED 3 +/** Generic failure */ +#define SSH_FX_FAILURE 4 +/** Garbage received from server */ +#define SSH_FX_BAD_MESSAGE 5 +/** No connection has been set up */ +#define SSH_FX_NO_CONNECTION 6 +/** There was a connection, but we lost it */ +#define SSH_FX_CONNECTION_LOST 7 +/** Operation not supported by the server */ +#define SSH_FX_OP_UNSUPPORTED 8 +/** Invalid file handle */ +#define SSH_FX_INVALID_HANDLE 9 +/** No such file or directory path exists */ +#define SSH_FX_NO_SUCH_PATH 10 +/** An attempt to create an already existing file or directory has been made */ +#define SSH_FX_FILE_ALREADY_EXISTS 11 +/** We are trying to write on a write-protected filesystem */ +#define SSH_FX_WRITE_PROTECT 12 +/** No media in remote drive */ +#define SSH_FX_NO_MEDIA 13 + +/** @} */ + +/* file flags */ +#define SSH_FXF_READ 0x01 +#define SSH_FXF_WRITE 0x02 +#define SSH_FXF_APPEND 0x04 +#define SSH_FXF_CREAT 0x08 +#define SSH_FXF_TRUNC 0x10 +#define SSH_FXF_EXCL 0x20 +#define SSH_FXF_TEXT 0x40 + +/* file type flags */ +#define SSH_S_IFMT 00170000 +#define SSH_S_IFSOCK 0140000 +#define SSH_S_IFLNK 0120000 +#define SSH_S_IFREG 0100000 +#define SSH_S_IFBLK 0060000 +#define SSH_S_IFDIR 0040000 +#define SSH_S_IFCHR 0020000 +#define SSH_S_IFIFO 0010000 + +/* rename flags */ +#define SSH_FXF_RENAME_OVERWRITE 0x00000001 +#define SSH_FXF_RENAME_ATOMIC 0x00000002 +#define SSH_FXF_RENAME_NATIVE 0x00000004 + +#define SFTP_OPEN SSH_FXP_OPEN +#define SFTP_CLOSE SSH_FXP_CLOSE +#define SFTP_READ SSH_FXP_READ +#define SFTP_WRITE SSH_FXP_WRITE +#define SFTP_LSTAT SSH_FXP_LSTAT +#define SFTP_FSTAT SSH_FXP_FSTAT +#define SFTP_SETSTAT SSH_FXP_SETSTAT +#define SFTP_FSETSTAT SSH_FXP_FSETSTAT +#define SFTP_OPENDIR SSH_FXP_OPENDIR +#define SFTP_READDIR SSH_FXP_READDIR +#define SFTP_REMOVE SSH_FXP_REMOVE +#define SFTP_MKDIR SSH_FXP_MKDIR +#define SFTP_RMDIR SSH_FXP_RMDIR +#define SFTP_REALPATH SSH_FXP_REALPATH +#define SFTP_STAT SSH_FXP_STAT +#define SFTP_RENAME SSH_FXP_RENAME +#define SFTP_READLINK SSH_FXP_READLINK +#define SFTP_SYMLINK SSH_FXP_SYMLINK +#define SFTP_EXTENDED SSH_FXP_EXTENDED + +/* openssh flags */ +#define SSH_FXE_STATVFS_ST_RDONLY 0x1 /* read-only */ +#define SSH_FXE_STATVFS_ST_NOSUID 0x2 /* no setuid */ + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_H */ + +/** @} */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sftp_priv.h b/src/libs/libssh-0.12.2/include/libssh/sftp_priv.h new file mode 100644 index 000000000000..0ddb5b5fd09a --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sftp_priv.h @@ -0,0 +1,120 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SFTP_PRIV_H +#define SFTP_PRIV_H + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +sftp_packet sftp_packet_read(sftp_session sftp); +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload); +void sftp_packet_free(sftp_packet packet); +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr); +sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname); +/** + * @brief Reply to the SSH_FXP_INIT message with the SSH_FXP_VERSION message + * + * @param client_msg The pointer to client message. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +int sftp_reply_version(sftp_client_message client_msg); +/** + * @brief Decode the data from channel buffer into sftp read_packet. + * + * @param sftp The sftp session handle. + * + * @param data The pointer to the data buffer of channel. + * @param len The data buffer length + * + * @return Length of data decoded. + */ +int sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len); + +void sftp_set_error(sftp_session sftp, int errnum); + +void sftp_message_free(sftp_message msg); + +int sftp_read_and_dispatch(sftp_session sftp); + +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id); + +/** + * @brief Receive the response of an sftp request + * + * In blocking mode, if the response hasn't arrived at the time of call, this + * function waits for the response to arrive. + * + * @param sftp The sftp session via which the request was sent. + * + * @param id The request identifier of the request whose + * corresponding response is required. + * + * @param blocking Flag to indicate the operating mode. true indicates + * blocking mode and false indicates non-blocking mode + * + * @param msg_ptr Pointer to the location to store the response message. + * In case of success, the message is allocated + * dynamically and must be freed (using + * sftp_message_free()) by the caller after usage. In case + * of failure, this is left untouched. + * + * @returns SSH_OK on success + * @returns SSH_ERROR on failure with the sftp and ssh errors set + * @returns SSH_AGAIN in case of non-blocking mode if the response hasn't + * arrived yet. + * + * @warning In blocking mode, this may block indefinitely for an invalid request + * identifier. + */ +int sftp_recv_response_msg(sftp_session sftp, + uint32_t id, + bool blocking, + sftp_message *msg_ptr); + +/** + * @brief Assigns a new SFTP ID for new requests and assures there is no + * collision between them. + * + * @param sftp The sftp session handle. + * @param id_out Pointer to store the new ID. + * + * @returns SSH_OK on success with the new ID stored in *id + * @returns SSH_ERROR on failure with the sftp and ssh errors set + */ +int sftp_get_new_id(sftp_session sftp, uint32_t *id_out); + +sftp_status_message parse_status_msg(sftp_message msg); + +void status_msg_free(sftp_status_message status); + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_PRIV_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sftpserver.h b/src/libs/libssh-0.12.2/include/libssh/sftpserver.h new file mode 100644 index 000000000000..9982830c45d1 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sftpserver.h @@ -0,0 +1,86 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2022 Zeyu Sheng + * Copyright (c) 2023 Red Hat, Inc. + * + * Authors: Jakub Jelen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SFTP_SERVER_H +#define SFTP_SERVER_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include + +#include "libssh/libssh.h" +#include "libssh/sftp.h" + +/** + * @defgroup libssh_sftp_server The libssh SFTP server API + * + * @brief SFTP server handling functions + * + * TODO + * + * @{ + */ + +/** + * @brief Macro to declare an SFTP message callback function. + * + * @param name The name of the callback function to declare. + * + * @return SSH_OK for properly processed messages (including errors reported to + * client over the channel and SSH_ERROR for protocol errors + * that the channel callback should treat as fatal. + */ +#define SSH_SFTP_CALLBACK(name) \ + static int name(sftp_client_message message) + +typedef int (*sftp_server_message_callback)(sftp_client_message message); + +struct sftp_message_handler +{ + const char *name; + const char *extended_name; + uint8_t type; + + sftp_server_message_callback cb; +}; + +LIBSSH_API int sftp_channel_default_subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata); +LIBSSH_API int sftp_channel_default_data_callback(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); + +/** @} */ + +#ifdef __cplusplus +} +#endif + +#endif /* SFTP_SERVER_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sk_api.h b/src/libs/libssh-0.12.2/include/libssh/sk_api.h new file mode 100644 index 000000000000..f85f4b8bdc50 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sk_api.h @@ -0,0 +1,283 @@ +/* + * Copyright (c) 2019 Google LLC + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +/* + * This file is a copy of the OpenSSH project's sk-api.h file pulled from + * https://github.com/openssh/openssh-portable/commit/a9cbe10da2be5be76755af0cea029db0f9c1f263 + * with only the flags, algorithms, error codes, and struct definitions. The + * function declarations and other OpenSSH-specific code have been removed. + */ + +#ifndef SK_API_H +#define SK_API_H 1 + +#include +#include + +/* FIDO2/U2F Operation Flags */ + +/** Requires user presence confirmation (tap/touch) */ +#ifndef SSH_SK_USER_PRESENCE_REQD +#define SSH_SK_USER_PRESENCE_REQD 0x01 +#endif + +/** Requires user verification (PIN/biometric) - FIDO2 only */ +#ifndef SSH_SK_USER_VERIFICATION_REQD +#define SSH_SK_USER_VERIFICATION_REQD 0x04 +#endif + +/** Force resident key enrollment even if a resident key with given user ID + * already exists - FIDO2 only */ +#ifndef SSH_SK_FORCE_OPERATION +#define SSH_SK_FORCE_OPERATION 0x10 +#endif + +/** Create/use resident key stored on authenticator - FIDO2 only */ +#ifndef SSH_SK_RESIDENT_KEY +#define SSH_SK_RESIDENT_KEY 0x20 +#endif + +/* Algorithms */ + +/** ECDSA with P-256 curve */ +#define SSH_SK_ECDSA 0x00 + +/** Ed25519 - FIDO2 only */ +#define SSH_SK_ED25519 0x01 + +/* Error codes */ + +/** General unspecified failure */ +#define SSH_SK_ERR_GENERAL -1 + +/** Requested algorithm/feature/option not supported */ +#define SSH_SK_ERR_UNSUPPORTED -2 + +/** PIN (or other user verification) required but either missing or invalid */ +#define SSH_SK_ERR_PIN_REQUIRED -3 + +/** No suitable security key / authenticator device was found */ +#define SSH_SK_ERR_DEVICE_NOT_FOUND -4 + +/** Attempt to create a resident key that already exists (duplicate) */ +#define SSH_SK_ERR_CREDENTIAL_EXISTS -5 + +/** + * @brief Response structure for FIDO2/U2F key enrollment operations + * + * Contains all data returned by a FIDO2/U2F authenticator after successful + * enrollment of a new credential. + */ +struct sk_enroll_response { + /** @brief FIDO2/U2F authenticator flags from the enrollment operation + * + * Contains flags indicating authenticator capabilities and state during + * enrollment, such as user presence (UP), user verification + * (UV), and resident key. + */ + uint8_t flags; + + /** @brief Public key data in standard format + * + * For ECDSA (P-256): 65 bytes in SEC1 uncompressed point format + * (0x04 prefix + 32-byte X coordinate + 32-byte Y coordinate) + * For Ed25519: 32 bytes containing the raw public key (FIDO2 only) + */ + uint8_t *public_key; + + /** @brief Length of public_key buffer in bytes + * + * Expected values: 65 for ECDSA P-256, 32 for Ed25519 + */ + size_t public_key_len; + + /** @brief Opaque credential handle/ID used to identify this key + * + * Authenticator-generated binary data that uniquely identifies this + * credential. Used in subsequent sign operations to specify which + * key to use. Format and contents are authenticator-specific. + */ + uint8_t *key_handle; + + /** @brief Length of key_handle buffer in bytes + * + * Length varies by authenticator. + */ + size_t key_handle_len; + + /** @brief Enrollment signature over the enrollment data + * + * FIDO2/U2F authenticator signature proving the credential was created + * by this specific authenticator. Used for enrollment verification. + * Format depends on algorithm. + */ + uint8_t *signature; + + /** @brief Length of signature buffer in bytes + * + * Length varies by algorithm. + */ + size_t signature_len; + + /** @brief X.509 attestation certificate + * + * Certificate that attests to the authenticity of the authenticator + * and the enrollment operation. Used to verify the authenticator's + * identity and manufacturer. + */ + uint8_t *attestation_cert; + + /** @brief Length of attestation_cert buffer in bytes */ + size_t attestation_cert_len; + + /** @brief FIDO2/U2F authenticator data from enrollment + * + * CBOR-encoded authenticator data containing RP ID hash, flags, + * counter, and attested credential data. Used for attestation + * verification according to the FIDO2 specification. + */ + uint8_t *authdata; + + /** @brief Length of authdata buffer in bytes + * + * Length varies depending on credential data and extensions. + */ + size_t authdata_len; +}; + +/** + * @brief Response structure for FIDO2/U2F key signing operations + * + * Contains signature components and metadata returned by a FIDO2/U2F + * authenticator after a successful signing operation. + */ +struct sk_sign_response { + /** @brief FIDO2/U2F authenticator flags from the signing operation + * + * Contains flags indicating authenticator state during signing, + * including user presence (UP) and user verification (UV) flags. + * Used to verify that proper user interaction occurred while signing. + */ + uint8_t flags; + + /** @brief Authenticator signature counter value + * + * Monotonically increasing counter maintained by the authenticator. + * Incremented on each successful signing operation. Used to detect + * cloned or duplicated authenticators. + */ + uint32_t counter; + + /** @brief R component of ECDSA signature or Ed25519 signature */ + uint8_t *sig_r; + + /** @brief Length of sig_r buffer in bytes */ + size_t sig_r_len; + + /** @brief S component of ECDSA signature */ + uint8_t *sig_s; + + /** @brief Length of sig_s buffer in bytes */ + size_t sig_s_len; +}; + +/** + * @brief Structure representing a resident/discoverable credential + * + * Represents a FIDO2 resident key (discoverable credential) that is + * stored on the authenticator and can be discovered without providing + * a credential ID. + */ +struct sk_resident_key { + /** @brief Cryptographic algorithm identifier for this key + * + * SSH_SK_ECDSA (0x00): ECDSA with P-256 curve + * SSH_SK_ED25519 (0x01): Ed25519 signature algorithm + */ + uint32_t alg; + + /** @brief Slot/index number of this key on the authenticator + * + * Zero-based index indicating the position of this resident key + * in the authenticator's internal storage. Used for key management + * and identification when multiple resident keys exist. + */ + size_t slot; + + /** @brief Relying Party (application) identifier string + * + * The RP ID (typically a domain name) that this resident key + * is associated with. Determines which application/service + * this key can be used for. + */ + char *application; + + /** @brief Embedded enrollment response containing key material + * + * Contains the same data as returned during initial enrollment, + * including public key, key handle, and associated metadata. + */ + struct sk_enroll_response key; + + /** @brief Flags associated with this resident key + * + * SSH_SK_USER_PRESENCE_REQD: Requires user presence for operations + * SSH_SK_USER_VERIFICATION_REQD: Requires user verification + * (PIN/biometric) + */ + uint8_t flags; + + /** @brief User identifier associated with this resident key + * + * Binary user ID that was provided during key enrollment. + * Used to identify which user account this key belongs to. + */ + uint8_t *user_id; + + /** @brief Length of user_id buffer in bytes + * + * Length of the user identifier. + */ + size_t user_id_len; +}; + +/** + * @brief Configuration option structure for FIDO2/U2F operations + * + * Represents a single configuration parameter that can be passed + * to FIDO2/U2F middleware. + */ +struct sk_option { + /** @brief Option name/key identifier */ + char *name; + + /** @brief Option value as bytes */ + char *value; + + /** @brief Indicates if this option is required for the operation + * + * Non-zero if this option must be processed and cannot be ignored. + * Zero if this option is advisory and can be skipped if the + * middleware does not support it. + */ + uint8_t required; +}; + +/** Current SK API version */ +#define SSH_SK_VERSION_MAJOR 0x000a0000 +#define SSH_SK_VERSION_MAJOR_MASK 0xffff0000 + +#endif /* SK_API_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sk_common.h b/src/libs/libssh-0.12.2/include/libssh/sk_common.h new file mode 100644 index 000000000000..7c18448de2b0 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sk_common.h @@ -0,0 +1,213 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef SK_COMMON_H +#define SK_COMMON_H + +#include "libssh/callbacks.h" +#include "libssh/sk_api.h" + +#include + +#define SK_MAX_USER_ID_LEN 64 + +#define SK_NOT_SUPPORTED_MSG \ + "Security Key functionality is not supported in this build of libssh. " \ + "Please enable support by building using the WITH_FIDO2 build option." + +/** + * @brief Convert security key error code to human-readable string + * + * Converts a security key error code to a descriptive string representation + * that can be used for logging user-facing error messages. + * + * @param[in] sk_err The security key error code to convert. + * + * @return Constant string describing the error. Never returns NULL. + * Returns "Unknown error" for unrecognized error codes. + * + * @note The returned string is statically allocated and should not be freed. + */ +const char *ssh_sk_err_to_string(int sk_err); + +/** + * @brief Securely clear the contents of an sk_enroll_response structure + * + * Overwrites sensitive data within the enrollment response structure with + * zeros to prevent information leakage. This function only clears and frees the + * contents and does not free the structure itself. + * + * @param[in] enroll_response The enrollment response structure to clear. + * Can be NULL (no operation performed). + * + * @note This function only frees the memory for the contents and does not free + * memory for the structure itself. Use sk_enroll_response_free() for complete + * cleanup, which also performs secure clearing internally. + */ +void sk_enroll_response_burn(struct sk_enroll_response *enroll_response); + +/** + * @brief Securely free an sk_enroll_response structure + * + * Performs secure clearing of sensitive data within the enrollment response + * structure before freeing the allocated memory. This function internally + * calls sk_enroll_response_burn() before deallocation. + * + * @param[in] enroll_response The enrollment response structure to free. + * Can be NULL (no operation performed). + * + * @note Developers do not need to call sk_enroll_response_burn() before + * calling this function, as secure clearing is performed automatically. + */ +void sk_enroll_response_free(struct sk_enroll_response *enroll_response); + +/** + * @brief Free an sk_sign_response structure + * + * Frees the memory allocated for a sign response structure and all its + * associated data. This function performs secure clearing of sensitive + * data before deallocation. + * + * @param[in] sign_response The sign response structure to free. + * Can be NULL (no operation performed). + * + * @note This is a secure free operation that clears sensitive data before + * memory deallocation to prevent information leakage. + */ +void sk_sign_response_free(struct sk_sign_response *sign_response); + +/** + * @brief Free an sk_resident_key structure + * + * Frees the memory allocated for a resident key structure and all its + * associated data. This function performs secure clearing of sensitive + * data before deallocation. + * + * @param[in] resident_key The resident key structure to free. + * Can be NULL (no operation performed). + * + * @note This is a secure free operation that clears sensitive data before + * memory deallocation to prevent information leakage. + */ +void sk_resident_key_free(struct sk_resident_key *resident_key); + +/** + * @brief Free an sk_option array and all its contents + * + * Frees a NULL-terminated array of sk_option structures, including all + * allocated memory for option names and values within each structure. + * + * @param[in] options NULL-terminated array of sk_option pointers to free. + * Can be NULL (no operation performed). + * + * @note The options array must be NULL-terminated for proper freeing. + * Each sk_option structure and its name/value strings will be freed. + */ +void sk_options_free(struct sk_option **options); + +/** + * @brief Validate options and extract values for specific keys + * + * Validates that all required options are supported and extracts values + * for the specified keys. This function is primarily intended for use + * by the SK callback implementations. + * + * @param[in] options NULL-terminated array of sk_option pointers to validate. + * @param[in] keys NULL-terminated array of supported option keys. + * @param[out] values Pointer to array that will be allocated and filled with + * copied values (same order as keys). The caller must free + * this array and all contained strings when done. + * + * @return SSH_OK on success, SSH_ERROR if unsupported required options found + * or memory allocation fails. + * + * @note The values array is allocated by this function and contains copies + * of the option values. The caller must free both the array and all + * non-NULL string values within it. Values for keys not found in + * options will be set to NULL. + */ +int sk_options_validate_get(const struct sk_option **options, + const char **keys, + char ***values); + +/** + * @brief Duplicate an array of sk_option structures + * + * Creates a deep copy of an array of security key options. Each option + * structure and its string fields are duplicated. + * + * @param[in] options The array of options to duplicate. Must be + * NULL-terminated array of struct sk_option pointers. + * Can be NULL. + * + * @return A newly allocated array of duplicated options on success, + * NULL on failure or if options is NULL. + * The returned array should be freed with SK_OPTIONS_FREE(). + */ +struct sk_option **sk_options_dup(const struct sk_option **options); + +/** + * @brief Check version compatibility of security key callbacks + * + * Validates that the provided security key callbacks use an SK API + * version whose major portion is the same as the major version that libssh + * supports. + * + * @param[in] callbacks Pointer to the sk_callbacks structure to check. + * + * @return true if the callbacks are compatible, false otherwise. + */ +bool sk_callbacks_check_compatibility( + const struct ssh_sk_callbacks_struct *callbacks); + +/* Convenience macros for secure freeing with NULL checks and pointer reset */ +#define SK_ENROLL_RESPONSE_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_enroll_response_free(x); \ + x = NULL; \ + } \ + } while (0) + +#define SK_SIGN_RESPONSE_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_sign_response_free(x); \ + x = NULL; \ + } \ + } while (0) + +#define SK_RESIDENT_KEY_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_resident_key_free(x); \ + x = NULL; \ + } \ + } while (0) + +#define SK_OPTIONS_FREE(x) \ + do { \ + if ((x) != NULL) { \ + sk_options_free(x); \ + x = NULL; \ + } \ + } while (0) + +#endif /* SK_COMMON_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sk_usbhid.h b/src/libs/libssh-0.12.2/include/libssh/sk_usbhid.h new file mode 100644 index 000000000000..025c3daf35d3 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sk_usbhid.h @@ -0,0 +1,37 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef SK_USBHID_H +#define SK_USBHID_H + +/** + * @brief Get the USB-HID security key callbacks. + * + * This function returns a pointer to the implementation of + * security key callbacks for FIDO2/U2F devices using the USB-HID + * protocol. + * + * @return Pointer to the ssh_sk_callbacks_struct + * + * @see ssh_sk_callbacks_struct + */ +const struct ssh_sk_callbacks_struct *ssh_sk_get_usbhid_callbacks(void); + +#endif /* SK_USBHID_H */ diff --git a/src/libs/libssh-0.12.2/include/libssh/sntrup761.h b/src/libs/libssh-0.12.2/include/libssh/sntrup761.h new file mode 100644 index 000000000000..aa05a9fd2030 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/sntrup761.h @@ -0,0 +1,82 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * Copyright (c) 2023 Simon Josefsson + * Copyright (c) 2025 Jakub Jelen + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, + * version 2.1 of the License. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SNTRUP761_H_ +#define SNTRUP761_H_ + +#include "config.h" +#include "curve25519.h" +#include "libssh.h" + +#ifdef __cplusplus +extern "C" { +#endif + +#ifdef HAVE_CURVE25519 +#define HAVE_SNTRUP761 1 +#endif + +/* + * Derived from public domain source, written by (in alphabetical order): + * - Daniel J. Bernstein + * - Chitchanok Chuengsatiansup + * - Tanja Lange + * - Christine van Vredendaal + */ + +#include +#include + +#define SNTRUP761_SECRETKEY_SIZE 1763 +#define SNTRUP761_PUBLICKEY_SIZE 1158 +#define SNTRUP761_CIPHERTEXT_SIZE 1039 +#define SNTRUP761_SIZE 32 + +typedef void sntrup761_random_func(void *ctx, size_t length, uint8_t *dst); + +void sntrup761_keypair(uint8_t *pk, + uint8_t *sk, + void *random_ctx, + sntrup761_random_func *random); +void sntrup761_enc(uint8_t *c, + uint8_t *k, + const uint8_t *pk, + void *random_ctx, + sntrup761_random_func *random); +void sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk); + +typedef unsigned char ssh_sntrup761_pubkey[SNTRUP761_PUBLICKEY_SIZE]; +typedef unsigned char ssh_sntrup761_privkey[SNTRUP761_SECRETKEY_SIZE]; +typedef unsigned char ssh_sntrup761_ciphertext[SNTRUP761_CIPHERTEXT_SIZE]; + +int ssh_client_sntrup761x25519_init(ssh_session session); +void ssh_client_sntrup761x25519_remove_callbacks(ssh_session session); + +#ifdef WITH_SERVER +void ssh_server_sntrup761x25519_init(ssh_session session); +#endif /* WITH_SERVER */ + +#ifdef __cplusplus +} +#endif + +#endif /* SNTRUP761_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/socket.h b/src/libs/libssh-0.12.2/include/libssh/socket.h new file mode 100644 index 000000000000..fadd252e2500 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/socket.h @@ -0,0 +1,77 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef SOCKET_H_ +#define SOCKET_H_ + +#include "libssh/callbacks.h" +struct ssh_poll_handle_struct; +/* socket.c */ + +struct ssh_socket_struct; +typedef struct ssh_socket_struct* ssh_socket; + +int ssh_socket_init(void); +void ssh_socket_cleanup(void); +ssh_socket ssh_socket_new(ssh_session session); +void ssh_socket_reset(ssh_socket s); +void ssh_socket_free(ssh_socket s); +int ssh_socket_set_fd(ssh_socket s, socket_t fd); +socket_t ssh_socket_get_fd(ssh_socket s); +void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p); +int ssh_socket_unix(ssh_socket s, const char *path); +#if WITH_EXEC +void ssh_execute_command(const char *command, socket_t in, socket_t out); +int ssh_socket_connect_proxycommand(ssh_socket s, const char *command); +#endif +#define VBOX_PROXY_PREFIX "#VBoxProxy" +#define VBOX_PROXY_PREFIX_LENGTH (sizeof(VBOX_PROXY_PREFIX) - 1) +int ssh_socket_connect_proxycommand_vbox(ssh_socket s, const char *host, + uint16_t port, const char *command); +int ssh_socket_connect_proxyjump(ssh_socket s); +void ssh_socket_close(ssh_socket s); +int ssh_socket_write(ssh_socket s,const void *buffer, uint32_t len); +int ssh_socket_is_open(ssh_socket s); +int ssh_socket_fd_isset(ssh_socket s, fd_set *set); +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd); +void ssh_socket_set_fd_in(ssh_socket s, socket_t fd); +void ssh_socket_set_fd_out(ssh_socket s, socket_t fd); +int ssh_socket_nonblocking_flush(ssh_socket s); +void ssh_socket_set_write_wontblock(ssh_socket s); +void ssh_socket_set_read_wontblock(ssh_socket s); +void ssh_socket_set_except(ssh_socket s); +int ssh_socket_get_status(ssh_socket s); +int ssh_socket_get_poll_flags(ssh_socket s); +int ssh_socket_buffered_write_bytes(ssh_socket s); +int ssh_socket_data_available(ssh_socket s); +int ssh_socket_data_writable(ssh_socket s); +int ssh_socket_set_nonblocking(socket_t fd); +int ssh_socket_set_blocking(socket_t fd); + +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks); +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, socket_t fd, int revents, void *v_s); +struct ssh_poll_handle_struct * ssh_socket_get_poll_handle(ssh_socket s); + +int ssh_socket_connect(ssh_socket s, + const char *host, + uint16_t port, + const char *bind_addr); + +#endif /* SOCKET_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/ssh2.h b/src/libs/libssh-0.12.2/include/libssh/ssh2.h new file mode 100644 index 000000000000..dc3954383dfe --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/ssh2.h @@ -0,0 +1,90 @@ +#ifndef __SSH2_H +#define __SSH2_H + +#define SSH2_MSG_DISCONNECT 1 +#define SSH2_MSG_IGNORE 2 +#define SSH2_MSG_UNIMPLEMENTED 3 +#define SSH2_MSG_DEBUG 4 +#define SSH2_MSG_SERVICE_REQUEST 5 +#define SSH2_MSG_SERVICE_ACCEPT 6 +#define SSH2_MSG_EXT_INFO 7 + +#define SSH2_MSG_KEXINIT 20 +#define SSH2_MSG_NEWKEYS 21 + +#define SSH2_MSG_KEXDH_INIT 30 +#define SSH2_MSG_KEXDH_REPLY 31 +#define SSH2_MSG_KEX_ECDH_INIT 30 +#define SSH2_MSG_KEX_ECDH_REPLY 31 +#define SSH2_MSG_KEX_HYBRID_INIT 30 +#define SSH2_MSG_KEX_HYBRID_REPLY 31 + +#define SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#define SSH2_MSG_KEX_DH_GEX_GROUP 31 +#define SSH2_MSG_KEX_DH_GEX_INIT 32 +#define SSH2_MSG_KEX_DH_GEX_REPLY 33 +#define SSH2_MSG_KEX_DH_GEX_REQUEST 34 + +#define SSH2_MSG_KEXGSS_INIT 30 +#define SSH2_MSG_KEXGSS_CONTINUE 31 +#define SSH2_MSG_KEXGSS_COMPLETE 32 +#define SSH2_MSG_KEXGSS_HOSTKEY 33 +#define SSH2_MSG_KEXGSS_ERROR 34 +#define SSH2_MSG_KEXGSS_GROUPREQ 40 +#define SSH2_MSG_KEXGSS_GROUP 41 + +#define SSH2_MSG_USERAUTH_REQUEST 50 +#define SSH2_MSG_USERAUTH_FAILURE 51 +#define SSH2_MSG_USERAUTH_SUCCESS 52 +#define SSH2_MSG_USERAUTH_BANNER 53 +#define SSH2_MSG_USERAUTH_PK_OK 60 +#define SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 +#define SSH2_MSG_USERAUTH_INFO_REQUEST 60 +#define SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 +#define SSH2_MSG_USERAUTH_INFO_RESPONSE 61 +#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_MSG_GLOBAL_REQUEST 80 +#define SSH2_MSG_REQUEST_SUCCESS 81 +#define SSH2_MSG_REQUEST_FAILURE 82 +#define SSH2_MSG_CHANNEL_OPEN 90 +#define SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 +#define SSH2_MSG_CHANNEL_OPEN_FAILURE 92 +#define SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 +#define SSH2_MSG_CHANNEL_DATA 94 +#define SSH2_MSG_CHANNEL_EXTENDED_DATA 95 +#define SSH2_MSG_CHANNEL_EOF 96 +#define SSH2_MSG_CHANNEL_CLOSE 97 +#define SSH2_MSG_CHANNEL_REQUEST 98 +#define SSH2_MSG_CHANNEL_SUCCESS 99 +#define SSH2_MSG_CHANNEL_FAILURE 100 + +#define SSH2_DISCONNECT_HOST_NOT_ALLOWED_TO_CONNECT 1 +#define SSH2_DISCONNECT_PROTOCOL_ERROR 2 +#define SSH2_DISCONNECT_KEY_EXCHANGE_FAILED 3 +#define SSH2_DISCONNECT_HOST_AUTHENTICATION_FAILED 4 +#define SSH2_DISCONNECT_RESERVED 4 +#define SSH2_DISCONNECT_MAC_ERROR 5 +#define SSH2_DISCONNECT_COMPRESSION_ERROR 6 +#define SSH2_DISCONNECT_SERVICE_NOT_AVAILABLE 7 +#define SSH2_DISCONNECT_PROTOCOL_VERSION_NOT_SUPPORTED 8 +#define SSH2_DISCONNECT_HOST_KEY_NOT_VERIFIABLE 9 +#define SSH2_DISCONNECT_CONNECTION_LOST 10 +#define SSH2_DISCONNECT_BY_APPLICATION 11 +#define SSH2_DISCONNECT_TOO_MANY_CONNECTIONS 12 +#define SSH2_DISCONNECT_AUTH_CANCELLED_BY_USER 13 +#define SSH2_DISCONNECT_NO_MORE_AUTH_METHODS_AVAILABLE 14 +#define SSH2_DISCONNECT_ILLEGAL_USER_NAME 15 + +#define SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED 1 +#define SSH2_OPEN_CONNECT_FAILED 2 +#define SSH2_OPEN_UNKNOWN_CHANNEL_TYPE 3 +#define SSH2_OPEN_RESOURCE_SHORTAGE 4 + +#define SSH2_EXTENDED_DATA_STDERR 1 + +#endif diff --git a/src/libs/libssh-0.12.2/include/libssh/string.h b/src/libs/libssh-0.12.2/include/libssh/string.h new file mode 100644 index 000000000000..35b2ea2e97ee --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/string.h @@ -0,0 +1,49 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef STRING_H_ +#define STRING_H_ +#include "libssh/priv.h" + +#ifdef __cplusplus +extern "C" { +#endif + +/* must be 32 bits number + immediately our data */ +#ifdef _MSC_VER +#pragma pack(1) +#endif +struct ssh_string_struct { + uint32_t size; + unsigned char data[1]; +} +#if defined(__GNUC__) +__attribute__ ((packed)) +#endif +#ifdef _MSC_VER +#pragma pack() +#endif +; + +#ifdef __cplusplus +} +#endif + +#endif /* STRING_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/threads.h b/src/libs/libssh-0.12.2/include/libssh/threads.h new file mode 100644 index 000000000000..47340d17a7a5 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/threads.h @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef THREADS_H_ +#define THREADS_H_ + +#include +#include + +#if HAVE_PTHREAD + +#include +#define SSH_MUTEX pthread_mutex_t + +#if defined(PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP) +#define SSH_MUTEX_STATIC_INIT PTHREAD_ERRORCHECK_MUTEX_INITIALIZER_NP +#else +#define SSH_MUTEX_STATIC_INIT PTHREAD_MUTEX_INITIALIZER +#endif + +#elif (defined _WIN32) || (defined _WIN64) + +#include +#include +#define SSH_MUTEX CRITICAL_SECTION * +#define SSH_MUTEX_STATIC_INIT NULL + +#else + +# define SSH_MUTEX void * +#define SSH_MUTEX_STATIC_INIT NULL + +#endif + +#ifdef __cplusplus +extern "C" { +#endif + +int ssh_threads_init(void); +void ssh_threads_finalize(void); +const char *ssh_threads_get_type(void); + +void ssh_mutex_lock(SSH_MUTEX *mutex); +void ssh_mutex_unlock(SSH_MUTEX *mutex); + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void); +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks); +void crypto_thread_finalize(void); + +#ifdef __cplusplus +} +#endif + +#endif /* THREADS_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/token.h b/src/libs/libssh-0.12.2/include/libssh/token.h new file mode 100644 index 000000000000..550ad792e2c2 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/token.h @@ -0,0 +1,61 @@ +/* + * token.h - Tokens list handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef TOKEN_H_ +#define TOKEN_H_ + +struct ssh_tokens_st { + char *buffer; + char **tokens; +}; + +#ifdef __cplusplus +extern "C" { +#endif + +struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator); + +void ssh_tokens_free(struct ssh_tokens_st *tokens); + +char *ssh_find_matching(const char *available_d, + const char *preferred_d); + +char *ssh_find_all_matching(const char *available_d, + const char *preferred_d); + +char *ssh_remove_duplicates(const char *list); + +char *ssh_append_without_duplicates(const char *list, + const char *appended_list); +char *ssh_prefix_without_duplicates(const char *list, + const char *prefixed_list); +char *ssh_remove_all_matching(const char *list, + const char *remove_list); + +#ifdef __cplusplus +} +#endif + +#endif /* TOKEN_H_ */ diff --git a/src/libs/libssh-0.12.2/include/libssh/wrapper.h b/src/libs/libssh-0.12.2/include/libssh/wrapper.h new file mode 100644 index 000000000000..9214a928e9e4 --- /dev/null +++ b/src/libs/libssh-0.12.2/include/libssh/wrapper.h @@ -0,0 +1,140 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#ifndef WRAPPER_H_ +#define WRAPPER_H_ + +#include + +#include "config.h" +#include "libssh/libssh.h" +#include "libssh/libcrypto.h" +#include "libssh/libgcrypt.h" +#include "libssh/libmbedcrypto.h" + +#ifdef __cplusplus +extern "C" { +#endif + +enum ssh_kdf_digest { + SSH_KDF_SHA1=1, + SSH_KDF_SHA256, + SSH_KDF_SHA384, + SSH_KDF_SHA512 +}; + +enum ssh_hmac_e { + SSH_HMAC_SHA1 = 1, + SSH_HMAC_SHA256, + SSH_HMAC_SHA512, + SSH_HMAC_MD5, + SSH_HMAC_AEAD_POLY1305, + SSH_HMAC_AEAD_GCM, + SSH_HMAC_NONE, +}; + +enum ssh_des_e { + SSH_3DES, + SSH_DES +}; + +struct ssh_hmac_struct { + const char* name; + enum ssh_hmac_e hmac_type; + bool etm; +}; + +enum ssh_crypto_direction_e { + SSH_DIRECTION_IN = 1, + SSH_DIRECTION_OUT = 2, + SSH_DIRECTION_BOTH = 3, +}; + +struct ssh_cipher_struct; +struct ssh_crypto_struct; + +typedef struct ssh_mac_ctx_struct *ssh_mac_ctx; +MD5CTX md5_init(void); +void md5_ctx_free(MD5CTX); +int md5_update(MD5CTX c, const void *data, size_t len); +int md5_final(unsigned char *md, MD5CTX c); +int md5(const unsigned char *digest, size_t len, unsigned char *hash); + +SHACTX sha1_init(void); +void sha1_ctx_free(SHACTX); +int sha1_update(SHACTX c, const void *data, size_t len); +int sha1_final(unsigned char *md,SHACTX c); +int sha1(const unsigned char *digest,size_t len, unsigned char *hash); + +SHA256CTX sha256_init(void); +void sha256_ctx_free(SHA256CTX); +int sha256_update(SHA256CTX c, const void *data, size_t len); +int sha256_final(unsigned char *md,SHA256CTX c); +int sha256(const unsigned char *digest, size_t len, unsigned char *hash); + +SHA384CTX sha384_init(void); +void sha384_ctx_free(SHA384CTX); +int sha384_update(SHA384CTX c, const void *data, size_t len); +int sha384_final(unsigned char *md,SHA384CTX c); +int sha384(const unsigned char *digest, size_t len, unsigned char *hash); + +SHA512CTX sha512_init(void); +void sha512_ctx_free(SHA512CTX); +int sha512_update(SHA512CTX c, const void *data, size_t len); +int sha512_final(unsigned char *md,SHA512CTX c); +int sha512(const unsigned char *digest, size_t len, unsigned char *hash); + +HMACCTX hmac_init(const void *key,size_t len, enum ssh_hmac_e type); +int hmac_update(HMACCTX c, const void *data, size_t len); +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *len); +size_t hmac_digest_len(enum ssh_hmac_e type); + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len); + +int crypt_set_algorithms_client(ssh_session session); +int crypt_set_algorithms_server(ssh_session session); +struct ssh_crypto_struct *crypto_new(void); +void crypto_free(struct ssh_crypto_struct *crypto); + +void ssh_reseed(void); +int ssh_crypto_init(void); +void ssh_crypto_finalize(void); + +void ssh_cipher_clear(struct ssh_cipher_struct *cipher); +struct ssh_hmac_struct *ssh_get_hmactab(void); +struct ssh_cipher_struct *ssh_get_ciphertab(void); +const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm); + +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L +int evp_build_pkey(const char* name, OSSL_PARAM_BLD *param_bld, EVP_PKEY **pkey, int selection); +int evp_dup_dsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote); +int evp_dup_ed25519_pkey(const ssh_key key, ssh_key new_key, int demote); +#endif /* HAVE_LIBCRYPTO && OPENSSL_VERSION_NUMBER */ + +#ifdef __cplusplus +} +#endif + +#endif /* WRAPPER_H_ */ diff --git a/src/libs/libssh-0.12.2/libssh.pc.cmake b/src/libs/libssh-0.12.2/libssh.pc.cmake new file mode 100644 index 000000000000..970db5e2f170 --- /dev/null +++ b/src/libs/libssh-0.12.2/libssh.pc.cmake @@ -0,0 +1,11 @@ +prefix=@CMAKE_INSTALL_PREFIX@ +exec_prefix=${prefix} +libdir=@CMAKE_INSTALL_FULL_LIBDIR@ +includedir=@CMAKE_INSTALL_FULL_INCLUDEDIR@ + +Name: @PROJECT_NAME@ +Description: The SSH Library +Version: @PROJECT_VERSION@ +Libs: -L${libdir} -lssh +Cflags: -I${includedir} +Requires.private: @LIBSSH_PC_REQUIRES_PRIVATE@ diff --git a/src/libs/libssh-0.12.2/src/ABI/current b/src/libs/libssh-0.12.2/src/ABI/current new file mode 100644 index 000000000000..bcd250ed080f --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/current @@ -0,0 +1 @@ +4.12.0 \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.0.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.0.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.1.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.1.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.2.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.2.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.2.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.3.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.3.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.3.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.4.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.4.symbols new file mode 100644 index 000000000000..34949837e47d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.10.4.symbols @@ -0,0 +1,445 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.0.symbols new file mode 100644 index 000000000000..0b1a917ec4e6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.0.symbols @@ -0,0 +1,465 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_get_users_groups_by_id +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_name_id_map_free +sftp_name_id_map_new +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_supported_methods +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_get_sk_application +ssh_key_get_sk_flags +ssh_key_get_sk_user_id +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_ctx_free +ssh_pki_ctx_get_sk_attestation_buffer +ssh_pki_ctx_new +ssh_pki_ctx_options_set +ssh_pki_ctx_set_sk_pin_callback +ssh_pki_ctx_sk_callbacks_option_set +ssh_pki_ctx_sk_callbacks_options_clear +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_generate_key +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_sk_resident_keys_load +ssh_string_burn +ssh_string_cmp +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_from_data +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +sshsig_sign +sshsig_verify +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.1.symbols new file mode 100644 index 000000000000..0b1a917ec4e6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.11.1.symbols @@ -0,0 +1,465 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_get_users_groups_by_id +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_name_id_map_free +sftp_name_id_map_new +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_supported_methods +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_get_sk_application +ssh_key_get_sk_flags +ssh_key_get_sk_user_id +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_ctx_free +ssh_pki_ctx_get_sk_attestation_buffer +ssh_pki_ctx_new +ssh_pki_ctx_options_set +ssh_pki_ctx_set_sk_pin_callback +ssh_pki_ctx_sk_callbacks_option_set +ssh_pki_ctx_sk_callbacks_options_clear +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_generate_key +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_sk_resident_keys_load +ssh_string_burn +ssh_string_cmp +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_from_data +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +sshsig_sign +sshsig_verify +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.12.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.12.0.symbols new file mode 100644 index 000000000000..0625b89be34e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.12.0.symbols @@ -0,0 +1,467 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_aio_begin_read +sftp_aio_begin_write +sftp_aio_free +sftp_aio_wait_read +sftp_aio_wait_write +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_channel_default_data_callback +sftp_channel_default_subsystem_request +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_expand_path +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_get_users_groups_by_id +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_hardlink +sftp_home_directory +sftp_init +sftp_limits +sftp_limits_free +sftp_lsetstat +sftp_lstat +sftp_mkdir +sftp_name_id_map_free +sftp_name_id_map_new +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_state +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_pty_size_modes +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_supported_methods +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_get_sk_application +ssh_key_get_sk_flags +ssh_key_get_sk_user_id +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_ctx_free +ssh_pki_ctx_get_sk_attestation_buffer +ssh_pki_ctx_new +ssh_pki_ctx_options_set +ssh_pki_ctx_set_sk_pin_callback +ssh_pki_ctx_sk_callbacks_option_set +ssh_pki_ctx_sk_callbacks_options_clear +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_base64_format +ssh_pki_export_privkey_file +ssh_pki_export_privkey_file_format +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_generate_key +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_request_no_more_sessions +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_kex_is_gss +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_sk_resident_keys_load +ssh_string_burn +ssh_string_cmp +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_from_data +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_gssapi_keyex +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +sshsig_sign +sshsig_verify +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.0.symbols new file mode 100644 index 000000000000..1848fe000f96 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.0.symbols @@ -0,0 +1,411 @@ +ssh_set_server_callbacks +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_add_channel_callbacks +ssh_remove_channel_callbacks +ssh_threads_set_callbacks +ssh_threads_get_pthread +ssh_threads_get_noop +ssh_set_log_callback +ssh_get_log_callback +ssh_auth_list +ssh_userauth_offer_pubkey +ssh_userauth_pubkey +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_privatekey_file +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_forward_accept +channel_close +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_shell +channel_request_send_signal +channel_request_sftp +channel_request_subsystem +channel_request_x11 +channel_send_eof +channel_select +channel_set_blocking +channel_write +privatekey_free +privatekey_from_file +publickey_free +ssh_publickey_to_file +publickey_from_file +publickey_from_privatekey +publickey_to_string +ssh_try_publickey_from_file +ssh_privatekey_type +ssh_get_pubkey +ssh_message_retrieve +ssh_message_auth_publickey +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char +ssh_blocking_flush +ssh_channel_accept_x11 +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_timeout +ssh_channel_read_nonblocking +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_shell +ssh_channel_request_send_signal +ssh_channel_request_send_break +ssh_channel_request_sftp +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_request_auth_agent +ssh_channel_send_eof +ssh_channel_select +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_write +ssh_channel_write_stderr +ssh_channel_window_size +ssh_basename +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_new +ssh_connector_free +ssh_connector_set_in_channel +ssh_connector_set_out_channel +ssh_connector_set_in_fd +ssh_connector_set_out_fd +ssh_copyright +ssh_disconnect +ssh_dirname +ssh_finalize +ssh_channel_accept_forward +ssh_channel_cancel_forward +ssh_channel_listen_forward +ssh_free +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_hexa +ssh_get_issue_banner +ssh_get_openssh_version +ssh_get_server_publickey +ssh_get_publickey_hash +ssh_get_pubkey_hash +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_get_publickey +ssh_get_random +ssh_get_version +ssh_get_status +ssh_get_poll_flags +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_knownhosts_entry_free +ssh_known_hosts_parse_line +ssh_session_has_known_hosts_entry +ssh_session_export_known_hosts_entry +ssh_session_update_known_hosts +ssh_session_is_known_server +ssh_set_log_level +ssh_get_log_level +ssh_get_log_userdata +ssh_set_log_userdata +_ssh_log +ssh_log +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_reply_success +ssh_message_free +ssh_message_get +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_options_get +ssh_options_get_port +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_key_new +ssh_key_free +ssh_key_type +ssh_key_type_to_char +ssh_key_type_from_name +ssh_key_is_public +ssh_key_is_private +ssh_key_cmp +ssh_pki_generate +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_export_privkey_file +ssh_pki_copy_cert_to_privkey +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hexa +ssh_send_ignore +ssh_send_debug +ssh_gssapi_set_creds +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_service_request +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_blocking +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_silent_disconnect +ssh_set_pcap_file +ssh_userauth_none +ssh_userauth_list +ssh_userauth_try_publickey +ssh_userauth_publickey +ssh_userauth_agent +ssh_userauth_publickey_auto +ssh_userauth_password +ssh_userauth_kbdint +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_setanswer +ssh_userauth_gssapi +ssh_version +ssh_write_knownhost +ssh_dump_knownhost +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_from_char +ssh_string_len +ssh_string_new +ssh_string_get_char +ssh_string_to_char +ssh_string_free_char +ssh_getpass +ssh_event_new +ssh_event_add_fd +ssh_event_add_session +ssh_event_add_connector +ssh_event_dopoll +ssh_event_remove_fd +ssh_event_remove_session +ssh_event_remove_connector +ssh_event_free +ssh_get_clientbanner +ssh_get_serverbanner +ssh_get_kex_algo +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_hmac_in +ssh_get_hmac_out +ssh_buffer_new +ssh_buffer_free +ssh_buffer_reinit +ssh_buffer_add_data +ssh_buffer_get_data +ssh_buffer_get +ssh_buffer_get_len +ssh_bind_new +ssh_bind_options_set +ssh_bind_listen +ssh_bind_set_callbacks +ssh_bind_set_blocking +ssh_bind_get_fd +ssh_bind_set_fd +ssh_bind_fd_toaccept +ssh_bind_accept +ssh_bind_accept_fd +ssh_gssapi_get_creds +ssh_handle_key_exchange +ssh_server_init_kex +ssh_bind_free +ssh_set_auth_methods +ssh_message_reply_default +ssh_message_auth_user +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_kbdint_is_response +ssh_message_auth_publickey_state +ssh_message_auth_reply_success +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_set_methods +ssh_message_auth_interactive_request +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_global_request_reply_success +ssh_set_message_callback +ssh_execute_message_callbacks +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_channel +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_command +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_single_connection +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_screen_number +ssh_message_global_request_address +ssh_message_global_request_port +ssh_channel_open_reverse_forward +ssh_channel_request_send_exit_status +ssh_channel_request_send_exit_signal +ssh_send_keepalive +ssh_accept +channel_write_stderr +sftp_new +sftp_new_channel +sftp_free +sftp_init +sftp_get_error +sftp_extensions_get_count +sftp_extensions_get_name +sftp_extensions_get_data +sftp_extension_supported +sftp_opendir +sftp_readdir +sftp_dir_eof +sftp_stat +sftp_lstat +sftp_fstat +sftp_attributes_free +sftp_closedir +sftp_close +sftp_open +sftp_file_set_nonblocking +sftp_file_set_blocking +sftp_read +sftp_async_read_begin +sftp_async_read +sftp_write +sftp_seek +sftp_seek64 +sftp_tell +sftp_tell64 +sftp_rewind +sftp_unlink +sftp_rmdir +sftp_mkdir +sftp_rename +sftp_setstat +sftp_chown +sftp_chmod +sftp_utimes +sftp_symlink +sftp_readlink +sftp_statvfs +sftp_fstatvfs +sftp_statvfs_free +sftp_fsync +sftp_canonicalize_path +sftp_server_version +sftp_server_new +sftp_server_init +sftp_get_client_message +sftp_client_message_free +sftp_client_message_get_type +sftp_client_message_get_filename +sftp_client_message_set_filename +sftp_client_message_get_data +sftp_client_message_get_flags +sftp_send_client_message +sftp_reply_name +sftp_reply_handle +sftp_handle_alloc +sftp_reply_attr +sftp_handle +sftp_reply_status +sftp_reply_names_add +sftp_reply_names +sftp_reply_data +sftp_handle_remove \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.5.1.symbols new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.6.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.6.0.symbols new file mode 100644 index 000000000000..1fd60955ca21 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.6.0.symbols @@ -0,0 +1,412 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.0.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.0.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.1.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.1.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.2.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.2.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.2.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.3.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.3.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.3.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.4.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.4.symbols new file mode 100644 index 000000000000..0e67b4edb0b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.7.4.symbols @@ -0,0 +1,415 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.0.symbols new file mode 100644 index 000000000000..6ef89434f3ef --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.0.symbols @@ -0,0 +1,419 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.1.symbols new file mode 100644 index 000000000000..dce4addd7442 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.8.1.symbols @@ -0,0 +1,421 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_try_publickey +ssh_version +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.0.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.0.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.0.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.1.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.1.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.1.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.2.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.2.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.2.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.3.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.3.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.3.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.4.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.4.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.4.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.5.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.5.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.5.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.6.symbols b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.6.symbols new file mode 100644 index 000000000000..a26e2c5e53e3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ABI/libssh-4.9.6.symbols @@ -0,0 +1,427 @@ +_ssh_log +buffer_free +buffer_get +buffer_get_len +buffer_new +channel_accept_x11 +channel_change_pty_size +channel_close +channel_forward_accept +channel_forward_cancel +channel_forward_listen +channel_free +channel_get_exit_status +channel_get_session +channel_is_closed +channel_is_eof +channel_is_open +channel_new +channel_open_forward +channel_open_session +channel_poll +channel_read +channel_read_buffer +channel_read_nonblocking +channel_request_env +channel_request_exec +channel_request_pty +channel_request_pty_size +channel_request_send_signal +channel_request_sftp +channel_request_shell +channel_request_subsystem +channel_request_x11 +channel_select +channel_send_eof +channel_set_blocking +channel_write +channel_write_stderr +privatekey_free +privatekey_from_file +publickey_free +publickey_from_file +publickey_from_privatekey +publickey_to_string +sftp_async_read +sftp_async_read_begin +sftp_attributes_free +sftp_canonicalize_path +sftp_chmod +sftp_chown +sftp_client_message_free +sftp_client_message_get_data +sftp_client_message_get_filename +sftp_client_message_get_flags +sftp_client_message_get_submessage +sftp_client_message_get_type +sftp_client_message_set_filename +sftp_close +sftp_closedir +sftp_dir_eof +sftp_extension_supported +sftp_extensions_get_count +sftp_extensions_get_data +sftp_extensions_get_name +sftp_file_set_blocking +sftp_file_set_nonblocking +sftp_free +sftp_fstat +sftp_fstatvfs +sftp_fsync +sftp_get_client_message +sftp_get_error +sftp_handle +sftp_handle_alloc +sftp_handle_remove +sftp_init +sftp_lstat +sftp_mkdir +sftp_new +sftp_new_channel +sftp_open +sftp_opendir +sftp_read +sftp_readdir +sftp_readlink +sftp_rename +sftp_reply_attr +sftp_reply_data +sftp_reply_handle +sftp_reply_name +sftp_reply_names +sftp_reply_names_add +sftp_reply_status +sftp_rewind +sftp_rmdir +sftp_seek +sftp_seek64 +sftp_send_client_message +sftp_server_free +sftp_server_init +sftp_server_new +sftp_server_version +sftp_setstat +sftp_stat +sftp_statvfs +sftp_statvfs_free +sftp_symlink +sftp_tell +sftp_tell64 +sftp_unlink +sftp_utimes +sftp_write +ssh_accept +ssh_add_channel_callbacks +ssh_auth_list +ssh_basename +ssh_bind_accept +ssh_bind_accept_fd +ssh_bind_fd_toaccept +ssh_bind_free +ssh_bind_get_fd +ssh_bind_listen +ssh_bind_new +ssh_bind_options_parse_config +ssh_bind_options_set +ssh_bind_set_blocking +ssh_bind_set_callbacks +ssh_bind_set_fd +ssh_blocking_flush +ssh_buffer_add_data +ssh_buffer_free +ssh_buffer_get +ssh_buffer_get_data +ssh_buffer_get_len +ssh_buffer_new +ssh_buffer_reinit +ssh_channel_accept_forward +ssh_channel_accept_x11 +ssh_channel_cancel_forward +ssh_channel_change_pty_size +ssh_channel_close +ssh_channel_free +ssh_channel_get_exit_status +ssh_channel_get_session +ssh_channel_is_closed +ssh_channel_is_eof +ssh_channel_is_open +ssh_channel_listen_forward +ssh_channel_new +ssh_channel_open_auth_agent +ssh_channel_open_forward +ssh_channel_open_forward_port +ssh_channel_open_forward_unix +ssh_channel_open_reverse_forward +ssh_channel_open_session +ssh_channel_open_x11 +ssh_channel_poll +ssh_channel_poll_timeout +ssh_channel_read +ssh_channel_read_nonblocking +ssh_channel_read_timeout +ssh_channel_request_auth_agent +ssh_channel_request_env +ssh_channel_request_exec +ssh_channel_request_pty +ssh_channel_request_pty_size +ssh_channel_request_send_break +ssh_channel_request_send_exit_signal +ssh_channel_request_send_exit_status +ssh_channel_request_send_signal +ssh_channel_request_sftp +ssh_channel_request_shell +ssh_channel_request_subsystem +ssh_channel_request_x11 +ssh_channel_select +ssh_channel_send_eof +ssh_channel_set_blocking +ssh_channel_set_counter +ssh_channel_window_size +ssh_channel_write +ssh_channel_write_stderr +ssh_clean_pubkey_hash +ssh_connect +ssh_connector_free +ssh_connector_new +ssh_connector_set_in_channel +ssh_connector_set_in_fd +ssh_connector_set_out_channel +ssh_connector_set_out_fd +ssh_copyright +ssh_dirname +ssh_disconnect +ssh_dump_knownhost +ssh_event_add_connector +ssh_event_add_fd +ssh_event_add_session +ssh_event_dopoll +ssh_event_free +ssh_event_new +ssh_event_remove_connector +ssh_event_remove_fd +ssh_event_remove_session +ssh_execute_message_callbacks +ssh_finalize +ssh_forward_accept +ssh_forward_cancel +ssh_forward_listen +ssh_free +ssh_get_cipher_in +ssh_get_cipher_out +ssh_get_clientbanner +ssh_get_disconnect_message +ssh_get_error +ssh_get_error_code +ssh_get_fd +ssh_get_fingerprint_hash +ssh_get_hexa +ssh_get_hmac_in +ssh_get_hmac_out +ssh_get_issue_banner +ssh_get_kex_algo +ssh_get_log_callback +ssh_get_log_level +ssh_get_log_userdata +ssh_get_openssh_version +ssh_get_poll_flags +ssh_get_pubkey +ssh_get_pubkey_hash +ssh_get_publickey +ssh_get_publickey_hash +ssh_get_random +ssh_get_server_publickey +ssh_get_serverbanner +ssh_get_status +ssh_get_version +ssh_getpass +ssh_gssapi_get_creds +ssh_gssapi_set_creds +ssh_handle_key_exchange +ssh_init +ssh_is_blocking +ssh_is_connected +ssh_is_server_known +ssh_key_cmp +ssh_key_dup +ssh_key_free +ssh_key_is_private +ssh_key_is_public +ssh_key_new +ssh_key_type +ssh_key_type_from_name +ssh_key_type_to_char +ssh_known_hosts_parse_line +ssh_knownhosts_entry_free +ssh_log +ssh_message_auth_interactive_request +ssh_message_auth_kbdint_is_response +ssh_message_auth_password +ssh_message_auth_pubkey +ssh_message_auth_publickey +ssh_message_auth_publickey_state +ssh_message_auth_reply_pk_ok +ssh_message_auth_reply_pk_ok_simple +ssh_message_auth_reply_success +ssh_message_auth_set_methods +ssh_message_auth_user +ssh_message_channel_request_channel +ssh_message_channel_request_command +ssh_message_channel_request_env_name +ssh_message_channel_request_env_value +ssh_message_channel_request_open_destination +ssh_message_channel_request_open_destination_port +ssh_message_channel_request_open_originator +ssh_message_channel_request_open_originator_port +ssh_message_channel_request_open_reply_accept +ssh_message_channel_request_open_reply_accept_channel +ssh_message_channel_request_pty_height +ssh_message_channel_request_pty_pxheight +ssh_message_channel_request_pty_pxwidth +ssh_message_channel_request_pty_term +ssh_message_channel_request_pty_width +ssh_message_channel_request_reply_success +ssh_message_channel_request_subsystem +ssh_message_channel_request_x11_auth_cookie +ssh_message_channel_request_x11_auth_protocol +ssh_message_channel_request_x11_screen_number +ssh_message_channel_request_x11_single_connection +ssh_message_free +ssh_message_get +ssh_message_global_request_address +ssh_message_global_request_port +ssh_message_global_request_reply_success +ssh_message_reply_default +ssh_message_retrieve +ssh_message_service_reply_success +ssh_message_service_service +ssh_message_subtype +ssh_message_type +ssh_mkdir +ssh_new +ssh_options_copy +ssh_options_get +ssh_options_get_port +ssh_options_getopt +ssh_options_parse_config +ssh_options_set +ssh_pcap_file_close +ssh_pcap_file_free +ssh_pcap_file_new +ssh_pcap_file_open +ssh_pki_copy_cert_to_privkey +ssh_pki_export_privkey_base64 +ssh_pki_export_privkey_file +ssh_pki_export_privkey_to_pubkey +ssh_pki_export_pubkey_base64 +ssh_pki_export_pubkey_file +ssh_pki_generate +ssh_pki_import_cert_base64 +ssh_pki_import_cert_file +ssh_pki_import_privkey_base64 +ssh_pki_import_privkey_file +ssh_pki_import_pubkey_base64 +ssh_pki_import_pubkey_file +ssh_pki_key_ecdsa_name +ssh_print_hash +ssh_print_hexa +ssh_privatekey_type +ssh_publickey_to_file +ssh_remove_channel_callbacks +ssh_scp_accept_request +ssh_scp_close +ssh_scp_deny_request +ssh_scp_free +ssh_scp_init +ssh_scp_leave_directory +ssh_scp_new +ssh_scp_pull_request +ssh_scp_push_directory +ssh_scp_push_file +ssh_scp_push_file64 +ssh_scp_read +ssh_scp_request_get_filename +ssh_scp_request_get_permissions +ssh_scp_request_get_size +ssh_scp_request_get_size64 +ssh_scp_request_get_warning +ssh_scp_write +ssh_select +ssh_send_debug +ssh_send_ignore +ssh_send_issue_banner +ssh_send_keepalive +ssh_server_init_kex +ssh_service_request +ssh_session_export_known_hosts_entry +ssh_session_get_known_hosts_entry +ssh_session_has_known_hosts_entry +ssh_session_is_known_server +ssh_session_set_disconnect_message +ssh_session_update_known_hosts +ssh_set_agent_channel +ssh_set_agent_socket +ssh_set_auth_methods +ssh_set_blocking +ssh_set_callbacks +ssh_set_channel_callbacks +ssh_set_counters +ssh_set_fd_except +ssh_set_fd_toread +ssh_set_fd_towrite +ssh_set_log_callback +ssh_set_log_level +ssh_set_log_userdata +ssh_set_message_callback +ssh_set_pcap_file +ssh_set_server_callbacks +ssh_silent_disconnect +ssh_string_burn +ssh_string_copy +ssh_string_data +ssh_string_fill +ssh_string_free +ssh_string_free_char +ssh_string_from_char +ssh_string_get_char +ssh_string_len +ssh_string_new +ssh_string_to_char +ssh_threads_get_default +ssh_threads_get_noop +ssh_threads_get_pthread +ssh_threads_set_callbacks +ssh_try_publickey_from_file +ssh_userauth_agent +ssh_userauth_agent_pubkey +ssh_userauth_autopubkey +ssh_userauth_gssapi +ssh_userauth_kbdint +ssh_userauth_kbdint_getanswer +ssh_userauth_kbdint_getinstruction +ssh_userauth_kbdint_getname +ssh_userauth_kbdint_getnanswers +ssh_userauth_kbdint_getnprompts +ssh_userauth_kbdint_getprompt +ssh_userauth_kbdint_setanswer +ssh_userauth_list +ssh_userauth_none +ssh_userauth_offer_pubkey +ssh_userauth_password +ssh_userauth_privatekey_file +ssh_userauth_pubkey +ssh_userauth_publickey +ssh_userauth_publickey_auto +ssh_userauth_publickey_auto_get_current_identity +ssh_userauth_try_publickey +ssh_version +ssh_vlog +ssh_write_knownhost +string_burn +string_copy +string_data +string_fill +string_free +string_from_char +string_len +string_new +string_to_char \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/src/CMakeLists.txt b/src/libs/libssh-0.12.2/src/CMakeLists.txt new file mode 100644 index 000000000000..a46f585bebc4 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/CMakeLists.txt @@ -0,0 +1,492 @@ +set(LIBSSH_PUBLIC_INCLUDE_DIRS ${libssh_SOURCE_DIR}/include) + +set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${libssh_BINARY_DIR}/include + ${libssh_BINARY_DIR} +) + +set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_REQUIRED_LIBRARIES} +) + +if (TARGET OpenSSL::Crypto) + list(APPEND LIBSSH_LINK_LIBRARIES OpenSSL::Crypto) +endif () + +if (TARGET MbedTLS::mbedcrypto) + list(APPEND LIBSSH_LINK_LIBRARIES MbedTLS::mbedcrypto) +endif () + +if (TARGET libgcrypt::libgcrypt) + list(APPEND LIBSSH_LINK_LIBRARIES ${GCRYPT_LIBRARIES}) +endif () + +if (WITH_ZLIB) + list(APPEND LIBSSH_LINK_LIBRARIES ZLIB::ZLIB) +endif (WITH_ZLIB) + +if (WITH_GSSAPI AND GSSAPI_FOUND) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${GSSAPI_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${GSSAPI_LIBRARIES} + ) +endif (WITH_GSSAPI AND GSSAPI_FOUND) + +if (WITH_NACL AND NACL_FOUND) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${NACL_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${NACL_LIBRARY} + ) +endif (WITH_NACL AND NACL_FOUND) + +if (MINGW AND Threads_FOUND) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + Threads::Threads + ) +endif() + +if (HAVE_LIBFIDO2) + set(LIBSSH_PRIVATE_INCLUDE_DIRS + ${LIBSSH_PRIVATE_INCLUDE_DIRS} + ${LIBFIDO2_INCLUDE_DIR} + ) + + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + ${LIBFIDO2_LIBRARIES} + ) +endif (HAVE_LIBFIDO2) + +# The ws2_32 needs to be last for mingw to build +# https://gitlab.com/libssh/libssh-mirror/-/issues/84 +if (WIN32) + set(LIBSSH_LINK_LIBRARIES + ${LIBSSH_LINK_LIBRARIES} + iphlpapi + ws2_32 + ) +endif (WIN32) + +if (BUILD_STATIC_LIB) + set(LIBSSH_STATIC_LIBRARY + ssh_static + CACHE INTERNAL "libssh static library" + ) +endif (BUILD_STATIC_LIB) + +set(libssh_SRCS + agent.c + auth.c + base64.c + bignum.c + buffer.c + callbacks.c + channels.c + client.c + config.c + connect.c + connector.c + crypto_common.c + curve25519.c + sntrup761.c + dh.c + ecdh.c + error.c + getpass.c + gzip.c + hybrid_mlkem.c + init.c + kdf.c + kex.c + known_hosts.c + knownhosts.c + legacy.c + log.c + match.c + messages.c + misc.c + mlkem.c + options.c + packet.c + packet_cb.c + packet_crypt.c + pcap.c + pki.c + pki_context.c + pki_container_openssh.c + poll.c + session.c + scp.c + socket.c + string.c + threads.c + ttyopts.c + wrapper.c + external/bcrypt_pbkdf.c + external/blowfish.c + config_parser.c + token.c + pki_ed25519_common.c +) + +if (DEFAULT_C_NO_DEPRECATION_FLAGS) + set_source_files_properties(known_hosts.c + PROPERTIES + COMPILE_FLAGS ${DEFAULT_C_NO_DEPRECATION_FLAGS}) +endif() + +if (CMAKE_USE_PTHREADS_INIT) + set(libssh_SRCS + ${libssh_SRCS} + threads/noop.c + threads/pthread.c + ) +elseif (CMAKE_USE_WIN32_THREADS_INIT) + set(libssh_SRCS + ${libssh_SRCS} + threads/noop.c + threads/winlocks.c + ) +else() + set(libssh_SRCS + ${libssh_SRCS} + threads/noop.c + ) +endif() + +if (WITH_GCRYPT) + set(libssh_SRCS + ${libssh_SRCS} + threads/libgcrypt.c + libgcrypt.c + gcrypt_missing.c + pki_gcrypt.c + ecdh_gcrypt.c + getrandom_gcrypt.c + md_gcrypt.c + dh_key.c + pki_ed25519.c + external/ed25519.c + external/fe25519.c + external/ge25519.c + external/sc25519.c + ) + if (NOT HAVE_GCRYPT_CHACHA_POLY) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif (NOT HAVE_GCRYPT_CHACHA_POLY) + + if (HAVE_GCRYPT_CURVE25519) + set(libssh_SRCS + ${libssh_SRCS} + curve25519_gcrypt.c + ) + endif(HAVE_GCRYPT_CURVE25519) + + if (HAVE_GCRYPT_MLKEM) + set(libssh_SRCS + ${libssh_SRCS} + mlkem_gcrypt.c + ) + endif (HAVE_GCRYPT_MLKEM) +elseif (WITH_MBEDTLS) + set(libssh_SRCS + ${libssh_SRCS} + threads/mbedtls.c + libmbedcrypto.c + mbedcrypto_missing.c + pki_mbedcrypto.c + ecdh_mbedcrypto.c + getrandom_mbedcrypto.c + md_mbedcrypto.c + dh_key.c + pki_ed25519.c + external/ed25519.c + external/fe25519.c + external/ge25519.c + external/sc25519.c + external/sntrup761.c + ) + if (NOT (HAVE_MBEDTLS_CHACHA20_H AND HAVE_MBEDTLS_POLY1305_H)) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif() + if (HAVE_MBEDTLS_CURVE25519) + set(libssh_SRCS + ${libssh_SRCS} + curve25519_mbedcrypto.c + ) + endif(HAVE_MBEDTLS_CURVE25519) +else (WITH_GCRYPT) + set(libssh_SRCS + ${libssh_SRCS} + threads/libcrypto.c + pki_crypto.c + ecdh_crypto.c + curve25519_crypto.c + getrandom_crypto.c + md_crypto.c + libcrypto.c + dh_crypto.c + external/sntrup761.c + ) + if (NOT HAVE_OPENSSL_EVP_CHACHA20) + set(libssh_SRCS + ${libssh_SRCS} + external/chacha.c + external/poly1305.c + chachapoly.c + ) + endif (NOT HAVE_OPENSSL_EVP_CHACHA20) + if (HAVE_OPENSSL_MLKEM) + set(libssh_SRCS + ${libssh_SRCS} + mlkem_crypto.c + ) + endif (HAVE_OPENSSL_MLKEM) +endif (WITH_GCRYPT) + +if (WITH_SFTP) + set(libssh_SRCS + ${libssh_SRCS} + sftp.c + sftp_common.c + sftp_aio.c + ) + + if (WITH_SERVER) + set(libssh_SRCS + ${libssh_SRCS} + sftpserver.c + ) + endif (WITH_SERVER) +endif (WITH_SFTP) + +if (WITH_SERVER) + set(libssh_SRCS + ${libssh_SRCS} + server.c + bind.c + bind_config.c + ) +endif (WITH_SERVER) + +if (WITH_GEX) + set(libssh_SRCS + ${libssh_SRCS} + dh-gex.c + ) +endif (WITH_GEX) + +if (WITH_GSSAPI AND GSSAPI_FOUND) + set(libssh_SRCS + ${libssh_SRCS} + gssapi.c + kex-gss.c + ) +endif (WITH_GSSAPI AND GSSAPI_FOUND) + +if (NOT WITH_NACL) + if (NOT (HAVE_LIBCRYPTO OR HAVE_MBEDTLS_CURVE25519 OR HAVE_GCRYPT_CURVE25519)) + set(libssh_SRCS + ${libssh_SRCS} + curve25519_fallback.c + external/curve25519_ref.c + ) + endif() +endif (NOT WITH_NACL) + +if (NOT HAVE_MLKEM1024) + set(libssh_SRCS + ${libssh_SRCS} + mlkem_native.c + external/libcrux_mlkem768_sha3.c + ) + if (WITH_WERROR_DECLARATION_AFTER_STATEMENT_FLAG) + set_source_files_properties(external/libcrux_mlkem768_sha3.c + PROPERTIES + COMPILE_FLAGS -Wno-error=declaration-after-statement) + endif() +endif() + +if (WITH_FIDO2) + set(libssh_SRCS + ${libssh_SRCS} + sk_common.c + pki_sk.c + ) + + if (HAVE_LIBFIDO2) + set(libssh_SRCS + ${libssh_SRCS} + sk_usbhid.c + ) + endif (HAVE_LIBFIDO2) +endif (WITH_FIDO2) + +# Set the path to the default map file +set(MAP_PATH "${CMAKE_CURRENT_SOURCE_DIR}/${PROJECT_NAME}.map") + +if (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT AND ABIMAP_FOUND) + # Get the list of header files + get_file_list(dev_header_list + DIRECTORIES "${LIBSSH_PUBLIC_INCLUDE_DIRS}/libssh" + FILES_PATTERNS "*.h") + + # Extract the symbols marked as "LIBSSH_API" from the header files + extract_symbols("${PROJECT_NAME}_dev.symbols" + HEADERS_LIST dev_header_list + FILTER_PATTERN "LIBSSH_API") + + if (WITH_ABI_BREAK) + set(ALLOW_ABI_BREAK "BREAK_ABI") + endif() + + # Generate the symbol version map file + generate_map_file("${PROJECT_NAME}_dev.map" + SYMBOLS "${PROJECT_NAME}_dev.symbols" + RELEASE_NAME_VERSION ${PROJECT_NAME}_AFTER_${LIBRARY_VERSION} + CURRENT_MAP ${MAP_PATH} + ${ALLOW_ABI_BREAK}) + + set(libssh_SRCS + ${libssh_SRCS} + ${PROJECT_NAME}_dev.map + ) +endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT AND ABIMAP_FOUND) + +# This gets built as a static library, if -DBUILD_SHARED_LIBS=OFF is passed to +# cmake. +add_library(ssh ${libssh_SRCS}) +target_compile_options(ssh + PRIVATE + ${DEFAULT_C_COMPILE_FLAGS}) +if (CYGWIN) + target_compile_definitions(ssh PRIVATE _GNU_SOURCE) +endif () +target_include_directories(ssh + PUBLIC + $ + $ + $ + PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) + +target_link_libraries(ssh + PRIVATE ${LIBSSH_LINK_LIBRARIES}) + +if (WIN32 AND NOT BUILD_SHARED_LIBS) + target_compile_definitions(ssh PUBLIC "LIBSSH_STATIC") +endif () + +add_library(ssh::ssh ALIAS ssh) + +if (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT) + if (ABIMAP_FOUND) + # Change path to devel map file + set(MAP_PATH "${CMAKE_CURRENT_BINARY_DIR}/${PROJECT_NAME}_dev.map") + endif (ABIMAP_FOUND) + + target_link_libraries(ssh PRIVATE "-Wl,--version-script,\"${MAP_PATH}\"") +endif (WITH_SYMBOL_VERSIONING AND HAVE_LD_VERSION_SCRIPT) + +set_target_properties(ssh + PROPERTIES + C_STANDARD + 99 + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + DEFINE_SYMBOL + LIBSSH_EXPORTS +) + +if (WITH_VISIBILITY_HIDDEN) + set_target_properties(ssh PROPERTIES C_VISIBILITY_PRESET hidden) +endif (WITH_VISIBILITY_HIDDEN) + +if (MINGW) + target_link_libraries(ssh PRIVATE "-Wl,--enable-stdcall-fixup") + target_compile_definitions(ssh PRIVATE "_POSIX_SOURCE") +endif () +if (WITH_COVERAGE) + include(CodeCoverage) + append_coverage_compiler_flags_to_target(ssh) +endif (WITH_COVERAGE) + + +install(TARGETS ssh + EXPORT libssh-config + RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR} + LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR} + ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR} + COMPONENT libraries) + +install(EXPORT libssh-config + DESTINATION ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME}) + +if (BUILD_STATIC_LIB) + add_library(ssh-static STATIC ${libssh_SRCS}) + target_compile_options(ssh-static + PRIVATE + ${DEFAULT_C_COMPILE_FLAGS}) + if (CYGWIN) + target_compile_definitions(ssh-static PRIVATE _GNU_SOURCE) + endif () + + target_include_directories(ssh-static + PUBLIC + $ + $ + $ + PRIVATE ${LIBSSH_PRIVATE_INCLUDE_DIRS}) + target_link_libraries(ssh-static + PUBLIC ${LIBSSH_LINK_LIBRARIES}) + add_library(ssh::static ALIAS ssh-static) + + if (MSVC) + set(OUTPUT_SUFFIX static) + else (MSVC) + set(OUTPUT_SUFFIX ) + endif (MSVC) + set_target_properties( + ssh-static + PROPERTIES + VERSION + ${LIBRARY_VERSION} + SOVERSION + ${LIBRARY_SOVERSION} + OUTPUT_NAME + ssh + ARCHIVE_OUTPUT_DIRECTORY + ${CMAKE_CURRENT_BINARY_DIR}/${OUTPUT_SUFFIX} + ) + + if (WIN32) + target_compile_definitions(ssh-static PUBLIC "LIBSSH_STATIC") + endif (WIN32) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(ssh-static) + endif (WITH_COVERAGE) +endif (BUILD_STATIC_LIB) + +message(STATUS "Threads_FOUND=${Threads_FOUND}") diff --git a/src/libs/libssh-0.12.2/src/agent.c b/src/libs/libssh-0.12.2/src/agent.c new file mode 100644 index 000000000000..bb1669c34557 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/agent.c @@ -0,0 +1,637 @@ +/* + * agent.c - ssh agent functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2013 by Andreas Schneider + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +/* This file is based on authfd.c from OpenSSH */ + +/* + * How does the ssh-agent work? + * + * a) client sends a request to get a list of all keys + * the agent returns the count and all public keys + * b) iterate over them to check if the server likes one + * c) the client sends a sign request to the agent + * type, pubkey as blob, data to sign, flags + * the agent returns the signed data + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#ifndef _WIN32 +#include +#include +#include +#else +#include +#include +#endif + +#include "libssh/agent.h" +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/poll.h" +#include "libssh/pki.h" +#include "libssh/bytearray.h" + +/* macro to check for "agent failure" message */ +#define agent_failed(x) \ + (((x) == SSH_AGENT_FAILURE) || ((x) == SSH_COM_AGENT2_FAILURE) || \ + ((x) == SSH2_AGENT_FAILURE)) + +static uint32_t +atomicio(struct ssh_agent_struct *agent, void *buf, uint32_t n, int do_read) +{ + char *b = buf; + uint32_t pos = 0; + ssize_t res; + ssh_pollfd_t pfd; + ssh_channel channel = agent->channel; + socket_t fd; + + /* Using a socket ? */ + if (channel == NULL) { + fd = ssh_socket_get_fd(agent->sock); + pfd.fd = fd; + pfd.events = do_read ? POLLIN : POLLOUT; + + while (n > pos) { + if (do_read) { + res = recv(fd, b + pos, n - pos, 0); + } else { + res = send(fd, b + pos, n - pos, 0); + } + switch (res) { + case -1: + if (errno == EINTR) { + continue; + } +#ifdef EWOULDBLOCK + if (errno == EAGAIN || errno == EWOULDBLOCK) { +#else + if (errno == EAGAIN) { +#endif + (void)ssh_poll(&pfd, 1, -1); + continue; + } + return 0; + case 0: + /* read returns 0 on end-of-file */ + errno = do_read ? 0 : EPIPE; + return pos; + default: + pos += (uint32_t)res; + } + } + return pos; + } else { + /* using an SSH channel */ + while (n > pos) { + if (do_read) { + res = ssh_channel_read(channel, b + pos, n - pos, 0); + } else { + res = ssh_channel_write(channel, b + pos, n - pos); + } + if (res == SSH_AGAIN) { + continue; + } + if (res == SSH_ERROR) { + return 0; + } + pos += (uint32_t)res; + } + return pos; + } +} + +ssh_agent ssh_agent_new(struct ssh_session_struct *session) +{ + ssh_agent agent = NULL; + + agent = calloc(1, sizeof(struct ssh_agent_struct)); + if (agent == NULL) { + return NULL; + } + + agent->count = 0; + agent->sock = ssh_socket_new(session); + if (agent->sock == NULL) { + SAFE_FREE(agent); + return NULL; + } + agent->channel = NULL; + return agent; +} + +static void agent_set_channel(struct ssh_agent_struct *agent, + ssh_channel channel) +{ + agent->channel = channel; +} + +/** + * @addtogroup libssh_auth + * + * @{ + */ + +/** @brief sets the SSH agent channel. + * The SSH agent channel will be used to authenticate this client using + * an agent through a channel, from another session. The most likely use + * is to implement SSH Agent forwarding into a SSH proxy. + * + * @param session the session + * + * @param[in] channel a SSH channel from another session. + * + * @returns SSH_OK in case of success + * SSH_ERROR in case of an error + */ +int ssh_set_agent_channel(ssh_session session, ssh_channel channel) +{ + if (!session) { + return SSH_ERROR; + } + if (!session->agent) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Session has no active agent"); + return SSH_ERROR; + } + agent_set_channel(session->agent, channel); + return SSH_OK; +} + +/** @brief sets the SSH agent socket. + * The SSH agent will be used to authenticate this client using + * the given socket to communicate with the ssh-agent. The caller + * is responsible for connecting to the socket prior to calling + * this function. + * @returns SSH_OK in case of success + * SSH_ERROR in case of an error + */ +int ssh_set_agent_socket(ssh_session session, socket_t fd) +{ + if (!session) { + return SSH_ERROR; + } + if (!session->agent) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Session has no active agent"); + return SSH_ERROR; + } + + return ssh_socket_set_fd(session->agent->sock, fd); +} + +/** + * @} + */ + +void ssh_agent_close(struct ssh_agent_struct *agent) +{ + if (agent == NULL) { + return; + } + + ssh_socket_close(agent->sock); +} + +void ssh_agent_free(ssh_agent agent) +{ + if (agent) { + if (agent->ident) { + SSH_BUFFER_FREE(agent->ident); + } + if (agent->sock) { + ssh_agent_close(agent); + ssh_socket_free(agent->sock); + } + SAFE_FREE(agent); + } +} + +static int agent_connect(ssh_session session) +{ + const char *auth_sock = NULL; + + if (session == NULL || session->agent == NULL) { + return -1; + } + + if (session->agent->channel != NULL) { + return 0; + } + + auth_sock = session->opts.agent_socket ? session->opts.agent_socket + : getenv("SSH_AUTH_SOCK"); + + if (auth_sock && *auth_sock) { + if (ssh_socket_unix(session->agent->sock, auth_sock) < 0) { + return -1; + } + return 0; + } + + return -1; +} + +#if 0 +static int agent_decode_reply(struct ssh_session_struct *session, int type) { + switch (type) { + case SSH_AGENT_FAILURE: + case SSH2_AGENT_FAILURE: + case SSH_COM_AGENT2_FAILURE: + ssh_log(session, SSH_LOG_RARE, "SSH_AGENT_FAILURE"); + return 0; + case SSH_AGENT_SUCCESS: + return 1; + default: + ssh_set_error(session, SSH_FATAL, + "Bad response from authentication agent: %d", type); + break; + } + + return -1; +} +#endif + +static int agent_talk(struct ssh_session_struct *session, + struct ssh_buffer_struct *request, + struct ssh_buffer_struct *reply) +{ + uint32_t len = 0; + uint8_t tmpbuf[4]; + uint8_t *payload = tmpbuf; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + len = ssh_buffer_get_len(request); + SSH_LOG(SSH_LOG_TRACE, "Request length: %" PRIu32, len); + PUSH_BE_U32(payload, 0, len); + + /* send length and then the request packet */ + if (atomicio(session->agent, payload, 4, 0) == 4) { + if (atomicio(session->agent, ssh_buffer_get(request), len, 0) != len) { + SSH_LOG(SSH_LOG_TRACE, + "atomicio sending request failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "atomicio sending request length failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + /* wait for response, read the length of the response packet */ + if (atomicio(session->agent, payload, 4, 1) != 4) { + SSH_LOG(SSH_LOG_TRACE, + "atomicio read response length failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + len = PULL_BE_U32(payload, 0); + if (len > 256 * 1024) { + ssh_set_error(session, + SSH_FATAL, + "Authentication response too long: %" PRIu32, + len); + return -1; + } + SSH_LOG(SSH_LOG_TRACE, "Response length: %" PRIu32, len); + + payload = ssh_buffer_allocate(reply, len); + if (payload == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Not enough space"); + return -1; + } + + if (atomicio(session->agent, payload, len, 1) != len) { + SSH_LOG(SSH_LOG_DEBUG, + "Error reading response from authentication socket."); + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(reply, len); + return -1; + } + + return 0; +} + +uint32_t ssh_agent_get_ident_count(struct ssh_session_struct *session) +{ + ssh_buffer request = NULL; + ssh_buffer reply = NULL; + unsigned int type = 0; + uint32_t count = 0; + uint32_t rc; + + /* send message to the agent requesting the list of identities */ + request = ssh_buffer_new(); + if (request == NULL) { + ssh_set_error_oom(session); + return 0; + } + if (ssh_buffer_add_u8(request, SSH2_AGENTC_REQUEST_IDENTITIES) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(request); + return 0; + } + + reply = ssh_buffer_new(); + if (reply == NULL) { + SSH_BUFFER_FREE(request); + ssh_set_error(session, SSH_FATAL, "Not enough space"); + return 0; + } + + if (agent_talk(session, request, reply) < 0) { + SSH_BUFFER_FREE(request); + SSH_BUFFER_FREE(reply); + return 0; + } + SSH_BUFFER_FREE(request); + + /* get message type and verify the answer */ + rc = ssh_buffer_get_u8(reply, (uint8_t *) &type); + if (rc != sizeof(uint8_t)) { + ssh_set_error(session, SSH_FATAL, + "Bad authentication reply size: %" PRIu32, rc); + SSH_BUFFER_FREE(reply); + return 0; + } +#ifdef WORDS_BIGENDIAN + type = bswap_32(type); +#endif + + SSH_LOG(SSH_LOG_TRACE, + "Answer type: %d, expected answer: %d", + type, SSH2_AGENT_IDENTITIES_ANSWER); + + if (agent_failed(type)) { + SSH_BUFFER_FREE(reply); + return 0; + } else if (type != SSH2_AGENT_IDENTITIES_ANSWER) { + ssh_set_error(session, SSH_FATAL, + "Bad authentication reply message type: %u", type); + SSH_BUFFER_FREE(reply); + return 0; + } + + rc = ssh_buffer_get_u32(reply, &count); + if (rc != 4) { + ssh_set_error(session, + SSH_FATAL, + "Failed to read count"); + SSH_BUFFER_FREE(reply); + return 0; + } + session->agent->count = ntohl(count); + SSH_LOG(SSH_LOG_DEBUG, "Agent count: %d", + session->agent->count); + if (session->agent->count > 1024) { + ssh_set_error(session, SSH_FATAL, + "Too many identities in authentication reply: %d", + session->agent->count); + SSH_BUFFER_FREE(reply); + return 0; + } + + ssh_buffer_free(session->agent->ident); + session->agent->ident = reply; + + return session->agent->count; +} + +/* caller has to free comment */ +ssh_key ssh_agent_get_first_ident(struct ssh_session_struct *session, + char **comment) { + if (ssh_agent_get_ident_count(session) > 0) { + return ssh_agent_get_next_ident(session, comment); + } + + return NULL; +} + +/* caller has to free comment */ +ssh_key ssh_agent_get_next_ident(struct ssh_session_struct *session, + char **comment) +{ + struct ssh_key_struct *key = NULL; + struct ssh_string_struct *blob = NULL; + struct ssh_string_struct *tmp = NULL; + int rc; + + if (session->agent->count == 0) { + return NULL; + } + + /* get the blob */ + blob = ssh_buffer_get_ssh_string(session->agent->ident); + if (blob == NULL) { + return NULL; + } + + /* get the comment */ + tmp = ssh_buffer_get_ssh_string(session->agent->ident); + if (tmp == NULL) { + SSH_STRING_FREE(blob); + + return NULL; + } + + if (comment) { + *comment = ssh_string_to_char(tmp); + } else { + SSH_STRING_FREE(blob); + SSH_STRING_FREE(tmp); + + return NULL; + } + SSH_STRING_FREE(tmp); + + /* get key from blob */ + rc = ssh_pki_import_pubkey_blob(blob, &key); + if (rc == SSH_ERROR) { + /* Try again as a cert. */ + rc = ssh_pki_import_cert_blob(blob, &key); + } + SSH_STRING_FREE(blob); + if (rc == SSH_ERROR) { + return NULL; + } + + return key; +} + +int ssh_agent_is_running(ssh_session session) +{ + if (session == NULL || session->agent == NULL) { + return 0; + } + + if (ssh_socket_is_open(session->agent->sock)) { + return 1; + } else { + if (agent_connect(session) < 0) { + return 0; + } else { + return 1; + } + } + + return 0; +} + +ssh_string ssh_agent_sign_data(ssh_session session, + const ssh_key pubkey, + struct ssh_buffer_struct *data) +{ + ssh_buffer request = NULL; + ssh_buffer reply = NULL; + ssh_string key_blob = NULL; + ssh_string sig_blob = NULL; + unsigned int type = 0; + unsigned int flags = 0; + uint32_t dlen; + size_t request_len; + int rc; + + request = ssh_buffer_new(); + if (request == NULL) { + return NULL; + } + + /* create request */ + if (ssh_buffer_add_u8(request, SSH2_AGENTC_SIGN_REQUEST) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &key_blob); + if (rc < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* + * make sure it already can contain all the expected content: + * - 1 x uint8_t + * - 2 x uint32_t + * - 1 x ssh_string (uint8_t + data) + */ + request_len = sizeof(uint8_t) * 2 + + sizeof(uint32_t) * 2 + + ssh_string_len(key_blob); + /* this can't overflow the uint32_t as the + * STRING_SIZE_MAX is (UINT32_MAX >> 8) + 1 */ + rc = ssh_buffer_allocate_size(request, (uint32_t)request_len); + if (rc < 0) { + SSH_STRING_FREE(key_blob); + SSH_BUFFER_FREE(request); + return NULL; + } + + /* adds len + blob */ + rc = ssh_buffer_add_ssh_string(request, key_blob); + SSH_STRING_FREE(key_blob); + if (rc < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* Add data */ + dlen = ssh_buffer_get_len(data); + if (ssh_buffer_add_u32(request, htonl(dlen)) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + if (ssh_buffer_add_data(request, ssh_buffer_get(data), dlen) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* Add Flags: SHA2 extension (RFC 8332) if negotiated */ + if (ssh_key_type_plain(pubkey->type) == SSH_KEYTYPE_RSA) { + if (session->extensions & SSH_EXT_SIG_RSA_SHA512) { + flags |= SSH_AGENT_RSA_SHA2_512; + } else if (session->extensions & SSH_EXT_SIG_RSA_SHA256) { + flags |= SSH_AGENT_RSA_SHA2_256; + } + } + if (ssh_buffer_add_u32(request, htonl(flags)) < 0) { + SSH_BUFFER_FREE(request); + return NULL; + } + + reply = ssh_buffer_new(); + if (reply == NULL) { + SSH_BUFFER_FREE(request); + return NULL; + } + + /* send the request */ + if (agent_talk(session, request, reply) < 0) { + SSH_BUFFER_FREE(request); + SSH_BUFFER_FREE(reply); + return NULL; + } + SSH_BUFFER_FREE(request); + + /* check if reply is valid */ + if (ssh_buffer_get_u8(reply, (uint8_t *) &type) != sizeof(uint8_t)) { + SSH_BUFFER_FREE(reply); + return NULL; + } +#ifdef WORDS_BIGENDIAN + type = bswap_32(type); +#endif + + if (agent_failed(type)) { + SSH_LOG(SSH_LOG_DEBUG, "Agent reports failure in signing the key"); + SSH_BUFFER_FREE(reply); + return NULL; + } else if (type != SSH2_AGENT_SIGN_RESPONSE) { + ssh_set_error(session, + SSH_FATAL, + "Bad authentication response: %u", + type); + SSH_BUFFER_FREE(reply); + return NULL; + } + + sig_blob = ssh_buffer_get_ssh_string(reply); + SSH_BUFFER_FREE(reply); + + return sig_blob; +} diff --git a/src/libs/libssh-0.12.2/src/auth.c b/src/libs/libssh-0.12.2/src/auth.c new file mode 100644 index 000000000000..b4b8fc4ed3b9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/auth.c @@ -0,0 +1,2580 @@ +/* + * auth.c - Authentication with SSH protocols + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * Copyright (c) 2008-2013 Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/agent.h" +#include "libssh/auth.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/gssapi.h" +#include "libssh/keys.h" +#include "libssh/legacy.h" +#include "libssh/misc.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +/** + * @defgroup libssh_auth The SSH authentication functions + * @ingroup libssh + * + * Functions to authenticate with a server. + * + * @{ + */ + +/** + * @internal + * + * @brief Ask for access to the ssh-userauth service. + * + * @param[in] session The SSH session handle. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN on nonblocking mode, if calling that function + * again is necessary + */ +static int ssh_userauth_request_service(ssh_session session) +{ + int rc; + + rc = ssh_service_request(session, "ssh-userauth"); + if ((rc != SSH_OK) && (rc != SSH_AGAIN)) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to request \"ssh-userauth\" service"); + } + + return rc; +} + +static int ssh_auth_response_termination(void *user) +{ + ssh_session session = (ssh_session)user; + switch (session->auth.state) { + case SSH_AUTH_STATE_NONE: + case SSH_AUTH_STATE_KBDINT_SENT: + case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: + case SSH_AUTH_STATE_GSSAPI_TOKEN: + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT: + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: + case SSH_AUTH_STATE_AUTH_NONE_SENT: + return 0; + default: + return 1; + } +} + +static const char *ssh_auth_get_current_method(ssh_session session) +{ + const char *method = "unknown"; + + switch (session->auth.current_method) { + case SSH_AUTH_METHOD_NONE: + method = "none"; + break; + case SSH_AUTH_METHOD_PASSWORD: + method = "password"; + break; + case SSH_AUTH_METHOD_PUBLICKEY: + method = "publickey"; + break; + case SSH_AUTH_METHOD_HOSTBASED: + method = "hostbased"; + break; + case SSH_AUTH_METHOD_INTERACTIVE: + method = "keyboard interactive"; + break; +#ifdef WITH_GSSAPI + case SSH_AUTH_METHOD_GSSAPI_MIC: + method = "gssapi"; + break; + case SSH_AUTH_METHOD_GSSAPI_KEYEX: + method = "gssapi-keyex"; + break; +#endif + default: + break; + } + + return method; +} + +/** + * @internal + * @brief Wait for a response of an authentication function. + * + * @param[in] session The SSH session. + * + * @returns SSH_AUTH_SUCCESS Authentication success, or pubkey accepted + * SSH_AUTH_PARTIAL Authentication succeeded but another mean + * of authentication is needed. + * SSH_AUTH_INFO Data for keyboard-interactive + * SSH_AUTH_AGAIN In nonblocking mode, call has to be made again + * SSH_AUTH_ERROR Error during the process. + */ +static int ssh_userauth_get_response(ssh_session session) +{ + int rc = SSH_AUTH_ERROR; + + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_auth_response_termination, session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + if (!ssh_auth_response_termination(session)) { + return SSH_AUTH_AGAIN; + } + + switch(session->auth.state) { + case SSH_AUTH_STATE_ERROR: + rc = SSH_AUTH_ERROR; + break; + case SSH_AUTH_STATE_FAILED: + rc = SSH_AUTH_DENIED; + break; + case SSH_AUTH_STATE_INFO: + rc = SSH_AUTH_INFO; + break; + case SSH_AUTH_STATE_PARTIAL: + rc = SSH_AUTH_PARTIAL; + break; + case SSH_AUTH_STATE_PK_OK: + case SSH_AUTH_STATE_SUCCESS: + rc = SSH_AUTH_SUCCESS; + break; + case SSH_AUTH_STATE_KBDINT_SENT: + case SSH_AUTH_STATE_GSSAPI_REQUEST_SENT: + case SSH_AUTH_STATE_GSSAPI_TOKEN: + case SSH_AUTH_STATE_GSSAPI_MIC_SENT: + case SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT: + case SSH_AUTH_STATE_PUBKEY_OFFER_SENT: + case SSH_AUTH_STATE_PUBKEY_AUTH_SENT: + case SSH_AUTH_STATE_PASSWORD_AUTH_SENT: + case SSH_AUTH_STATE_AUTH_NONE_SENT: + case SSH_AUTH_STATE_NONE: + /* not reached */ + rc = SSH_AUTH_ERROR; + break; + } + + return rc; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_BANNER packet. + * + * This banner should be shown to user prior to authentication + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_banner) +{ + ssh_string banner = NULL; + (void)type; + (void)user; + + banner = ssh_buffer_get_ssh_string(packet); + if (banner == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid SSH_USERAUTH_BANNER packet"); + } else { + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_USERAUTH_BANNER packet"); + if (session->banner != NULL) + SSH_STRING_FREE(session->banner); + session->banner = banner; + } + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_FAILURE packet. + * + * This handles the complete or partial authentication failure. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_failure) { + const char *current_method = ssh_auth_get_current_method(session); + char *auth_methods = NULL; + uint8_t partial = 0; + int rc; + (void) type; + (void) user; + + rc = ssh_buffer_unpack(packet, "sb", &auth_methods, &partial); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, + "Invalid SSH_MSG_USERAUTH_FAILURE message"); + session->auth.state = SSH_AUTH_STATE_ERROR; + goto end; + } + + if (partial) { + session->auth.state = SSH_AUTH_STATE_PARTIAL; + SSH_LOG(SSH_LOG_DEBUG, + "Partial success for '%s'. Authentication that can continue: %s", + current_method, + auth_methods); + } else { + session->auth.state = SSH_AUTH_STATE_FAILED; + ssh_set_error(session, SSH_REQUEST_DENIED, + "Access denied for '%s'. Authentication that can continue: %s", + current_method, + auth_methods); + SSH_LOG(SSH_LOG_DEBUG, + "%s", + ssh_get_error(session)); + + } + session->auth.supported_methods = 0; + if (strstr(auth_methods, "password") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_PASSWORD; + } + if (strstr(auth_methods, "keyboard-interactive") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_INTERACTIVE; + } + if (strstr(auth_methods, "publickey") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_PUBLICKEY; + } + if (strstr(auth_methods, "hostbased") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_HOSTBASED; + } +#ifdef WITH_GSSAPI + if (strstr(auth_methods, "gssapi-with-mic") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_MIC; + } + if (strstr(auth_methods, "gssapi-keyex") != NULL) { + session->auth.supported_methods |= SSH_AUTH_METHOD_GSSAPI_KEYEX; + } +#endif + +end: + session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN; + SAFE_FREE(auth_methods); + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_SUCCESS packet. + * + * It is also used to communicate the new to the upper levels. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_success) +{ + struct ssh_crypto_struct *crypto = NULL; + + (void)packet; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_DEBUG, "Authentication successful"); + SSH_LOG(SSH_LOG_TRACE, "Received SSH_USERAUTH_SUCCESS"); + + session->auth.state = SSH_AUTH_STATE_SUCCESS; + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto != NULL && crypto->delayed_compress_out) { + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression OUT"); + crypto->do_compress_out = 1; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL && crypto->delayed_compress_in) { + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression IN"); + crypto->do_compress_in = 1; + } + + /* Reset errors by previous authentication methods. */ + ssh_reset_error(session); + session->auth.current_method = SSH_AUTH_METHOD_UNKNOWN; + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handles a SSH_USERAUTH_PK_OK or SSH_USERAUTH_INFO_REQUEST packet. + * + * Since the two types of packets share the same code, additional work is done + * to understand if we are in a public key or keyboard-interactive context. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_pk_ok) { + int rc; + + SSH_LOG(SSH_LOG_TRACE, + "Received SSH_USERAUTH_PK_OK/INFO_REQUEST/GSSAPI_RESPONSE"); + + if (session->auth.state == SSH_AUTH_STATE_KBDINT_SENT) { + /* Assuming we are in keyboard-interactive context */ + SSH_LOG(SSH_LOG_TRACE, + "keyboard-interactive context, " + "assuming SSH_USERAUTH_INFO_REQUEST"); + rc = ssh_packet_userauth_info_request(session,type,packet,user); +#ifdef WITH_GSSAPI + } else if (session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT) { + rc = ssh_packet_userauth_gssapi_response(session, type, packet, user); +#endif + } else if (session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT) { + session->auth.state = SSH_AUTH_STATE_PK_OK; + SSH_LOG(SSH_LOG_TRACE, "Assuming SSH_USERAUTH_PK_OK"); + rc = SSH_PACKET_USED; + } else { + session->auth.state = SSH_AUTH_STATE_ERROR; + SSH_LOG(SSH_LOG_TRACE, "SSH_USERAUTH_PK_OK received in wrong state"); + rc = SSH_PACKET_USED; + } + + return rc; +} + +/** + * @brief Get available authentication methods from the server. + * + * This requires the function ssh_userauth_none() to be called before the + * methods are available. The server MAY return a list of methods that may + * continue. + * + * @param[in] session The SSH session. + * + * @param[in] username Deprecated, set to NULL. + * + * @returns A bitfield of the following values: + * - SSH_AUTH_METHOD_PASSWORD + * - SSH_AUTH_METHOD_PUBLICKEY + * - SSH_AUTH_METHOD_HOSTBASED + * - SSH_AUTH_METHOD_INTERACTIVE + * + * @warning Other reserved flags may appear in future versions. + * @see ssh_userauth_none() + */ +int ssh_userauth_list(ssh_session session, const char *username) +{ + (void) username; /* unused */ + + if (session == NULL) { + return 0; + } + + return session->auth.supported_methods; +} + +/** + * @brief Try to authenticate through the "none" method. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @returns SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: Authentication failed: use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + */ +int ssh_userauth_none(ssh_session session, const char *username) +{ + int rc; + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_NONE: + goto pending; + default: + ssh_set_error(session, SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsss", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "none" + ); + if (rc < 0) { + goto fail; + } + + session->auth.current_method = SSH_AUTH_METHOD_NONE; + session->auth.state = SSH_AUTH_STATE_AUTH_NONE_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_NONE; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * + * @brief Adds the server's public key to the authentication request. + * + * This function is used internally when the hostbound public key authentication + * extension is enabled. It export the server's public key and adds it to the + * authentication buffer. + * + * @param[in] session The SSH session. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +static int add_hostbound_pubkey(ssh_session session) +{ + int rc; + ssh_string server_pubkey_s = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Invalid session or server public key"); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(session->current_crypto->server_pubkey, + &server_pubkey_s); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_s); + if (rc < 0) { + goto error; + } + +error: + SSH_STRING_FREE(server_pubkey_s); + return rc; +} + +/** + * @internal + * + * @brief Build a public key authentication request. + * + * This helper function creates a SSH2_MSG_USERAUTH_REQUEST message for public + * key authentication and adds the server's public key if the hostbound + * extension is enabled. + * + * @param[in] session The SSH session. + * @param[in] username The username, may be NULL. + * @param[in] auth_type Authentication type (0 for key offer, 1 for actual + * auth). + * @param[in] sig_type_c The signature algorithm name. + * @param[in] pubkey_s The public key string. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + */ +static int build_pubkey_auth_request(ssh_session session, + const char *username, + int has_signature, + const char *sig_type_c, + ssh_string pubkey_s) +{ + int rc; + const char *auth_method = "publickey"; + + if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND && + session->current_crypto->server_pubkey != NULL) { + auth_method = "publickey-hostbound-v00@openssh.com"; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, + "bsssbsS", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + auth_method, + has_signature, /* private key? */ + sig_type_c, /* algo */ + pubkey_s /* public key */ + ); + if (rc < 0) { + return SSH_ERROR; + } + + if (session->extensions & SSH_EXT_PUBLICKEY_HOSTBOUND && + session->current_crypto->server_pubkey != NULL) { + rc = add_hostbound_pubkey(session); + if (rc < 0) { + return SSH_ERROR; + } + } + + return SSH_OK; +} + +/** + * @brief Try to authenticate with the given public key. + * + * To avoid unnecessary processing and user interaction, the following method + * is provided for querying whether authentication using the 'pubkey' would + * be possible. + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] pubkey The public key to try. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use + * ssh_userauth_publickey().\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + */ +int ssh_userauth_try_publickey(ssh_session session, + const char *username, + const ssh_key pubkey) +{ + ssh_string pubkey_s = NULL; + const char *sig_type_c = NULL; + bool allowed; + int rc; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (pubkey == NULL || !ssh_key_is_public(pubkey)) { + ssh_set_error(session, SSH_FATAL, "Invalid pubkey"); + return SSH_AUTH_ERROR; + } + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_OFFER_PUBKEY: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_AUTH_ERROR; + } + + /* Note, that this is intentionally before checking the signature type + * compatibility to make sure the possible EXT_INFO packet is processed, + * extensions recorded and the right signature type is used below + */ + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* Check if the given public key algorithm is allowed */ + sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type); + if (sig_type_c == NULL) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Invalid key type (unknown)"); + return SSH_AUTH_DENIED; + } + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The key algorithm '%s' is not allowed to be used by" + " PUBLICKEY_ACCEPTED_TYPES configuration option", + sig_type_c); + return SSH_AUTH_DENIED; + } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", + sig_type_c, + ssh_key_size(pubkey)); + return SSH_AUTH_DENIED; + } + + /* public key */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_s); + if (rc < 0) { + goto fail; + } + + SSH_LOG(SSH_LOG_TRACE, "Trying signature type %s", sig_type_c); + rc = build_pubkey_auth_request(session, username, 0, sig_type_c, pubkey_s); + if (rc < 0) { + goto fail; + } + SSH_STRING_FREE(pubkey_s); + + session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; + session->auth.state = SSH_AUTH_STATE_PUBKEY_OFFER_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_OFFER_PUBKEY; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + SSH_STRING_FREE(pubkey_s); + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @brief Authenticate with public/private key or certificate. + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] privkey The private key for authentication. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted.\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + */ +int ssh_userauth_publickey(ssh_session session, + const char *username, + const ssh_key privkey) +{ + ssh_string str = NULL; + bool allowed; + int rc; + const char *sig_type_c = NULL; + enum ssh_keytypes_e key_type; + enum ssh_digest_e hash_type; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + ssh_set_error(session, SSH_FATAL, "Invalid private key"); + return SSH_AUTH_ERROR; + } + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_PUBKEY: + goto pending; + default: + ssh_set_error( + session, + SSH_FATAL, + "Bad call during pending SSH call in ssh_userauth_try_publickey"); + return SSH_AUTH_ERROR; + } + + /* Note, that this is intentionally before checking the signature type + * compatibility to make sure the possible EXT_INFO packet is processed, + * extensions recorded and the right signature type is used below + */ + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* Cert auth requires presenting the cert type name (*-cert@openssh.com) */ + key_type = privkey->cert != NULL ? privkey->cert_type : privkey->type; + + /* Check if the given public key algorithm is allowed */ + sig_type_c = ssh_key_get_signature_algorithm(session, key_type); + if (sig_type_c == NULL) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Invalid key type (unknown)"); + return SSH_AUTH_DENIED; + } + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The key algorithm '%s' is not allowed to be used by" + " PUBLICKEY_ACCEPTED_TYPES configuration option", + sig_type_c); + return SSH_AUTH_DENIED; + } + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", + sig_type_c, + ssh_key_size(privkey)); + return SSH_AUTH_DENIED; + } + + /* get public key or cert */ + rc = ssh_pki_export_pubkey_blob(privkey, &str); + if (rc < 0) { + goto fail; + } + + SSH_LOG(SSH_LOG_TRACE, "Sending signature type %s", sig_type_c); + rc = build_pubkey_auth_request(session, username, 1, sig_type_c, str); + if (rc < 0) { + goto fail; + } + SSH_STRING_FREE(str); + + /* Get the hash type to be used in the signature based on the key type */ + hash_type = ssh_key_type_to_hash(session, privkey->type); + + /* sign the buffer with the private key */ + str = ssh_pki_do_sign(session, session->out_buffer, privkey, hash_type); + if (str == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, str); + SSH_STRING_FREE(str); + str = NULL; + if (rc < 0) { + goto fail; + } + + session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; + session->auth.state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_PUBKEY; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + SSH_STRING_FREE(str); + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +static int ssh_userauth_agent_publickey(ssh_session session, + const char *username, + ssh_key pubkey) +{ + ssh_string pubkey_s = NULL; + ssh_string sig_blob = NULL; + const char *sig_type_c = NULL; + bool allowed; + int rc; + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_AGENT: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Bad call during pending SSH call in %s", + __func__); + return SSH_ERROR; + } + + /* Note, that this is intentionally before checking the signature type + * compatibility to make sure the possible EXT_INFO packet is processed, + * extensions recorded and the right signature type is used below + */ + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* public key */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_s); + if (rc < 0) { + goto fail; + } + + /* Check if the given public key algorithm is allowed */ + sig_type_c = ssh_key_get_signature_algorithm(session, pubkey->type); + if (sig_type_c == NULL) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Invalid key type (unknown)"); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } + rc = ssh_key_algorithm_allowed(session, sig_type_c); + if (!rc) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The key algorithm '%s' is not allowed to be used by" + " PUBLICKEY_ACCEPTED_TYPES configuration option", + sig_type_c); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } + allowed = ssh_key_size_allowed(session, pubkey); + if (!allowed) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The '%s' key type of size %d is not allowed by " + "RSA_MIN_SIZE", + sig_type_c, + ssh_key_size(pubkey)); + SSH_STRING_FREE(pubkey_s); + return SSH_AUTH_DENIED; + } + + rc = build_pubkey_auth_request(session, username, 1, sig_type_c, pubkey_s); + if (rc < 0) { + goto fail; + } + SSH_STRING_FREE(pubkey_s); + + /* sign the buffer with the private key */ + sig_blob = ssh_pki_do_sign_agent(session, session->out_buffer, pubkey); + if (sig_blob == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc < 0) { + goto fail; + } + + session->auth.current_method = SSH_AUTH_METHOD_PUBLICKEY; + session->auth.state = SSH_AUTH_STATE_PUBKEY_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_AGENT; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + SSH_STRING_FREE(pubkey_s); + + return SSH_AUTH_ERROR; +} + +enum ssh_agent_state_e { + SSH_AGENT_STATE_NONE = 0, + SSH_AGENT_STATE_PUBKEY, + SSH_AGENT_STATE_CERT, + SSH_AGENT_STATE_AUTH +}; + +struct ssh_agent_state_struct { + enum ssh_agent_state_e state; + ssh_key pubkey; + char *comment; +}; + +/* Internal function */ +void ssh_agent_state_free(void *data) +{ + struct ssh_agent_state_struct *state = data; + + if (state) { + SSH_STRING_FREE_CHAR(state->comment); + ssh_key_free(state->pubkey); + free(state); + } +} + +/** + * @brief Try to do public key authentication with ssh agent. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: The public key is accepted, you want now to use + * ssh_userauth_publickey().\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + */ +int ssh_userauth_agent(ssh_session session, const char *username) +{ + int rc = SSH_AUTH_ERROR; + struct ssh_agent_state_struct *state = NULL; + ssh_key *configKeys = NULL; + ssh_key *configCerts = NULL; + size_t configKeysCount = 0; + size_t configCertsCount = 0; + size_t i; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if (!ssh_agent_is_running(session)) { + return SSH_AUTH_DENIED; + } + + if (!session->agent_state) { + session->agent_state = calloc(1, sizeof(struct ssh_agent_state_struct)); + if (!session->agent_state) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + session->agent_state->state = SSH_AGENT_STATE_NONE; + } + + state = session->agent_state; + if (state->pubkey == NULL) { + state->pubkey = ssh_agent_get_first_ident(session, &state->comment); + } + + if (state->pubkey == NULL) { + return SSH_AUTH_DENIED; + } + + if (session->opts.identities_only) { + /* + * Read keys mentioned in the config, so we can check if key from agent + * is in there. + */ + size_t identityLen = ssh_list_count(session->opts.identity); + size_t certsLen = ssh_list_count(session->opts.certificate); + struct ssh_iterator *it = ssh_list_get_iterator(session->opts.identity); + + configKeys = malloc(identityLen * sizeof(ssh_key)); + configCerts = malloc((certsLen + identityLen) * sizeof(ssh_key)); + if (configKeys == NULL || configCerts == NULL) { + free(configKeys); + free(configCerts); + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + while (it != NULL && configKeysCount < identityLen) { + const char *privkeyFile = it->data; + size_t certPathLen; + char *certFile = NULL; + ssh_key pubkey = NULL; + ssh_key cert = NULL; + + /* + * Read the private key file listed in the config, but we're only + * interested in the public key. Don't try to decrypt private key. + */ + rc = ssh_pki_import_pubkey_file(privkeyFile, &pubkey); + if (rc == SSH_OK) { + configKeys[configKeysCount++] = pubkey; + } else { + char *pubkeyFile = NULL; + size_t pubkeyPathLen = strlen(privkeyFile) + sizeof(".pub"); + + SSH_KEY_FREE(pubkey); + + /* + * If we couldn't get the public key from the private key file, + * try a .pub file instead. + */ + pubkeyFile = malloc(pubkeyPathLen); + if (!pubkeyFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(pubkeyFile, pubkeyPathLen, "%s.pub", privkeyFile); + rc = ssh_pki_import_pubkey_file(pubkeyFile, &pubkey); + free(pubkeyFile); + if (rc == SSH_OK) { + configKeys[configKeysCount++] = pubkey; + } else if (pubkey) { + SSH_KEY_FREE(pubkey); + } + } + /* Now try to see if there is a certificate with default name + * do not merge it yet with the key as we need to try first the + * non-certified key */ + certPathLen = strlen(privkeyFile) + sizeof("-cert.pub"); + certFile = malloc(certPathLen); + if (!certFile) { + ssh_set_error_oom(session); + rc = SSH_AUTH_ERROR; + goto done; + } + snprintf(certFile, certPathLen, "%s-cert.pub", privkeyFile); + rc = ssh_pki_import_cert_file(certFile, &cert); + free(certFile); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + /* And now load separately-listed certificates. */ + it = ssh_list_get_iterator(session->opts.certificate); + while (it != NULL && configCertsCount < certsLen + identityLen) { + const char *certFile = it->data; + ssh_key cert = NULL; + + rc = ssh_pki_import_cert_file(certFile, &cert); + if (rc == SSH_OK) { + configCerts[configCertsCount++] = cert; + } else if (cert) { + SSH_KEY_FREE(cert); + } + + it = it->next; + } + } + + while (state->pubkey != NULL) { + if (state->state == SSH_AGENT_STATE_NONE) { + SSH_LOG(SSH_LOG_DEBUG, + "Trying identity %s", + state->comment); + if (session->opts.identities_only) { + /* Check if this key is one of the keys listed in the config */ + bool found_key = false; + for (i = 0; i < configKeysCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configKeys[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } + /* or in separate certificates */ + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found_key = true; + break; + } + } + + if (!found_key) { + SSH_LOG(SSH_LOG_DEBUG, + "Identities only is enabled and identity %s was " + "not listed in config, skipping", + state->comment); + SSH_STRING_FREE_CHAR(state->comment); + state->comment = NULL; + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident( + session, &state->comment); + + if (state->pubkey == NULL) { + rc = SSH_AUTH_DENIED; + } + continue; + } + } + } + if (state->state == SSH_AGENT_STATE_NONE || + state->state == SSH_AGENT_STATE_PUBKEY || + state->state == SSH_AGENT_STATE_CERT) { + rc = ssh_userauth_try_publickey(session, username, state->pubkey); + if (rc == SSH_AUTH_ERROR) { + ssh_agent_state_free(state); + session->agent_state = NULL; + goto done; + } else if (rc == SSH_AUTH_AGAIN) { + state->state = (state->state == SSH_AGENT_STATE_NONE ? + SSH_AGENT_STATE_PUBKEY : state->state); + goto done; + } else if (rc != SSH_AUTH_SUCCESS) { + SSH_LOG(SSH_LOG_DEBUG, + "Public key of %s refused by server", + state->comment); + if (state->state == SSH_AGENT_STATE_PUBKEY) { + for (i = 0; i < configCertsCount; i++) { + int cmp = ssh_key_cmp(state->pubkey, + configCerts[i], + SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Retry with matching certificate"); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_key_dup(configCerts[i]); + state->state = SSH_AGENT_STATE_CERT; + continue; + } + } + } + SSH_STRING_FREE_CHAR(state->comment); + state->comment = NULL; + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); + state->state = SSH_AGENT_STATE_NONE; + continue; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Public key of %s accepted by server", + state->comment); + state->state = SSH_AGENT_STATE_AUTH; + } + if (state->state == SSH_AGENT_STATE_AUTH) { + rc = ssh_userauth_agent_publickey(session, username, state->pubkey); + if (rc == SSH_AUTH_AGAIN) { + goto done; + } + SSH_STRING_FREE_CHAR(state->comment); + state->comment = NULL; + if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_PARTIAL) { + ssh_agent_state_free(session->agent_state); + session->agent_state = NULL; + goto done; + } else if (rc != SSH_AUTH_SUCCESS) { + SSH_LOG(SSH_LOG_DEBUG, + "Server accepted public key but refused the signature"); + SSH_KEY_FREE(state->pubkey); + state->pubkey = ssh_agent_get_next_ident(session, + &state->comment); + state->state = SSH_AGENT_STATE_NONE; + continue; + } + ssh_agent_state_free (session->agent_state); + session->agent_state = NULL; + rc = SSH_AUTH_SUCCESS; + goto done; + } + } + + ssh_agent_state_free (session->agent_state); + session->agent_state = NULL; +done: + for (i = 0; i < configKeysCount; i++) { + ssh_key_free(configKeys[i]); + } + free(configKeys); + for (i = 0; i < configCertsCount; i++) { + ssh_key_free(configCerts[i]); + } + free(configCerts); + return rc; +} + +enum ssh_auth_auto_state_e { + SSH_AUTH_AUTO_STATE_NONE = 0, + SSH_AUTH_AUTO_STATE_PUBKEY, + SSH_AUTH_AUTO_STATE_KEY_IMPORTED, + SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE, + SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT, + SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION, + SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED +}; + +struct ssh_auth_auto_state_struct { + enum ssh_auth_auto_state_e state; + struct ssh_iterator *it; + ssh_key privkey; + ssh_key pubkey; + ssh_key cert; + struct ssh_iterator *cert_it; +}; + +/** + * @brief Get the identity that is currently being processed by + * ssh_userauth_publickey_auto() + * + * This is meant to be used by a callback that happens as part of the + * execution of ssh_userauth_publickey_auto(). The auth_function + * callback might want to know which key a passphrase is needed for, + * for example. + * + * @param[in] session The SSH session. + * + * @param[out] value The value to get into. As a char**, space will be + * allocated by the function for the value, it is + * your responsibility to free the memory using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_userauth_publickey_auto_get_current_identity(ssh_session session, + char** value) +{ + const char *id = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (value == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + if (session->auth.auto_state != NULL && + session->auth.auto_state->it != NULL) { + id = session->auth.auto_state->it->data; + } + + if (id == NULL) { + return SSH_ERROR; + } + + *value = strdup(id); + if (*value == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Tries to automatically authenticate with public key and "none" + * + * It may fail, for instance it doesn't ask for a password and uses a default + * asker for passphrases (in case the private key is encrypted). + * + * @param[in] session The SSH session. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] passphrase Use this passphrase to unlock the privatekey. Use NULL + * if you don't want to use a passphrase or the user + * should be asked. + * + * @return SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: The server doesn't accept that public key as an + * authentication token. Try another key or another + * method.\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method.\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + * + * The OpenSSH iterates over the identities and first try the plain public key + * and then the certificate if it is in place. + */ +int ssh_userauth_publickey_auto(ssh_session session, + const char *username, + const char *passphrase) +{ + ssh_auth_callback auth_fn = NULL; + void *auth_data = NULL; + struct ssh_auth_auto_state_struct *state = NULL; + int rc; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + SSH_LOG(SSH_LOG_INFO, + "Starting authentication as a user %s", + username ? username : session->opts.username); + + if (! (session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH)) { + session->auth.supported_methods &= ~SSH_AUTH_METHOD_PUBLICKEY; + return SSH_AUTH_DENIED; + } + if (session->common.callbacks) { + auth_fn = session->common.callbacks->auth_function; + auth_data = session->common.callbacks->userdata; + } + if (!session->auth.auto_state) { + session->auth.auto_state = + calloc(1, sizeof(struct ssh_auth_auto_state_struct)); + if (!session->auth.auto_state) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + /* Set state explicitly */ + session->auth.auto_state->state = SSH_AUTH_AUTO_STATE_NONE; + } + state = session->auth.auto_state; + if (state->state == SSH_AUTH_AUTO_STATE_NONE) { + /* Try authentication with ssh-agent first */ + rc = ssh_userauth_agent(session, username); + if (rc == SSH_AUTH_SUCCESS || + rc == SSH_AUTH_PARTIAL || + rc == SSH_AUTH_AGAIN) { + return rc; + } + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + } + if (state->it == NULL) { + state->it = ssh_list_get_iterator(session->opts.identity); + } + + while (state->it != NULL) { + const char *privkey_file = state->it->data; + char pubkey_file[PATH_MAX] = {0}; + + if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY) { + SSH_LOG(SSH_LOG_DEBUG, + "Trying to authenticate with %s", + privkey_file); + state->cert = NULL; + state->privkey = NULL; + state->pubkey = NULL; + +#ifdef WITH_PKCS11_URI + if (ssh_pki_is_uri(privkey_file)) { + char *pub_uri_from_priv = NULL; + SSH_LOG(SSH_LOG_INFO, + "Authenticating with PKCS #11 URI."); + pub_uri_from_priv = ssh_pki_export_pub_uri_from_priv_uri(privkey_file); + if (pub_uri_from_priv == NULL) { + return SSH_ERROR; + } else { + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s", + pub_uri_from_priv); + SAFE_FREE(pub_uri_from_priv); + } + } else +#endif /* WITH_PKCS11_URI */ + { + snprintf(pubkey_file, + sizeof(pubkey_file), + "%s.pub", + privkey_file); + } + + rc = ssh_pki_import_pubkey_file(pubkey_file, &state->pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to import public key: %s", + pubkey_file); + SAFE_FREE(session->auth.auto_state); + return SSH_AUTH_ERROR; + } else if (rc == SSH_EOF) { + /* Read the private key and save the public key to file */ + rc = ssh_pki_import_privkey_file(privkey_file, + passphrase, + auth_fn, + auth_data, + &state->privkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; + continue; + } else if (rc == SSH_EOF) { + /* If the file doesn't exist, continue */ + SSH_LOG(SSH_LOG_DEBUG, + "Private key %s doesn't exist.", + privkey_file); + state->it = state->it->next; + continue; + } + + rc = ssh_pki_export_privkey_to_pubkey(state->privkey, + &state->pubkey); + if (rc == SSH_ERROR) { + SSH_KEY_FREE(state->privkey); + SAFE_FREE(session->auth.auto_state); + return SSH_AUTH_ERROR; + } + + rc = ssh_pki_export_pubkey_file(state->pubkey, pubkey_file); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Could not write public key to file: %s", + pubkey_file); + } + } + state->state = SSH_AUTH_AUTO_STATE_KEY_IMPORTED; + } + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT || + state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + ssh_key k = state->pubkey; + if (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + k = state->cert; + } + rc = ssh_userauth_try_publickey(session, username, k); + if (rc == SSH_AUTH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Public key authentication error for %s", + privkey_file); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + SAFE_FREE(session->auth.auto_state); + return rc; + } else if (rc == SSH_AUTH_AGAIN) { + return rc; + } else if (rc != SSH_AUTH_SUCCESS) { + int r; /* do not reuse `rc` as it is used to return from here */ + SSH_KEY_FREE(state->cert); + SSH_LOG(SSH_LOG_DEBUG, + "Public key for %s%s refused by server", + privkey_file, + (state->state != SSH_AUTH_AUTO_STATE_KEY_IMPORTED + ? " (with certificate)" : "")); + /* Try certificate file by appending -cert.pub (if present) */ + if (state->state == SSH_AUTH_AUTO_STATE_KEY_IMPORTED) { + char cert_file[PATH_MAX] = {0}; + ssh_key cert = NULL; + + snprintf(cert_file, + sizeof(cert_file), + "%s-cert.pub", + privkey_file); + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (default path)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + /* TODO check the pubkey and certs match */ + SSH_LOG(SSH_LOG_TRACE, + "Certificate loaded %s. Retry the authentication.", + cert_file); + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_FILE; + SSH_KEY_FREE(state->cert); + state->cert = cert; + /* try to authenticate with this certificate */ + continue; + } + /* if the file does not exists, try configuration options */ + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT; + } + /* Try certificate files loaded through options */ + if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION_INIT) { + state->cert_it = ssh_list_get_iterator(session->opts.certificate); + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + } + if (state->state == SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION) { + SSH_KEY_FREE(state->cert); + while (state->cert_it != NULL) { + const char *cert_file = state->cert_it->data; + ssh_key cert = NULL; + + SSH_LOG(SSH_LOG_TRACE, + "Trying to load the certificate %s (options)", + cert_file); + r = ssh_pki_import_cert_file(cert_file, &cert); + if (r == SSH_OK) { + int cmp = ssh_key_cmp(cert, + state->pubkey, + SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + state->cert_it = state->cert_it->next; + SSH_KEY_FREE(cert); + continue; /* with next cert */ + } + SSH_LOG(SSH_LOG_TRACE, + "Found matching certificate %s in options. Retry the authentication.", + cert_file); + state->cert = cert; + cert = NULL; + state->state = SSH_AUTH_AUTO_STATE_CERTIFICATE_OPTION; + state->cert_it = state->cert_it->next; + /* try to authenticate with this identity */ + break; /* try this cert */ + } + /* continue with next identity */ + state->cert_it = state->cert_it->next; + } + if (state->cert != NULL) { + continue; /* retry with the certificate */ + } + } + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + state->state = SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED; + } + if (state->state == SSH_AUTH_AUTO_STATE_PUBKEY_ACCEPTED) { + /* Public key has been accepted by the server */ + if (state->privkey == NULL) { + rc = ssh_pki_import_privkey_file(privkey_file, + passphrase, + auth_fn, + auth_data, + &state->privkey); + if (rc == SSH_ERROR) { + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); + ssh_set_error(session, + SSH_FATAL, + "Failed to read private key: %s", + privkey_file); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } else if (rc == SSH_EOF) { + /* If the file doesn't exist, continue */ + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->pubkey); + SSH_LOG(SSH_LOG_DEBUG, + "Private key %s doesn't exist.", + privkey_file); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + } + if (state->cert != NULL && !is_cert_type(state->privkey->cert_type)) { + rc = ssh_pki_copy_cert_to_privkey(state->cert, state->privkey); + if (rc != SSH_OK) { + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + ssh_set_error(session, + SSH_FATAL, + "Failed to copy cert to private key"); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + continue; + } + } + + rc = ssh_userauth_publickey(session, username, state->privkey); + if (rc != SSH_AUTH_AGAIN && rc != SSH_AUTH_DENIED) { + bool cert_used = (state->cert != NULL); + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + SAFE_FREE(session->auth.auto_state); + if (rc == SSH_AUTH_SUCCESS) { + SSH_LOG(SSH_LOG_DEBUG, + "Successfully authenticated using %s%s", + privkey_file, + (cert_used ? " and certificate" : "")); + } + return rc; + } + if (rc == SSH_AUTH_AGAIN) { + return rc; + } + + SSH_KEY_FREE(state->cert); + SSH_KEY_FREE(state->privkey); + SSH_KEY_FREE(state->pubkey); + + SSH_LOG(SSH_LOG_DEBUG, + "The server accepted the public key but refused the signature"); + state->it = state->it->next; + state->state = SSH_AUTH_AUTO_STATE_PUBKEY; + /* continue */ + } + } + SSH_LOG(SSH_LOG_WARN, + "Access denied: Tried every public key, none matched"); + SAFE_FREE(session->auth.auto_state); + return SSH_AUTH_DENIED; +} + +/** + * @brief Try to authenticate by password. + * + * This authentication method is normally disabled on SSHv2 server. You should + * use keyboard-interactive mode. + * + * The 'password' value MUST be encoded UTF-8. It is up to the server how to + * interpret the password and validate it against the password database. + * However, if you read the password in some other encoding, you MUST convert + * the password to UTF-8. + * + * @param[in] session The ssh session to use. + * + * @param[in] username The username, this SHOULD be NULL. + * + * @param[in] password The password to authenticate in UTF-8. + * + * @returns SSH_AUTH_ERROR: A serious error happened.\n + * SSH_AUTH_DENIED: Authentication failed: use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @note Most server implementations do not permit changing the username during + * authentication. The username should only be set with ssh_options_set() only + * before you connect to the server. + * + * @see ssh_userauth_none() + * @see ssh_userauth_kbdint() + */ +int ssh_userauth_password(ssh_session session, + const char *username, + const char *password) +{ + int rc; + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_PASSWORD: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsssbs", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "password", + 0, /* false */ + password + ); + if (rc < 0) { + goto fail; + } + + /* Set the buffer as secure to be explicitly zeroed when freed */ + ssh_buffer_set_secure(session->out_buffer); + + session->auth.current_method = SSH_AUTH_METHOD_PASSWORD; + session->auth.state = SSH_AUTH_STATE_PASSWORD_AUTH_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_PASSWORD; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } + + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/* LEGACY */ +int ssh_userauth_agent_pubkey(ssh_session session, + const char *username, + ssh_public_key publickey) +{ + ssh_key key = NULL; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_AUTH_ERROR; + } + + key->type = publickey->type; + key->type_c = ssh_key_type_to_char(key->type); + key->flags = SSH_KEY_FLAG_PUBLIC; +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = publickey->rsa_pub; +#elif defined(HAVE_LIBCRYPTO) + key->key = publickey->key_pub; +#else + key->rsa = publickey->rsa_pub; +#endif /* HAVE_LIBCRYPTO */ + + rc = ssh_userauth_agent_publickey(session, username, key); + +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + key->key = NULL; +#else + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + ssh_key_free(key); + + return rc; +} + +ssh_kbdint ssh_kbdint_new(void) +{ + ssh_kbdint kbd; + + kbd = calloc(1, sizeof(struct ssh_kbdint_struct)); + if (kbd == NULL) { + return NULL; + } + + return kbd; +} + + +void ssh_kbdint_free(ssh_kbdint kbd) +{ + size_t i, n; + + if (kbd == NULL) { + return; + } + + SAFE_FREE(kbd->name); + SAFE_FREE(kbd->instruction); + SAFE_FREE(kbd->echo); + + n = kbd->nprompts; + if (kbd->prompts) { + for (i = 0; i < n; i++) { + if (kbd->prompts[i] != NULL) { + ssh_burn(kbd->prompts[i], strlen(kbd->prompts[i])); + } + SAFE_FREE(kbd->prompts[i]); + } + SAFE_FREE(kbd->prompts); + } + + n = kbd->nanswers; + if (kbd->answers) { + for (i = 0; i < n; i++) { + if (kbd->answers[i] != NULL) { + ssh_burn(kbd->answers[i], strlen(kbd->answers[i])); + } + SAFE_FREE(kbd->answers[i]); + } + SAFE_FREE(kbd->answers); + } + + SAFE_FREE(kbd); +} + +void ssh_kbdint_clean(ssh_kbdint kbd) +{ + size_t i, n; + + if (kbd == NULL) { + return; + } + + SAFE_FREE(kbd->name); + SAFE_FREE(kbd->instruction); + SAFE_FREE(kbd->echo); + + n = kbd->nprompts; + if (kbd->prompts) { + for (i = 0; i < n; i++) { + ssh_burn(kbd->prompts[i], strlen(kbd->prompts[i])); + SAFE_FREE(kbd->prompts[i]); + } + SAFE_FREE(kbd->prompts); + } + + n = kbd->nanswers; + + if (kbd->answers) { + for (i = 0; i < n; i++) { + ssh_burn(kbd->answers[i], strlen(kbd->answers[i])); + SAFE_FREE(kbd->answers[i]); + } + SAFE_FREE(kbd->answers); + } + + kbd->nprompts = 0; + kbd->nanswers = 0; +} + +/* + * This function sends the first packet as explained in RFC 3066 section 3.1. + */ +static int ssh_userauth_kbdint_init(ssh_session session, + const char *username, + const char *submethods) +{ + int rc; + + if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT) { + goto pending; + } + if (session->pending_call_state != SSH_PENDING_CALL_NONE) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } + if (rc != SSH_OK) { + return SSH_AUTH_ERROR; + } + + /* request */ + rc = ssh_buffer_pack(session->out_buffer, "bsssss", + SSH2_MSG_USERAUTH_REQUEST, + username ? username : session->opts.username, + "ssh-connection", + "keyboard-interactive", + "", /* lang (ignore it) */ + submethods ? submethods : "" + ); + if (rc < 0) { + goto fail; + } + + + session->auth.state = SSH_AUTH_STATE_KBDINT_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_INIT; + + SSH_LOG(SSH_LOG_DEBUG, + "Sending keyboard-interactive init request"); + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * + * @brief Send the current challenge response and wait for a reply from the + * server. + * + * @returns SSH_AUTH_INFO if more info is needed + * @returns SSH_AUTH_SUCCESS + * @returns SSH_AUTH_FAILURE + * @returns SSH_AUTH_PARTIAL + */ +static int ssh_userauth_kbdint_send(ssh_session session) +{ + uint32_t i; + int rc; + if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND) + goto pending; + if (session->pending_call_state != SSH_PENDING_CALL_NONE) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + rc = ssh_buffer_pack(session->out_buffer, "bd", + SSH2_MSG_USERAUTH_INFO_RESPONSE, + session->kbdint->nprompts); + if (rc < 0) { + goto fail; + } + + for (i = 0; i < session->kbdint->nprompts; i++) { + rc = ssh_buffer_pack(session->out_buffer, "s", + session->kbdint->answers && session->kbdint->answers[i] ? + session->kbdint->answers[i]:""); + if (rc < 0) { + goto fail; + } + } + + session->auth.current_method = SSH_AUTH_METHOD_INTERACTIVE; + session->auth.state = SSH_AUTH_STATE_KBDINT_SENT; + session->pending_call_state = SSH_PENDING_CALL_AUTH_KBDINT_SEND; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + SSH_LOG(SSH_LOG_DEBUG, + "Sending keyboard-interactive response packet"); + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; +fail: + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + + return SSH_AUTH_ERROR; +} + +/** + * @internal + * @brief handles a SSH_USERAUTH_INFO_REQUEST packet, as used in + * keyboard-interactive authentication, and changes the + * authentication state. + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_request) { + ssh_string tmp = NULL; + uint32_t nprompts; + uint32_t i; + int rc; + (void)user; + (void)type; + + + if (session->kbdint == NULL) { + session->kbdint = ssh_kbdint_new(); + if (session->kbdint == NULL) { + ssh_set_error_oom(session); + return SSH_PACKET_USED; + } + } else { + ssh_kbdint_clean(session->kbdint); + } + + rc = ssh_buffer_unpack(packet, "ssSd", + &session->kbdint->name, /* name of the "asking" window shown to client */ + &session->kbdint->instruction, + &tmp, /* to ignore */ + &nprompts + ); + + /* We don't care about tmp */ + SSH_STRING_FREE(tmp); + + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid USERAUTH_INFO_REQUEST msg"); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_DEBUG, + "%" PRIu32 " keyboard-interactive prompts", nprompts); + if (nprompts > KBDINT_MAX_PROMPT) { + ssh_set_error(session, SSH_FATAL, + "Too much prompts requested by the server: %" PRIu32 " (0x%.4" PRIx32 ")", + nprompts, nprompts); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + + session->kbdint->nprompts = nprompts; + session->kbdint->nanswers = nprompts; + session->kbdint->prompts = calloc(nprompts, sizeof(char *)); + if (session->kbdint->prompts == NULL) { + session->kbdint->nprompts = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + + session->kbdint->echo = calloc(nprompts, sizeof(unsigned char)); + if (session->kbdint->echo == NULL) { + session->kbdint->nprompts = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + + for (i = 0; i < nprompts; i++) { + rc = ssh_buffer_unpack(packet, "sb", + &session->kbdint->prompts[i], + &session->kbdint->echo[i]); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Short INFO_REQUEST packet"); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + return SSH_PACKET_USED; + } + } + session->auth.state=SSH_AUTH_STATE_INFO; + + return SSH_PACKET_USED; +} + +/** + * @brief Try to authenticate through the "keyboard-interactive" method. + * + * @param[in] session The ssh session to use. + * + * @param[in] user The username to authenticate. You can specify NULL if + * ssh_option_set_username() has been used. You cannot try + * two different logins in a row. + * + * @param[in] submethods Undocumented. Set it to NULL. + * + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_INFO: The server asked some questions. Use + * ssh_userauth_kbdint_getnprompts() and such.\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + * + * @see ssh_userauth_kbdint_getnprompts() + * @see ssh_userauth_kbdint_getname() + * @see ssh_userauth_kbdint_getinstruction() + * @see ssh_userauth_kbdint_getprompt() + * @see ssh_userauth_kbdint_setanswer() + */ +int ssh_userauth_kbdint(ssh_session session, const char *user, + const char *submethods) +{ + int rc = SSH_AUTH_ERROR; + + if (session == NULL) { + return SSH_AUTH_ERROR; + } + + if ((session->pending_call_state == SSH_PENDING_CALL_NONE && session->kbdint == NULL) || + session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_INIT) + rc = ssh_userauth_kbdint_init(session, user, submethods); + else if (session->pending_call_state == SSH_PENDING_CALL_AUTH_KBDINT_SEND || + session->kbdint != NULL) { + /* + * If we are at this point, it is because session->kbdint exists. + * It means the user has set some information there we need to send + * the server and then we need to ack the status (new questions or ok + * pass in). + * It is possible that session->kbdint is NULL while we're waiting for + * a reply, hence the test for the pending call. + */ + rc = ssh_userauth_kbdint_send(session); + } else { + /* We are here because session->kbdint == NULL & state != NONE. + * This should not happen + */ + rc = SSH_AUTH_ERROR; + ssh_set_error(session, SSH_FATAL, "Invalid state in %s", __func__); + } + return rc; +} + +/** + * @brief Get the number of prompts (questions) the server has given. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The number of prompts. + */ +int ssh_userauth_kbdint_getnprompts(ssh_session session) +{ + if (session == NULL) { + return SSH_ERROR; + } + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + return session->kbdint->nprompts; +} + +/** + * @brief Get the "name" of the message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The name of the message block. Do not free it. + */ +const char *ssh_userauth_kbdint_getname(ssh_session session) +{ + if (session == NULL) { + return NULL; + } + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + return session->kbdint->name; +} + +/** + * @brief Get the "instruction" of the message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @returns The instruction of the message block. + */ + +const char *ssh_userauth_kbdint_getinstruction(ssh_session session) +{ + if (session == NULL) + return NULL; + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + return session->kbdint->instruction; +} + +/** + * @brief Get a prompt from a message block. + * + * Once you have called ssh_userauth_kbdint() and received SSH_AUTH_INFO return + * code, this function can be used to retrieve information about the keyboard + * interactive authentication questions sent by the remote host. + * + * @param[in] session The ssh session to use. + * + * @param[in] i The index number of the i'th prompt. + * + * @param[out] echo This is an optional variable. You can obtain a + * boolean if the user input should be echoed or + * hidden. For passwords it is usually hidden. + * + * @returns A pointer to the prompt. Do not free it. + * + * @code + * const char prompt; + * char echo; + * + * prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + * if (echo) ... + * @endcode + */ +const char * +ssh_userauth_kbdint_getprompt(ssh_session session, unsigned int i, char *echo) +{ + if (session == NULL) + return NULL; + if (session->kbdint == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + if (i >= session->kbdint->nprompts) { + ssh_set_error_invalid(session); + return NULL; + } + + if (echo) { + *echo = (char)session->kbdint->echo[i]; + } + + return session->kbdint->prompts[i]; +} + +#ifdef WITH_SERVER +/** + * @brief Get the number of answers the client has given. + * + * @param[in] session The ssh session to use. + * + * @returns The number of answers. + */ +int ssh_userauth_kbdint_getnanswers(ssh_session session) +{ + if (session == NULL || session->kbdint == NULL) { + return SSH_ERROR; + } + return session->kbdint->nanswers; +} + +/** + * @brief Get the answer to a question from a message block. + * + * @param[in] session The ssh session to use. + * + * @param[in] i index The number of the ith answer. + * + * @return The answer string, or NULL if the answer is not + * available. Do not free the string. + */ +const char *ssh_userauth_kbdint_getanswer(ssh_session session, unsigned int i) +{ + if (session == NULL || session->kbdint == NULL + || session->kbdint->answers == NULL) { + return NULL; + } + if (i >= session->kbdint->nanswers) { + return NULL; + } + + return session->kbdint->answers[i]; +} +#endif + +/** + * @brief Set the answer for a question from a message block. + * + * If you have called ssh_userauth_kbdint() and got SSH_AUTH_INFO, this + * function returns the questions from the server. + * + * @param[in] session The ssh session to use. + * + * @param[in] i index The number of the ith prompt. + * + * @param[in] answer The answer to give to the server. The answer MUST be + * encoded UTF-8. It is up to the server how to interpret + * the value and validate it. However, if you read the + * answer in some other encoding, you MUST convert it to + * UTF-8. + * + * @return 0 on success, < 0 on error. + */ +int +ssh_userauth_kbdint_setanswer(ssh_session session, unsigned int i, + const char *answer) +{ + if (session == NULL) { + return -1; + } + if (answer == NULL || session->kbdint == NULL || + i >= session->kbdint->nprompts) { + ssh_set_error_invalid(session); + return -1; + } + + if (session->kbdint->answers == NULL) { + session->kbdint->answers = calloc(session->kbdint->nprompts, sizeof(char *)); + if (session->kbdint->answers == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (session->kbdint->answers[i]) { + ssh_burn(session->kbdint->answers[i], + strlen(session->kbdint->answers[i])); + SAFE_FREE(session->kbdint->answers[i]); + } + + session->kbdint->answers[i] = strdup(answer); + if (session->kbdint->answers[i] == NULL) { + ssh_set_error_oom(session); + return -1; + } + + return 0; +} + +/** + * @brief Try to authenticate through the "gssapi-with-mic" method. + * + * @param[in] session The ssh session to use. + * + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_PARTIAL: You've been partially authenticated, you still + * have to use another method\n + * SSH_AUTH_SUCCESS: Authentication success\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_userauth_gssapi(ssh_session session) +{ + int rc = SSH_AUTH_DENIED; +#ifdef WITH_GSSAPI + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_GSSAPI_MIC: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-with-mic"); + + session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_MIC; + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_GSSAPI_MIC; + rc = ssh_gssapi_auth_mic(session); + + if (rc == SSH_AUTH_ERROR || rc == SSH_AUTH_DENIED) { + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; + } + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } +#else + (void) session; /* unused */ +#endif + return rc; +} + +/** + * @brief Try to authenticate through the "gssapi-keyex" method. + * + * @param[in] session The ssh session to use. + * + * @returns + * - `SSH_AUTH_ERROR`: A serious error happened. + * - `SSH_AUTH_DENIED`: Authentication failed : use another method. + * - `SSH_AUTH_PARTIAL`: You've been partially authenticated, you still + * have to use another method. + * - `SSH_AUTH_SUCCESS`: Authentication success. + * - `SSH_AUTH_AGAIN`: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_userauth_gssapi_keyex(ssh_session session) +{ + int rc = SSH_AUTH_DENIED; +#ifdef WITH_GSSAPI + OM_uint32 min_stat; + gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; + + switch (session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX: + goto pending; + default: + ssh_set_error(session, + SSH_FATAL, + "Wrong state (%d) during pending SSH call", + session->pending_call_state); + return SSH_ERROR; + } + + /* Check if GSSAPI Key exchange was performed */ + if (!ssh_session_kex_is_gss(session)) { + ssh_set_error(session, + SSH_FATAL, + "Attempt to authenticate with gssapi-keyex without " + "doing GSSAPI Key exchange."); + return SSH_ERROR; + } + + if (session->gssapi == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + return SSH_ERROR; + } + + rc = ssh_userauth_request_service(session); + if (rc == SSH_AGAIN) { + return SSH_AUTH_AGAIN; + } else if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi-keyex"); + + session->auth.current_method = SSH_AUTH_METHOD_GSSAPI_KEYEX; + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_AUTH_GSSAPI_KEYEX; + + SAFE_FREE(session->gssapi->user); + session->gssapi->user = strdup(session->opts.username); + if (session->gssapi->user == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_gssapi_auth_keyex_mic(session, &mic_token_buf); + if (rc != SSH_OK) { + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bsssdP", + SSH2_MSG_USERAUTH_REQUEST, + session->opts.username, + "ssh-connection", + "gssapi-keyex", + mic_token_buf.length, + (size_t)mic_token_buf.length, + mic_token_buf.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + session->auth.state = SSH_AUTH_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + gss_release_buffer(&min_stat, &mic_token_buf); + return rc; + } + + gss_release_buffer(&min_stat, &mic_token_buf); + + session->auth.state = SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT; + + ssh_packet_send(session); + +pending: + rc = ssh_userauth_get_response(session); + if (rc != SSH_AUTH_AGAIN) { + session->pending_call_state = SSH_PENDING_CALL_NONE; + } +#else + (void)session; /* unused */ +#endif + return rc; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/base64.c b/src/libs/libssh-0.12.2/src/base64.c new file mode 100644 index 000000000000..81dc7a1b6a5e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/base64.c @@ -0,0 +1,314 @@ +/*#pragma GCC error "ERROR"*/ +/* + * base64.c - support for base64 alphabet system, described in RFC1521 + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2005 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* just the dirtiest part of code i ever made */ +#include "config.h" + +#include + +#include "libssh/priv.h" +#include "libssh/buffer.h" + +/* Do not allow encoding more than 256MB of data */ +#define BASE64_MAX_INPUT_LEN 256 * 1024 * 1024 + +static +const uint8_t alphabet[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ" + "abcdefghijklmnopqrstuvwxyz" + "0123456789+/"; + +/* Transformations */ +#define SET_A(n, i) do { (n) |= ((i) & 63) <<18; } while (0) +#define SET_B(n, i) do { (n) |= ((i) & 63) <<12; } while (0) +#define SET_C(n, i) do { (n) |= ((i) & 63) << 6; } while (0) +#define SET_D(n, i) do { (n) |= ((i) & 63); } while (0) + +#define GET_A(n) (unsigned char) (((n) & 0xff0000) >> 16) +#define GET_B(n) (unsigned char) (((n) & 0xff00) >> 8) +#define GET_C(n) (unsigned char) ((n) & 0xff) + +static int _base64_to_bin(unsigned char dest[3], const char *source, int num); +static int get_equals(char *string); + +/* First part: base64 to binary */ + +/** + * @internal + * + * @brief Translates a base64 string into a binary one. + * + * @returns A buffer containing the decoded string, NULL if something went + * wrong (e.g. incorrect char). + */ +ssh_buffer base64_to_bin(const char *source) +{ + ssh_buffer buffer = NULL; + unsigned char block[3]; + char *base64 = NULL; + char *ptr = NULL; + size_t len; + int equals; + + base64 = strdup(source); + if (base64 == NULL) { + return NULL; + } + ptr = base64; + + /* Get the number of equals signs, which mirrors the padding */ + equals = get_equals(ptr); + if (equals > 2) { + SAFE_FREE(base64); + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + SAFE_FREE(base64); + return NULL; + } + /* + * The base64 buffer often contains sensitive data. Make sure we don't leak + * sensitive data + */ + ssh_buffer_set_secure(buffer); + + len = strlen(ptr); + while (len > 4) { + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + len -= 4; + ptr += 4; + } + + /* + * Depending on the number of bytes resting, there are 3 possibilities + * from the RFC. + */ + switch (len) { + /* + * (1) The final quantum of encoding input is an integral multiple of + * 24 bits. Here, the final unit of encoded output will be an integral + * multiple of 4 characters with no "=" padding + */ + case 4: + if (equals != 0) { + goto error; + } + if (_base64_to_bin(block, ptr, 3) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 3) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + /* + * (2) The final quantum of encoding input is exactly 8 bits; here, the + * final unit of encoded output will be two characters followed by + * two "=" padding characters. + */ + case 2: + if (equals != 2) { + goto error; + } + + if (_base64_to_bin(block, ptr, 1) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 1) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + /* + * The final quantum of encoding input is exactly 16 bits. Here, the final + * unit of encoded output will be three characters followed by one "=" + * padding character. + */ + case 3: + if (equals != 1) { + goto error; + } + if (_base64_to_bin(block, ptr, 2) < 0) { + goto error; + } + if (ssh_buffer_add_data(buffer, block, 2) < 0) { + goto error; + } + SAFE_FREE(base64); + + return buffer; + default: + /* 4,3,2 are the only padding size allowed */ + goto error; + } + +error: + SAFE_FREE(base64); + SSH_BUFFER_FREE(buffer); + return NULL; +} + +#define BLOCK(letter, n) do {ptr = strchr((const char *)alphabet, source[n]); \ + if(!ptr) return -1; \ + i = ptr - (const char *)alphabet; \ + SET_##letter(*block, i); \ + } while(0) + +/* Returns 0 if ok, -1 if not (ie invalid char into the stuff) */ +static int to_block4(unsigned long *block, const char *source, int num) +{ + const char *ptr = NULL; + size_t i; + + *block = 0; + if (num < 1) { + return 0; + } + + BLOCK(A, 0); /* 6 bit */ + BLOCK(B, 1); /* 12 bit */ + + if (num < 2) { + return 0; + } + + BLOCK(C, 2); /* 18 bit */ + + if (num < 3) { + return 0; + } + + BLOCK(D, 3); /* 24 bit */ + + return 0; +} + +/* num = numbers of final bytes to be decoded */ +static int _base64_to_bin(unsigned char dest[3], const char *source, int num) +{ + unsigned long block; + + if (to_block4(&block, source, num) < 0) { + return -1; + } + dest[0] = GET_A(block); + dest[1] = GET_B(block); + dest[2] = GET_C(block); + + return 0; +} + +/* Count the number of "=" signs and replace them by zeroes */ +static int get_equals(char *string) +{ + char *ptr = string; + int num = 0; + + while ((ptr = strchr(ptr, '=')) != NULL) { + num++; + *ptr = '\0'; + ptr++; + } + + return num; +} + +/* thanks sysk for debugging my mess :) */ +static void _bin_to_base64(uint8_t *dest, + const uint8_t source[3], + size_t len) +{ +#define BITS(n) ((1 << (n)) - 1) + switch (len) { + case 1: + dest[0] = alphabet[(source[0] >> 2)]; + dest[1] = alphabet[((source[0] & BITS(2)) << 4)]; + dest[2] = '='; + dest[3] = '='; + break; + case 2: + dest[0] = alphabet[source[0] >> 2]; + dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)]; + dest[2] = alphabet[(source[1] & BITS(4)) << 2]; + dest[3] = '='; + break; + case 3: + dest[0] = alphabet[(source[0] >> 2)]; + dest[1] = alphabet[(source[1] >> 4) | ((source[0] & BITS(2)) << 4)]; + dest[2] = alphabet[(source[2] >> 6) | (source[1] & BITS(4)) << 2]; + dest[3] = alphabet[source[2] & BITS(6)]; + break; + } +#undef BITS +} + +/** + * @internal + * + * @brief Converts binary data to a base64 string. + * + * @returns the converted string + */ +uint8_t *bin_to_base64(const uint8_t *source, size_t len) +{ + uint8_t *base64 = NULL; + uint8_t *ptr = NULL; + size_t flen = 0; + + /* Set the artificial upper limit for the input. Otherwise on 32b arch, the + * following line could overflow for sizes larger than SIZE_MAX / 4 */ + if (len > BASE64_MAX_INPUT_LEN) { + return NULL; + } + + flen = len + (3 - (len % 3)); /* round to upper 3 multiple */ + flen = (4 * flen) / 3 + 1; + + base64 = malloc(flen); + if (base64 == NULL) { + return NULL; + } + ptr = base64; + + while (len > 0) { + _bin_to_base64(ptr, source, len > 3 ? 3 : len); + ptr += 4; + if (len < 3) { + break; + } + source += 3; + len -= 3; + } + ptr[0] = '\0'; + + return base64; +} diff --git a/src/libs/libssh-0.12.2/src/bignum.c b/src/libs/libssh-0.12.2/src/bignum.c new file mode 100644 index 000000000000..b18c1162ce14 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/bignum.c @@ -0,0 +1,107 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "libssh/priv.h" +#include "libssh/bignum.h" +#include "libssh/string.h" + +static ssh_string make_bignum_string(bignum num, size_t pad_to_len) +{ + ssh_string ptr = NULL; + size_t pad = 0; + size_t len = bignum_num_bytes(num); + size_t bits = bignum_num_bits(num); + + if (pad_to_len == 0) { + /* If the first bit is set we have a negative number */ + if (!(bits % 8) && bignum_is_bit_set(num, bits - 1)) { + pad++; + } + } else { + if (len > pad_to_len) { + return NULL; + } + pad = pad_to_len - len; + } + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_TRACE, "%zu bits, %zu bytes, %zu padding", bits, len, pad); +#endif /* DEBUG_CRYPTO */ + + ptr = ssh_string_new(len + pad); + if (ptr == NULL) { + return NULL; + } + + /* We have a negative number so we need a leading zero */ + if (pad) { + memset(ptr->data, 0, pad); + } + + bignum_bn2bin(num, len, ptr->data + pad); + + return ptr; +} + +ssh_string ssh_make_bignum_string(bignum num) +{ + return make_bignum_string(num, 0); +} + +ssh_string ssh_make_padded_bignum_string(bignum num, size_t pad_len) +{ + return make_bignum_string(num, pad_len); +} + +bignum ssh_make_string_bn(ssh_string string) +{ + bignum bn = NULL; + size_t len = ssh_string_len(string); + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_TRACE, + "Importing a %zu bits, %zu bytes object ...", + len * 8, + len); +#endif /* DEBUG_CRYPTO */ + + bignum_bin2bn(string->data, (int)len, &bn); + + return bn; +} + +/* prints the bignum on stderr */ +void ssh_print_bignum(const char *name, const_bignum num) +{ + unsigned char *hex = NULL; + if (num != NULL) { + bignum_bn2hex(num, &hex); + } + SSH_LOG(SSH_LOG_DEBUG, + "%s value: %s", + name, + (hex == NULL) ? "(null)" : (char *)hex); + ssh_crypto_free(hex); +} diff --git a/src/libs/libssh-0.12.2/src/bind.c b/src/libs/libssh-0.12.2/src/bind.c new file mode 100644 index 000000000000..97feaac26849 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/bind.c @@ -0,0 +1,611 @@ +/* + * bind.c : all ssh_bind functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2004-2005 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/bind.h" +#include "libssh/libssh.h" +#include "libssh/server.h" +#include "libssh/pki.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/token.h" + +/** + * @addtogroup libssh_server + * + * @{ + */ + + +#ifdef _WIN32 +#include +#include +#include + +/* + * is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. + */ +#ifdef HAVE_WSPIAPI_H +# include +#endif + +#define SOCKOPT_TYPE_ARG4 char + +#else /* _WIN32 */ + +#include +#include +#include +#define SOCKOPT_TYPE_ARG4 int + +#endif /* _WIN32 */ + +static socket_t bind_socket(ssh_bind sshbind, const char *hostname, + int port) { + char port_c[6]; + struct addrinfo *ai = NULL; + struct addrinfo hints; + int opt = 1; + socket_t s; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + ZERO_STRUCT(hints); + + hints.ai_flags = AI_PASSIVE; + hints.ai_socktype = SOCK_STREAM; + + snprintf(port_c, 6, "%d", port); + rc = getaddrinfo(hostname, port_c, &hints, &ai); + if (rc != 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Resolving %s: %s", hostname, gai_strerror(rc)); + return -1; + } + + s = socket (ai->ai_family, + ai->ai_socktype, + ai->ai_protocol); + if (s == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + freeaddrinfo (ai); + return -1; + } + + if (setsockopt(s, SOL_SOCKET, SO_REUSEADDR, + (char *)&opt, sizeof(opt)) < 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Setting socket options failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + freeaddrinfo (ai); + CLOSE_SOCKET(s); + return -1; + } + + if (bind(s, ai->ai_addr, ai->ai_addrlen) != 0) { + ssh_set_error(sshbind, + SSH_FATAL, + "Binding to %s:%d: %s", + hostname, + port, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + freeaddrinfo (ai); + CLOSE_SOCKET(s); + return -1; + } + + freeaddrinfo (ai); + return s; +} + +ssh_bind ssh_bind_new(void) +{ + ssh_bind ptr = NULL; + + ptr = calloc(1, sizeof(struct ssh_bind_struct)); + if (ptr == NULL) { + return NULL; + } + ptr->bindfd = SSH_INVALID_SOCKET; + ptr->bindport = 22; + ptr->common.log_verbosity = 0; + + return ptr; +} + +static int ssh_bind_import_keys(ssh_bind sshbind) { + int rc; + +#ifdef HAVE_ECC + if (sshbind->ecdsa == NULL && sshbind->ecdsakey != NULL) { + rc = ssh_pki_import_privkey_file(sshbind->ecdsakey, + NULL, + NULL, + NULL, + &sshbind->ecdsa); + if (rc == SSH_ERROR || rc == SSH_EOF) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private ECDSA host key"); + return SSH_ERROR; + } + + if (!is_ecdsa_key_type(ssh_key_type(sshbind->ecdsa))) { + ssh_set_error(sshbind, SSH_FATAL, + "The ECDSA host key has the wrong type"); + ssh_key_free(sshbind->ecdsa); + sshbind->ecdsa = NULL; + return SSH_ERROR; + } + } +#endif + + if (sshbind->rsa == NULL && sshbind->rsakey != NULL) { + rc = ssh_pki_import_privkey_file(sshbind->rsakey, + NULL, + NULL, + NULL, + &sshbind->rsa); + if (rc == SSH_ERROR || rc == SSH_EOF) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private RSA host key"); + return SSH_ERROR; + } + + if (ssh_key_type(sshbind->rsa) != SSH_KEYTYPE_RSA) { + ssh_set_error(sshbind, SSH_FATAL, + "The RSA host key has the wrong type"); + ssh_key_free(sshbind->rsa); + sshbind->rsa = NULL; + return SSH_ERROR; + } + } + + if (sshbind->ed25519 == NULL && sshbind->ed25519key != NULL) { + rc = ssh_pki_import_privkey_file(sshbind->ed25519key, + NULL, + NULL, + NULL, + &sshbind->ed25519); + if (rc == SSH_ERROR || rc == SSH_EOF) { + ssh_set_error(sshbind, SSH_FATAL, + "Failed to import private ED25519 host key"); + return SSH_ERROR; + } + + if (ssh_key_type(sshbind->ed25519) != SSH_KEYTYPE_ED25519) { + ssh_set_error(sshbind, SSH_FATAL, + "The ED25519 host key has the wrong type"); + ssh_key_free(sshbind->ed25519); + sshbind->ed25519 = NULL; + return SSH_ERROR; + } + } + + return SSH_OK; +} + +int ssh_bind_listen(ssh_bind sshbind) +{ + const char *host = NULL; + socket_t fd; + int rc; + + /* Apply global bind configurations, if it hasn't been applied before */ + rc = ssh_bind_options_parse_config(sshbind, NULL); + if (rc != 0) { + ssh_set_error(sshbind, SSH_FATAL, "Could not parse global config"); + return SSH_ERROR; + } + + /* Set default hostkey paths if no hostkey was found before */ + if (sshbind->ecdsakey == NULL && + sshbind->rsakey == NULL && + sshbind->ed25519key == NULL) { + + sshbind->ecdsakey = strdup("/etc/ssh/ssh_host_ecdsa_key"); + sshbind->rsakey = strdup("/etc/ssh/ssh_host_rsa_key"); + sshbind->ed25519key = strdup("/etc/ssh/ssh_host_ed25519_key"); + } + + if (sshbind->rsa == NULL && + sshbind->ecdsa == NULL && + sshbind->ed25519 == NULL) { + rc = ssh_bind_import_keys(sshbind); + if (rc == SSH_ERROR) { + if (!sshbind->gssapi_key_exchange) { + ssh_set_error(sshbind, SSH_FATAL, "No usable hostkeys found"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, + "No usable hostkeys found: Using \"null\" hostkey algorithm"); + } + } + + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + host = sshbind->bindaddr; + if (host == NULL) { + host = "0.0.0.0"; + } + + fd = bind_socket(sshbind, host, sshbind->bindport); + if (fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + + if (listen(fd, 10) < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(sshbind, + SSH_FATAL, + "Listening to socket %d: %s", + fd, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); + return SSH_ERROR; + } + + sshbind->bindfd = fd; + } else { + SSH_LOG(SSH_LOG_DEBUG, "Using app-provided bind socket"); + } + return 0; +} + +int ssh_bind_set_callbacks(ssh_bind sshbind, ssh_bind_callbacks callbacks, void *userdata) +{ + if (sshbind == NULL) { + return SSH_ERROR; + } + if (callbacks == NULL) { + ssh_set_error_invalid(sshbind); + return SSH_ERROR; + } + if (callbacks->size <= 0 || callbacks->size > 1024 * sizeof(void *)) { + ssh_set_error(sshbind, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + } + sshbind->bind_callbacks = callbacks; + sshbind->bind_callbacks_userdata = userdata; + return 0; +} + +/** @internal + * @brief callback being called by poll when an event happens + * + */ +static int ssh_bind_poll_callback(ssh_poll_handle sshpoll, socket_t fd, int revents, void *user) +{ + ssh_bind sshbind = (ssh_bind)user; + (void)sshpoll; + (void)fd; + + if (revents & POLLIN) { + /* new incoming connection */ + if (ssh_callbacks_exists(sshbind->bind_callbacks, incoming_connection)) { + sshbind->bind_callbacks->incoming_connection(sshbind, + sshbind->bind_callbacks_userdata); + } + } + return 0; +} + +/** @internal + * @brief returns the current poll handle, or creates it + * @param sshbind the ssh_bind object + * @returns a ssh_poll handle suitable for operation + */ +ssh_poll_handle ssh_bind_get_poll(ssh_bind sshbind) +{ + short events = POLLIN; + + if (sshbind->poll) { + return sshbind->poll; + } + +#ifdef POLLRDHUP + events |= POLLRDHUP; +#endif /* POLLRDHUP */ + + sshbind->poll = ssh_poll_new(sshbind->bindfd, + events, + ssh_bind_poll_callback, + sshbind); + + return sshbind->poll; +} + +void ssh_bind_set_blocking(ssh_bind sshbind, int blocking) +{ + sshbind->blocking = blocking ? 1 : 0; +} + +socket_t ssh_bind_get_fd(ssh_bind sshbind) +{ + return sshbind->bindfd; +} + +void ssh_bind_set_fd(ssh_bind sshbind, socket_t fd) +{ + sshbind->bindfd = fd; +} + +void ssh_bind_fd_toaccept(ssh_bind sshbind) +{ + sshbind->toaccept = 1; +} + +void ssh_bind_free(ssh_bind sshbind){ + int i; + + if (sshbind == NULL) { + return; + } + + if (sshbind->bindfd >= 0) { + CLOSE_SOCKET(sshbind->bindfd); + } + sshbind->bindfd = SSH_INVALID_SOCKET; + + /* options */ + SAFE_FREE(sshbind->banner); + SAFE_FREE(sshbind->moduli_file); + SAFE_FREE(sshbind->bindaddr); + SAFE_FREE(sshbind->config_dir); + SAFE_FREE(sshbind->pubkey_accepted_key_types); + + SAFE_FREE(sshbind->rsakey); + SAFE_FREE(sshbind->ecdsakey); + SAFE_FREE(sshbind->ed25519key); + SAFE_FREE(sshbind->gssapi_key_exchange_algs); + + ssh_key_free(sshbind->rsa); + sshbind->rsa = NULL; + ssh_key_free(sshbind->ecdsa); + sshbind->ecdsa = NULL; + ssh_key_free(sshbind->ed25519); + sshbind->ed25519 = NULL; + + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (sshbind->wanted_methods[i]) { + SAFE_FREE(sshbind->wanted_methods[i]); + } + } + + SAFE_FREE(sshbind); +} + +int ssh_bind_accept_fd(ssh_bind sshbind, ssh_session session, socket_t fd) +{ + ssh_poll_handle handle = NULL; + int i, rc; + + if (sshbind == NULL) { + return SSH_ERROR; + } + + if (session == NULL){ + ssh_set_error(sshbind, SSH_FATAL,"session is null"); + return SSH_ERROR; + } + + session->server = 1; + + /* Copy options from bind to session */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (sshbind->wanted_methods[i]) { + session->opts.wanted_methods[i] = strdup(sshbind->wanted_methods[i]); + if (session->opts.wanted_methods[i] == NULL) { + return SSH_ERROR; + } + } + } + + if (sshbind->bindaddr == NULL) + session->opts.bindaddr = NULL; + else { + SAFE_FREE(session->opts.bindaddr); + session->opts.bindaddr = strdup(sshbind->bindaddr); + if (session->opts.bindaddr == NULL) { + return SSH_ERROR; + } + } + + if (sshbind->pubkey_accepted_key_types != NULL) { + if (session->opts.pubkey_accepted_types == NULL) { + session->opts.pubkey_accepted_types = strdup(sshbind->pubkey_accepted_key_types); + if (session->opts.pubkey_accepted_types == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } else { + char *p = NULL; + /* If something was set to the session prior to calling this + * function, keep only what is allowed by the options set in + * sshbind */ + p = ssh_find_all_matching(sshbind->pubkey_accepted_key_types, + session->opts.pubkey_accepted_types); + if (p == NULL) { + return SSH_ERROR; + } + + SAFE_FREE(session->opts.pubkey_accepted_types); + session->opts.pubkey_accepted_types = p; + } + } + + session->common.log_verbosity = sshbind->common.log_verbosity; + session->opts.gssapi_key_exchange = sshbind->gssapi_key_exchange; + + if (sshbind->gssapi_key_exchange_algs != NULL) { + SAFE_FREE(session->opts.gssapi_key_exchange_algs); + session->opts.gssapi_key_exchange_algs = + strdup(sshbind->gssapi_key_exchange_algs); + if (session->opts.gssapi_key_exchange_algs == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + if (sshbind->banner != NULL) { + session->server_opts.custombanner = strdup(sshbind->banner); + if (session->server_opts.custombanner == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + if (sshbind->moduli_file != NULL) { + session->server_opts.moduli_file = strdup(sshbind->moduli_file); + if (session->server_opts.moduli_file == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + session->opts.rsa_min_size = sshbind->rsa_min_size; + + ssh_socket_free(session->socket); + session->socket = ssh_socket_new(session); + if (session->socket == NULL) { + /* perhaps it may be better to copy the error from session to sshbind */ + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + rc = ssh_socket_set_fd(session->socket, fd); + if (rc != SSH_OK) { + return rc; + } + handle = ssh_socket_get_poll_handle(session->socket); + if (handle == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + ssh_socket_set_connected(session->socket, handle); + + /* We must try to import any keys that could be imported in case + * we are not using ssh_bind_listen (which is the other place + * where keys can be imported) on this ssh_bind and are instead + * only using ssh_bind_accept_fd to manage sockets ourselves. + */ + if (sshbind->rsa == NULL && + sshbind->ecdsa == NULL && + sshbind->ed25519 == NULL) { + rc = ssh_bind_import_keys(sshbind); + if (rc == SSH_ERROR) { + if (!sshbind->gssapi_key_exchange) { + ssh_set_error(sshbind, SSH_FATAL, "No usable hostkeys found"); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, + "No usable hostkeys found: Using \"null\" hostkey algorithm"); + } + } + +#ifdef HAVE_ECC + if (sshbind->ecdsa) { + session->srv.ecdsa_key = ssh_key_dup(sshbind->ecdsa); + if (session->srv.ecdsa_key == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } +#endif + if (sshbind->rsa) { + session->srv.rsa_key = ssh_key_dup(sshbind->rsa); + if (session->srv.rsa_key == NULL) { + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + if (sshbind->ed25519 != NULL) { + session->srv.ed25519_key = ssh_key_dup(sshbind->ed25519); + if (session->srv.ed25519_key == NULL){ + ssh_set_error_oom(sshbind); + return SSH_ERROR; + } + } + + /* force PRNG to change state in case we fork after ssh_bind_accept */ + ssh_reseed(); + return SSH_OK; +} + +int ssh_bind_accept(ssh_bind sshbind, ssh_session session) +{ + socket_t fd = SSH_INVALID_SOCKET; + int rc; + + if (sshbind->bindfd == SSH_INVALID_SOCKET) { + ssh_set_error(sshbind, SSH_FATAL, + "Can't accept new clients on a not bound socket."); + return SSH_ERROR; + } + + if (session == NULL) { + ssh_set_error(sshbind, SSH_FATAL, "session is null"); + return SSH_ERROR; + } + + fd = accept(sshbind->bindfd, NULL, NULL); + if (fd == SSH_INVALID_SOCKET) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + if (errno == EINTR) { + ssh_set_error(sshbind, SSH_EINTR, + "Accepting a new connection (child signal error): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } else { + ssh_set_error(sshbind, SSH_FATAL, + "Accepting a new connection: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } + return SSH_ERROR; + } + rc = ssh_bind_accept_fd(sshbind, session, fd); + + if (rc == SSH_ERROR) { + CLOSE_SOCKET(fd); + ssh_socket_free(session->socket); + } + + return rc; +} + + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/bind_config.c b/src/libs/libssh-0.12.2/src/bind_config.c new file mode 100644 index 000000000000..a8bf37039eca --- /dev/null +++ b/src/libs/libssh-0.12.2/src/bind_config.c @@ -0,0 +1,741 @@ +/* + * bind_config.c - Parse the SSH server configuration file + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#ifdef HAVE_GLOB_H +# include +#endif + +#include "libssh/bind.h" +#include "libssh/bind_config.h" +#include "libssh/config_parser.h" +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/options.h" + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 1024 +#endif + +/* Flags used for the parser state */ +#define PARSING 1 +#define IN_MATCH (1<<1) + +struct ssh_bind_config_keyword_table_s { + const char *name; + enum ssh_bind_config_opcode_e opcode; + bool allowed_in_match; +}; + +static struct ssh_bind_config_keyword_table_s +ssh_bind_config_keyword_table[] = { + { + .name = "include", + .opcode = BIND_CFG_INCLUDE + }, + { + .name = "hostkey", + .opcode = BIND_CFG_HOSTKEY + }, + { + .name = "listenaddress", + .opcode = BIND_CFG_LISTENADDRESS + }, + { + .name = "port", + .opcode = BIND_CFG_PORT + }, + { + .name = "loglevel", + .opcode = BIND_CFG_LOGLEVEL, + .allowed_in_match = true, + }, + { + .name = "ciphers", + .opcode = BIND_CFG_CIPHERS + }, + { + .name = "macs", + .opcode = BIND_CFG_MACS + }, + { + .name = "kexalgorithms", + .opcode = BIND_CFG_KEXALGORITHMS + }, + { + .name = "match", + .opcode = BIND_CFG_MATCH, + .allowed_in_match = true + }, + { + .name = "pubkeyacceptedkeytypes", + .opcode = BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES, + .allowed_in_match = true + }, + { + .name = "hostkeyalgorithms", + .opcode = BIND_CFG_HOSTKEY_ALGORITHMS, + .allowed_in_match = true + }, + { + .name = "requiredrsasize", + .opcode = BIND_CFG_REQUIRED_RSA_SIZE, + .allowed_in_match = true + }, + { + .opcode = BIND_CFG_UNKNOWN, + } +}; + +enum ssh_bind_config_match_e { + BIND_MATCH_UNKNOWN = -1, + BIND_MATCH_ALL, + BIND_MATCH_USER, + BIND_MATCH_GROUP, + BIND_MATCH_HOST, + BIND_MATCH_LOCALADDRESS, + BIND_MATCH_LOCALPORT, + BIND_MATCH_RDOMAIN, + BIND_MATCH_ADDRESS, +}; + +struct ssh_bind_config_match_keyword_table_s { + const char *name; + enum ssh_bind_config_match_e opcode; +}; + +static struct ssh_bind_config_match_keyword_table_s +ssh_bind_config_match_keyword_table[] = { + { + .name = "all", + .opcode = BIND_MATCH_ALL + }, + { + .name = "user", + .opcode = BIND_MATCH_USER + }, + { + .name = "group", + .opcode = BIND_MATCH_GROUP + }, + { + .name = "host", + .opcode = BIND_MATCH_HOST + }, + { + .name = "localaddress", + .opcode = BIND_MATCH_LOCALADDRESS + }, + { + .name = "localport", + .opcode = BIND_MATCH_LOCALPORT + }, + { + .name = "rdomain", + .opcode = BIND_MATCH_RDOMAIN + }, + { + .name = "address", + .opcode = BIND_MATCH_ADDRESS + }, + { + .opcode = BIND_MATCH_UNKNOWN + }, +}; + +static enum ssh_bind_config_opcode_e +ssh_bind_config_get_opcode(char *keyword, uint32_t *parser_flags) +{ + int i; + + for (i = 0; ssh_bind_config_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_bind_config_keyword_table[i].name) == 0) { + if ((*parser_flags & IN_MATCH) && + !(ssh_bind_config_keyword_table[i].allowed_in_match)) + { + return BIND_CFG_NOT_ALLOWED_IN_MATCH; + } + return ssh_bind_config_keyword_table[i].opcode; + } + } + + return BIND_CFG_UNKNOWN; +} + +static int +ssh_bind_config_parse_line(ssh_bind bind, + const char *line, + unsigned int count, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth); + +#define LIBSSH_BIND_CONF_MAX_DEPTH 16 +static void +local_parse_file(ssh_bind bind, + const char *filename, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth) +{ + FILE *f = NULL; + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + int rv; + + if (depth > LIBSSH_BIND_CONF_MAX_DEPTH) { + ssh_set_error(bind, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { + SSH_LOG(SSH_LOG_RARE, "Cannot find file %s to load", + filename); + return; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", + filename); + + while (fgets(line, sizeof(line), f)) { + count++; + rv = ssh_bind_config_parse_line(bind, line, count, parser_flags, seen, depth); + if (rv < 0) { + fclose(f); + return; + } + } + + fclose(f); + return; +} + +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) +static void local_parse_glob(ssh_bind bind, + const char *fileglob, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth) +{ + glob_t globbuf = { + .gl_flags = 0, + }; + int rt; + u_int i; + + rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf); + if (rt == GLOB_NOMATCH) { + globfree(&globbuf); + return; + } else if (rt != 0) { + SSH_LOG(SSH_LOG_RARE, "Glob error: %s", + fileglob); + globfree(&globbuf); + return; + } + + for (i = 0; i < globbuf.gl_pathc; i++) { + local_parse_file(bind, globbuf.gl_pathv[i], parser_flags, seen, depth); + } + + globfree(&globbuf); +} +#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */ + +static enum ssh_bind_config_match_e +ssh_bind_config_get_match_opcode(const char *keyword) +{ + size_t i; + + for (i = 0; ssh_bind_config_match_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_bind_config_match_keyword_table[i].name) == 0) { + return ssh_bind_config_match_keyword_table[i].opcode; + } + } + + return BIND_MATCH_UNKNOWN; +} + +static int +ssh_bind_config_parse_line(ssh_bind bind, + const char *line, + unsigned int count, + uint32_t *parser_flags, + uint8_t *seen, + unsigned int depth) +{ + enum ssh_bind_config_opcode_e opcode; + const char *p = NULL; + char *s = NULL, *x = NULL; + char *keyword = NULL; + long l; + size_t len; + + int rc = 0; + + if (bind == NULL) { + return -1; + } + + /* Ignore empty lines */ + if (line == NULL || *line == '\0') { + return 0; + } + + if (parser_flags == NULL) { + ssh_set_error_invalid(bind); + return -1; + } + + x = s = strdup(line); + if (s == NULL) { + ssh_set_error_oom(bind); + return -1; + } + + /* Remove trailing spaces */ + for (len = strlen(s) - 1; len > 0; len--) { + if (! isspace(s[len])) { + break; + } + s[len] = '\0'; + } + + keyword = ssh_config_get_token(&s); + if (keyword == NULL || *keyword == '#' || + *keyword == '\0' || *keyword == '\n') { + SAFE_FREE(x); + return 0; + } + + opcode = ssh_bind_config_get_opcode(keyword, parser_flags); + if ((*parser_flags & PARSING) && + opcode != BIND_CFG_HOSTKEY && + opcode != BIND_CFG_INCLUDE && + opcode != BIND_CFG_MATCH && + opcode > BIND_CFG_UNSUPPORTED) { /* Ignore all unknown types here */ + /* Skip all the options that were already applied */ + if (seen[opcode] != 0) { + SAFE_FREE(x); + return 0; + } + seen[opcode] = 1; + } + + switch (opcode) { + case BIND_CFG_INCLUDE: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + local_parse_glob(bind, p, parser_flags, seen, depth + 1); +#else + local_parse_file(bind, p, parser_flags, seen, depth + 1); +#endif /* HAVE_GLOB */ + } + break; + + case BIND_CFG_HOSTKEY: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HOSTKEY, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set Hostkey value '%s'", + count, p); + } + } + break; + case BIND_CFG_LISTENADDRESS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set ListenAddress value '%s'", + count, p); + } + } + break; + case BIND_CFG_PORT: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set Port value '%s'", + count, p); + } + } + break; + case BIND_CFG_CIPHERS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set C->S Ciphers value '%s'", + count, p); + break; + } + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set S->C Ciphers value '%s'", + count, p); + } + } + break; + case BIND_CFG_MACS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set C->S MAC value '%s'", + count, p); + break; + } + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set S->C MAC value '%s'", + count, p); + } + } + break; + case BIND_CFG_LOGLEVEL: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + int value = -1; + + if (strcasecmp(p, "quiet") == 0) { + value = SSH_LOG_NONE; + } else if (strcasecmp(p, "fatal") == 0 || + strcasecmp(p, "error")== 0) { + value = SSH_LOG_WARN; + } else if (strcasecmp(p, "verbose") == 0 || + strcasecmp(p, "info") == 0) { + value = SSH_LOG_INFO; + } else if (strcasecmp(p, "DEBUG") == 0 || + strcasecmp(p, "DEBUG1") == 0) { + value = SSH_LOG_DEBUG; + } else if (strcasecmp(p, "DEBUG2") == 0 || + strcasecmp(p, "DEBUG3") == 0) { + value = SSH_LOG_TRACE; + } + if (value != -1) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, + &value); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set LogLevel value '%s'", + count, p); + } + } + } + break; + case BIND_CFG_KEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_KEY_EXCHANGE, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set KexAlgorithms value '%s'", + count, p); + } + } + break; + case BIND_CFG_MATCH: { + bool negate; + int result = PARSING; + size_t args = 0; + enum ssh_bind_config_match_e opt; + const char *p2 = NULL; + + /* The options set in Match blocks should be applied when a connection + * is accepted, and not right away when parsing the file (as it is + * currently done). This means the configuration files should be parsed + * again or the options set in the Match blocks should be stored and + * applied as necessary. */ + + /* If this is the first Match block, erase the seen table to allow + * options to be overridden. Erasing the seen table was the easiest way + * to allow overriding an option, but only for the first occurrence of + * an option in a Match block. This is sufficient for the current + * implementation which supports only the 'All' criterion, meaning the + * options can be applied right away. */ + if (!(*parser_flags & IN_MATCH)) { + memset(seen, 0x00, BIND_CFG_MAX * sizeof(uint8_t)); + } + + /* In this line the PARSING bit is cleared from the flags */ + *parser_flags = IN_MATCH; + do { + p = p2 = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + break; + } + args++; + SSH_LOG(SSH_LOG_TRACE, "line %d: Processing Match keyword '%s'", + count, p); + + /* If the option is prefixed with ! the result should be negated */ + negate = false; + if (p[0] == '!') { + negate = true; + p++; + } + + opt = ssh_bind_config_get_match_opcode(p); + switch (opt) { + case BIND_MATCH_ALL: + p = ssh_config_get_str_tok(&s, NULL); + if ((args == 1) && (p == NULL || p[0] == '\0')) { + /* The "all" keyword does not accept arguments or modifiers + */ + if (negate == true) { + result = 0; + } + break; + } + ssh_set_error(bind, SSH_FATAL, + "line %d: ERROR - Match all cannot be combined with " + "other Match attributes", count); + SAFE_FREE(x); + return -1; + case BIND_MATCH_USER: + case BIND_MATCH_GROUP: + case BIND_MATCH_HOST: + case BIND_MATCH_LOCALADDRESS: + case BIND_MATCH_LOCALPORT: + case BIND_MATCH_RDOMAIN: + case BIND_MATCH_ADDRESS: + /* Only "All" is supported for now */ + /* Skip one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " + "'%s' requires argument\n", count, p2); + SAFE_FREE(x); + return -1; + } + args++; + SSH_LOG(SSH_LOG_DEBUG, + "line %d: Unsupported Match keyword '%s', ignoring\n", + count, + p2); + result = 0; + break; + case BIND_MATCH_UNKNOWN: + default: + ssh_set_error(bind, SSH_FATAL, + "ERROR - Unknown argument '%s' for Match keyword", p); + SAFE_FREE(x); + return -1; + } + } while (p != NULL && p[0] != '\0'); + if (args == 0) { + ssh_set_error(bind, SSH_FATAL, + "ERROR - Match keyword requires an argument"); + SAFE_FREE(x); + return -1; + } + /* This line only sets the PARSING flag if all checks passed */ + *parser_flags |= result; + break; + } + case BIND_CFG_PUBKEY_ACCEPTED_KEY_TYPES: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set PubKeyAcceptedKeyTypes value '%s'", + count, p); + } + } + break; + case BIND_CFG_HOSTKEY_ALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + if (p && (*parser_flags & PARSING)) { + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, p); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set HostkeyAlgorithms value '%s'", + count, p); + } + } + break; + case BIND_CFG_REQUIRED_RSA_SIZE: + l = ssh_config_get_long(&s, -1); + if (l >= 0 && l <= INT_MAX && (*parser_flags & PARSING)) { + int i = (int)l; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &i); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "line %d: Failed to set RequiredRSASize value '%ld'", + count, + l); + } + } + break; + case BIND_CFG_NOT_ALLOWED_IN_MATCH: + SSH_LOG(SSH_LOG_DEBUG, "Option not allowed in Match block: %s, line: %d", + keyword, count); + break; + case BIND_CFG_UNKNOWN: + SSH_LOG(SSH_LOG_TRACE, "Unknown option: %s, line: %d", + keyword, count); + break; + case BIND_CFG_UNSUPPORTED: + SSH_LOG(SSH_LOG_TRACE, "Unsupported option: %s, line: %d", + keyword, count); + break; + case BIND_CFG_NA: + SSH_LOG(SSH_LOG_TRACE, "Option not applicable: %s, line: %d", + keyword, count); + break; + default: + ssh_set_error(bind, SSH_FATAL, "ERROR - unimplemented opcode: %d", + opcode); + SAFE_FREE(x); + return -1; + break; + } + + SAFE_FREE(x); + return rc; +} + +int ssh_bind_config_parse_file(ssh_bind bind, const char *filename) +{ + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + FILE *f = NULL; + uint32_t parser_flags; + int rv; + + /* This local table is used during the parsing of the current file (and + * files included recursively in this file) to prevent an option to be + * redefined, i.e. the first value set is kept. But this DO NOT prevent the + * option to be redefined later by another file. */ + uint8_t seen[BIND_CFG_MAX] = {0}; + + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { + return 0; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename); + + parser_flags = PARSING; + while (fgets(line, sizeof(line), f)) { + count++; + rv = ssh_bind_config_parse_line(bind, line, count, &parser_flags, seen, 0); + if (rv) { + fclose(f); + return -1; + } + } + + fclose(f); + return 0; +} + +/* @brief Parse configuration string and set the options to the given bind session + * + * @params[in] bind The ssh bind session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_bind_config_parse_string(ssh_bind bind, const char *input) +{ + char line[MAX_LINE_SIZE] = {0}; + const char *c = input, *line_start = input; + unsigned int line_num = 0; + size_t line_len; + uint32_t parser_flags; + int rv; + + /* This local table is used during the parsing of the current file (and + * files included recursively in this file) to prevent an option to be + * redefined, i.e. the first value set is kept. But this DO NOT prevent the + * option to be redefined later by another file. */ + uint8_t seen[BIND_CFG_MAX] = {0}; + + SSH_LOG(SSH_LOG_DEBUG, "Reading bind configuration data from string:"); + SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input); + + parser_flags = PARSING; + while (1) { + line_num++; + line_start = c; + c = strchr(line_start, '\n'); + if (c == NULL) { + /* if there is no newline at the end of the string */ + c = strchr(line_start, '\0'); + } + if (c == NULL) { + /* should not happen, would mean a string without trailing '\0' */ + SSH_LOG(SSH_LOG_WARN, "No trailing '\\0' in config string"); + return SSH_ERROR; + } + line_len = c - line_start; + if (line_len > MAX_LINE_SIZE - 1) { + SSH_LOG(SSH_LOG_WARN, + "Line %u too long: %zu characters", + line_num, + line_len); + return SSH_ERROR; + } + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); + rv = ssh_bind_config_parse_line(bind, line, line_num, &parser_flags, seen, 0); + if (rv < 0) { + return SSH_ERROR; + } + if (*c == '\0') { + break; + } + c++; + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/buffer.c b/src/libs/libssh-0.12.2/src/buffer.c new file mode 100644 index 000000000000..601191755f2b --- /dev/null +++ b/src/libs/libssh-0.12.2/src/buffer.c @@ -0,0 +1,1450 @@ +/* + * buffer.c - buffer functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/bignum.h" + +/* + * Describes a buffer state + * [XXXXXXXXXXXXDATA PAYLOAD XXXXXXXXXXXXXXXXXXXXXXXX] + * ^ ^ ^ ^] + * \_data points\_pos points here \_used points here | / + * here Allocated + */ +struct ssh_buffer_struct { + bool secure; + uint32_t used; + uint32_t allocated; + uint32_t pos; + uint8_t *data; +}; + +/* Buffer size maximum is 256M */ +#define BUFFER_SIZE_MAX 0x10000000 + +/** + * @defgroup libssh_buffer The SSH buffer functions + * @ingroup libssh + * + * Functions to handle SSH buffers. + * + * @{ + */ + + +#ifdef DEBUG_BUFFER +/** + * @internal + * + * @brief Check that preconditions and postconditions are valid. + * + * @param[in] buf The buffer to check. + */ +static void buffer_verify(ssh_buffer buf) +{ + bool do_abort = false; + + if (buf->data == NULL) { + return; + } + + if (buf->used > buf->allocated) { + fprintf(stderr, + "BUFFER ERROR: allocated %u, used %u\n", + buf->allocated, + buf->used); + do_abort = true; + } + if (buf->pos > buf->used) { + fprintf(stderr, + "BUFFER ERROR: position %u, used %u\n", + buf->pos, + buf->used); + do_abort = true; + } + if (buf->pos > buf->allocated) { + fprintf(stderr, + "BUFFER ERROR: position %u, allocated %u\n", + buf->pos, + buf->allocated); + do_abort = true; + } + if (do_abort) { + abort(); + } +} + +#else +#define buffer_verify(x) +#endif + +/** + * @brief Create a new SSH buffer. + * + * @return A newly initialized SSH buffer, NULL on error. + */ +struct ssh_buffer_struct *ssh_buffer_new(void) +{ + struct ssh_buffer_struct *buf = NULL; + int rc; + + buf = calloc(1, sizeof(struct ssh_buffer_struct)); + if (buf == NULL) { + return NULL; + } + + /* + * Always preallocate 64 bytes. + * + * -1 for realloc_buffer magic. + */ + rc = ssh_buffer_allocate_size(buf, 64 - 1); + if (rc != 0) { + SAFE_FREE(buf); + return NULL; + } + buffer_verify(buf); + + return buf; +} + +/** + * @brief Deallocate a SSH buffer. + * + * \param[in] buffer The buffer to free. + */ +void ssh_buffer_free(struct ssh_buffer_struct *buffer) +{ + if (buffer == NULL) { + return; + } + buffer_verify(buffer); + + if (buffer->secure && buffer->allocated > 0) { + /* burn the data */ + ssh_burn(buffer->data, buffer->allocated); + SAFE_FREE(buffer->data); + + ssh_burn(buffer, sizeof(struct ssh_buffer_struct)); + } else { + SAFE_FREE(buffer->data); + } + SAFE_FREE(buffer); +} + +/** + * @brief Sets the buffer as secure. + * + * A secure buffer will never leave cleartext data in the heap + * after being reallocated or freed. + * + * @param[in] buffer buffer to set secure. + */ +void ssh_buffer_set_secure(ssh_buffer buffer) +{ + buffer->secure = true; +} + +static int realloc_buffer(struct ssh_buffer_struct *buffer, uint32_t needed) +{ + uint32_t smallest = 1; + uint8_t *new = NULL; + + buffer_verify(buffer); + + /* Find the smallest power of two which is greater or equal to needed */ + while(smallest <= needed) { + if (smallest == 0) { + return -1; + } + smallest <<= 1; + } + needed = smallest; + + if (needed > BUFFER_SIZE_MAX) { + return -1; + } + + if (buffer->secure) { + new = malloc(needed); + if (new == NULL) { + return -1; + } + memcpy(new, buffer->data, buffer->used); + ssh_burn(buffer->data, buffer->used); + SAFE_FREE(buffer->data); + } else { + new = realloc(buffer->data, needed); + if (new == NULL) { + return -1; + } + } + buffer->data = new; + buffer->allocated = needed; + + buffer_verify(buffer); + return 0; +} + +/** @internal + * @brief shifts a buffer to remove unused data in the beginning + * @param buffer SSH buffer + */ +static void buffer_shift(ssh_buffer buffer) +{ + size_t burn_pos = buffer->pos; + + buffer_verify(buffer); + + if (buffer->pos == 0) { + return; + } + memmove(buffer->data, + buffer->data + buffer->pos, + buffer->used - buffer->pos); + buffer->used -= buffer->pos; + buffer->pos = 0; + + if (buffer->secure) { + void *ptr = buffer->data + buffer->used; + ssh_burn(ptr, burn_pos); + } + + buffer_verify(buffer); +} + +/** + * @brief Reinitialize a SSH buffer. + * + * In case the buffer has exceeded 64K in size, the buffer will be reallocated + * to 64K. + * + * @param[in] buffer The buffer to reinitialize. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_reinit(struct ssh_buffer_struct *buffer) +{ + if (buffer == NULL) { + return -1; + } + + buffer_verify(buffer); + + if (buffer->secure && buffer->allocated > 0) { + ssh_burn(buffer->data, buffer->allocated); + } + buffer->used = 0; + buffer->pos = 0; + + /* If the buffer is bigger then 64K, reset it to 64K */ + if (buffer->allocated > 65536) { + int rc; + + /* -1 for realloc_buffer magic */ + rc = realloc_buffer(buffer, 65536 - 1); + if (rc != 0) { + return -1; + } + } + + buffer_verify(buffer); + + return 0; +} + +/** + * @brief Add data at the tail of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] data A pointer to the data to add. + * + * @param[in] len The length of the data to add. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_add_data(struct ssh_buffer_struct *buffer, const void *data, uint32_t len) +{ + if (buffer == NULL) { + return -1; + } + + buffer_verify(buffer); + + if (data == NULL) { + return -1; + } + + if (buffer->used + len < len) { + return -1; + } + + if (buffer->allocated < (buffer->used + len)) { + if (buffer->pos > 0) { + buffer_shift(buffer); + } + if (realloc_buffer(buffer, buffer->used + len) < 0) { + return -1; + } + } + + memcpy(buffer->data + buffer->used, data, len); + buffer->used += len; + buffer_verify(buffer); + return 0; +} + +/** + * @brief Ensure the buffer has at least a certain preallocated size. + * + * @param[in] buffer The buffer to enlarge. + * + * @param[in] len The length to ensure as allocated. + * + * @return 0 on success, < 0 on error. + */ +int ssh_buffer_allocate_size(struct ssh_buffer_struct *buffer, + uint32_t len) +{ + buffer_verify(buffer); + + if (buffer->allocated < len) { + if (buffer->pos > 0) { + buffer_shift(buffer); + } + if (realloc_buffer(buffer, len) < 0) { + return -1; + } + } + + buffer_verify(buffer); + + return 0; +} + +/** + * @internal + * + * @brief Allocate space for data at the tail of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] len The length of the data to add. + * + * @return Pointer on the allocated space + * NULL on error. + */ +void *ssh_buffer_allocate(struct ssh_buffer_struct *buffer, uint32_t len) +{ + void *ptr = NULL; + + buffer_verify(buffer); + + if (buffer->used + len < len) { + return NULL; + } + + if (buffer->allocated < (buffer->used + len)) { + if (buffer->pos > 0) { + buffer_shift(buffer); + } + + if (realloc_buffer(buffer, buffer->used + len) < 0) { + return NULL; + } + } + + ptr = buffer->data + buffer->used; + buffer->used+=len; + buffer_verify(buffer); + + return ptr; +} + +/** + * @internal + * + * @brief Add a SSH string to the tail of a buffer. + * + * @param[in] buffer The buffer to add the string. + * + * @param[in] string The SSH String to add. + * + * @return 0 on success, < 0 on error. + */ +int +ssh_buffer_add_ssh_string(struct ssh_buffer_struct *buffer, + struct ssh_string_struct *string) +{ + size_t len; + int rc; + + if (string == NULL) { + return -1; + } + + len = ssh_string_len(string) + sizeof(uint32_t); + /* this can't overflow the uint32_t as the + * STRING_SIZE_MAX is (UINT32_MAX >> 8) + 1 */ + rc = ssh_buffer_add_data(buffer, string, (uint32_t)len); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 32 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 32 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u32(struct ssh_buffer_struct *buffer,uint32_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(data)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 16 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 16 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u16(struct ssh_buffer_struct *buffer,uint16_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(data)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 64 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 64 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u64(struct ssh_buffer_struct *buffer, uint64_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(data)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add a 8 bits unsigned integer to the tail of a buffer. + * + * @param[in] buffer The buffer to add the integer. + * + * @param[in] data The 8 bits integer to add. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_u8(struct ssh_buffer_struct *buffer,uint8_t data) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, &data, sizeof(uint8_t)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @internal + * + * @brief Add data at the head of a buffer. + * + * @param[in] buffer The buffer to add the data. + * + * @param[in] data The data to prepend. + * + * @param[in] len The length of data to prepend. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_prepend_data(struct ssh_buffer_struct *buffer, const void *data, + uint32_t len) { + buffer_verify(buffer); + + if(len <= buffer->pos){ + /* It's possible to insert data between begin and pos */ + memcpy(buffer->data + (buffer->pos - len), data, len); + buffer->pos -= len; + buffer_verify(buffer); + return 0; + } + /* pos isn't high enough */ + if (buffer->used - buffer->pos + len < len) { + return -1; + } + + if (buffer->allocated < (buffer->used - buffer->pos + len)) { + if (realloc_buffer(buffer, buffer->used - buffer->pos + len) < 0) { + return -1; + } + } + memmove(buffer->data + len, buffer->data + buffer->pos, buffer->used - buffer->pos); + memcpy(buffer->data, data, len); + buffer->used += len - buffer->pos; + buffer->pos = 0; + buffer_verify(buffer); + return 0; +} + +/** + * @internal + * + * @brief Append data from a buffer to the tail of another buffer. + * + * @param[in] buffer The destination buffer. + * + * @param[in] source The source buffer to append. It doesn't take the + * position of the buffer into account. + * + * @return 0 on success, -1 on error. + */ +int ssh_buffer_add_buffer(struct ssh_buffer_struct *buffer, + struct ssh_buffer_struct *source) +{ + int rc; + + rc = ssh_buffer_add_data(buffer, + ssh_buffer_get(source), + ssh_buffer_get_len(source)); + if (rc < 0) { + return -1; + } + + return 0; +} + +/** + * @brief Get a pointer to the head of a buffer at the current position. + * + * @param[in] buffer The buffer to get the head pointer. + * + * @return A pointer to the data from current position. + * + * @see ssh_buffer_get_len() + */ +void *ssh_buffer_get(struct ssh_buffer_struct *buffer){ + return buffer->data + buffer->pos; +} + +/** + * @brief Get the length of the buffer from the current position. + * + * @param[in] buffer The buffer to get the length from. + * + * @return The length of the buffer. + * + * @see ssh_buffer_get() + */ +uint32_t ssh_buffer_get_len(struct ssh_buffer_struct *buffer){ + buffer_verify(buffer); + return buffer->used - buffer->pos; +} + +/** + * @internal + * + * @brief Duplicate an existing buffer. + * + * Creates a new ssh_buffer and copies all data from the source buffer. + * The new buffer preserves the secure flag setting of the source. + * + * @param[in] buffer The buffer to duplicate. Can be NULL. + * + * @return A new buffer containing a copy of the data on success, + * NULL on failure or if buffer is NULL. + * + * @see ssh_buffer_free() + */ +ssh_buffer ssh_buffer_dup(const ssh_buffer buffer) +{ + ssh_buffer new_buffer = NULL; + int rc; + + if (buffer == NULL) { + return NULL; + } + + buffer_verify(buffer); + + new_buffer = ssh_buffer_new(); + if (new_buffer == NULL) { + return NULL; + } + + new_buffer->secure = buffer->secure; + + if (ssh_buffer_get_len(buffer) > 0) { + rc = ssh_buffer_add_data(new_buffer, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc != SSH_OK) { + ssh_buffer_free(new_buffer); + return NULL; + } + } + + buffer_verify(new_buffer); + return new_buffer; +} + +/** + * @internal + * + * @brief Advance the position in the buffer. + * + * This has effect to "eat" bytes at head of the buffer. + * + * @param[in] buffer The buffer to advance the position. + * + * @param[in] len The number of bytes to eat. + * + * @return The new size of the buffer. + */ +uint32_t ssh_buffer_pass_bytes(struct ssh_buffer_struct *buffer, uint32_t len){ + buffer_verify(buffer); + + if (buffer->pos + len < len || buffer->used < buffer->pos + len) { + return 0; + } + + buffer->pos+=len; + /* if the buffer is empty after having passed the whole bytes into it, we can clean it */ + if(buffer->pos==buffer->used){ + buffer->pos=0; + buffer->used=0; + } + buffer_verify(buffer); + return len; +} + +/** + * @internal + * + * @brief Cut the end of the buffer. + * + * @param[in] buffer The buffer to cut. + * + * @param[in] len The number of bytes to remove from the tail. + * + * @return The new size of the buffer. + */ +uint32_t ssh_buffer_pass_bytes_end(struct ssh_buffer_struct *buffer, uint32_t len){ + buffer_verify(buffer); + + if (buffer->used < len) { + return 0; + } + + buffer->used-=len; + buffer_verify(buffer); + return len; +} + +/** + * @brief Get the remaining data out of the buffer and adjust the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data The data buffer where to store the data. + * + * @param[in] len The length to read from the buffer. + * + * @returns 0 if there is not enough data in buffer, len otherwise. + */ +uint32_t ssh_buffer_get_data(struct ssh_buffer_struct *buffer, void *data, uint32_t len) +{ + int rc; + + /* + * Check for a integer overflow first, then check if not enough data is in + * the buffer. + */ + rc = ssh_buffer_validate_length(buffer, len); + if (rc != SSH_OK) { + return 0; + } + memcpy(data,buffer->data+buffer->pos,len); + buffer->pos+=len; + return len; /* no yet support for partial reads (is it really needed ?? ) */ +} + +/** + * @internal + * + * @brief Get a 8 bits unsigned int out of the buffer and adjust the read + * pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint8_t where to store the data. + * + * @returns 0 if there is not enough data in buffer, 1 otherwise. + */ +uint32_t ssh_buffer_get_u8(struct ssh_buffer_struct *buffer, uint8_t *data){ + return ssh_buffer_get_data(buffer,data,sizeof(uint8_t)); +} + +/** + * @internal + * + * @brief gets a 32 bits unsigned int out of the buffer. Adjusts the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint32_t where to store the data. + * + * @returns 0 if there is not enough data in buffer, 4 otherwise. + */ +uint32_t ssh_buffer_get_u32(struct ssh_buffer_struct *buffer, uint32_t *data){ + return ssh_buffer_get_data(buffer,data,sizeof(uint32_t)); +} +/** + * @internal + * + * @brief Get a 64 bits unsigned int out of the buffer and adjusts the read + * pointer. + * + * @param[in] buffer The buffer to read. + * + * @param[in] data A pointer to a uint64_t where to store the data. + * + * @returns 0 if there is not enough data in buffer, 8 otherwise. + */ +uint32_t ssh_buffer_get_u64(struct ssh_buffer_struct *buffer, uint64_t *data){ + return ssh_buffer_get_data(buffer,data,sizeof(uint64_t)); +} + +/** + * @brief Validates that the given length can be obtained from the buffer. + * + * @param[in] buffer The buffer to read from. + * + * @param[in] len The length to be checked. + * + * @return SSH_OK if the length is valid, SSH_ERROR otherwise. + */ +int ssh_buffer_validate_length(struct ssh_buffer_struct *buffer, size_t len) +{ + if (buffer == NULL || buffer->pos + len < len || + buffer->pos + len > buffer->used) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief Get an SSH String out of the buffer and adjust the read pointer. + * + * @param[in] buffer The buffer to read. + * + * @returns The SSH String, NULL on error. + */ +struct ssh_string_struct * +ssh_buffer_get_ssh_string(struct ssh_buffer_struct *buffer) +{ + uint32_t stringlen; + uint32_t hostlen; + struct ssh_string_struct *str = NULL; + int rc; + + rc = ssh_buffer_get_u32(buffer, &stringlen); + if (rc == 0) { + return NULL; + } + hostlen = ntohl(stringlen); + /* verify if there is enough space in buffer to get it */ + rc = ssh_buffer_validate_length(buffer, hostlen); + if (rc != SSH_OK) { + return NULL; /* it is indeed */ + } + str = ssh_string_new(hostlen); + if (str == NULL) { + return NULL; + } + + stringlen = ssh_buffer_get_data(buffer, ssh_string_data(str), hostlen); + if (stringlen != hostlen) { + /* should never happen */ + SAFE_FREE(str); + return NULL; + } + + return str; +} + +/** + * @brief Pre-calculate the size we need for packing the buffer. + * + * This makes sure that enough memory is allocated for packing the buffer and + * we only have to do one memory allocation. + * + * @param[in] buffer The buffer to allocate + * + * @param[in] format A format string of arguments. + * + * @param[in] argc The number of arguments. + * + * @param[in] ap The va_list of arguments. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +static int ssh_buffer_pack_allocate_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) +{ + const char *p = NULL; + ssh_string string = NULL; + char *cstring = NULL; + bignum b = NULL; + size_t needed_size = 0; + size_t len; + size_t count; + int rc = SSH_OK; + + for (p = format, count = 0; *p != '\0'; p++, count++) { + /* Invalid number of arguments passed */ + if (count > argc) { + return SSH_ERROR; + } + + switch(*p) { + case 'b': + va_arg(ap, unsigned int); + needed_size += sizeof(uint8_t); + break; + case 'w': + va_arg(ap, unsigned int); + needed_size += sizeof(uint16_t); + break; + case 'd': + va_arg(ap, uint32_t); + needed_size += sizeof(uint32_t); + break; + case 'q': + va_arg(ap, uint64_t); + needed_size += sizeof(uint64_t); + break; + case 'S': + string = va_arg(ap, ssh_string); + needed_size += sizeof(uint32_t) + ssh_string_len(string); + string = NULL; + break; + case 's': + cstring = va_arg(ap, char *); + needed_size += sizeof(uint32_t) + strlen(cstring); + cstring = NULL; + break; + case 'P': + len = va_arg(ap, size_t); + needed_size += len; + va_arg(ap, void *); + count++; /* increase argument count */ + break; + case 'F': + case 'B': + b = va_arg(ap, bignum); + if (*p == 'F') { + /* For padded bignum, we know the exact length */ + len = va_arg(ap, size_t); + count++; /* increase argument count */ + needed_size += sizeof(uint32_t) + len; + } else { + /* The bignum bytes + 1 for possible padding */ + needed_size += sizeof(uint32_t) + bignum_num_bytes(b) + 1; + } + break; + case 't': + cstring = va_arg(ap, char *); + needed_size += strlen(cstring); + cstring = NULL; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); + rc = SSH_ERROR; + } + if (rc != SSH_OK){ + break; + } + } + + if (argc != count) { + return SSH_ERROR; + } + + if (rc != SSH_ERROR){ + /* + * Check if our canary is intact, if not, something really bad happened. + */ + uint32_t canary = va_arg(ap, uint32_t); + if (canary != SSH_BUFFER_PACK_END) { + abort(); + } + } + + rc = ssh_buffer_allocate_size(buffer, (uint32_t)needed_size); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** @internal + * @brief Add multiple values in a buffer on a single function call + * @param[in] buffer The buffer to add to + * @param[in] format A format string of arguments. + * @param[in] ap A va_list of arguments. + * @returns SSH_OK on success + * SSH_ERROR on error + * @see ssh_buffer_add_format() for format list values. + */ +static int +ssh_buffer_pack_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) +{ + int rc = SSH_ERROR; + const char *p = NULL; + union { + uint8_t byte; + uint16_t word; + uint32_t dword; + uint64_t qword; + ssh_string string; + void *data; + } o; + char *cstring = NULL; + bignum b; + size_t len; + size_t count; + + if (argc > 256) { + return SSH_ERROR; + } + + for (p = format, count = 0; *p != '\0'; p++, count++) { + /* Invalid number of arguments passed */ + if (count > argc) { + return SSH_ERROR; + } + + switch(*p) { + case 'b': + o.byte = (uint8_t)va_arg(ap, unsigned int); + rc = ssh_buffer_add_u8(buffer, o.byte); + break; + case 'w': + o.word = (uint16_t)va_arg(ap, unsigned int); + o.word = htons(o.word); + rc = ssh_buffer_add_u16(buffer, o.word); + break; + case 'd': + o.dword = va_arg(ap, uint32_t); + o.dword = htonl(o.dword); + rc = ssh_buffer_add_u32(buffer, o.dword); + break; + case 'q': + o.qword = va_arg(ap, uint64_t); + o.qword = htonll(o.qword); + rc = ssh_buffer_add_u64(buffer, o.qword); + break; + case 'S': + o.string = va_arg(ap, ssh_string); + rc = ssh_buffer_add_ssh_string(buffer, o.string); + o.string = NULL; + break; + case 's': + cstring = va_arg(ap, char *); + len = strlen(cstring); + if (len > UINT32_MAX) { + rc = SSH_ERROR; + break; + } + o.dword = (uint32_t)len; + rc = ssh_buffer_add_u32(buffer, htonl(o.dword)); + if (rc == SSH_OK){ + rc = ssh_buffer_add_data(buffer, cstring, o.dword); + } + cstring = NULL; + break; + case 'P': + len = va_arg(ap, size_t); + if (len > UINT32_MAX) { + rc = SSH_ERROR; + break; + } + + o.data = va_arg(ap, void *); + count++; /* increase argument count */ + + rc = ssh_buffer_add_data(buffer, o.data, (uint32_t)len); + o.data = NULL; + break; + case 'F': + case 'B': + b = va_arg(ap, bignum); + if (*p == 'F') { + len = va_arg(ap, size_t); + count++; /* increase argument count */ + o.string = ssh_make_padded_bignum_string(b, len); + } else { + o.string = ssh_make_bignum_string(b); + } + if(o.string == NULL){ + rc = SSH_ERROR; + break; + } + rc = ssh_buffer_add_ssh_string(buffer, o.string); + SAFE_FREE(o.string); + break; + case 't': + cstring = va_arg(ap, char *); + len = strlen(cstring); + if (len > UINT32_MAX) { + rc = SSH_ERROR; + break; + } + rc = ssh_buffer_add_data(buffer, cstring, (uint32_t)len); + cstring = NULL; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); + rc = SSH_ERROR; + } + if (rc != SSH_OK){ + break; + } + } + + if (argc != count) { + return SSH_ERROR; + } + + if (rc != SSH_ERROR){ + /* Check if our canary is intact, if not something really bad happened */ + uint32_t canary = va_arg(ap, uint32_t); + if (canary != SSH_BUFFER_PACK_END) { + abort(); + } + } + return rc; +} + +/** @internal + * @brief Add multiple values in a buffer on a single function call + * @param[in] buffer The buffer to add to + * @param[in] format A format string of arguments. This string contains single + * letters describing the order and type of arguments: + * 'b': uint8_t (pushed in network byte order) + * 'w': uint16_t (pushed in network byte order) + * 'd': uint32_t (pushed in network byte order) + * 'q': uint64_t (pushed in network byte order) + * 'S': ssh_string + * 's': char * (C string, pushed as SSH string) + * 't': char * (C string, pushed as free text) + * 'P': size_t, void * (len of data, pointer to data) + * only pushes data. + * 'B': bignum (pushed as SSH string) + * 'F': bignum, size_t (bignum, padded to fixed length, + * pushed as SSH string) + * @returns SSH_OK on success + * SSH_ERROR on error + * @warning when using 'P' with a constant size (e.g. 8), do not + * forget to cast to (size_t). + */ +int _ssh_buffer_pack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...) +{ + va_list ap; + int rc; + + if (argc > 256) { + return SSH_ERROR; + } + + va_start(ap, argc); + rc = ssh_buffer_pack_allocate_va(buffer, format, argc, ap); + va_end(ap); + + if (rc != SSH_OK) { + return rc; + } + + va_start(ap, argc); + rc = ssh_buffer_pack_va(buffer, format, argc, ap); + va_end(ap); + + return rc; +} + +/** @internal + * @brief Get multiple values from a buffer on a single function call + * @param[in] buffer The buffer to get from + * @param[in] format A format string of arguments. + * @param[in] ap A va_list of arguments. + * @returns SSH_OK on success + * SSH_ERROR on error + * @see ssh_buffer_get_format() for format list values. + */ +int ssh_buffer_unpack_va(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + va_list ap) +{ + int rc = SSH_ERROR; + const char *p = format, *last = NULL; + union { + uint8_t *byte; + uint16_t *word; + uint32_t *dword; + uint64_t *qword; + ssh_string *string; + char **cstring; + bignum *bignum; + void **data; + } o; + size_t len; + uint32_t rlen, max_len; + ssh_string tmp_string = NULL; + va_list ap_copy; + size_t count; + + max_len = ssh_buffer_get_len(buffer); + + /* copy the argument list in case a rollback is needed */ + va_copy(ap_copy, ap); + + if (argc > 256) { + rc = SSH_ERROR; + goto cleanup; + } + + for (count = 0; *p != '\0'; p++, count++) { + /* Invalid number of arguments passed */ + if (count > argc) { + rc = SSH_ERROR; + goto cleanup; + } + + rc = SSH_ERROR; + switch (*p) { + case 'b': + o.byte = va_arg(ap, uint8_t *); + rlen = ssh_buffer_get_u8(buffer, o.byte); + rc = rlen==1 ? SSH_OK : SSH_ERROR; + break; + case 'w': + o.word = va_arg(ap, uint16_t *); + rlen = ssh_buffer_get_data(buffer, o.word, sizeof(uint16_t)); + if (rlen == 2) { + *o.word = ntohs(*o.word); + rc = SSH_OK; + } + break; + case 'd': + o.dword = va_arg(ap, uint32_t *); + rlen = ssh_buffer_get_u32(buffer, o.dword); + if (rlen == 4) { + *o.dword = ntohl(*o.dword); + rc = SSH_OK; + } + break; + case 'q': + o.qword = va_arg(ap, uint64_t*); + rlen = ssh_buffer_get_u64(buffer, o.qword); + if (rlen == 8) { + *o.qword = ntohll(*o.qword); + rc = SSH_OK; + } + break; + case 'B': + o.bignum = va_arg(ap, bignum *); + *o.bignum = NULL; + tmp_string = ssh_buffer_get_ssh_string(buffer); + if (tmp_string == NULL) { + break; + } + *o.bignum = ssh_make_string_bn(tmp_string); + ssh_string_burn(tmp_string); + SSH_STRING_FREE(tmp_string); + rc = (*o.bignum != NULL) ? SSH_OK : SSH_ERROR; + break; + case 'S': + o.string = va_arg(ap, ssh_string *); + *o.string = ssh_buffer_get_ssh_string(buffer); + rc = *o.string != NULL ? SSH_OK : SSH_ERROR; + o.string = NULL; + break; + case 's': { + uint32_t u32len = 0; + + o.cstring = va_arg(ap, char **); + *o.cstring = NULL; + rlen = ssh_buffer_get_u32(buffer, &u32len); + if (rlen != 4){ + break; + } + u32len = ntohl(u32len); + if (u32len > max_len - 1) { + break; + } + + rc = ssh_buffer_validate_length(buffer, u32len); + if (rc != SSH_OK) { + break; + } + + *o.cstring = malloc(u32len + 1); + if (*o.cstring == NULL){ + rc = SSH_ERROR; + break; + } + rlen = ssh_buffer_get_data(buffer, *o.cstring, u32len); + if (rlen != u32len) { + SAFE_FREE(*o.cstring); + rc = SSH_ERROR; + break; + } + (*o.cstring)[u32len] = '\0'; + o.cstring = NULL; + rc = SSH_OK; + break; + } + case 'P': + len = va_arg(ap, size_t); + if (len > max_len - 1) { + rc = SSH_ERROR; + break; + } + + rc = ssh_buffer_validate_length(buffer, len); + if (rc != SSH_OK) { + break; + } + + o.data = va_arg(ap, void **); + count++; + + *o.data = malloc(len); + if(*o.data == NULL){ + rc = SSH_ERROR; + break; + } + rlen = ssh_buffer_get_data(buffer, *o.data, (uint32_t)len); + if (rlen != len){ + SAFE_FREE(*o.data); + rc = SSH_ERROR; + break; + } + o.data = NULL; + rc = SSH_OK; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Invalid buffer format %c", *p); + } + if (rc != SSH_OK) { + break; + } + } + + if (argc != count) { + rc = SSH_ERROR; + } + +cleanup: + if (rc != SSH_ERROR){ + /* Check if our canary is intact, if not something really bad happened */ + uint32_t canary = va_arg(ap, uint32_t); + if (canary != SSH_BUFFER_PACK_END){ + abort(); + } + } + + if (rc != SSH_OK){ + /* Reset the format string and erase everything that was allocated */ + last = p; + for(p=format;psecure) { + ssh_burn(o.byte, sizeof(uint8_t)); + break; + } + break; + case 'w': + o.word = va_arg(ap_copy, uint16_t *); + if (buffer->secure) { + ssh_burn(o.word, sizeof(uint16_t)); + break; + } + break; + case 'd': + o.dword = va_arg(ap_copy, uint32_t *); + if (buffer->secure) { + ssh_burn(o.dword, sizeof(uint32_t)); + break; + } + break; + case 'q': + o.qword = va_arg(ap_copy, uint64_t *); + if (buffer->secure) { + ssh_burn(o.qword, sizeof(uint64_t)); + break; + } + break; + case 'B': + o.bignum = va_arg(ap_copy, bignum *); + bignum_safe_free(*o.bignum); + break; + case 'S': + o.string = va_arg(ap_copy, ssh_string *); + if (buffer->secure) { + ssh_string_burn(*o.string); + } + SAFE_FREE(*o.string); + break; + case 's': + o.cstring = va_arg(ap_copy, char **); + if (buffer->secure) { + ssh_burn(*o.cstring, strlen(*o.cstring)); + } + SAFE_FREE(*o.cstring); + break; + case 'P': + len = va_arg(ap_copy, size_t); + o.data = va_arg(ap_copy, void **); + if (buffer->secure) { + ssh_burn(*o.data, len); + } + SAFE_FREE(*o.data); + break; + default: + (void)va_arg(ap_copy, void *); + break; + } + } + } + va_end(ap_copy); + + return rc; +} + +/** @internal + * @brief Get multiple values from a buffer on a single function call + * @param[in] buffer The buffer to get from + * @param[in] format A format string of arguments. This string contains single + * letters describing the order and type of arguments: + * 'b': uint8_t * (pulled in network byte order) + * 'w': uint16_t * (pulled in network byte order) + * 'd': uint32_t * (pulled in network byte order) + * 'q': uint64_t * (pulled in network byte order) + * 'S': ssh_string * + * 's': char ** (C string, pulled as SSH string) + * 'P': size_t, void ** (len of data, pointer to data) + * only pulls data. + * 'B': bignum * (pulled as SSH string) + * @returns SSH_OK on success + * SSH_ERROR on error + * @warning when using 'P' with a constant size (e.g. 8), do not + * forget to cast to (size_t). + */ +int _ssh_buffer_unpack(struct ssh_buffer_struct *buffer, + const char *format, + size_t argc, + ...) +{ + va_list ap; + int rc; + + va_start(ap, argc); + rc = ssh_buffer_unpack_va(buffer, format, argc, ap); + va_end(ap); + return rc; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/callbacks.c b/src/libs/libssh-0.12.2/src/callbacks.c new file mode 100644 index 000000000000..94ea729eaad5 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/callbacks.c @@ -0,0 +1,156 @@ +/* + * callbacks.c - callback functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/callbacks.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +#define is_callback_valid(session, cb) \ + (cb->size > 0 || cb->size <= 1024 * sizeof(void *)) + +/* LEGACY */ +static void ssh_legacy_log_callback(int priority, + const char *function, + const char *buffer, + void *userdata) +{ + ssh_session session = (ssh_session)userdata; + ssh_log_callback log_fn = session->common.callbacks->log_function; + void *log_data = session->common.callbacks->userdata; + + (void)function; /* unused */ + + log_fn(session, priority, buffer, log_data); +} + +void _ssh_remove_legacy_log_cb(void) +{ + if (ssh_get_log_callback() == ssh_legacy_log_callback) { + _ssh_reset_log_cb(); + ssh_set_log_userdata(NULL); + } +} + +int ssh_set_callbacks(ssh_session session, ssh_callbacks cb) +{ + if (session == NULL || cb == NULL) { + return SSH_ERROR; + } + + if (!is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + session->common.callbacks = cb; + + /* LEGACY */ + if (ssh_get_log_callback() == NULL && cb->log_function) { + ssh_set_log_callback(ssh_legacy_log_callback); + ssh_set_log_userdata(session); + } + + return 0; +} + +static int ssh_add_set_channel_callbacks(ssh_channel channel, + ssh_channel_callbacks cb, + int prepend) +{ + ssh_session session = NULL; + int rc; + + if (channel == NULL || cb == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if (!is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + if (channel->callbacks == NULL) { + channel->callbacks = ssh_list_new(); + if (channel->callbacks == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + if (prepend) { + rc = ssh_list_prepend(channel->callbacks, cb); + } else { + rc = ssh_list_append(channel->callbacks, cb); + } + + return rc; +} + +int ssh_set_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) +{ + return ssh_add_set_channel_callbacks(channel, cb, 1); +} + +int ssh_add_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) +{ + return ssh_add_set_channel_callbacks(channel, cb, 0); +} + +int ssh_remove_channel_callbacks(ssh_channel channel, ssh_channel_callbacks cb) +{ + struct ssh_iterator *it = NULL; + + if (channel == NULL || channel->callbacks == NULL) { + return SSH_ERROR; + } + + it = ssh_list_find(channel->callbacks, cb); + if (it == NULL) { + return SSH_ERROR; + } + + ssh_list_remove(channel->callbacks, it); + + return SSH_OK; +} + +int ssh_set_server_callbacks(ssh_session session, ssh_server_callbacks cb) +{ + if (session == NULL || cb == NULL) { + return SSH_ERROR; + } + + if (!is_callback_valid(session, cb)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid callback passed in (badly initialized)"); + return SSH_ERROR; + }; + session->server_callbacks = cb; + + return 0; +} diff --git a/src/libs/libssh-0.12.2/src/chachapoly.c b/src/libs/libssh-0.12.2/src/chachapoly.c new file mode 100644 index 000000000000..354a0d266487 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/chachapoly.c @@ -0,0 +1,205 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2015 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/libssh.h" +#include "libssh/crypto.h" +#include "libssh/chacha.h" +#include "libssh/poly1305.h" +#include "libssh/misc.h" +#include "libssh/chacha20-poly1305-common.h" + +struct chacha20_poly1305_keysched { + /* key used for encrypting the length field*/ + struct chacha_ctx k1; + /* key used for encrypting the packets */ + struct chacha_ctx k2; +}; + +static const uint8_t zero_block_counter[8] = {0, 0, 0, 0, 0, 0, 0, 0}; +static const uint8_t payload_block_counter[8] = {1, 0, 0, 0, 0, 0, 0, 0}; + +static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + struct chacha20_poly1305_keysched *sched = NULL; + uint8_t *u8key = key; + (void)IV; + + if (cipher->chacha20_schedule == NULL) { + sched = malloc(sizeof *sched); + if (sched == NULL){ + return -1; + } + } else { + sched = cipher->chacha20_schedule; + } + + chacha_keysetup(&sched->k2, u8key, CHACHA20_KEYLEN * 8); + chacha_keysetup(&sched->k1, u8key + CHACHA20_KEYLEN, CHACHA20_KEYLEN * 8); + cipher->chacha20_schedule = sched; + + return 0; +} + +/** + * @internal + * + * @brief encrypts an outgoing packet with chacha20 and authenticates it + * with poly1305. + */ +static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + uint8_t poly1305_ctx[POLY1305_KEYLEN] = {0}; + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + + seq = htonll(seq); + /* step 1, prepare the poly1305 key */ + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k2, + poly1305_ctx, + poly1305_ctx, + POLY1305_KEYLEN); + + /* step 2, encrypt length field */ + chacha_ivsetup(&keys->k1, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k1, + (uint8_t *)&in_packet->length, + (uint8_t *)&out_packet->length, + sizeof(uint32_t)); + + /* step 3, encrypt packet payload */ + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, payload_block_counter); + chacha_encrypt_bytes(&keys->k2, + in_packet->payload, + out_packet->payload, + len - sizeof(uint32_t)); + + /* ssh_log_hexdump("poly1305_ctx", poly1305_ctx, sizeof(poly1305_ctx)); */ + /* step 4, compute the MAC */ + poly1305_auth(tag, (uint8_t *)out_packet, len, poly1305_ctx); + /* ssh_log_hexdump("poly1305 src", (uint8_t *)out_packet, len); + ssh_log_hexdump("poly1305 tag", tag, POLY1305_TAGLEN); */ +} + +static int chacha20_poly1305_aead_decrypt_length( + struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + seq = htonll(seq); + + chacha_ivsetup(&keys->k1, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k1, + in, + (uint8_t *)out, + sizeof(uint32_t)); + return SSH_OK; +} + +static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + uint8_t poly1305_ctx[POLY1305_KEYLEN] = {0}; + uint8_t tag[POLY1305_TAGLEN] = {0}; + struct chacha20_poly1305_keysched *keys = cipher->chacha20_schedule; + uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + encrypted_size; + int cmp; + + seq = htonll(seq); + + ZERO_STRUCT(poly1305_ctx); + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, zero_block_counter); + chacha_encrypt_bytes(&keys->k2, + poly1305_ctx, + poly1305_ctx, + POLY1305_KEYLEN); +#if 0 + ssh_log_hexdump("poly1305_ctx", poly1305_ctx, sizeof(poly1305_ctx)); +#endif + + poly1305_auth(tag, (uint8_t *)complete_packet, encrypted_size + + sizeof(uint32_t), poly1305_ctx); +#if 0 + ssh_log_hexdump("poly1305 src", + (uint8_t*)complete_packet, + encrypted_size + 4); + ssh_log_hexdump("poly1305 tag", tag, POLY1305_TAGLEN); + ssh_log_hexdump("received tag", mac, POLY1305_TAGLEN); +#endif + + cmp = secure_memcmp(tag, mac, POLY1305_TAGLEN); + if(cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET,"poly1305 verify error"); + return SSH_ERROR; + } + chacha_ivsetup(&keys->k2, (uint8_t *)&seq, payload_block_counter); + chacha_encrypt_bytes(&keys->k2, + (uint8_t *)complete_packet + sizeof(uint32_t), + out, + encrypted_size); + + return SSH_OK; +} + +static void chacha20_cleanup(struct ssh_cipher_struct *cipher) { + SAFE_FREE(cipher->chacha20_schedule); +} + +const struct ssh_cipher_struct chacha20poly1305_cipher = { + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 512, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_set_encrypt_key, + .set_decrypt_key = chacha20_set_encrypt_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_cleanup +}; + +const struct ssh_cipher_struct *ssh_get_chacha20poly1305_cipher(void) +{ + return &chacha20poly1305_cipher; +} diff --git a/src/libs/libssh-0.12.2/src/channels.c b/src/libs/libssh-0.12.2/src/channels.c new file mode 100644 index 000000000000..ea64221ffe50 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/channels.c @@ -0,0 +1,4162 @@ +/* + * channels.c - SSH channel functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/messages.h" +#if WITH_SERVER +#include "libssh/server.h" +#endif + +/* + * All implementations MUST be able to process packets with an + * uncompressed payload length of 32768 bytes or less and a total packet + * size of 35000 bytes or less. + */ +#define CHANNEL_MAX_PACKET 32768 + +/* + * WINDOW_DEFAULT matches the default OpenSSH session window size. + * This controls how much data the peer can send before needing to receive + * a round-trip SSH2_MSG_CHANNEL_WINDOW_ADJUST message that increases the window. + */ +#define WINDOW_DEFAULT (64*CHANNEL_MAX_PACKET) + +/** + * @defgroup libssh_channel The SSH channel functions + * @ingroup libssh + * + * Functions that manage a SSH channel. + * + * @{ + */ + +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet); + +/** + * @brief Allocate a new channel. + * + * @param[in] session The ssh session to use. + * + * @return A pointer to a newly allocated channel, NULL on error. + * The channel needs to be freed with ssh_channel_free(). + * + * @see ssh_channel_free() + */ +ssh_channel ssh_channel_new(ssh_session session) +{ + ssh_channel channel = NULL; + + if (session == NULL) { + return NULL; + } + + /* Check if we have an authenticated session */ + if (!(session->flags & SSH_SESSION_FLAG_AUTHENTICATED)) { + return NULL; + } + + channel = calloc(1, sizeof(struct ssh_channel_struct)); + if (channel == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(channel); + return NULL; + } + + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + SAFE_FREE(channel); + return NULL; + } + + channel->session = session; + channel->exit.code = (uint32_t)-1; + channel->flags = SSH_CHANNEL_FLAG_NOT_BOUND; + + if (session->channels == NULL) { + session->channels = ssh_list_new(); + if (session->channels == NULL) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + SSH_BUFFER_FREE(channel->stderr_buffer); + SAFE_FREE(channel); + return NULL; + } + } + + ssh_list_prepend(session->channels, channel); + + /* Set states explicitly */ + channel->state = SSH_CHANNEL_STATE_NOT_OPEN; + channel->request_state = SSH_CHANNEL_REQ_STATE_NONE; + + return channel; +} + +/** + * @internal + * + * @brief Create a new channel identifier. + * + * @param[in] session The SSH session to use. + * + * @return The new channel identifier. + */ +uint32_t ssh_channel_new_id(ssh_session session) +{ + return ++(session->maxchannel); +} + +/** + * @internal + * + * @brief Handle a SSH_PACKET_CHANNEL_OPEN_CONFIRMATION packet. + * + * Constructs the channel object. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_conf) +{ + uint32_t channelid = 0; + ssh_channel channel = NULL; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); + + rc = ssh_buffer_unpack(packet, "d", &channelid); + if (rc != SSH_OK) + goto error; + channel = ssh_channel_from_local(session, channelid); + if (channel == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Unknown channel id %" PRIu32, + (uint32_t)channelid); + /* TODO: Set error marking in channel object */ + + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, + "ddd", + &channel->remote_channel, + &channel->remote_window, + &channel->remote_maxpacket); + if (rc != SSH_OK) + goto error; + + if (channel->remote_maxpacket == 0) { + SSH_LOG(SSH_LOG_RARE, + "Invalid maximum packet size 0 in " + "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION"); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Received a CHANNEL_OPEN_CONFIRMATION for channel %" PRIu32 + ":%" PRIu32, + channel->local_channel, + channel->remote_channel); + + if (channel->state != SSH_CHANNEL_STATE_OPENING) { + SSH_LOG(SSH_LOG_RARE, + "SSH2_MSG_CHANNEL_OPEN_CONFIRMATION received in incorrect " + "channel state %d", + channel->state); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Remote window : %" PRIu32 ", maxpacket : %" PRIu32, + channel->remote_window, + channel->remote_maxpacket); + + channel->state = SSH_CHANNEL_STATE_OPEN; + channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + true /* is_success */); + + return SSH_PACKET_USED; + +error: + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_OPEN_FAILURE and set the state of the channel. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_open_fail) +{ + ssh_channel channel = NULL; + char *error = NULL; + uint32_t code; + int rc; + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_RARE, "Invalid channel in packet"); + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, "ds", &code, &error); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; + } + + if (channel->state != SSH_CHANNEL_STATE_OPENING) { + SSH_LOG(SSH_LOG_RARE, + "SSH2_MSG_CHANNEL_OPEN_FAILURE received in incorrect channel " + "state %d", + channel->state); + SAFE_FREE(error); + goto error; + } + + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Channel opening failure: channel %" PRIu32 " error (%" PRIu32 + ") %s", + channel->local_channel, + code, + error); + SAFE_FREE(error); + channel->state = SSH_CHANNEL_STATE_OPEN_DENIED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_open_response_function, + channel->session, + channel, + false /* is_success */); + + return SSH_PACKET_USED; + +error: + ssh_set_error(session, SSH_FATAL, "Invalid packet"); + return SSH_PACKET_USED; +} + +static int ssh_channel_open_termination(void *c) +{ + ssh_channel channel = (ssh_channel) c; + if (channel->state != SSH_CHANNEL_STATE_OPENING || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @internal + * + * @brief Open a channel by sending a SSH_OPEN_CHANNEL message and + * wait for the reply. + * + * @param[in] channel The current channel. + * + * @param[in] type A C string describing the kind of channel (e.g. "exec"). + * + * @param[in] window The receiving window of the channel. The window is the + * maximum size of data that can stay in buffers and + * network. + * + * @param[in] maxpacket The maximum packet size allowed (like MTU). + * + * @param[in] payload The buffer containing additional payload for the query. + * + * @return `SSH_OK` if successful; `SSH_ERROR` otherwise. + */ +static int +channel_open(ssh_channel channel, + const char *type, + uint32_t window, + uint32_t maxpacket, + ssh_buffer payload) +{ + ssh_session session = channel->session; + int err = SSH_ERROR; + int rc; + + switch (channel->state) { + case SSH_CHANNEL_STATE_NOT_OPEN: + break; + case SSH_CHANNEL_STATE_OPENING: + goto pending; + case SSH_CHANNEL_STATE_OPEN: + case SSH_CHANNEL_STATE_CLOSED: + case SSH_CHANNEL_STATE_OPEN_DENIED: + goto end; + default: + ssh_set_error(session, SSH_FATAL, "Bad state in channel_open: %d", + channel->state); + } + + channel->local_channel = ssh_channel_new_id(session); + channel->local_maxpacket = maxpacket; + channel->local_window = window; + + SSH_LOG(SSH_LOG_DEBUG, + "Creating a channel %" PRIu32 " with %" PRIu32 " window and %" PRIu32 " max packet", + channel->local_channel, window, maxpacket); + + rc = ssh_buffer_pack(session->out_buffer, + "bsddd", + SSH2_MSG_CHANNEL_OPEN, + type, + channel->local_channel, + channel->local_window, + channel->local_maxpacket); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return err; + } + + if (payload != NULL) { + if (ssh_buffer_add_buffer(session->out_buffer, payload) < 0) { + ssh_set_error_oom(session); + + return err; + } + } + channel->state = SSH_CHANNEL_STATE_OPENING; + if (ssh_packet_send(session) == SSH_ERROR) { + return err; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_OPEN type %s for channel %" PRIu32, + type, channel->local_channel); + +pending: + /* wait until channel is opened by server */ + err = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_open_termination, + channel); + + if (session->session_state == SSH_SESSION_STATE_ERROR) { + err = SSH_ERROR; + } + +end: + /* This needs to pass the SSH_AGAIN from the above, + * but needs to catch failed channel states */ + if (channel->state == SSH_CHANNEL_STATE_OPEN) { + err = SSH_OK; + } else if (err != SSH_AGAIN) { + /* Messages were handled correctly, but the channel state is invalid */ + err = SSH_ERROR; + } + + return err; +} + +/* return channel with corresponding local id, or NULL if not found */ +ssh_channel ssh_channel_from_local(ssh_session session, uint32_t id) +{ + struct ssh_iterator *it = NULL; + ssh_channel channel = NULL; + + for (it = ssh_list_get_iterator(session->channels); it != NULL; + it = it->next) { + channel = ssh_iterator_value(ssh_channel, it); + if (channel == NULL) { + continue; + } + if (channel->local_channel == id) { + return channel; + } + } + + return NULL; +} + +/** + * @internal + * @brief grows the local window and sends a packet to the other party + * @param session SSH session + * @param channel SSH channel + * @return `SSH_OK` if successful; `SSH_ERROR` otherwise. + */ +static int grow_window(ssh_session session, + ssh_channel channel) +{ + uint32_t used; + uint32_t increment; + int rc; + + /* Calculate the increment taking into account what the peer may still send + * (local_window) and what we've already buffered (stdout_buffer and + * stderr_buffer). + */ + used = channel->local_window; + if (channel->stdout_buffer != NULL) { + used += ssh_buffer_get_len(channel->stdout_buffer); + } + if (channel->stderr_buffer != NULL) { + used += ssh_buffer_get_len(channel->stderr_buffer); + } + /* Avoid a negative increment in case the peer sent more than the window allowed */ + increment = WINDOW_DEFAULT > used ? WINDOW_DEFAULT - used : 0; + /* Don't grow until we can request at least half a window */ + if (increment < (WINDOW_DEFAULT / 2)) { + SSH_LOG(SSH_LOG_DEBUG, + "growing window (channel %" PRIu32 ":%" PRIu32 ") to %" PRIu32 " bytes : not needed (%" PRIu32 " bytes)", + channel->local_channel, channel->remote_channel, WINDOW_DEFAULT, + channel->local_window); + + return SSH_OK; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdd", + SSH2_MSG_CHANNEL_WINDOW_ADJUST, + channel->remote_channel, + increment); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + if (ssh_packet_send(session) == SSH_ERROR) { + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "growing window (channel %" PRIu32 ":%" PRIu32 ") by %" PRIu32 " bytes", + channel->local_channel, + channel->remote_channel, + increment); + + channel->local_window += increment; + + return SSH_OK; +error: + ssh_buffer_reinit(session->out_buffer); + + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Parse a channel-related packet to resolve it to a ssh_channel. + * + * @param[in] session The current SSH session. + * + * @param[in] packet The buffer to parse packet from. The read pointer will + * be moved after the call. + * + * @return The related ssh_channel, or NULL if the channel is + * unknown or the packet is invalid. + */ +static ssh_channel channel_from_msg(ssh_session session, ssh_buffer packet) +{ + ssh_channel channel = NULL; + uint32_t chan; + int rc; + + rc = ssh_buffer_unpack(packet, "d", &chan); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Getting channel from message: short read"); + return NULL; + } + + channel = ssh_channel_from_local(session, chan); + if (channel == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Server specified invalid channel %" PRIu32, + (uint32_t)chan); + } + + return channel; +} + +SSH_PACKET_CALLBACK(channel_rcv_change_window) +{ + ssh_channel channel = NULL; + uint32_t bytes; + int rc; + bool was_empty; + + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + } + + rc = ssh_buffer_unpack(packet, "d", &bytes); + if (channel == NULL || rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, + "Error getting a window adjust message: invalid packet"); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Adding %" PRIu32 " bytes to channel (%" PRIu32 ":%" PRIu32 + ") (from %" PRIu32 " bytes)", + bytes, + channel->local_channel, + channel->remote_channel, + channel->remote_window); + + was_empty = channel->remote_window == 0; + + if (UINT32_MAX - channel->remote_window < bytes) { + ssh_set_error(session, + SSH_FATAL, + "Window adjust %" PRIu32 " overflows remote window.", + bytes); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; + } + + channel->remote_window += bytes; + + /* Writing to the channel is non-blocking until the receive window is empty. + * When the receive window becomes non-zero again, call + * channel_write_wontblock_function. */ + if (was_empty && bytes > 0) { + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_write_wontblock_function, + session, + channel, + channel->remote_window); + } + + return SSH_PACKET_USED; +} + +/* is_stderr is set to 1 if the data are extended, ie stderr */ +SSH_PACKET_CALLBACK(channel_rcv_data) +{ + ssh_channel channel = NULL; + ssh_string str = NULL; + ssh_buffer buf = NULL; + void *data = NULL; + uint32_t len; + int extended, is_stderr = 0; + int rest; + + (void)user; + + if (type == SSH2_MSG_CHANNEL_DATA) { + extended = 0; + } else { /* SSH_MSG_CHANNEL_EXTENDED_DATA */ + extended = 1; + } + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + if (extended) { + uint32_t data_type_code, rc; + rc = ssh_buffer_get_u32(packet, &data_type_code); + if (rc != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_PACKET, + "Failed to read data type code: rc = %" PRIu32, rc); + + return SSH_PACKET_USED; + } + is_stderr = 1; + data_type_code = ntohl(data_type_code); + if (data_type_code != SSH2_EXTENDED_DATA_STDERR) { + SSH_LOG(SSH_LOG_PACKET, "Invalid data type code %" PRIu32 "!", + data_type_code); + } + } + + str = ssh_buffer_get_ssh_string(packet); + if (str == NULL) { + SSH_LOG(SSH_LOG_PACKET, "Invalid data packet!"); + + return SSH_PACKET_USED; + } + /* STRING_SIZE_MAX < UINT32_MAX */ + len = (uint32_t)ssh_string_len(str); + + SSH_LOG(SSH_LOG_PACKET, + "Channel receiving %" PRIu32 " bytes data%s (local win=%" PRIu32 + " remote win=%" PRIu32 ") on channel %" PRIu32 ":%" PRIu32, + len, + is_stderr ? " in stderr" : "", + channel->local_window, + channel->remote_window, + channel->local_channel, + channel->remote_channel); + + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) { + SSH_LOG(SSH_LOG_WARNING, "Received data on (remotely) closed channel"); + ssh_set_error(session, SSH_FATAL, "Received data on (remotely) closed channel"); + SSH_STRING_FREE(str); + return SSH_PACKET_USED; + } + + if (len > channel->local_window) { + SSH_LOG(SSH_LOG_RARE, + "Data packet too big for our window(%" PRIu32 " vs %" PRIu32 ")", + len, + channel->local_window); + + SSH_STRING_FREE(str); + + ssh_set_error(session, SSH_FATAL, "Window exceeded"); + + return SSH_PACKET_USED; + } + + data = ssh_string_data(str); + if (channel_default_bufferize(channel, data, len, is_stderr) < 0) { + SSH_STRING_FREE(str); + + return SSH_PACKET_USED; + } + + channel->local_window -= len; + + SSH_LOG(SSH_LOG_PACKET, + "Channel windows are now (local win=%" PRIu32 " remote win=%" PRIu32 ")", + channel->local_window, + channel->remote_window); + + SSH_STRING_FREE(str); + + if (is_stderr) { + buf = channel->stderr_buffer; + } else { + buf = channel->stdout_buffer; + } + + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_data_function) { + if (ssh_buffer_get(buf) == NULL) { + break; + } + rest = ssh_callbacks_iterate_exec(channel_data_function, + channel->session, + channel, + ssh_buffer_get(buf), + ssh_buffer_get_len(buf), + is_stderr); + if (rest > 0) { + int rc; + if (channel->counter != NULL) { + channel->counter->in_bytes += rest; + } + ssh_buffer_pass_bytes(buf, rest); + + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; + } + } + } + ssh_callbacks_iterate_end(); + + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_eof) +{ + ssh_channel channel = NULL; + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received eof on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + /* channel->remote_window = 0; */ + channel->remote_eof = 1; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_eof_function, + channel->session, + channel); + + return SSH_PACKET_USED; +} + +static bool ssh_channel_has_unread_data(ssh_channel channel) +{ + if (channel == NULL) { + return false; + } + + if ((channel->stdout_buffer && + ssh_buffer_get_len(channel->stdout_buffer) > 0) || + (channel->stderr_buffer && + ssh_buffer_get_len(channel->stderr_buffer) > 0)) + { + return true; + } + + return false; +} + +SSH_PACKET_CALLBACK(channel_rcv_close) +{ + ssh_channel channel = NULL; + (void)user; + (void)type; + + channel = channel_from_msg(session,packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received close on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + + if (!ssh_channel_has_unread_data(channel)) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } else { + channel->delayed_close = 1; + } + + if (channel->remote_eof == 0) { + SSH_LOG(SSH_LOG_PACKET, + "Remote host not polite enough to send an eof before close"); + } + /* + * The remote eof doesn't break things if there was still data into read + * buffer because the eof is ignored until the buffer is empty. + */ + channel->remote_eof = 1; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_close_function, + channel->session, + channel); + + channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE; + if(channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL) + ssh_channel_do_free(channel); + + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(channel_rcv_request) +{ + ssh_channel channel = NULL; + char *request = NULL; + uint8_t want_reply; + int rc; + (void)user; + (void)type; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + return SSH_PACKET_USED; + } + + rc = ssh_buffer_unpack(packet, "sb", &request, &want_reply); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-status") == 0) { + SAFE_FREE(request); + rc = ssh_buffer_unpack(packet, "d", &channel->exit.code); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid exit-status packet"); + return SSH_PACKET_USED; + } + channel->exit.status = true; + + SSH_LOG(SSH_LOG_PACKET, + "received exit-status %u on channel %" PRIu32 ":%" PRIu32, + channel->exit.code, + channel->local_channel, + channel->remote_channel); + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_exit_status_function, + channel->session, + channel, + channel->exit.code); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "signal") == 0) { + char *sig = NULL; + + SAFE_FREE(request); + SSH_LOG(SSH_LOG_PACKET, "received signal"); + + rc = ssh_buffer_unpack(packet, "s", &sig); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, "Remote connection sent a signal SIG %s", sig); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_signal_function, + channel->session, + channel, + sig); + SAFE_FREE(sig); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "exit-signal") == 0) { + const char *core = "(core dumped)"; + char *sig = NULL; + char *errmsg = NULL; + char *lang = NULL; + uint8_t core_dumped; + + SAFE_FREE(request); + + rc = ssh_buffer_unpack(packet, + "sbss", + &sig, /* signal name */ + &core_dumped, /* core dumped */ + &errmsg, /* error message */ + &lang); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Invalid MSG_CHANNEL_REQUEST"); + return SSH_PACKET_USED; + } + + if (core_dumped == 0) { + core = ""; + } + + SSH_LOG(SSH_LOG_PACKET, + "Remote connection closed by signal SIG %s %s", + sig, + core); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_exit_signal_function, + channel->session, + channel, + sig, + core_dumped, + errmsg, + lang); + + channel->exit.core_dumped = core_dumped; + if (sig != NULL) { + SAFE_FREE(channel->exit.signal); + channel->exit.signal = sig; + } + channel->exit.status = true; + + SAFE_FREE(lang); + SAFE_FREE(errmsg); + + return SSH_PACKET_USED; + } + if (strcmp(request, "keepalive@openssh.com") == 0) { + SAFE_FREE(request); + SSH_LOG(SSH_LOG_DEBUG, "Responding to Openssh's keepalive"); + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_FAILURE, + channel->remote_channel); + if (rc != SSH_OK) { + return SSH_PACKET_USED; + } + ssh_packet_send(session); + + return SSH_PACKET_USED; + } + + if (strcmp(request, "auth-agent-req@openssh.com") == 0) { + int status; + + SAFE_FREE(request); + SSH_LOG(SSH_LOG_DEBUG, "Received an auth-agent-req request"); + + status = SSH2_MSG_CHANNEL_FAILURE; + ssh_callbacks_iterate (channel->callbacks, + ssh_channel_callbacks, + channel_auth_agent_req_function) { + ssh_callbacks_iterate_exec(channel_auth_agent_req_function, + channel->session, + channel); + /* in lieu of a return value, if the callback exists it's supported + */ + status = SSH2_MSG_CHANNEL_SUCCESS; + break; + } + ssh_callbacks_iterate_end(); + + if (want_reply) { + rc = ssh_buffer_pack(session->out_buffer, + "bd", + status, + channel->remote_channel); + if (rc != SSH_OK) { + return SSH_PACKET_USED; + } + ssh_packet_send(session); + } + + return SSH_PACKET_USED; + } +#ifdef WITH_SERVER + /* If we are here, that means we have a request that is not in the + * understood client requests. That means we need to create a ssh message to + * be passed to the user code handling ssh messages + */ + ssh_message_handle_channel_request(session, + channel, + packet, + request, + want_reply); +#else + SSH_LOG(SSH_LOG_DEBUG, "Unhandled channel request %s", request); +#endif + + SAFE_FREE(request); + + return SSH_PACKET_USED; +} + +/* + * When data has been received from the ssh server, it can be applied to the + * known user function, with help of the callback, or inserted here + * + * FIXME is the window changed? + */ +int channel_default_bufferize(ssh_channel channel, + void *data, uint32_t len, + bool is_stderr) +{ + ssh_session session = NULL; + + if (channel == NULL) { + return -1; + } + + session = channel->session; + + if (data == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, + "placing %" PRIu32 " bytes into channel buffer (%s)", + len, + is_stderr ? "stderr" : "stdout"); + if (!is_stderr) { + /* stdout */ + if (channel->stdout_buffer == NULL) { + channel->stdout_buffer = ssh_buffer_new(); + if (channel->stdout_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (ssh_buffer_add_data(channel->stdout_buffer, data, len) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stdout_buffer); + channel->stdout_buffer = NULL; + return -1; + } + } else { + /* stderr */ + if (channel->stderr_buffer == NULL) { + channel->stderr_buffer = ssh_buffer_new(); + if (channel->stderr_buffer == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + + if (ssh_buffer_add_data(channel->stderr_buffer, data, len) < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(channel->stderr_buffer); + channel->stderr_buffer = NULL; + return -1; + } + } + + return 0; +} + +/** + * @brief Open a session channel (suited for a shell, not TCP forwarding). + * + * @param[in] channel An allocated channel. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @see ssh_channel_open_forward() + * @see ssh_channel_request_env() + * @see ssh_channel_request_shell() + * @see ssh_channel_request_exec() + */ +int ssh_channel_open_session(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + + return channel_open(channel, + "session", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + NULL); +} + +/** + * @brief Open an agent authentication forwarding channel. This type of channel + * can be opened by a server towards a client in order to provide SSH-Agent + * services to the server-side process. This channel can only be opened if the + * client claimed support by sending a channel request beforehand. + * + * @param[in] channel An allocated channel. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @see ssh_channel_open_forward() + */ +int ssh_channel_open_auth_agent(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + + return channel_open(channel, + "auth-agent@openssh.com", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + NULL); +} + +/** + * @brief Open a TCP/IP forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotehost The remote host to connected (host name or IP). + * + * @param[in] remoteport The remote port. + * + * @param[in] sourcehost The numeric IP address of the machine from where the + * connection request originates. This is mostly for + * logging purposes. + * + * @param[in] localport The port on the host from where the connection + * originated. This is mostly for logging purposes. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the local port and does not + * automatically forward the content of a socket to the channel. You still have + * to use ssh_channel_read and ssh_channel_write for this. + */ +int ssh_channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return rc; + } + + session = channel->session; + + if (remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(session); + return rc; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, + "sdsd", + remotehost, + remoteport, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_open(channel, + "direct-tcpip", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + SSH_STRING_FREE(str); + + return rc; +} + +/** + * @brief Open a TCP/IP - UNIX domain socket forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotepath The UNIX socket path on the remote machine + * + * @param[in] sourcehost The numeric IP address of the machine from where the + * connection request originates. This is mostly for + * logging purposes. + * + * @param[in] localport The port on the host from where the connection + * originated. This is mostly for logging purposes. + * + * @return `SSH_OK on` success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the local port and does not + * automatically forward the content of a socket to the channel. + * You still have to use ssh_channel_read and ssh_channel_write for this. + * @warning Requires support of OpenSSH for UNIX domain socket forwarding. + */ +int ssh_channel_open_forward_unix(ssh_channel channel, + const char *remotepath, + const char *sourcehost, + int localport) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + int rc = SSH_ERROR; + int version; + + if (channel == NULL) { + return rc; + } + + session = channel->session; + + version = ssh_get_openssh_version(session); + if (version == 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "We're not connected to an OpenSSH server!"); + return SSH_ERROR; + } + + if (remotepath == NULL || sourcehost == NULL) { + ssh_set_error_invalid(session); + return rc; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, + "ssd", + remotepath, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_open(channel, + "direct-streamlocal@openssh.com", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + + return rc; +} + +/** + * @brief Open a TCP/IP - VPN tunnel channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remoteunit The remote interface number, or may be 0x7fffffff + * to allow the server to automatically choose + * an interface. + * + * @return SSH_OK on success, + * SSH_ERROR if an error occurred, + * SSH_AGAIN if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the channel to a local interface + * and does not automatically forward packets from a local interface + * to the channel. + * You still have to use channel_read and channel_write for this. + * @warning Requires support of OpenSSH for VPN tunneling. + */ +int ssh_channel_open_tunnel(ssh_channel channel, + int remoteunit) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + ssh_string str = NULL; + int rc = SSH_ERROR; + int version; + + if (channel == NULL) { + return rc; + } + + session = channel->session; + + version = ssh_get_openssh_version(session); + if (version == 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "We're not connected to an OpenSSH server!"); + return SSH_ERROR; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, + "dd", + 2, // SSH_TUNMODE_ETHERNET + remoteunit); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_open(channel, + "tun@openssh.com", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + SSH_STRING_FREE(str); + + return rc; +} + +/** + * @brief Close and free a channel. + * + * @param[in] channel The channel to free. + * + * @warning Any data unread on this channel will be lost. + */ +void ssh_channel_free(ssh_channel channel) +{ + ssh_session session = NULL; + + if (channel == NULL) { + return; + } + + session = channel->session; + if (session->alive) { + bool send_close = false; + + switch (channel->state) { + case SSH_CHANNEL_STATE_OPEN: + send_close = true; + break; + case SSH_CHANNEL_STATE_CLOSED: + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) { + send_close = true; + } + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) { + send_close = false; + } + break; + default: + send_close = false; + break; + } + + if (send_close) { + ssh_channel_close(channel); + } + } + channel->flags |= SSH_CHANNEL_FLAG_FREED_LOCAL; + + if (channel->callbacks != NULL) { + ssh_list_free(channel->callbacks); + channel->callbacks = NULL; + } + + /* The idea behind the flags is the following : it is well possible + * that a client closes a channel that still exists on the server side. + * We definitively close the channel when we receive a close message *and* + * the user closed it. + */ + if ((channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + (channel->flags & SSH_CHANNEL_FLAG_NOT_BOUND)) { + ssh_channel_do_free(channel); + } +} + +/** + * @internal + * @brief Effectively free a channel, without caring about flags + */ + +void ssh_channel_do_free(ssh_channel channel) +{ + struct ssh_iterator *it = NULL; + ssh_session session = channel->session; + + it = ssh_list_find(session->channels, channel); + if (it != NULL) { + ssh_list_remove(session->channels, it); + } + + SSH_BUFFER_FREE(channel->stdout_buffer); + SSH_BUFFER_FREE(channel->stderr_buffer); + + if (channel->callbacks != NULL) { + ssh_list_free(channel->callbacks); + channel->callbacks = NULL; + } + SAFE_FREE(channel->exit.signal); + + channel->session = NULL; + SAFE_FREE(channel); +} + +/** + * @brief Send an end of file on the channel. + * + * This doesn't close the channel. You may still read from it but not write. + * + * @param[in] channel The channel to send the eof to. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + * + * Example: +@code + rc = ssh_channel_send_eof(channel); + if (rc == SSH_ERROR) { + return -1; + } + while(!ssh_channel_is_eof(channel)) { + rc = ssh_channel_read(channel, buf, sizeof(buf), 0); + if (rc == SSH_ERROR) { + return -1; + } + } + ssh_channel_close(channel); +@endcode + * + * @see ssh_channel_close() + * @see ssh_channel_free() + * @see ssh_channel_is_eof() + */ +int ssh_channel_send_eof(ssh_channel channel) +{ + ssh_session session = NULL; + int rc = SSH_ERROR; + int err; + + if (channel == NULL || channel->session == NULL) { + return rc; + } + + /* If the EOF has already been sent we're done here. */ + if (channel->local_eof != 0) { + return SSH_OK; + } + + session = channel->session; + + err = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_EOF, + channel->remote_channel); + if (err != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_packet_send(session); + SSH_LOG(SSH_LOG_PACKET, + "Sent a EOF on client channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_channel_flush(channel); + if (rc == SSH_ERROR) { + goto error; + } + channel->local_eof = 1; + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/** + * @brief Close a channel. + * + * This sends an end of file and then closes the channel. You won't be able + * to recover any data the server was going to send or was in buffers. + * + * @param[in] channel The channel to close. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + * + * @see ssh_channel_free() + * @see ssh_channel_is_eof() + */ +int ssh_channel_close(ssh_channel channel) +{ + ssh_session session = NULL; + int rc = 0; + + if(channel == NULL) { + return SSH_ERROR; + } + + /* If the channel close has already been sent we're done here. */ + if (channel->flags & SSH_CHANNEL_FLAG_CLOSED_LOCAL) { + return SSH_OK; + } + + session = channel->session; + + rc = ssh_channel_send_eof(channel); + if (rc != SSH_OK) { + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_CLOSE, + channel->remote_channel); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_packet_send(session); + SSH_LOG(SSH_LOG_PACKET, + "Sent a close on client channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + + if (rc == SSH_OK) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + channel->flags |= SSH_CHANNEL_FLAG_CLOSED_LOCAL; + } + + rc = ssh_channel_flush(channel); + if(rc == SSH_ERROR) { + goto error; + } + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/* this termination function waits for a window growing condition */ +static int ssh_channel_waitwindow_termination(void *c) +{ + ssh_channel channel = (ssh_channel) c; + if (channel->remote_window > 0 || + channel->session->session_state == SSH_SESSION_STATE_ERROR || + channel->state == SSH_CHANNEL_STATE_CLOSED) + return 1; + else + return 0; +} + +/* This termination function waits until the session is not in blocked status + * anymore, e.g. because of a key re-exchange. + */ +static int ssh_waitsession_unblocked(void *s) +{ + ssh_session session = (ssh_session)s; + switch (session->session_state){ + case SSH_SESSION_STATE_DH: + case SSH_SESSION_STATE_INITIAL_KEX: + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + return 0; + default: + return 1; + } +} +/** + * @internal + * @brief Flushes a channel (and its session) until the output buffer + * is empty, or timeout elapsed. + * @param channel SSH channel + * @return `SSH_OK` On success, + * `SSH_ERROR` On error. + * `SSH_AGAIN` Timeout elapsed (or in nonblocking mode). + */ +int ssh_channel_flush(ssh_channel channel) +{ + return ssh_blocking_flush(channel->session, SSH_TIMEOUT_DEFAULT); +} + +static int channel_write_common(ssh_channel channel, + const void *data, + uint32_t len, int is_stderr) +{ + ssh_session session = NULL; + uint32_t origlen = len; + uint32_t effectivelen; + int rc; + + if (channel == NULL) { + return -1; + } + session = channel->session; + if (data == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + if (len > INT_MAX) { + SSH_LOG(SSH_LOG_TRACE, + "Length (%" PRIu32 ") is bigger than INT_MAX", + len); + return SSH_ERROR; + } + + if (channel->local_eof) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't write to channel %" PRIu32 ":%" PRIu32 + " after EOF was sent", + channel->local_channel, + channel->remote_channel); + return -1; + } + + if (channel->state != SSH_CHANNEL_STATE_OPEN || + channel->delayed_close != 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, "Remote channel is closed"); + + return -1; + } + + if (session->session_state == SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + + if (ssh_waitsession_unblocked(session) == 0) { + rc = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_waitsession_unblocked, + session); + if (rc == SSH_ERROR || !ssh_waitsession_unblocked(session)) + goto out; + } + while (len > 0) { + if (channel->remote_window < len) { + SSH_LOG(SSH_LOG_DEBUG, + "Remote window is %" PRIu32 + " bytes. going to write %" PRIu32 " bytes", + channel->remote_window, + len); + /* When the window is zero, wait for it to grow */ + if (channel->remote_window == 0) { + /* nothing can be written */ + SSH_LOG(SSH_LOG_DEBUG, "Wait for a growing window message..."); + rc = ssh_handle_packets_termination( + session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_waitwindow_termination, + channel); + if (rc == SSH_ERROR || + !ssh_channel_waitwindow_termination(channel) || + session->session_state == SSH_SESSION_STATE_ERROR || + channel->state == SSH_CHANNEL_STATE_CLOSED) + goto out; + continue; + } + /* When the window is non-zero, accept data up to the window size */ + effectivelen = MIN(len, channel->remote_window); + } else { + effectivelen = len; + } + + /* + * Like OpenSSH, don't subtract bytes for the header fields + * and allow to send a payload of remote_maxpacket length. + */ + effectivelen = MIN(effectivelen, channel->remote_maxpacket); + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + is_stderr ? SSH2_MSG_CHANNEL_EXTENDED_DATA + : SSH2_MSG_CHANNEL_DATA, + channel->remote_channel); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + /* stderr message has an extra field */ + if (is_stderr) { + rc = ssh_buffer_pack(session->out_buffer, + "d", + SSH2_EXTENDED_DATA_STDERR); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + } + + /* append payload data */ + rc = ssh_buffer_pack(session->out_buffer, + "dP", + effectivelen, + (size_t)effectivelen, + data); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "ssh_channel_write wrote %" PRIu32 " bytes", + effectivelen); + + channel->remote_window -= effectivelen; + len -= effectivelen; + data = ((uint8_t *)data + effectivelen); + if (channel->counter != NULL) { + channel->counter->out_bytes += effectivelen; + } + } + + /* it's a good idea to flush the socket now */ + rc = ssh_channel_flush(channel); + if (rc == SSH_ERROR) { + goto error; + } + +out: + return (int)(origlen - len); + +error: + ssh_buffer_reinit(session->out_buffer); + + return SSH_ERROR; +} + +/** + * @brief Get the remote window size. + * + * This is the maximum amount of bytes the remote side expects us to send + * before growing the window again. + * + * @param[in] channel The channel to query. + * + * @return The remote window size + * + * @warning A nonzero return value does not guarantee the socket is ready + * to send that much data. Buffering may happen in the local SSH + * packet buffer, so beware of really big window sizes. + * + * @warning A zero return value means ssh_channel_write (default settings) + * will block until the window grows back. + */ +uint32_t ssh_channel_window_size(ssh_channel channel) +{ + return channel->remote_window; +} + +/** + * @brief Blocking write on a channel. + * + * @param[in] channel The channel to write to. + * + * @param[in] data A pointer to the data to write. + * + * @param[in] len The length of the buffer to write to. + * + * @return The number of bytes written, `SSH_ERROR` on error. + * + * @see ssh_channel_read() + */ +int ssh_channel_write(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 0); +} + +/** + * @brief Check if the channel is open or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is closed, nonzero otherwise. + * + * @see ssh_channel_is_closed() + */ +int ssh_channel_is_open(ssh_channel channel) +{ + if (channel == NULL || channel->session == NULL) { + return 0; + } + return (channel->state == SSH_CHANNEL_STATE_OPEN && channel->session->alive != 0); +} + +/** + * @brief Check if the channel is closed or not. + * + * @param[in] channel The channel to check. + * + * @return 0 if channel is opened, nonzero otherwise. + * + * @see ssh_channel_is_open() + */ +int ssh_channel_is_closed(ssh_channel channel) +{ + if (channel == NULL || channel->session == NULL) { + return SSH_ERROR; + } + return (channel->state != SSH_CHANNEL_STATE_OPEN || channel->session->alive == 0); +} + +/** + * @brief Check if remote has sent an EOF. + * + * @param[in] channel The channel to check. + * + * @return 0 if there is no EOF, nonzero otherwise. + */ +int ssh_channel_is_eof(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + if (ssh_channel_has_unread_data(channel)) { + return 0; + } + + return (channel->remote_eof != 0); +} + +/** + * @brief Put the channel into blocking or nonblocking mode. + * + * @param[in] channel The channel to use. + * + * @param[in] blocking A boolean for blocking or nonblocking. + * + * @warning A side-effect of this is to put the whole session + * in non-blocking mode. + * @see ssh_set_blocking() + */ +void ssh_channel_set_blocking(ssh_channel channel, int blocking) +{ + if (channel == NULL) { + return; + } + ssh_set_blocking(channel->session, blocking); +} + +/** + * @internal + * + * @brief handle a SSH_CHANNEL_SUCCESS packet and set the channel state. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_success) +{ + ssh_channel channel = NULL; + (void)type; + (void)user; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_CHANNEL_SUCCESS on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + if (channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING) { + SSH_LOG(SSH_LOG_RARE, + "SSH_CHANNEL_SUCCESS received in incorrect state %d", + channel->request_state); + } else { + channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); + } + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_CHANNEL_FAILURE packet and set the channel state. + */ +SSH_PACKET_CALLBACK(ssh_packet_channel_failure) +{ + ssh_channel channel = NULL; + (void)type; + (void)user; + + channel = channel_from_msg(session, packet); + if (channel == NULL) { + SSH_LOG(SSH_LOG_FUNCTIONS, "%s", ssh_get_error(session)); + + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_CHANNEL_FAILURE on channel (%" PRIu32 ":%" PRIu32 ")", + channel->local_channel, + channel->remote_channel); + if (channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING) { + SSH_LOG(SSH_LOG_RARE, + "SSH_CHANNEL_FAILURE received in incorrect state %d", + channel->request_state); + } else { + channel->request_state = SSH_CHANNEL_REQ_STATE_DENIED; + + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_request_response_function, + channel->session, + channel); + } + + return SSH_PACKET_USED; +} + +static int ssh_channel_request_termination(void *c) +{ + ssh_channel channel = (ssh_channel)c; + if(channel->request_state != SSH_CHANNEL_REQ_STATE_PENDING || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +static int channel_request(ssh_channel channel, const char *request, + ssh_buffer buffer, int reply) +{ + ssh_session session = channel->session; + int rc = SSH_ERROR; + int ret; + + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + ret = ssh_buffer_pack(session->out_buffer, + "bdsb", + SSH2_MSG_CHANNEL_REQUEST, + channel->remote_channel, + request, + reply == 0 ? 0 : 1); + if (ret != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + if (buffer != NULL) { + if (ssh_buffer_add_data(session->out_buffer, ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)) < 0) { + ssh_set_error_oom(session); + goto error; + } + } + channel->request_state = SSH_CHANNEL_REQ_STATE_PENDING; + if (ssh_packet_send(session) == SSH_ERROR) { + return rc; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a SSH_MSG_CHANNEL_REQUEST %s on channel %" PRIu32 ":%" PRIu32, + request, + channel->local_channel, + channel->remote_channel); + if (reply == 0) { + channel->request_state = SSH_CHANNEL_REQ_STATE_NONE; + return SSH_OK; + } +pending: + rc = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_request_termination, + channel); + + if(session->session_state == SSH_SESSION_STATE_ERROR || rc == SSH_ERROR) { + channel->request_state = SSH_CHANNEL_REQ_STATE_ERROR; + } + /* we received something */ + switch (channel->request_state){ + case SSH_CHANNEL_REQ_STATE_ERROR: + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Channel request %s failed on channel %" PRIu32 ":%" PRIu32, + request, + channel->local_channel, + channel->remote_channel); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + SSH_LOG(SSH_LOG_DEBUG, + "Channel request %s success on channel %" PRIu32 ":%" PRIu32, + request, + channel->local_channel, + channel->remote_channel); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_PENDING: + rc = SSH_AGAIN; + return rc; + case SSH_CHANNEL_REQ_STATE_NONE: + /* Never reached */ + ssh_set_error(session, SSH_FATAL, "Invalid state in channel_request()"); + rc=SSH_ERROR; + break; + } + channel->request_state=SSH_CHANNEL_REQ_STATE_NONE; + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/** + * @brief Request a pty with a specific type and size. + * + * @param[in] channel The channel to send the request. + * + * @param[in] terminal The terminal type ("vt100, xterm,..."). + * + * @param[in] col The number of columns. + * + * @param[in] row The number of rows. + * + * @param[in] modes Encoded SSH terminal modes for the PTY + * + * @param[in] modes_len Number of bytes in 'modes' + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_request_pty_size_modes(ssh_channel channel, const char *terminal, + int col, int row, const unsigned char* modes, size_t modes_len) +{ + ssh_session session = NULL; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if (terminal == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + switch (channel->request_state) { + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "sdddddP", + terminal, + col, + row, + 0, /* pix */ + 0, /* pix */ + (uint32_t)modes_len, + (size_t)modes_len, + modes); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_request(channel, "pty-req", buffer, 1); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Request a PTY with a specific size using current TTY modes. + * + * Encodes @p terminal modes from the current TTY and sends a PTY request + * for the given channel, terminal type, and size in columns/rows. + * + * @param[in] channel The channel to send the request on. + * @param[in] terminal The terminal type (e.g. "xterm"). + * @param[in] col Number of columns. + * @param[in] row Number of rows. + * + * @return `SSH_OK` on success; `SSH_ERROR` on failure. + */ +int ssh_channel_request_pty_size(ssh_channel channel, const char *terminal, + int col, int row) +{ + /* use modes from the current TTY */ + unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + int rc = encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + if (rc < 0) { + return rc; + } + return ssh_channel_request_pty_size_modes(channel, + terminal, + col, + row, + modes_buf, + (size_t)rc); +} + +/** + * @brief Request a PTY. + * + * @param[in] channel The channel to send the request. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @see ssh_channel_request_pty_size() + */ +int ssh_channel_request_pty(ssh_channel channel) +{ + return ssh_channel_request_pty_size(channel, "xterm", 80, 24); +} + +/** + * @brief Change the size of the terminal associated to a channel. + * + * @param[in] channel The channel to change the size. + * + * @param[in] cols The new number of columns. + * + * @param[in] rows The new number of rows. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + * + * @warning Do not call it from a signal handler if you are not sure any other + * libssh function using the same channel/session is running at the + * same time (not 100% threadsafe). + */ +int ssh_channel_change_pty_size(ssh_channel channel, int cols, int rows) +{ + ssh_session session = channel->session; + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "dddd", + cols, + rows, + 0, /* pix */ + 0 /* pix */); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + rc = channel_request(channel, "window-change", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Request a shell. + * + * @param[in] channel The channel to send the request. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_request_shell(ssh_channel channel) +{ + if (channel == NULL) { + return SSH_ERROR; + } + + return channel_request(channel, "shell", NULL, 1); +} + +/** + * @brief Request a subsystem (for example "sftp"). + * + * @param[in] channel The channel to send the request. + * + * @param[in] subsys The subsystem to request (for example "sftp"). + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @warning You normally don't have to call it for sftp, see sftp_new(). + */ +int ssh_channel_request_subsystem(ssh_channel channel, const char *subsys) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(subsys == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "s", subsys); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "subsystem", buffer, 1); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Request sftp subsystem on the channel + * + * @param[in] channel The channel to request the sftp subsystem. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @note You should use sftp_new() which does this for you. + */ +int ssh_channel_request_sftp( ssh_channel channel) +{ + if(channel == NULL) { + return SSH_ERROR; + } + return ssh_channel_request_subsystem(channel, "sftp"); +} + +static char *generate_cookie(void) +{ + static const char *hex = "0123456789abcdef"; + char s[36]; + unsigned char rnd[16]; + int ok; + int i; + + ok = ssh_get_random(rnd, sizeof(rnd), 0); + if (!ok) { + return NULL; + } + + for (i = 0; i < 16; i++) { + s[i*2] = hex[rnd[i] & 0x0f]; + s[i*2+1] = hex[rnd[i] >> 4]; + } + s[32] = '\0'; + return strdup(s); +} + +/** + * @brief Sends the "x11-req" channel request over an existing session channel. + * + * This will enable redirecting the display of the remote X11 applications to + * local X server over a secure tunnel. + * + * @param[in] channel An existing session channel where the remote X11 + * applications are going to be executed. + * + * @param[in] single_connection A boolean to mark only one X11 app will be + * redirected. + * + * @param[in] protocol A x11 authentication protocol. Pass NULL to use the + * default value MIT-MAGIC-COOKIE-1. + * + * @param[in] cookie A x11 authentication cookie. Pass NULL to generate + * a random cookie. + * + * @param[in] screen_number The screen number. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number) +{ + ssh_buffer buffer = NULL; + char *c = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + if (cookie == NULL) { + c = generate_cookie(); + if (c == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + } + + rc = ssh_buffer_pack(buffer, + "bssd", + single_connection == 0 ? 0 : 1, + protocol ? protocol : "MIT-MAGIC-COOKIE-1", + cookie ? cookie : c, + screen_number); + if (c != NULL){ + SAFE_FREE(c); + } + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "x11-req", buffer, 1); + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +static ssh_channel ssh_channel_accept(ssh_session session, int channeltype, + int timeout_ms, int *destination_port, char **originator, int *originator_port) +{ +#ifndef _WIN32 + static const struct timespec ts = { + .tv_sec = 0, + .tv_nsec = 50000000 /* 50ms */ + }; +#endif + ssh_message msg = NULL; + ssh_channel channel = NULL; + struct ssh_iterator *iterator = NULL; + int t; + + /* + * We sleep for 50 ms in ssh_handle_packets() and later sleep for + * 50 ms. So we need to decrement by 100 ms. + */ + for (t = timeout_ms; t >= 0; t -= 100) { + if (timeout_ms == 0) { + ssh_handle_packets(session, 0); + } else { + ssh_handle_packets(session, 50); + } + + if (session->ssh_message_list) { + iterator = ssh_list_get_iterator(session->ssh_message_list); + while (iterator) { + msg = (ssh_message)iterator->data; + if (ssh_message_type(msg) == SSH_REQUEST_CHANNEL_OPEN && + ssh_message_subtype(msg) == channeltype) { + ssh_list_remove(session->ssh_message_list, iterator); + channel = ssh_message_channel_request_open_reply_accept(msg); + if(destination_port) { + *destination_port=msg->channel_request_open.destination_port; + } + if(originator) { + *originator=strdup(msg->channel_request_open.originator); + } + if(originator_port) { + *originator_port=msg->channel_request_open.originator_port; + } + + ssh_message_free(msg); + return channel; + } + iterator = iterator->next; + } + } + if(t>0){ +#ifdef _WIN32 + Sleep(50); /* 50ms */ +#else + nanosleep(&ts, NULL); +#endif + } + } + + ssh_set_error(session, SSH_NO_ERROR, "No channel request of this type from server"); + return NULL; +} + +/** + * @brief Accept an X11 forwarding channel. + * + * @param[in] channel An x11-enabled session channel. + * + * @param[in] timeout_ms Timeout in milliseconds. + * + * @return A newly created channel, or NULL if no X11 request from + * the server. + */ +ssh_channel ssh_channel_accept_x11(ssh_channel channel, int timeout_ms) +{ + return ssh_channel_accept(channel->session, SSH_CHANNEL_X11, timeout_ms, NULL, NULL, NULL); +} + +/** + * @brief Send an "auth-agent-req" channel request over an existing session + * channel. + * + * This client-side request will enable forwarding the agent over an secure + * tunnel. When the server is ready to open one authentication agent channel, an + * ssh_channel_open_request_auth_agent_callback event will be generated. + * + * @param[in] channel The channel to send signal. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred + */ +int ssh_channel_request_auth_agent(ssh_channel channel) { + if (channel == NULL) { + return SSH_ERROR; + } + + return channel_request(channel, "auth-agent-req@openssh.com", NULL, 0); +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_SUCCESS packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_success){ + (void)type; + (void)user; + (void)packet; + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_REQUEST_SUCCESS"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + SSH_LOG(SSH_LOG_RARE, "SSH_REQUEST_SUCCESS received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_ACCEPTED; + } + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_REQUEST_DENIED packet normally sent after a global + * request. + */ +SSH_PACKET_CALLBACK(ssh_request_denied){ + (void)type; + (void)user; + (void)packet; + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_REQUEST_FAILURE"); + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING){ + SSH_LOG(SSH_LOG_RARE, "SSH_REQUEST_DENIED received in incorrect state %d", + session->global_req_state); + } else { + session->global_req_state=SSH_CHANNEL_REQ_STATE_DENIED; + } + + return SSH_PACKET_USED; + +} + +static int ssh_global_request_termination(void *s) +{ + ssh_session session = (ssh_session) s; + if (session->global_req_state != SSH_CHANNEL_REQ_STATE_PENDING || + session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @internal + * + * @brief Send a global request (needed for forward listening) and wait for the + * result. + * + * @param[in] session The SSH session handle. + * + * @param[in] request The type of request (defined in RFC). + * + * @param[in] buffer Additional data to put in packet. + * + * @param[in] reply Set if you expect a reply from server. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_global_request(ssh_session session, + const char *request, + ssh_buffer buffer, + int reply) +{ + int rc; + + switch (session->global_req_state) { + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bsb", + SSH2_MSG_GLOBAL_REQUEST, + request, + reply == 0 ? 0 : 1); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + + if (buffer != NULL) { + rc = ssh_buffer_add_data(session->out_buffer, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc < 0) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + } + + session->global_req_state = SSH_CHANNEL_REQ_STATE_PENDING; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return rc; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a SSH_MSG_GLOBAL_REQUEST %s", request); + + if (reply == 0) { + session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE; + + return SSH_OK; + } +pending: + rc = ssh_handle_packets_termination(session, + SSH_TIMEOUT_DEFAULT, + ssh_global_request_termination, + session); + + if(rc==SSH_ERROR || session->session_state == SSH_SESSION_STATE_ERROR){ + session->global_req_state = SSH_CHANNEL_REQ_STATE_ERROR; + } + switch(session->global_req_state){ + case SSH_CHANNEL_REQ_STATE_ACCEPTED: + SSH_LOG(SSH_LOG_DEBUG, "Global request %s success",request); + rc=SSH_OK; + break; + case SSH_CHANNEL_REQ_STATE_DENIED: + SSH_LOG(SSH_LOG_PACKET, + "Global request %s failed", request); + ssh_set_error(session, SSH_REQUEST_DENIED, + "Global request %s failed", request); + rc=SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_ERROR: + case SSH_CHANNEL_REQ_STATE_NONE: + rc = SSH_ERROR; + break; + case SSH_CHANNEL_REQ_STATE_PENDING: + return SSH_AGAIN; + } + session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE; + + return rc; +error: + ssh_buffer_reinit(session->out_buffer); + + return rc; +} + +/** + * @brief Sends the "tcpip-forward" global request to ask the server to begin + * listening for inbound connections. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The address to bind to on the server. Pass NULL to bind + * to all available addresses on all protocol families + * supported by the server. + * + * @param[in] port The port to bind to on the server. Pass 0 to ask the + * server to allocate the next available unprivileged port + * number + * + * @param[in] bound_port The pointer to get actual bound port. Pass NULL to + * ignore. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + **/ +int ssh_channel_listen_forward(ssh_session session, + const char *address, + int port, + int *bound_port) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE) + goto pending; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "sd", + address ? address : "", + port); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } +pending: + rc = ssh_global_request(session, "tcpip-forward", buffer, 1); + + /* TODO: FIXME no guarantee the last packet we received contains + * that info */ + if (rc == SSH_OK && port == 0 && bound_port != NULL) { + rc = ssh_buffer_unpack(session->in_buffer, "d", bound_port); + if (rc != SSH_OK) + *bound_port = 0; + } + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/* DEPRECATED */ +int ssh_forward_listen(ssh_session session, const char *address, int port, int *bound_port) +{ + return ssh_channel_listen_forward(session, address, port, bound_port); +} + +/* DEPRECATED */ +ssh_channel ssh_forward_accept(ssh_session session, int timeout_ms) +{ + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, NULL, NULL, NULL); +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel and get some information + * about incoming connection + * + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @param[in] destination_port A pointer to destination port or NULL. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + */ +ssh_channel ssh_channel_accept_forward(ssh_session session, int timeout_ms, int* destination_port) { + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, NULL, NULL); +} + +/** + * @brief Accept an incoming TCP/IP forwarding channel and get information + * about incoming connection + * + * @param[in] session The ssh session to use. + * + * @param[in] timeout_ms A timeout in milliseconds. + * + * @param[out] destination_port A pointer to destination port or NULL. + * + * @param[out] originator A pointer to a pointer to a string of originator host or NULL. + * That the caller is responsible for to ssh_string_free_char(). + * + * @param[out] originator_port A pointer to originator port or NULL. + * + * @return Newly created channel, or NULL if no incoming channel request from + * the server + * + * @see ssh_string_free_char() + */ +ssh_channel ssh_channel_open_forward_port(ssh_session session, int timeout_ms, int *destination_port, char **originator, int *originator_port) { + return ssh_channel_accept(session, SSH_CHANNEL_FORWARDED_TCPIP, timeout_ms, destination_port, originator, originator_port); +} + +/** + * @brief Sends the "cancel-tcpip-forward" global request to ask the server to + * cancel the tcpip-forward request. + * + * @param[in] session The ssh session to send the request. + * + * @param[in] address The bound address on the server. + * + * @param[in] port The bound port on the server. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + */ +int ssh_channel_cancel_forward(ssh_session session, + const char *address, + int port) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(session->global_req_state != SSH_CHANNEL_REQ_STATE_NONE) + goto pending; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "sd", + address ? address : "", + port); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } +pending: + rc = ssh_global_request(session, "cancel-tcpip-forward", buffer, 1); + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/* DEPRECATED */ +int ssh_forward_cancel(ssh_session session, const char *address, int port) +{ + return ssh_channel_cancel_forward(session, address, port); +} + +/** + * @brief Set environment variables. + * + * @param[in] channel The channel to set the environment variables. + * + * @param[in] name The name of the variable. + * + * @param[in] value The value to set. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * @warning Some environment variables may be refused by security reasons. + */ +int ssh_channel_request_env(ssh_channel channel, const char *name, const char *value) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(name == NULL || value == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "ss", + name, + value); + if (rc != SSH_OK){ + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "env", buffer,1); +error: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @brief Run a shell command without an interactive shell. + * + * This is similar to 'sh -c command'. + * + * @param[in] channel The channel to execute the command. + * + * @param[in] cmd The command to execute + * (e.g. "ls ~/ -al | grep -i reports"). + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * Example: +@code + rc = ssh_channel_request_exec(channel, "ps aux"); + if (rc > 0) { + return -1; + } + + while ((rc = ssh_channel_read(channel, buffer, sizeof(buffer), 0)) > 0) { + if (fwrite(buffer, 1, rc, stdout) != (unsigned int) rc) { + return -1; + } + } +@endcode + * + * @warning In a single channel, only ONE command can be executed! + * If you want to executed multiple commands, allocate separate channels for + * them or consider opening interactive shell. + * Attempting to run multiple consecutive commands in one channel will fail. + * See RFC 4254 Section 6.5. + * + * @see ssh_channel_request_shell() + */ +int ssh_channel_request_exec(ssh_channel channel, const char *cmd) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + if(cmd == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + switch(channel->request_state){ + case SSH_CHANNEL_REQ_STATE_NONE: + break; + default: + goto pending; + } + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "s", cmd); + + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } +pending: + rc = channel_request(channel, "exec", buffer, 1); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/** + * @brief Send a signal to remote process (as described in RFC 4254, + * section 6.9). + * + * Sends a signal 'sig' to the remote process. + * Note, that remote system may not support signals concept. + * In such a case this request will be silently ignored. + * + * @param[in] channel The channel to send signal. + * + * @param[in] sig The signal to send (without SIG prefix) + * \n\n + * SIGABRT -> ABRT \n + * SIGALRM -> ALRM \n + * SIGFPE -> FPE \n + * SIGHUP -> HUP \n + * SIGILL -> ILL \n + * SIGINT -> INT \n + * SIGKILL -> KILL \n + * SIGPIPE -> PIPE \n + * SIGQUIT -> QUIT \n + * SIGSEGV -> SEGV \n + * SIGTERM -> TERM \n + * SIGUSR1 -> USR1 \n + * SIGUSR2 -> USR2 \n + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + */ +int ssh_channel_request_send_signal(ssh_channel channel, const char *sig) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return SSH_ERROR; + } + if (sig == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "s", sig); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "signal", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/** + * @brief Send a break signal to the server (as described in RFC 4335). + * + * Sends a break signal to the remote process. + * Note, that remote system may not support breaks. + * In such a case this request will be silently ignored. + * + * @param[in] channel The channel to send the break to. + * + * @param[in] length The break-length in milliseconds to send. + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred + */ +int ssh_channel_request_send_break(ssh_channel channel, uint32_t length) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "d", length); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "break", buffer, 0); + +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/** + * @brief Read data from a channel into a buffer. + * + * @param[in] channel The channel to read from. + * + * @param[out] buffer The buffer which will get the data. + * + * @param[in] count The count of bytes to be read. If it is bigger than 0, + * the exact size will be read, else (bytes=0) it will return once anything is + * available. + * + * @param is_stderr A boolean value to mark reading from the stderr stream. + * + * @return The number of bytes read, 0 on end of file, `SSH_AGAIN` + * on timeout and `SSH_ERROR` on error. + * + * @deprecated Please use ssh_channel_read instead + * @warning This function doesn't work in nonblocking/timeout mode + * @see ssh_channel_read + */ +int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + int is_stderr) +{ + ssh_session session = NULL; + char *buffer_tmp = NULL; + int r; + uint32_t total = 0; + + if (channel == NULL) { + return SSH_ERROR; + } + session = channel->session; + + if (buffer == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + ssh_buffer_reinit(buffer); + if (count == 0) { + do { + r = ssh_channel_poll(channel, is_stderr); + if (r < 0) { + return r; + } + if (r > 0) { + count = r; + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + r = ssh_channel_read(channel, buffer_tmp, r, is_stderr); + if (r < 0) { + ssh_buffer_pass_bytes_end(buffer, count); + return r; + } + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - r); + + return r; + } + if (ssh_channel_is_eof(channel)) { + return 0; + } + ssh_handle_packets(channel->session, SSH_TIMEOUT_INFINITE); + } while (r == 0); + } + + buffer_tmp = ssh_buffer_allocate(buffer, count); + if (buffer_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + while (total < count) { + r = ssh_channel_read(channel, buffer_tmp, count - total, is_stderr); + if (r < 0) { + ssh_buffer_pass_bytes_end(buffer, count); + return r; + } + if (r == 0) { + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(buffer, count - total); + return total; + } + total += r; + } + + return total; +} + +struct ssh_channel_read_termination_struct { + ssh_channel channel; + ssh_buffer buffer; +}; + +static int ssh_channel_read_termination(void *s) +{ + struct ssh_channel_read_termination_struct *ctx = s; + if (ssh_buffer_get_len(ctx->buffer) >= 1 || + ctx->channel->remote_eof || + ctx->channel->session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @brief Reads data from a channel. + * + * @param[in] channel The channel to read from. + * + * @param[out] dest The destination buffer which will get the data. + * + * @param[in] count The count of bytes to be read. + * + * @param[in] is_stderr A boolean value to mark reading from the stderr flow. + * + * @return The number of bytes read, 0 on end of file, `SSH_AGAIN` + * on timeout and `SSH_ERROR` on error. + * + * @warning This function may return less than count bytes of data, and won't + * block until count bytes have been read. + */ +int ssh_channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr) +{ + return ssh_channel_read_timeout(channel, + dest, + count, + is_stderr, + SSH_TIMEOUT_DEFAULT); +} + +/** + * @brief Reads data from a channel. + * + * @param[in] channel The channel to read from. + * + * @param[out] dest The destination buffer which will get the data. + * + * @param[in] count The count of bytes to be read. + * + * @param[in] is_stderr A boolean value to mark reading from the stderr flow. + * + * @param[in] timeout_ms A timeout in milliseconds. A value of -1 means + * infinite timeout. + * + * @return The number of bytes read, 0 on end of file, `SSH_AGAIN` + * on timeout, `SSH_ERROR` on error. + * + * @warning This function may return less than count bytes of data, and won't + * block until count bytes have been read. + */ +int ssh_channel_read_timeout(ssh_channel channel, + void *dest, + uint32_t count, + int is_stderr, + int timeout_ms) +{ + ssh_session session = NULL; + ssh_buffer stdbuf = NULL; + uint32_t len; + struct ssh_channel_read_termination_struct ctx; + int rc; + + if (channel == NULL) { + return SSH_ERROR; + } + if (dest == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + session = channel->session; + stdbuf = channel->stdout_buffer; + + if (count == 0) { + return 0; + } + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + + SSH_LOG(SSH_LOG_PACKET, + "Read (%" PRIu32 ") buffered : %" PRIu32 " bytes. Window: %" PRIu32, + count, + ssh_buffer_get_len(stdbuf), + channel->local_window); + + /* block reading until at least one byte has been read + * and ignore the trivial case count=0 + */ + ctx.channel = channel; + ctx.buffer = stdbuf; + + if (timeout_ms < SSH_TIMEOUT_DEFAULT) { + timeout_ms = SSH_TIMEOUT_INFINITE; + } + + rc = ssh_handle_packets_termination(session, + timeout_ms, + ssh_channel_read_termination, + &ctx); + if (rc == SSH_ERROR || rc == SSH_AGAIN) { + return rc; + } + + /* + * If the channel is closed or in an error state, reading from it is an + * error + */ + if (session->session_state == SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + /* If the server closed the channel properly, there is nothing to do */ + if (channel->remote_eof && ssh_buffer_get_len(stdbuf) == 0) { + return 0; + } + if (channel->state == SSH_CHANNEL_STATE_CLOSED) { + ssh_set_error(session, SSH_FATAL, "Remote channel is closed."); + return SSH_ERROR; + } + len = ssh_buffer_get_len(stdbuf); + /* Read count bytes if len is greater, everything otherwise */ + len = (len > count ? count : len); + memcpy(dest, ssh_buffer_get(stdbuf), len); + ssh_buffer_pass_bytes(stdbuf, len); + if (channel->counter != NULL) { + channel->counter->in_bytes += len; + } + /* Try completing the delayed_close */ + if (channel->delayed_close && !ssh_channel_has_unread_data(channel)) { + channel->state = SSH_CHANNEL_STATE_CLOSED; + } + + rc = grow_window(session, channel); + if (rc == SSH_ERROR) { + return -1; + } + + return len; +} + +/** + * @brief Do a nonblocking read on the channel. + * + * A nonblocking read on the specified channel. it will return <= count bytes of + * data read atomically. It will also trigger any callbacks set on the channel. + * + * @param[in] channel The channel to read from. + * + * @param[out] dest A pointer to a destination buffer. + * + * @param[in] count The count of bytes of data to be read. + * + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes read, `SSH_AGAIN` if nothing is + * available, `SSH_ERROR` on error, and `SSH_EOF` if the channel is EOF. + * + * @see ssh_channel_is_eof() + */ +int ssh_channel_read_nonblocking(ssh_channel channel, + void *dest, + uint32_t count, + int is_stderr) +{ + ssh_session session = NULL; + uint32_t to_read; + int rc; + int blocking; + + if(channel == NULL) { + return SSH_ERROR; + } + if(dest == NULL) { + ssh_set_error_invalid(channel->session); + return SSH_ERROR; + } + + session = channel->session; + + rc = ssh_channel_poll(channel, is_stderr); + + if (rc <= 0) { + if (session->session_state == SSH_SESSION_STATE_ERROR){ + return SSH_ERROR; + } + + return rc; /* may be an error code */ + } + + to_read = (unsigned int)rc; + + if (to_read > count) { + to_read = count; + } + blocking = ssh_is_blocking(session); + ssh_set_blocking(session, 0); + rc = ssh_channel_read(channel, dest, to_read, is_stderr); + ssh_set_blocking(session,blocking); + + return rc; +} + +/** + * @brief Polls a channel for data to read. + * + * If callbacks are set on the channel, they will be called. + * + * @param[in] channel The channel to poll. + * + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes available for reading, 0 if nothing + * is available or `SSH_ERROR` on error. + * When a channel is freed the function returns + * `SSH_ERROR` immediately. + * + * @warning When the channel is in EOF state, the function returns `SSH_EOF`. + * + * @see ssh_channel_is_eof() + */ +int ssh_channel_poll(ssh_channel channel, int is_stderr) +{ + ssh_buffer stdbuf; + + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { + return SSH_ERROR; + } + + stdbuf = channel->stdout_buffer; + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + + if (channel->remote_eof == 0) { + if (channel->session->session_state == SSH_SESSION_STATE_ERROR){ + return SSH_ERROR; + } + if (ssh_handle_packets(channel->session, SSH_TIMEOUT_NONBLOCKING)==SSH_ERROR) { + return SSH_ERROR; + } + } + + if (ssh_buffer_get_len(stdbuf) > 0){ + return ssh_buffer_get_len(stdbuf); + } + + if (channel->remote_eof) { + return SSH_EOF; + } + + return ssh_buffer_get_len(stdbuf); +} + +/** + * @brief Polls a channel for data to read, waiting for a certain timeout. + * + * @param[in] channel The channel to poll. + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying a negative + * value means an infinite timeout. This parameter is + * passed to the poll() function. + * @param[in] is_stderr A boolean to select the stderr stream. + * + * @return The number of bytes available for reading, + * 0 if nothing is available (timeout elapsed), + * `SSH_EOF` on end of file, + * `SSH_ERROR` on error. + * + * @warning When the channel is in EOF state, the function returns `SSH_EOF`. + * When a channel is freed the function returns `SSH_ERROR` + * immediately. + * + * @see ssh_channel_is_eof() + */ +int ssh_channel_poll_timeout(ssh_channel channel, int timeout, int is_stderr) +{ + ssh_session session = NULL; + ssh_buffer stdbuf = NULL; + struct ssh_channel_read_termination_struct ctx; + size_t len; + int rc; + + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { + return SSH_ERROR; + } + + session = channel->session; + stdbuf = channel->stdout_buffer; + + if (is_stderr) { + stdbuf = channel->stderr_buffer; + } + ctx.buffer = stdbuf; + ctx.channel = channel; + rc = ssh_handle_packets_termination(channel->session, + timeout, + ssh_channel_read_termination, + &ctx); + if (rc == SSH_ERROR || + session->session_state == SSH_SESSION_STATE_ERROR) { + rc = SSH_ERROR; + goto out; + } else if (rc == SSH_AGAIN) { + /* If the above timeout expired, it is ok and we do not need to + * attempt to check the read buffer. The calling functions do not + * expect us to return SSH_AGAIN either here. */ + rc = SSH_OK; + goto out; + } + len = ssh_buffer_get_len(stdbuf); + if (len > 0) { + if (len > INT_MAX) { + rc = SSH_ERROR; + } else { + rc = (int)len; + } + goto out; + } + if (channel->remote_eof) { + rc = SSH_EOF; + } + +out: + return rc; +} + +/** + * @brief Recover the session in which belongs a channel. + * + * @param[in] channel The channel to recover the session from. + * + * @return The session pointer. + */ +ssh_session ssh_channel_get_session(ssh_channel channel) +{ + if (channel == NULL) { + return NULL; + } + + return channel->session; +} + +static int ssh_channel_exit_status_termination(void *c) +{ + ssh_channel channel = c; + if (channel->exit.status || + /* When a channel is closed, no exit status message can + * come anymore */ + (channel->flags & SSH_CHANNEL_FLAG_CLOSED_REMOTE) || + channel->session->session_state == SSH_SESSION_STATE_ERROR) + { + return 1; + } + return 0; +} + +/** + * @brief Get the exit state of the channel (error code from the executed + * instruction or signal). + * + * @param[in] channel The channel to get the status from. + * + * @param[out] pexit_code A pointer to an uint32_t to store the exit status. + * + * @param[out] pexit_signal A pointer to store the exit signal as a string. + * The signal is without the SIG prefix, e.g. "TERM" or + * "KILL"). The caller has to free the memory. + * + * @param[out] pcore_dumped A pointer to store a boolean value if it dumped a + * core. + * + * @return `SSH_OK` on success, `SSH_AGAIN` if we don't have a + * status or an SSH error. + * @warning This function may block until a timeout (or never) + * if the other side is not willing to close the channel. + * When a channel is freed the function returns + * `SSH_ERROR` immediately. + * + * If you're looking for an async handling of this register a callback for the + * exit status! + * + * @see ssh_channel_exit_status_callback + * @see ssh_channel_exit_signal_callback + */ +int ssh_channel_get_exit_state(ssh_channel channel, + uint32_t *pexit_code, + char **pexit_signal, + int *pcore_dumped) +{ + ssh_session session = NULL; + int rc; + + if ((channel == NULL) || (channel->flags & SSH_CHANNEL_FLAG_FREED_LOCAL)) { + return SSH_ERROR; + } + session = channel->session; + + rc = ssh_handle_packets_termination(channel->session, + SSH_TIMEOUT_DEFAULT, + ssh_channel_exit_status_termination, + channel); + if (rc == SSH_ERROR || channel->session->session_state == + SSH_SESSION_STATE_ERROR) { + return SSH_ERROR; + } + + /* If we don't have any kind of exit state, return SSH_AGAIN */ + if (!channel->exit.status) { + return SSH_AGAIN; + } + + if (pexit_code != NULL) { + *pexit_code = channel->exit.code; + } + + if (pexit_signal != NULL) { + *pexit_signal = NULL; + if (channel->exit.signal != NULL) { + *pexit_signal = strdup(channel->exit.signal); + if (*pexit_signal == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + } + + if (pcore_dumped != NULL) { + *pcore_dumped = channel->exit.core_dumped; + } + + return SSH_OK; +} + +/** + * @brief Get the exit status of the channel (error code from the executed + * instruction). + * + * @param[in] channel The channel to get the status from. + * + * @return The exit status, -1 if no exit status has been returned + * (yet), or `SSH_ERROR` on error. + * @warning This function may block until a timeout (or never) + * if the other side is not willing to close the channel. + * When a channel is freed the function returns + * `SSH_ERROR` immediately. + * + * If you're looking for an async handling of this register a callback for the + * exit status. + * + * @see ssh_channel_exit_status_callback + * @deprecated Please use ssh_channel_exit_state() + */ +int ssh_channel_get_exit_status(ssh_channel channel) +{ + uint32_t exit_status = (uint32_t)-1; + int rc; + + rc = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + return exit_status; +} + +/* + * This function acts as a meta select. + * + * First, channels are analyzed to seek potential can-write or can-read ones, + * then if no channel has been elected, it goes in a loop with the posix + * select(2). + * This is made in two parts: protocol select and network select. The protocol + * select does not use the network functions at all + */ +static int +channel_protocol_select(ssh_channel *rchans, ssh_channel *wchans, + ssh_channel *echans, ssh_channel *rout, + ssh_channel *wout, ssh_channel *eout) +{ + ssh_channel chan = NULL; + int i; + int j = 0; + + for (i = 0; rchans[i] != NULL; i++) { + chan = rchans[i]; + + while (ssh_channel_is_open(chan) && + ssh_socket_data_available(chan->session->socket)) { + ssh_handle_packets(chan->session, SSH_TIMEOUT_NONBLOCKING); + } + + if ((chan->stdout_buffer && + ssh_buffer_get_len(chan->stdout_buffer) > 0) || + (chan->stderr_buffer && + ssh_buffer_get_len(chan->stderr_buffer) > 0) || + chan->remote_eof) { + rout[j] = chan; + j++; + } + } + rout[j] = NULL; + + j = 0; + for (i = 0; wchans[i] != NULL; i++) { + chan = wchans[i]; + /* It's not our business to seek if the file descriptor is writable */ + if (ssh_socket_data_writable(chan->session->socket) && + ssh_channel_is_open(chan) && (chan->remote_window > 0)) { + wout[j] = chan; + j++; + } + } + wout[j] = NULL; + + j = 0; + for (i = 0; echans[i] != NULL; i++) { + chan = echans[i]; + + if (!ssh_socket_is_open(chan->session->socket) || + ssh_channel_is_closed(chan)) { + eout[j] = chan; + j++; + } + } + eout[j] = NULL; + + return 0; +} + +/* Just count number of pointers in the array */ +static size_t count_ptrs(ssh_channel *ptrs) +{ + size_t c; + for (c = 0; ptrs[c] != NULL; c++) + ; + + return c; +} + +/** + * @brief Act like the standard select(2) on channels. + * + * The list of pointers are then actualized and will only contain pointers to + * channels that are respectively readable, writable or have an exception to + * trap. + * + * @param[in] readchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] writechans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] exceptchans A NULL pointer or an array of channel pointers, + * terminated by a NULL. + * + * @param[in] timeout Timeout as defined by select(2). + * + * @return `SSH_OK` on a successful operation, `SSH_EINTR` if the + * select(2) syscall was interrupted, then relaunch the + * function, or `SSH_ERROR` on error. + */ +int ssh_channel_select(ssh_channel *readchans, ssh_channel *writechans, + ssh_channel *exceptchans, struct timeval * timeout) +{ + ssh_channel *rchans = NULL, *wchans = NULL, *echans = NULL; + ssh_channel dummy = NULL; + ssh_event event = NULL; + int rc; + int i; + int tm, tm_base; + int firstround = 1; + struct ssh_timestamp ts; + + if (timeout != NULL) + tm_base = timeout->tv_sec * 1000 + timeout->tv_usec / 1000; + else + tm_base = SSH_TIMEOUT_INFINITE; + ssh_timestamp_init(&ts); + tm = tm_base; + /* don't allow NULL pointers */ + if (readchans == NULL) { + readchans = &dummy; + } + + if (writechans == NULL) { + writechans = &dummy; + } + + if (exceptchans == NULL) { + exceptchans = &dummy; + } + + if (readchans[0] == NULL && writechans[0] == NULL && + exceptchans[0] == NULL) { + /* No channel to poll?? Go away! */ + return 0; + } + + /* Prepare the outgoing temporary arrays */ + rchans = calloc(count_ptrs(readchans) + 1, sizeof(ssh_channel)); + if (rchans == NULL) { + return SSH_ERROR; + } + + wchans = calloc(count_ptrs(writechans) + 1, sizeof(ssh_channel)); + if (wchans == NULL) { + SAFE_FREE(rchans); + return SSH_ERROR; + } + + echans = calloc(count_ptrs(exceptchans) + 1, sizeof(ssh_channel)); + if (echans == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + return SSH_ERROR; + } + + /* + * First, try without doing network stuff then, use the ssh_poll + * infrastructure to poll on all sessions. + */ + do { + channel_protocol_select(readchans, + writechans, + exceptchans, + rchans, + wchans, + echans); + if (rchans[0] != NULL || wchans[0] != NULL || echans[0] != NULL) { + /* At least one channel has an event */ + break; + } + /* Add all channels' sessions right into an event object */ + if (event == NULL) { + event = ssh_event_new(); + if (event == NULL) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + + return SSH_ERROR; + } + for (i = 0; readchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(readchans[i]->session); + ssh_event_add_session(event, readchans[i]->session); + } + for (i = 0; writechans[i] != NULL; i++) { + ssh_poll_get_default_ctx(writechans[i]->session); + ssh_event_add_session(event, writechans[i]->session); + } + for (i = 0; exceptchans[i] != NULL; i++) { + ssh_poll_get_default_ctx(exceptchans[i]->session); + ssh_event_add_session(event, exceptchans[i]->session); + } + } + /* Get out if the timeout has elapsed */ + if (!firstround && ssh_timeout_elapsed(&ts, tm_base)) { + break; + } + /* Here we go */ + rc = ssh_event_dopoll(event, tm); + if (rc != SSH_OK) { + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + ssh_event_free(event); + return rc; + } + tm = ssh_timeout_update(&ts, tm_base); + firstround = 0; + } while (1); + + if (readchans != &dummy) { + memcpy(readchans, + rchans, + (count_ptrs(rchans) + 1) * sizeof(ssh_channel)); + } + if (writechans != &dummy) { + memcpy(writechans, + wchans, + (count_ptrs(wchans) + 1) * sizeof(ssh_channel)); + } + if (exceptchans != &dummy) { + memcpy(exceptchans, + echans, + (count_ptrs(echans) + 1) * sizeof(ssh_channel)); + } + SAFE_FREE(rchans); + SAFE_FREE(wchans); + SAFE_FREE(echans); + if (event) + ssh_event_free(event); + return 0; +} + +/** + * @brief Set the channel data counter. + * + * @code + * struct ssh_counter_struct counter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * ssh_channel_set_counter(channel, &counter); + * @endcode + * + * @param[in] channel The SSH channel. + * + * @param[in] counter Counter for bytes handled by the channel. + */ +void ssh_channel_set_counter(ssh_channel channel, + ssh_counter counter) +{ + if (channel != NULL) { + channel->counter = counter; + } +} + +/** + * @brief Blocking write on a channel stderr. + * + * @param[in] channel The channel to write to. + * + * @param[in] data A pointer to the data to write. + * + * @param[in] len The length of the buffer to write to. + * + * @return The number of bytes written, SSH_ERROR on error. + * + * @see ssh_channel_read() + */ +int ssh_channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) +{ + return channel_write_common(channel, data, len, 1); +} + +#if WITH_SERVER + +/** + * @brief Open a TCP/IP reverse forwarding channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] remotehost The remote host to connected (host name or IP). + * + * @param[in] remoteport The remote port. + * + * @param[in] sourcehost The source host (your local computer). It's optional + * and for logging purpose. + * + * @param[in] localport The source port (your local computer). It's optional + * and for logging purpose. + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * + * @warning This function does not bind the local port and does not + * automatically forward the content of a socket to the channel. You + * still have to use ssh_channel_read and ssh_channel_write for this. + */ +int ssh_channel_open_reverse_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return rc; + } + if (remotehost == NULL || sourcehost == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + session = channel->session; + + if (channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_pack(payload, + "sdsd", + remotehost, + remoteport, + sourcehost, + localport); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_open(channel, + "forwarded-tcpip", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + + return rc; +} + +/** + * @brief Open a X11 channel. + * + * @param[in] channel An allocated channel. + * + * @param[in] orig_addr The source host (the local server). + * + * @param[in] orig_port The source port (the local server). + * + * @return `SSH_OK` on success, + * `SSH_ERROR` if an error occurred, + * `SSH_AGAIN` if in nonblocking mode and call has + * to be done again. + * @warning This function does not bind the local port and does not + * automatically forward the content of a socket to the channel. You + * still have to use shh_channel_read and ssh_channel_write for this. + */ +int ssh_channel_open_x11(ssh_channel channel, + const char *orig_addr, int orig_port) +{ + ssh_session session = NULL; + ssh_buffer payload = NULL; + int rc = SSH_ERROR; + + if (channel == NULL) { + return rc; + } + if (orig_addr == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + session = channel->session; + + if (channel->state != SSH_CHANNEL_STATE_NOT_OPEN) + goto pending; + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_pack(payload, "sd", orig_addr, orig_port); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } +pending: + rc = channel_open(channel, + "x11", + WINDOW_DEFAULT, + CHANNEL_MAX_PACKET, + payload); + +error: + SSH_BUFFER_FREE(payload); + + return rc; +} + +/** + * @brief Send the exit status to the remote process + * + * Sends the exit status to the remote process (as described in RFC 4254, + * section 6.10). + * + * @param[in] channel The channel to send exit status. + * + * @param[in] exit_status The exit status to send + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred. + */ +int ssh_channel_request_send_exit_status(ssh_channel channel, int exit_status) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, "d", exit_status); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "exit-status", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +/** + * @brief Send an exit signal to remote process (RFC 4254, section 6.10). + * + * This sends the exit status of the remote process. + * Note, that remote system may not support signals concept. + * In such a case this request will be silently ignored. + * + * @param[in] channel The channel to send signal. + * + * @param[in] sig The signal to send (without SIG prefix) + * (e.g. "TERM" or "KILL"). + * @param[in] core A boolean to tell if a core was dumped + * @param[in] errmsg A CRLF explanation text about the error condition + * @param[in] lang The language used in the message (format: RFC 3066) + * + * @return `SSH_OK` on success, `SSH_ERROR` if an error occurred + */ +int ssh_channel_request_send_exit_signal(ssh_channel channel, const char *sig, + int core, const char *errmsg, const char *lang) +{ + ssh_buffer buffer = NULL; + int rc = SSH_ERROR; + + if(channel == NULL) { + return rc; + } + if(sig == NULL || errmsg == NULL || lang == NULL) { + ssh_set_error_invalid(channel->session); + return rc; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = ssh_buffer_pack(buffer, + "sbss", + sig, + core ? 1 : 0, + errmsg, + lang); + if (rc != SSH_OK) { + ssh_set_error_oom(channel->session); + goto error; + } + + rc = channel_request(channel, "exit-signal", buffer, 0); +error: + SSH_BUFFER_FREE(buffer); + return rc; +} + +#endif + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/client.c b/src/libs/libssh-0.12.2/src/client.c new file mode 100644 index 000000000000..323a7a93c4a9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/client.c @@ -0,0 +1,936 @@ +/* + * client.c - SSH client functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/buffer.h" +#include "libssh/kex-gss.h" +#include "libssh/dh.h" +#include "libssh/options.h" +#include "libssh/packet.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#ifdef WITH_GEX +#include "libssh/dh-gex.h" +#endif /* WITH_GEX */ +#include "libssh/ecdh.h" +#include "libssh/threads.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/kex.h" +#include "libssh/hybrid_mlkem.h" + +#ifndef _WIN32 +#ifdef HAVE_PTHREAD +extern int proxy_disconnect; +#endif /* HAVE_PTHREAD */ +#endif /* _WIN32 */ + +#define set_status(session, status) do {\ + if (session->common.callbacks && session->common.callbacks->connect_status_function) \ + session->common.callbacks->connect_status_function(session->common.callbacks->userdata, status); \ + } while (0) + +/** + * @internal + * @brief Callback to be called when the socket is connected or had a + * connection error. Changes the state of the session and updates the error + * message. + * @param code one of SSH_SOCKET_CONNECTED_OK or SSH_SOCKET_CONNECTED_ERROR + * @param user is a pointer to session + */ +static void socket_callback_connected(int code, int errno_code, void *user) +{ + ssh_session session=(ssh_session)user; + + if (session->session_state != SSH_SESSION_STATE_CONNECTING && + session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) + { + ssh_set_error(session,SSH_FATAL, "Wrong state in socket_callback_connected : %d", + session->session_state); + + return; + } + + SSH_LOG(SSH_LOG_TRACE,"Socket connection callback: %d (%d)",code, errno_code); + if(code == SSH_SOCKET_CONNECTED_OK) + session->session_state=SSH_SESSION_STATE_SOCKET_CONNECTED; + else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session,SSH_FATAL,"%s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); + } + session->ssh_connection_callback(session); +} + +/** + * @internal + * + * @brief Gets the banner from socket and saves it in session. + * Updates the session state + * + * @param data pointer to the beginning of header + * @param len size of the banner + * @param user is a pointer to session + * @returns Number of bytes processed, or zero if the banner is not complete. + */ +static size_t callback_receive_banner(const void *data, size_t len, void *user) +{ + char *buffer = (char *)data; + ssh_session session = (ssh_session) user; + char *str = NULL; + uint32_t i; + int ret=0; + + if (session->session_state != SSH_SESSION_STATE_SOCKET_CONNECTED) { + ssh_set_error(session,SSH_FATAL, + "Wrong state in callback_receive_banner : %d", + session->session_state); + + return 0; + } + for (i = 0; i < len; ++i) { +#ifdef WITH_PCAP + if (session->pcap_ctx && buffer[i] == '\n') { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + buffer,i+1, + i+1); + } +#endif + if (buffer[i] == '\r') { + buffer[i] = '\0'; + } + if (buffer[i] == '\n') { + int cmp; + + buffer[i] = '\0'; + + /* The server MAY send other lines of data... */ + cmp = strncmp(buffer, "SSH-", 4); + if (cmp == 0) { + str = strdup(buffer); + if (str == NULL) { + return SSH_ERROR; + } + /* number of bytes read */ + ret = i + 1; + session->serverbanner = str; + session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED; + SSH_LOG(SSH_LOG_PACKET, "Received banner: %s", str); + session->ssh_connection_callback(session); + + return ret; + } else { + SSH_LOG(SSH_LOG_DEBUG, + "ssh_protocol_version_exchange: %s", + buffer); + ret = i + 1; + break; + } + } + /* According to RFC 4253 the max banner length is 255 */ + if (i > 255) { + /* Too big banner */ + session->session_state=SSH_SESSION_STATE_ERROR; + ssh_set_error(session, + SSH_FATAL, + "Receiving banner: too large banner"); + + return 0; + } + } + + return ret; +} + +/** @internal + * @brief Sends a SSH banner to the server. + * + * @param session The SSH session to use. + * + * @param server Send client or server banner. + * + * @return 0 on success, < 0 on error. + */ +int ssh_send_banner(ssh_session session, int server) +{ + const char *banner = CLIENT_BANNER_SSH2; + const char *terminator = "\r\n"; + /* The maximum banner length is 255 for SSH2 */ + char buffer[256] = {0}; + size_t len; + int rc = SSH_ERROR; + + if (server == 1) { + if (session->server_opts.custombanner == NULL) { + session->serverbanner = strdup(banner); + if (session->serverbanner == NULL) { + goto end; + } + } else { + len = strlen(session->server_opts.custombanner); + session->serverbanner = malloc(len + 8 + 1); + if(session->serverbanner == NULL) { + goto end; + } + snprintf(session->serverbanner, + len + 8 + 1, + "SSH-2.0-%s", + session->server_opts.custombanner); + } + + snprintf(buffer, + sizeof(buffer), + "%s%s", + session->serverbanner, + terminator); + } else { + session->clientbanner = strdup(banner); + if (session->clientbanner == NULL) { + goto end; + } + + snprintf(buffer, + sizeof(buffer), + "%s%s", + session->clientbanner, + terminator); + } + + rc = ssh_socket_write(session->socket, buffer, (uint32_t)strlen(buffer)); + if (rc == SSH_ERROR) { + goto end; + } +#ifdef WITH_PCAP + if (session->pcap_ctx != NULL) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_OUT, + buffer, + (uint32_t)strlen(buffer), + (uint32_t)strlen(buffer)); + } +#endif + + rc = SSH_OK; +end: + return rc; +} + +/** @internal + * @brief launches the DH handshake state machine + * @param session session handle + * @returns SSH_OK or SSH_ERROR + * @warning this function returning is no proof that DH handshake is + * completed + */ +int dh_handshake(ssh_session session) +{ + int rc = SSH_AGAIN; + + SSH_LOG(SSH_LOG_TRACE, + "dh_handshake_state = %d, kex_type = %d", + session->dh_handshake_state, + session->next_crypto->kex_type); + + switch (session->dh_handshake_state) { + case DH_STATE_INIT: + switch (session->next_crypto->kex_type) { +#ifdef WITH_GSSAPI + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_client_gss_kex_init(session); + break; +#endif + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_client_dh_init(session); + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + rc = ssh_client_dhgex_init(session); + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + rc = ssh_client_ecdh_init(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + rc = ssh_client_curve25519_init(session); + break; +#endif +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + rc = ssh_client_sntrup761x25519_init(session); + break; +#endif + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_client_hybrid_mlkem_init(session); + break; + default: + rc = SSH_ERROR; + } + + break; + case DH_STATE_INIT_SENT: + /* wait until ssh_packet_dh_reply is called */ + break; + case DH_STATE_NEWKEYS_SENT: + /* wait until ssh_packet_newkeys is called */ + break; + case DH_STATE_FINISHED: + return SSH_OK; + default: + ssh_set_error(session, + SSH_FATAL, + "Invalid state in dh_handshake(): %d", + session->dh_handshake_state); + + return SSH_ERROR; + } + + return rc; +} + +static int ssh_service_request_termination(void *s) +{ + ssh_session session = (ssh_session)s; + + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->auth.service_state != SSH_AUTH_SERVICE_SENT) + return 1; + else + return 0; +} + +/** + * @addtogroup libssh_session + * + * @{ + */ + +/** + * @internal + * @brief Request a service from the SSH server. + * + * Service requests are for example: ssh-userauth, ssh-connection, etc. + * + * @param session The session to use to ask for a service request. + * @param service The service request. + * + * @return SSH_OK on success + * @return SSH_ERROR on error + * @return SSH_AGAIN No response received yet + * @bug actually only works with ssh-userauth + */ +int ssh_service_request(ssh_session session, const char *service) +{ + int rc = SSH_ERROR; + + if(session->auth.service_state != SSH_AUTH_SERVICE_NONE) + goto pending; + + rc = ssh_buffer_pack(session->out_buffer, + "bs", + SSH2_MSG_SERVICE_REQUEST, + service); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + session->auth.service_state = SSH_AUTH_SERVICE_SENT; + if (ssh_packet_send(session) == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, + "Sending SSH2_MSG_SERVICE_REQUEST failed."); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent SSH_MSG_SERVICE_REQUEST (service %s)", service); +pending: + rc=ssh_handle_packets_termination(session,SSH_TIMEOUT_USER, + ssh_service_request_termination, session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + switch(session->auth.service_state) { + case SSH_AUTH_SERVICE_DENIED: + ssh_set_error(session,SSH_FATAL,"ssh_auth_service request denied"); + break; + case SSH_AUTH_SERVICE_ACCEPTED: + rc=SSH_OK; + break; + case SSH_AUTH_SERVICE_SENT: + rc=SSH_AGAIN; + break; + case SSH_AUTH_SERVICE_NONE: + rc=SSH_ERROR; + break; + } + + return rc; +} + +/** + * @internal + * + * @brief A function to be called each time a step has been done in the + * connection. + */ +static void ssh_client_connection_callback(ssh_session session) +{ + int rc; + + SSH_LOG(SSH_LOG_DEBUG, "session_state=%d", session->session_state); + + switch (session->session_state) { + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + break; + case SSH_SESSION_STATE_SOCKET_CONNECTED: + ssh_set_fd_towrite(session); + ssh_send_banner(session, 0); + + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->serverbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + SSH_LOG(SSH_LOG_DEBUG, "SSH server banner: %s", session->serverbanner); + + /* Here we analyze the different protocols the server allows. */ + rc = ssh_analyze_banner(session, 0); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "No version of SSH protocol usable (banner: %s)", + session->serverbanner); + goto error; + } + + ssh_packet_register_socket_callback(session, session->socket); + + ssh_packet_set_default_callbacks(session); + session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + set_status(session, 0.5f); + + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session, 0.6f); + ssh_list_kex(&session->next_crypto->server_kex); + if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + /* in rekeying state if next_crypto client_kex might be empty */ + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + } + if (ssh_kex_select_methods(session) == SSH_ERROR) + goto error; + set_status(session, 0.8f); + session->session_state = SSH_SESSION_STATE_DH; + + /* If the init packet was already sent in previous step, this will be no + * operation */ + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } + FALL_THROUGH; + case SSH_SESSION_STATE_DH: + if (session->dh_handshake_state == DH_STATE_FINISHED) { + set_status(session, 1.0f); + session->connected = 1; + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + } else { + session->session_state = SSH_SESSION_STATE_AUTHENTICATING; + } + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state %d", + session->session_state); + } + + return; +error: + ssh_session_socket_close(session); + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(session)); +} + +/** @internal + * @brief describe under which conditions the ssh_connect function may stop + */ +static int ssh_connect_termination(void *user) +{ + ssh_session session = (ssh_session)user; + + switch (session->session_state) { + case SSH_SESSION_STATE_ERROR: + case SSH_SESSION_STATE_AUTHENTICATING: + case SSH_SESSION_STATE_DISCONNECTED: + return 1; + default: + return 0; + } +} + +/** + * @brief Connect to the ssh server. + * + * @param[in] session The ssh session to connect. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN, if the session is in nonblocking mode, + * and call must be done again. + * + * @see ssh_new() + * @see ssh_disconnect() + */ +int ssh_connect(ssh_session session) +{ + int ret; + + if (!is_ssh_initialized()) { + ssh_set_error(session, SSH_FATAL, + "Library not initialized."); + + return SSH_ERROR; + } + + if (session == NULL) { + return SSH_ERROR; + } + + switch(session->pending_call_state) { + case SSH_PENDING_CALL_NONE: + break; + case SSH_PENDING_CALL_CONNECT: + goto pending; + default: + ssh_set_error(session, SSH_FATAL, + "Bad call during pending SSH call in ssh_connect"); + + return SSH_ERROR; + } + session->alive = 0; + session->client = 1; + + if (session->opts.fd == SSH_INVALID_SOCKET && + session->opts.host == NULL && + session->opts.ProxyCommand == NULL) + { + ssh_set_error(session, SSH_FATAL, "Hostname required"); + return SSH_ERROR; + } + + /* If the system configuration files were not yet processed, do it now */ + if (!session->opts.config_processed) { + ret = ssh_options_parse_config(session, NULL); + if (ret != 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to process system configuration files"); + return SSH_ERROR; + } + } + + ret = ssh_options_apply(session); + if (ret < 0) { + ssh_set_error(session, SSH_FATAL, "Couldn't apply options"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, + "libssh %s, using threading %s", + ssh_copyright(), + ssh_threads_get_type()); + + session->ssh_connection_callback = ssh_client_connection_callback; + session->session_state = SSH_SESSION_STATE_CONNECTING; + ssh_socket_set_callbacks(session->socket, &session->socket_callbacks); + session->socket_callbacks.connected = socket_callback_connected; + session->socket_callbacks.data = callback_receive_banner; + session->socket_callbacks.exception = ssh_socket_exception_callback; + session->socket_callbacks.userdata = session; + + if (session->opts.fd != SSH_INVALID_SOCKET) { + session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; + ret = ssh_socket_set_fd(session->socket, session->opts.fd); + } else if (session->opts.ProxyCommand != NULL && strncmp(VBOX_PROXY_PREFIX, session->opts.ProxyCommand, VBOX_PROXY_PREFIX_LENGTH) == 0) { + ret = ssh_socket_connect_proxycommand_vbox(session->socket, + session->opts.host, + session->opts.port > 0 ? session->opts.port : 22, + session->opts.ProxyCommand + VBOX_PROXY_PREFIX_LENGTH); +#ifndef _WIN32 +#ifdef HAVE_PTHREAD + } else if (ssh_libssh_proxy_jumps() && + ssh_list_count(session->opts.proxy_jumps) != 0) { + ret = ssh_socket_connect_proxyjump(session->socket); +#endif /* HAVE_PTHREAD */ +#endif /* _WIN32 */ + } else if (session->opts.ProxyCommand != NULL) { +#ifdef WITH_EXEC + ret = ssh_socket_connect_proxycommand(session->socket, + session->opts.ProxyCommand); +#else + ssh_set_error(session, + SSH_FATAL, + "The libssh is built without support for proxy commands."); + ret = SSH_ERROR; +#endif /* WITH_EXEC */ + } else { + ret = ssh_socket_connect(session->socket, + session->opts.host, + session->opts.port > 0 ? session->opts.port : 22, + session->opts.bindaddr); + } + if (ret == SSH_ERROR) { + return SSH_ERROR; + } + + set_status(session, 0.2f); + + session->alive = 1; + SSH_LOG(SSH_LOG_DEBUG, + "Socket connecting, now waiting for the callbacks to work"); + +pending: + session->pending_call_state = SSH_PENDING_CALL_CONNECT; + if(ssh_is_blocking(session)) { + int timeout = (session->opts.timeout * 1000) + + (session->opts.timeout_usec / 1000); + if (timeout == 0) { + timeout = 10 * 1000; + } + SSH_LOG(SSH_LOG_PACKET, "Actual timeout : %d", timeout); + ret = ssh_handle_packets_termination(session, timeout, + ssh_connect_termination, session); + if (session->session_state != SSH_SESSION_STATE_ERROR && + (ret == SSH_ERROR || !ssh_connect_termination(session))) + { + ssh_set_error(session, SSH_FATAL, + "Timeout connecting to %s", session->opts.host); + session->session_state = SSH_SESSION_STATE_ERROR; + } + } else { + ret = ssh_handle_packets_termination(session, + SSH_TIMEOUT_NONBLOCKING, + ssh_connect_termination, + session); + if (ret == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + } + + SSH_LOG(SSH_LOG_PACKET, "current state : %d", session->session_state); + if (!ssh_is_blocking(session) && !ssh_connect_termination(session)) { + return SSH_AGAIN; + } + + session->pending_call_state = SSH_PENDING_CALL_NONE; + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->session_state == SSH_SESSION_STATE_DISCONNECTED) + { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Get the issue banner from the server. + * + * This is the banner showing a disclaimer to users who log in, + * typically their right or the fact that they will be monitored. + * + * @param[in] session The SSH session to use. + * + * @return A newly allocated string with the banner, NULL on error. + */ +char *ssh_get_issue_banner(ssh_session session) +{ + if (session == NULL || session->banner == NULL) { + return NULL; + } + + return ssh_string_to_char(session->banner); +} + +/** + * @brief Get the version of the OpenSSH server, if it is not an OpenSSH server + * then 0 will be returned. + * + * You can use the SSH_VERSION_INT macro to compare version numbers. + * + * @param[in] session The SSH session to use. + * + * @return The version number if available, 0 otherwise. + * + * @code + * int openssh = ssh_get_openssh_version(); + * + * if (openssh == SSH_INT_VERSION(6, 1, 0)) { + * printf("Version match!\m"); + * } + * @endcode + */ +int ssh_get_openssh_version(ssh_session session) +{ + if (session == NULL) { + return 0; + } + + return session->openssh; +} + +/** + * @brief Most SSH connections will only ever request a single session, but an + * attacker may abuse a running ssh client to surreptitiously open + * additional sessions under their control. OpenSSH provides a global + * request "no-more-sessions@openssh.com" to mitigate this attack. + * + * @param[in] session The SSH session to use. + * + * @returns SSH_OK on success, SSH_ERROR on error. + * @returns SSH_AGAIN, if the session is in nonblocking mode, + * and call must be done again. + */ +int ssh_request_no_more_sessions(ssh_session session) +{ + if (session == NULL) { + return SSH_ERROR; + } + + return ssh_global_request(session, "no-more-sessions@openssh.com", NULL, 1); +} + +/** + * @brief Add disconnect message when ssh_session is disconnected + * To add a disconnect message to give peer a better hint. + * @param session The SSH session to use. + * @param message The message to send after the session is disconnected. + * If no message is passed then a default message i.e + * "Bye Bye" will be sent. + */ +int +ssh_session_set_disconnect_message(ssh_session session, const char *message) +{ + if (session == NULL) { + return SSH_ERROR; + } + + if (message == NULL || strlen(message) == 0) { + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; + } + SAFE_FREE(session->disconnect_message); //To free any message set earlier. + session->disconnect_message = strdup(message); + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Disconnect from a session (client or server). + * + * The session can then be reused to open a new session. + * + * @note Note that this function won't close the socket if it was set with + * ssh_options_set and SSH_OPTIONS_FD. You're responsible for closing the + * socket. This is new behavior in libssh 0.10. + * + * @param[in] session The SSH session to use. + */ +void +ssh_disconnect(ssh_session session) +{ + struct ssh_iterator *it = NULL; + int rc; + + if (session == NULL) { + return; + } + +#ifndef _WIN32 +#ifdef HAVE_PTHREAD + /* Only send the disconnect to all other threads when the root session calls + * ssh_disconnect() */ + if (session->proxy_root) { + proxy_disconnect = 1; + } +#endif /* HAVE_PTHREAD */ +#endif /* _WIN32 */ + + if (session->disconnect_message == NULL) { + session->disconnect_message = strdup("Bye Bye") ; + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + goto error; + } + } + + if (session->socket != NULL && ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bdss", + SSH2_MSG_DISCONNECT, + SSH2_DISCONNECT_BY_APPLICATION, + session->disconnect_message, + ""); /* language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + ssh_packet_send(session); + ssh_session_socket_close(session); + } + +error: + session->recv_seq = 0; + session->send_seq = 0; + session->alive = 0; + if (session->socket != NULL){ + ssh_socket_reset(session->socket); + } + session->opts.fd = SSH_INVALID_SOCKET; + session->session_state = SSH_SESSION_STATE_DISCONNECTED; + session->pending_call_state = SSH_PENDING_CALL_NONE; + session->packet_state = PACKET_STATE_INIT; + + while ((it = ssh_list_get_iterator(session->channels)) != NULL) { + ssh_channel_do_free(ssh_iterator_value(ssh_channel, it)); + ssh_list_remove(session->channels, it); + } + if (session->current_crypto) { + crypto_free(session->current_crypto); + session->current_crypto = NULL; + } + if (session->next_crypto) { + crypto_free(session->next_crypto); + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + ssh_set_error_oom(session); + } + } + if (session->in_buffer) { + ssh_buffer_reinit(session->in_buffer); + } + if (session->out_buffer) { + ssh_buffer_reinit(session->out_buffer); + } + if (session->in_hashbuf) { + ssh_buffer_reinit(session->in_hashbuf); + } + if (session->out_hashbuf) { + ssh_buffer_reinit(session->out_hashbuf); + } + session->auth.supported_methods = 0; + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + SAFE_FREE(session->disconnect_message); + + if (session->ssh_message_list) { + ssh_message msg = NULL; + + while ((msg = ssh_list_pop_head(ssh_message, + session->ssh_message_list)) != NULL) { + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + session->ssh_message_list = NULL; + } + + if (session->packet_callbacks) { + ssh_list_free(session->packet_callbacks); + session->packet_callbacks = NULL; + } +} + +/** + * @brief Copyright information + * + * Returns copyright information + * + * @returns SSH_STRING copyright + */ +const char *ssh_copyright(void) +{ + return SSH_STRINGIFY(LIBSSH_VERSION) " (c) 2003-2026 " + "Aris Adamantiadis, Andreas Schneider " + "and libssh contributors. " + "Distributed under the LGPL, please refer to COPYING " + "file for information about your rights"; +} +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/config.c b/src/libs/libssh-0.12.2/src/config.c new file mode 100644 index 000000000000..84fb12384426 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/config.c @@ -0,0 +1,1790 @@ +/* + * config.c - parse the ssh config file + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#ifdef HAVE_GLOB_H +# include +#endif +#include +#include +#ifndef _WIN32 +# include +# include +# include +# include +# include +# include +# include +# include +#endif +#ifdef HAVE_IFADDRS_H +#include +#endif + +#include "libssh/config_parser.h" +#include "libssh/config.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/options.h" + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 1024 +#endif + +struct ssh_config_keyword_table_s { + const char *name; + enum ssh_config_opcode_e opcode; + bool cli_supported; +}; + +static struct ssh_config_keyword_table_s ssh_config_keyword_table[] = { + {"host", SOC_HOST, true}, + {"match", SOC_MATCH, false}, + {"hostname", SOC_HOSTNAME, true}, + {"port", SOC_PORT, true}, + {"user", SOC_USERNAME, true}, + {"identityfile", SOC_IDENTITY, true}, + {"ciphers", SOC_CIPHERS, true}, + {"macs", SOC_MACS, true}, + {"compression", SOC_COMPRESSION, true}, + {"connecttimeout", SOC_TIMEOUT, true}, + {"stricthostkeychecking", SOC_STRICTHOSTKEYCHECK, true}, + {"userknownhostsfile", SOC_KNOWNHOSTS, true}, + {"proxycommand", SOC_PROXYCOMMAND, true}, + {"gssapiserveridentity", SOC_GSSAPISERVERIDENTITY, false}, + {"gssapiclientidentity", SOC_GSSAPICLIENTIDENTITY, false}, + {"gssapidelegatecredentials", SOC_GSSAPIDELEGATECREDENTIALS, true}, + {"include", SOC_INCLUDE, true}, + {"bindaddress", SOC_BINDADDRESS, true}, + {"globalknownhostsfile", SOC_GLOBALKNOWNHOSTSFILE, true}, + {"loglevel", SOC_LOGLEVEL, true}, + {"hostkeyalgorithms", SOC_HOSTKEYALGORITHMS, true}, + {"kexalgorithms", SOC_KEXALGORITHMS, true}, + {"gssapiauthentication", SOC_GSSAPIAUTHENTICATION, true}, + {"kbdinteractiveauthentication", SOC_KBDINTERACTIVEAUTHENTICATION, true}, + {"passwordauthentication", SOC_PASSWORDAUTHENTICATION, true}, + {"pubkeyauthentication", SOC_PUBKEYAUTHENTICATION, true}, + {"addkeystoagent", SOC_UNSUPPORTED, true}, + {"addressfamily", SOC_ADDRESSFAMILY, true}, + {"batchmode", SOC_UNSUPPORTED, true}, + {"canonicaldomains", SOC_UNSUPPORTED, true}, + {"canonicalizefallbacklocal", SOC_UNSUPPORTED, true}, + {"canonicalizehostname", SOC_UNSUPPORTED, true}, + {"canonicalizemaxdots", SOC_UNSUPPORTED, true}, + {"canonicalizepermittedcnames", SOC_UNSUPPORTED, true}, + {"certificatefile", SOC_CERTIFICATE, true}, + {"kbdinteractiveauthentication", SOC_UNSUPPORTED, true}, + {"checkhostip", SOC_UNSUPPORTED, true}, + {"connectionattempts", SOC_UNSUPPORTED, true}, + {"enablesshkeysign", SOC_UNSUPPORTED, true}, + {"fingerprinthash", SOC_UNSUPPORTED, true}, + {"forwardagent", SOC_UNSUPPORTED, true}, + {"hashknownhosts", SOC_UNSUPPORTED, true}, + {"hostbasedauthentication", SOC_UNSUPPORTED, true}, + {"hostbasedacceptedalgorithms", SOC_UNSUPPORTED, true}, + {"hostkeyalias", SOC_UNSUPPORTED, true}, + {"identitiesonly", SOC_IDENTITIESONLY, true}, + {"identityagent", SOC_IDENTITYAGENT, true}, + {"ipqos", SOC_UNSUPPORTED, true}, + {"kbdinteractivedevices", SOC_UNSUPPORTED, true}, + {"nohostauthenticationforlocalhost", SOC_UNSUPPORTED, true}, + {"numberofpasswordprompts", SOC_UNSUPPORTED, true}, + {"pkcs11provider", SOC_UNSUPPORTED, true}, + {"preferredauthentications", SOC_UNSUPPORTED, true}, + {"proxyjump", SOC_PROXYJUMP, true}, + {"proxyusefdpass", SOC_UNSUPPORTED, true}, + {"pubkeyacceptedalgorithms", SOC_PUBKEYACCEPTEDKEYTYPES, true}, + {"rekeylimit", SOC_REKEYLIMIT, true}, + {"remotecommand", SOC_UNSUPPORTED, true}, + {"revokedhostkeys", SOC_UNSUPPORTED, true}, + {"serveralivecountmax", SOC_UNSUPPORTED, true}, + {"serveraliveinterval", SOC_UNSUPPORTED, true}, + {"streamlocalbindmask", SOC_UNSUPPORTED, true}, + {"streamlocalbindunlink", SOC_UNSUPPORTED, true}, + {"syslogfacility", SOC_UNSUPPORTED, true}, + {"tcpkeepalive", SOC_UNSUPPORTED, true}, + {"updatehostkeys", SOC_UNSUPPORTED, true}, + {"verifyhostkeydns", SOC_UNSUPPORTED, true}, + {"visualhostkey", SOC_UNSUPPORTED, true}, + {"clearallforwardings", SOC_NA, true}, + {"controlmaster", SOC_NA, true}, + {"controlpersist", SOC_NA, true}, + {"controlpath", SOC_NA, true}, + {"dynamicforward", SOC_NA, true}, + {"escapechar", SOC_NA, true}, + {"exitonforwardfailure", SOC_NA, true}, + {"forwardx11", SOC_NA, true}, + {"forwardx11timeout", SOC_NA, true}, + {"forwardx11trusted", SOC_NA, true}, + {"gatewayports", SOC_NA, true}, + {"ignoreunknown", SOC_NA, true}, + {"localcommand", SOC_NA, true}, + {"localforward", SOC_NA, true}, + {"permitlocalcommand", SOC_NA, true}, + {"remoteforward", SOC_NA, true}, + {"requesttty", SOC_NA, true}, + {"sendenv", SOC_NA, true}, + {"tunnel", SOC_NA, true}, + {"tunneldevice", SOC_NA, true}, + {"xauthlocation", SOC_NA, true}, + {"pubkeyacceptedkeytypes", SOC_PUBKEYACCEPTEDKEYTYPES, true}, + {"requiredrsasize", SOC_REQUIRED_RSA_SIZE, true}, + {"gssapikeyexchange", SOC_GSSAPIKEYEXCHANGE, true}, + {"gssapikexalgorithms", SOC_GSSAPIKEXALGORITHMS, true}, + {NULL, SOC_UNKNOWN, false}, +}; + +enum ssh_config_match_e { + MATCH_UNKNOWN = -1, + MATCH_ALL, + MATCH_FINAL, + MATCH_CANONICAL, + MATCH_EXEC, + MATCH_HOST, + MATCH_ORIGINALHOST, + MATCH_USER, + MATCH_LOCALUSER, + MATCH_LOCALNETWORK +}; + +struct ssh_config_match_keyword_table_s { + const char *name; + enum ssh_config_match_e opcode; +}; + +static struct ssh_config_match_keyword_table_s + ssh_config_match_keyword_table[] = { + {"all", MATCH_ALL}, + {"canonical", MATCH_CANONICAL}, + {"final", MATCH_FINAL}, + {"exec", MATCH_EXEC}, + {"host", MATCH_HOST}, + {"originalhost", MATCH_ORIGINALHOST}, + {"user", MATCH_USER}, + {"localuser", MATCH_LOCALUSER}, + {"localnetwork", MATCH_LOCALNETWORK}, + {NULL, MATCH_UNKNOWN}, +}; + +int ssh_config_parse_line(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global); + +static int ssh_config_parse_line_internal(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global, + bool is_cli, + bool fail_on_unknown); + +int ssh_config_parse_line_cli(ssh_session session, const char *line); + +enum ssh_config_opcode_e ssh_config_get_opcode(char *keyword) +{ + int i; + + for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_config_keyword_table[i].name) == 0) { + return ssh_config_keyword_table[i].opcode; + } + } + + return SOC_UNKNOWN; +} + +static bool ssh_config_is_cli_supported(enum ssh_config_opcode_e opcode) +{ + int i; + + for (i = 0; ssh_config_keyword_table[i].name != NULL; i++) { + if (opcode == ssh_config_keyword_table[i].opcode) { + return ssh_config_keyword_table[i].cli_supported; + } + } + + return false; +} + +#define LIBSSH_CONF_MAX_DEPTH 16 +static void +local_parse_file(ssh_session session, + const char *filename, + int *parsing, + unsigned int depth, + bool global) +{ + FILE *f = NULL; + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + int rv; + + if (depth > LIBSSH_CONF_MAX_DEPTH) { + ssh_set_error(session, SSH_FATAL, + "ERROR - Too many levels of configuration includes " + "when processing file '%s'", filename); + return; + } + + f = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (f == NULL) { + /* The underlying function logs the reasons */ + return; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading additional configuration data from %s", filename); + while (fgets(line, sizeof(line), f)) { + count++; + rv = ssh_config_parse_line(session, line, count, parsing, depth, global); + if (rv < 0) { + fclose(f); + return; + } + } + + fclose(f); + return; +} + +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) +static void local_parse_glob(ssh_session session, + const char *fileglob, + int *parsing, + unsigned int depth, + bool global) +{ + glob_t globbuf = { + .gl_flags = 0, + }; + int rt; + size_t i; + + rt = glob(fileglob, GLOB_TILDE, NULL, &globbuf); + if (rt == GLOB_NOMATCH) { + globfree(&globbuf); + return; + } else if (rt != 0) { + SSH_LOG(SSH_LOG_RARE, "Glob error: %s", + fileglob); + globfree(&globbuf); + return; + } + + for (i = 0; i < globbuf.gl_pathc; i++) { + local_parse_file(session, globbuf.gl_pathv[i], parsing, depth, global); + } + + globfree(&globbuf); +} +#endif /* HAVE_GLOB HAVE_GLOB_GL_FLAGS_MEMBER */ + +static enum ssh_config_match_e +ssh_config_get_match_opcode(const char *keyword) +{ + size_t i; + + for (i = 0; ssh_config_match_keyword_table[i].name != NULL; i++) { + if (strcasecmp(keyword, ssh_config_match_keyword_table[i].name) == 0) { + return ssh_config_match_keyword_table[i].opcode; + } + } + + return MATCH_UNKNOWN; +} + +static int +ssh_config_match(char *value, const char *pattern, bool negate) +{ + int ok, result = 0; + + ok = match_pattern_list(value, pattern, strlen(pattern), 0); + if (ok <= 0 && negate == true) { + result = 1; + } else if (ok > 0 && negate == false) { + result = 1; + } + SSH_LOG(SSH_LOG_TRACE, "%s '%s' against pattern '%s'%s (ok=%d)", + result == 1 ? "Matched" : "Not matched", value, pattern, + negate == true ? " (negated)" : "", ok); + return result; +} + +#ifdef WITH_EXEC +/* FIXME reuse the ssh_execute_command() from socket.c */ +static int +ssh_exec_shell(char *cmd) +{ + char *shell = NULL; + pid_t pid; + int status, devnull, rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + shell = getenv("SHELL"); + if (shell == NULL || shell[0] == '\0') { + shell = (char *)"/bin/sh"; + } + + rc = access(shell, X_OK); + if (rc != 0) { + SSH_LOG(SSH_LOG_WARN, "The shell '%s' is not executable", shell); + return -1; + } + + /* Need this to redirect subprocess stdin/out */ + devnull = open("/dev/null", O_RDWR); + if (devnull == -1) { + SSH_LOG(SSH_LOG_WARN, "Failed to open(/dev/null): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + SSH_LOG(SSH_LOG_DEBUG, "Running command '%s'", cmd); + pid = fork(); + if (pid == 0) { /* Child */ + char *argv[4]; + + /* Redirect child stdin and stdout. Leave stderr */ + rc = dup2(devnull, STDIN_FILENO); + if (rc == -1) { + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + exit(1); + } + rc = dup2(devnull, STDOUT_FILENO); + if (rc == -1) { + SSH_LOG(SSH_LOG_WARN, "dup2: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + exit(1); + } + if (devnull > STDERR_FILENO) { + close(devnull); + } + + argv[0] = shell; + argv[1] = (char *) "-c"; + argv[2] = strdup(cmd); + argv[3] = NULL; + + rc = execv(argv[0], argv); + if (rc == -1) { + SSH_LOG(SSH_LOG_WARN, "Failed to execute command '%s': %s", cmd, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + /* Die with signal to make this error apparent to parent. */ + signal(SIGTERM, SIG_DFL); + kill(getpid(), SIGTERM); + _exit(1); + } + } + + /* Parent */ + close(devnull); + if (pid == -1) { /* Error */ + SSH_LOG(SSH_LOG_WARN, "Failed to fork child: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + + } + + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + SSH_LOG(SSH_LOG_WARN, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + } + if (!WIFEXITED(status)) { + SSH_LOG(SSH_LOG_WARN, "Command %s exited abnormally", cmd); + return -1; + } + SSH_LOG(SSH_LOG_TRACE, "Command '%s' returned %d", cmd, WEXITSTATUS(status)); + return WEXITSTATUS(status); +} + +static int +ssh_match_exec(ssh_session session, const char *command, bool negate) +{ + int rv, result = 0; + char *cmd = NULL; + + /* TODO There should be more supported expansions */ + cmd = ssh_path_expand_escape(session, command); + if (cmd == NULL) { + return 0; + } + rv = ssh_exec_shell(cmd); + if (rv > 0 && negate == true) { + result = 1; + } else if (rv == 0 && negate == false) { + result = 1; + } + SSH_LOG(SSH_LOG_TRACE, "%s 'exec' command '%s'%s (rv=%d)", + result == 1 ? "Matched" : "Not matched", cmd, + negate == true ? " (negated)" : "", rv); + free(cmd); + return result; +} +#else +static int +ssh_match_exec(ssh_session session, const char *command, bool negate) +{ + (void)session; + (void)command; + (void)negate; + + SSH_LOG(SSH_LOG_TRACE, + "Unsupported 'exec' command on Windows '%s'", + command); + return 0; +} +#endif /* WITH_EXEC */ + +/** + * @brief: Parse the ProxyJump configuration line and if parsing, + * stores the result in the configuration option + * + * @param[in] session The ssh session + * @param[in] s The string to be parsed. + * @param[in] do_parsing Whether to parse or not. + * + * @returns SSH_OK if the provided string is formatted and parsed correctly + * SSH_ERROR on failure + */ +int +ssh_config_parse_proxy_jump(ssh_session session, const char *s, bool do_parsing) +{ + char *c = NULL, *cp = NULL, *endp = NULL; + char *username = NULL; + char *hostname = NULL; + char *port = NULL; + char *next = NULL; + int cmp, rv = SSH_ERROR; + struct ssh_jump_info_struct *jump_host = NULL; + bool parse_entry = do_parsing; + bool libssh_proxy_jump = ssh_libssh_proxy_jumps(); + + if (do_parsing) { + SAFE_FREE(session->opts.proxy_jumps_str); + ssh_proxyjumps_free(session->opts.proxy_jumps); + } + /* Special value none disables the proxy */ + cmp = strcasecmp(s, "none"); + if (cmp == 0) { + if (!libssh_proxy_jump && do_parsing) { + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, s); + } + return SSH_OK; + } + + /* This is comma-separated list of [user@]host[:port] entries */ + c = strdup(s); + if (c == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (do_parsing) { + /* Store the whole string in session */ + SAFE_FREE(session->opts.proxy_jumps_str); + session->opts.proxy_jumps_str = strdup(s); + if (session->opts.proxy_jumps_str == NULL) { + free(c); + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + + cp = c; + do { + endp = strchr(cp, ','); + if (endp != NULL) { + /* Split out the token */ + *endp = '\0'; + } + if (parse_entry && libssh_proxy_jump) { + jump_host = calloc(1, sizeof(struct ssh_jump_info_struct)); + if (jump_host == NULL) { + ssh_set_error_oom(session); + rv = SSH_ERROR; + goto out; + } + + rv = ssh_config_parse_uri(cp, + &jump_host->username, + &jump_host->hostname, + &port, + false); + if (rv != SSH_OK) { + ssh_set_error_invalid(session); + SAFE_FREE(jump_host); + goto out; + } + /* Leave the port at 0 when it is not given, so that the jump + * host's own configuration can supply it later. */ + if (port != NULL) { + jump_host->port = strtol(port, NULL, 10); + SAFE_FREE(port); + } + + /* Prepend because we will recursively proxy jump */ + rv = ssh_list_prepend(session->opts.proxy_jumps, jump_host); + if (rv != SSH_OK) { + ssh_set_error_oom(session); + SAFE_FREE(jump_host); + goto out; + } + } else if (parse_entry) { + /* We actually care only about the first item */ + rv = ssh_config_parse_uri(cp, &username, &hostname, &port, false); + if (rv != SSH_OK) { + ssh_set_error_invalid(session); + goto out; + } + /* The rest of the list needs to be passed on */ + if (endp != NULL) { + next = strdup(endp + 1); + if (next == NULL) { + ssh_set_error_oom(session); + rv = SSH_ERROR; + goto out; + } + } + } else { + /* The rest is just sanity-checked to avoid failures later */ + rv = ssh_config_parse_uri(cp, NULL, NULL, NULL, false); + if (rv != SSH_OK) { + ssh_set_error_invalid(session); + goto out; + } + } + if (!libssh_proxy_jump) { + parse_entry = 0; + } + if (endp != NULL) { + cp = endp + 1; + } else { + cp = NULL; /* end */ + } + } while (cp != NULL); + + if (!libssh_proxy_jump && hostname != NULL && do_parsing) { + char com[512] = {0}; + + rv = snprintf(com, sizeof(com), "ssh%s%s%s%s%s%s -W '[%%h]:%%p' %s", + username ? " -l " : "", + username ? username : "", + port ? " -p " : "", + port ? port : "", + next ? " -J " : "", + next ? next : "", + hostname); + if (rv < 0 || rv >= (int)sizeof(com)) { + SSH_LOG(SSH_LOG_TRACE, "Too long ProxyJump configuration line"); + rv = SSH_ERROR; + goto out; + } + rv = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, com); + if (rv != SSH_OK) { + ssh_set_error_oom(session); + goto out; + } + } + + rv = SSH_OK; + +out: + if (rv != SSH_OK) { + ssh_proxyjumps_free(session->opts.proxy_jumps); + } + SAFE_FREE(username); + SAFE_FREE(hostname); + SAFE_FREE(port); + SAFE_FREE(next); + SAFE_FREE(c); + return rv; +} + +static char * +ssh_config_make_absolute(ssh_session session, + const char *path, + bool global) +{ + size_t outlen = 0; + char *out = NULL; + int rv; + + /* Looks like absolute path */ + if (path[0] == '/') { + return strdup(path); + } + + /* relative path */ + if (global) { + /* Parsing global config */ + outlen = strlen(path) + strlen("/etc/ssh/") + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "/etc/ssh/%s", path); + if (rv < 1) { + free(out); + return NULL; + } + return out; + } + + /* paths starting with tilde are already absolute */ + if (path[0] == '~') { + return ssh_path_expand_tilde(path); + } + + /* Parsing user config relative to home directory (generally ~/.ssh) */ + if (session->opts.sshdir == NULL) { + ssh_set_error_invalid(session); + return NULL; + } + outlen = strlen(path) + strlen(session->opts.sshdir) + 1 + 1; + out = malloc(outlen); + if (out == NULL) { + ssh_set_error_oom(session); + return NULL; + } + rv = snprintf(out, outlen, "%s/%s", session->opts.sshdir, path); + if (rv < 1) { + free(out); + return NULL; + } + return out; +} + +#ifdef HAVE_IFADDRS_H +/** + * @brief Checks if host address matches the local network specified. + * + * Verify whether a local network interface address matches any of the CIDR + * patterns. + * + * @param addrlist The CIDR pattern-list to be checked, can contain both + * IPv4 and IPv6 addresses and has to be comma separated + * (',' only, space after comma not allowed). + * + * @param negate The negate condition. The return value is negated + * (returns 1 instead of 0 and vice versa). + * + * @return 1 if match found. + * @return 0 if no match found. + * @return -1 on errors. + */ +static int +ssh_match_localnetwork(const char *addrlist, bool negate) +{ + struct ifaddrs *ifa = NULL, *ifaddrs = NULL; + int r, found = 0; + char address[NI_MAXHOST], err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socklen_t sa_len; + + r = getifaddrs(&ifaddrs); + if (r != 0) { + SSH_LOG(SSH_LOG_WARN, + "Match localnetwork: getifaddrs() failed: %s", + ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); + return -1; + } + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) { + continue; + } + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + sa_len = sizeof(struct sockaddr_in6); + break; + default: + SSH_LOG(SSH_LOG_TRACE, + "Interface %s: unsupported address family %d", + ifa->ifa_name, + ifa->ifa_addr->sa_family); + continue; + } + + r = getnameinfo(ifa->ifa_addr, + sa_len, + address, + sizeof(address), + NULL, + 0, + NI_NUMERICHOST); + if (r != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Interface %s getnameinfo failed: %s", + ifa->ifa_name, + gai_strerror(r)); + continue; + } + SSH_LOG(SSH_LOG_TRACE, + "Interface %s address %s", + ifa->ifa_name, + address); + + r = match_cidr_address_list(address, + addrlist, + ifa->ifa_addr->sa_family); + if (r == 1) { + SSH_LOG(SSH_LOG_TRACE, + "Matched interface %s: address %s in %s", + ifa->ifa_name, + address, + addrlist); + found = 1; + break; + } + } + + freeifaddrs(ifaddrs); + + return (found == (negate ? 0 : 1)); +} +#endif /* HAVE_IFADDRS_H */ + +static enum ssh_options_e +ssh_config_get_auth_option(enum ssh_config_opcode_e opcode) +{ + struct auth_option_map { + enum ssh_config_opcode_e opcode; + const char *name; + enum ssh_options_e option; + }; + + static struct auth_option_map auth_options[] = { + { + SOC_GSSAPIAUTHENTICATION, + "GSSAPIAuthentication", + SSH_OPTIONS_GSSAPI_AUTH, + }, + { + SOC_KBDINTERACTIVEAUTHENTICATION, + "KbdInteractiveAuthentication", + SSH_OPTIONS_KBDINT_AUTH, + }, + { + SOC_PASSWORDAUTHENTICATION, + "PasswordAuthentication", + SSH_OPTIONS_PASSWORD_AUTH, + }, + { + SOC_PUBKEYAUTHENTICATION, + "PubkeyAuthentication", + SSH_OPTIONS_PUBKEY_AUTH, + }, + {0, NULL, 0}, + }; + + for (struct auth_option_map *map = auth_options; map->name != NULL; map++) { + if (map->opcode == opcode) { + return map->option; + } + } + return -1; +} + +#define CHECK_COND_OR_FAIL(cond, error_message) \ + if ((cond)) { \ + SSH_LOG(SSH_LOG_DEBUG, \ + "line %d: %s: %s", \ + count, \ + error_message, \ + keyword); \ + if (fail_on_unknown) { \ + ssh_set_error(session, \ + SSH_FATAL, \ + is_cli ? "%s '%s' value on CLI" \ + : "%s '%s' value at line %d", \ + error_message, \ + keyword, \ + is_cli ? 0 : count); \ + SAFE_FREE(x); \ + return SSH_ERROR; \ + } \ + break; \ + } + +static int ssh_config_parse_line_internal(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global, + bool is_cli, + bool fail_on_unknown) +{ + enum ssh_config_opcode_e opcode; + const char *p = NULL, *p2 = NULL; + char *s = NULL, *x = NULL; + char *keyword = NULL; + char *lowerhost = NULL; + size_t len; + int i, rv; + uint8_t *seen = session->opts.options_seen; + long l; + int64_t ll; + + /* Ignore empty lines */ + if (line == NULL || *line == '\0') { + if (is_cli) { + return SSH_ERROR; + } + return 0; + } + + x = s = strdup(line); + if (s == NULL) { + ssh_set_error_oom(session); + return -1; + } + + /* Remove trailing spaces */ + for (len = strlen(s) - 1; len > 0; len--) { + if (! isspace(s[len])) { + break; + } + s[len] = '\0'; + } + + keyword = ssh_config_get_token(&s); + if (keyword == NULL || *keyword == '#' || + *keyword == '\0' || *keyword == '\n') { + SAFE_FREE(x); + return 0; + } + + opcode = ssh_config_get_opcode(keyword); + if (is_cli && !ssh_config_is_cli_supported(opcode)) { + ssh_set_error( + session, + SSH_FATAL, + "Option '%s' is not supported in command-line configuration", + keyword); + SAFE_FREE(x); + return SSH_ERROR; + } + + if (*parsing == 1 && + opcode != SOC_HOST && + opcode != SOC_MATCH && + opcode != SOC_INCLUDE && + opcode != SOC_IDENTITY && + opcode != SOC_CERTIFICATE && + opcode > SOC_UNSUPPORTED && + opcode < SOC_MAX) { /* Ignore all unknown types here */ + /* Skip all the options that were already applied */ + if (seen[opcode] != 0) { + SAFE_FREE(x); + return 0; + } + seen[opcode] = 1; + } + + switch (opcode) { + case SOC_INCLUDE: /* recursive include of other files */ + + p = ssh_config_get_str_tok(&s, NULL); + if (p && *parsing) { + char *path = ssh_config_make_absolute(session, p, global); + if (path == NULL) { + SSH_LOG(SSH_LOG_WARN, "line %d: Failed to allocate memory " + "for the include path expansion", count); + SAFE_FREE(x); + return -1; + } +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + local_parse_glob(session, path, parsing, depth + 1, global); +#else + local_parse_file(session, path, parsing, depth + 1, global); +#endif /* HAVE_GLOB */ + free(path); + } + break; + + case SOC_MATCH: { + bool negate; + int result = 1; + size_t args = 0; + enum ssh_config_match_e opt; + char *localuser = NULL; + + *parsing = 0; + do { + p = p2 = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + break; + } + args++; + SSH_LOG(SSH_LOG_DEBUG, "line %d: Processing Match keyword '%s'", + count, p); + + /* If the option is prefixed with ! the result should be negated */ + negate = false; + if (p[0] == '!') { + negate = true; + p++; + } + + opt = ssh_config_get_match_opcode(p); + switch (opt) { + case MATCH_ALL: + p = ssh_config_get_str_tok(&s, NULL); + if (args <= 2 && (p == NULL || p[0] == '\0')) { + /* The first or second, but last argument. The "all" keyword + * can be prefixed with either "final" or "canonical" + * keywords which do not have any effect here. */ + if (negate == true) { + result = 0; + } + break; + } + + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match all cannot be combined with " + "other Match attributes", count); + SAFE_FREE(x); + return -1; + + case MATCH_FINAL: + case MATCH_CANONICAL: + SSH_LOG(SSH_LOG_DEBUG, + "line %d: Unsupported Match keyword '%s', skipping", + count, + p); + /* Not set any result here -- the result is dependent on the + * following matches after this keyword */ + break; + + case MATCH_EXEC: + /* Skip one argument (including in quotes) */ + p = ssh_config_get_token(&s); + if (p == NULL || p[0] == '\0') { + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " + "'%s' requires argument", count, p2); + SAFE_FREE(x); + return -1; + } + if (result != 1) { + SSH_LOG(SSH_LOG_DEBUG, "line %d: Skipped match exec " + "'%s' as previous conditions already failed.", + count, p2); + continue; + } + result &= ssh_match_exec(session, p, negate); + args++; + break; + + case MATCH_LOCALUSER: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Match localuser keyword " + "requires argument", + count); + SAFE_FREE(x); + return -1; + } + localuser = ssh_get_local_username(); + if (localuser == NULL) { + SSH_LOG(SSH_LOG_TRACE, "line %d: Can not get local username " + "for conditional matching.", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(localuser, p, negate); + SAFE_FREE(localuser); + args++; + break; + + case MATCH_ORIGINALHOST: + /* Skip one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + SSH_LOG(SSH_LOG_TRACE, "line %d: Match keyword " + "'%s' requires argument", count, p2); + SAFE_FREE(x); + return -1; + } + args++; + SSH_LOG(SSH_LOG_TRACE, + "line %d: Unsupported Match keyword '%s', ignoring", + count, + p2); + result = 0; + break; + + case MATCH_HOST: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match host keyword " + "requires argument", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(session->opts.host, p, negate); + args++; + break; + + case MATCH_USER: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, SSH_FATAL, + "line %d: ERROR - Match user keyword " + "requires argument", count); + SAFE_FREE(x); + return -1; + } + result &= ssh_config_match(session->opts.username, p, negate); + args++; + break; + + case MATCH_LOCALNETWORK: + /* Here we match only one argument */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL || p[0] == '\0') { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Match local network keyword" + "requires argument", + count); + SAFE_FREE(x); + return -1; + } +#ifdef HAVE_IFADDRS_H + rv = match_cidr_address_list(NULL, p, -1); + if (rv == -1) { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - List invalid entry: %s", + count, + p); + SAFE_FREE(x); + return -1; + } + rv = ssh_match_localnetwork(p, negate); + if (rv == -1) { + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - Error while retrieving " + "network interface information -" + " List entry: %s", + count, + p); + SAFE_FREE(x); + return -1; + } + + result &= rv; +#else /* HAVE_IFADDRS_H */ + ssh_set_error(session, + SSH_FATAL, + "line %d: ERROR - match localnetwork " + "not supported on this platform", + count); + SAFE_FREE(x); + return -1; +#endif /* HAVE_IFADDRS_H */ + args++; + break; + + case MATCH_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, + "Unknown argument '%s' for Match keyword. Not matching", + p); + result = 0; + break; + } + } while (p != NULL && p[0] != '\0'); + if (args == 0) { + SSH_LOG(SSH_LOG_WARN, + "ERROR - Match keyword requires an argument. Not matching"); + result = 0; + } + *parsing = result; + break; + } + case SOC_HOST: { + int ok = 0, result = -1; + + *parsing = 0; + lowerhost = (session->opts.host) ? ssh_lowercase(session->opts.host) : NULL; + for (p = ssh_config_get_str_tok(&s, NULL); + p != NULL && p[0] != '\0'; + p = ssh_config_get_str_tok(&s, NULL)) { + if (ok >= 0) { + ok = match_hostname(lowerhost, p, strlen(p)); + if (result == -1 && ok < 0) { + result = 0; + } else if (result == -1 && ok > 0) { + result = 1; + } + } + } + SAFE_FREE(lowerhost); + if (result != -1) { + *parsing = result; + } + break; + } + case SOC_HOSTNAME: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + char *z = ssh_path_expand_escape(session, p); + if (z == NULL) { + z = strdup(p); + } + ssh_options_set(session, SSH_OPTIONS_HOST, z); + free(z); + } + break; + case SOC_PORT: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_PORT_STR, p); + } + break; + case SOC_USERNAME: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_USER, p); + } + break; + case SOC_IDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, p); + } + break; + case SOC_CIPHERS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, p); + ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, p); + } + break; + case SOC_MACS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, p); + ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, p); + } + break; + case SOC_COMPRESSION: + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + if (i) { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes"); + } else { + ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); + } + } + break; + case SOC_TIMEOUT: + l = ssh_config_get_long(&s, -1); + CHECK_COND_OR_FAIL(l < 0, "Invalid argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &l); + } + break; + case SOC_STRICTHOSTKEYCHECK: + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &i); + } + break; + case SOC_KNOWNHOSTS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, p); + } + break; + case SOC_PROXYCOMMAND: + p = ssh_config_get_cmd(&s); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + /* We share the seen value with the ProxyJump */ + if (*parsing && !seen[SOC_PROXYJUMP]) { + ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, p); + } + break; + case SOC_PROXYJUMP: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SAFE_FREE(x); + return -1; + } + /* We share the seen value with the ProxyCommand */ + rv = ssh_config_parse_proxy_jump(session, + p, + (*parsing && !seen[SOC_PROXYCOMMAND])); + if (rv != SSH_OK) { + SAFE_FREE(x); + return -1; + } + break; + case SOC_GSSAPISERVERIDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, p); + } + break; + case SOC_GSSAPICLIENTIDENTITY: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, p); + } + break; + case SOC_GSSAPIDELEGATECREDENTIALS: + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &i); + } + break; + case SOC_BINDADDRESS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_BINDADDR, p); + } + break; + case SOC_GLOBALKNOWNHOSTSFILE: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, p); + } + break; + case SOC_LOGLEVEL: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + int value = -1; + + if (strcasecmp(p, "quiet") == 0) { + value = SSH_LOG_NONE; + } else if (strcasecmp(p, "fatal") == 0 || + strcasecmp(p, "error")== 0) { + value = SSH_LOG_WARN; + } else if (strcasecmp(p, "verbose") == 0 || + strcasecmp(p, "info") == 0) { + value = SSH_LOG_INFO; + } else if (strcasecmp(p, "DEBUG") == 0 || + strcasecmp(p, "DEBUG1") == 0) { + value = SSH_LOG_DEBUG; + } else if (strcasecmp(p, "DEBUG2") == 0 || + strcasecmp(p, "DEBUG3") == 0) { + value = SSH_LOG_TRACE; + } + CHECK_COND_OR_FAIL(value == -1, "Invalid value"); + if (value != -1) { + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &value); + } + } + break; + case SOC_HOSTKEYALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, p); + } + break; + case SOC_PUBKEYACCEPTEDKEYTYPES: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, p); + } + break; + case SOC_KEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, p); + } + break; + case SOC_REKEYLIMIT: + /* Parse the data limit */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + CHECK_COND_OR_FAIL(1, "Missing data limit"); + break; + } else if (strcmp(p, "default") == 0) { + /* Default rekey limits enforced automatically */ + ll = 0; + } else { + char *endp = NULL; + ll = strtoll(p, &endp, 10); + if (p == endp || ll < 0) { + CHECK_COND_OR_FAIL(1, "Invalid data limit"); + break; + } + switch (*endp) { + case 'G': + if (ll > LLONG_MAX / 1024) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 1024; + FALL_THROUGH; + case 'M': + if (ll > LLONG_MAX / 1024) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 1024; + FALL_THROUGH; + case 'K': + if (ll > LLONG_MAX / 1024) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 1024; + endp++; + FALL_THROUGH; + case '\0': + /* just the number */ + break; + default: + /* Invalid suffix */ + ll = -1; + break; + } + if (*endp != ' ' && *endp != '\0') { + CHECK_COND_OR_FAIL(1, "Invalid trailing characters"); + break; + } + } + CHECK_COND_OR_FAIL(ll < 0, "Invalid data limit"); + if (*parsing) { + uint64_t v = (uint64_t)ll; + ssh_options_set(session, SSH_OPTIONS_REKEY_DATA, &v); + } + /* Parse the time limit */ + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + CHECK_COND_OR_FAIL(1, "Missing time limit"); + break; + } else if (strcmp(p, "none") == 0) { + ll = 0; + } else { + char *endp = NULL; + ll = strtoll(p, &endp, 10); + if (p == endp || ll < 0) { + /* No number or negative */ + CHECK_COND_OR_FAIL(1, "Invalid time limit"); + break; + } + switch (*endp) { + case 'w': + case 'W': + if (ll > LLONG_MAX / 7) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 7; + FALL_THROUGH; + case 'd': + case 'D': + if (ll > LLONG_MAX / 24) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 24; + FALL_THROUGH; + case 'h': + case 'H': + if (ll > LLONG_MAX / 60) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 60; + FALL_THROUGH; + case 'm': + case 'M': + if (ll > LLONG_MAX / 60) { + SSH_LOG(SSH_LOG_TRACE, "Possible overflow of rekey limit"); + ll = -1; + break; + } + ll = ll * 60; + FALL_THROUGH; + case 's': + case 'S': + endp++; + FALL_THROUGH; + case '\0': + /* just the number */ + break; + default: + /* Invalid suffix */ + ll = -1; + break; + } + if (*endp != '\0') { + CHECK_COND_OR_FAIL(1, "Invalid trailing characters"); + break; + } + } + CHECK_COND_OR_FAIL(ll < 0, "Invalid time limit"); + if (ll > -1 && *parsing) { + uint32_t v = (uint32_t)ll; + ssh_options_set(session, SSH_OPTIONS_REKEY_TIME, &v); + } + break; + case SOC_GSSAPIAUTHENTICATION: + case SOC_KBDINTERACTIVEAUTHENTICATION: + case SOC_PASSWORDAUTHENTICATION: + case SOC_PUBKEYAUTHENTICATION: { + enum ssh_options_e option = ssh_config_get_auth_option(opcode); + i = ssh_config_get_yesno(&s, 0); + + CHECK_COND_OR_FAIL(i < 0, "Authentication option"); + if (*parsing) { + ssh_options_set(session, option, &i); + } + break; + } + case SOC_NA: + CHECK_COND_OR_FAIL(1, "Unapplicable option"); + break; + case SOC_UNSUPPORTED: + CHECK_COND_OR_FAIL(1, "Unsupported option"); + break; + case SOC_UNKNOWN: + CHECK_COND_OR_FAIL(1, "Unknown option"); + break; + case SOC_IDENTITYAGENT: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_IDENTITY_AGENT, p); + } + break; + case SOC_IDENTITIESONLY: + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + bool b = i; + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &b); + } + break; + case SOC_CONTROLMASTER: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "ControlMaster"); + if (*parsing) { + int value = -1; + + if (strcasecmp(p, "auto") == 0) { + value = SSH_CONTROL_MASTER_AUTO; + } else if (strcasecmp(p, "yes") == 0) { + value = SSH_CONTROL_MASTER_YES; + } else if (strcasecmp(p, "no") == 0) { + value = SSH_CONTROL_MASTER_NO; + } else if (strcasecmp(p, "autoask") == 0) { + value = SSH_CONTROL_MASTER_AUTOASK; + } else if (strcasecmp(p, "ask") == 0) { + value = SSH_CONTROL_MASTER_ASK; + } + + CHECK_COND_OR_FAIL(value == -1, "Invalid argument"); + if (value != -1) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_MASTER, &value); + } + } + break; + case SOC_CONTROLPATH: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SAFE_FREE(x); + return -1; + } + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, p); + } + break; + case SOC_CERTIFICATE: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, p); + } + break; + case SOC_GSSAPIKEYEXCHANGE: { + i = ssh_config_get_yesno(&s, -1); + CHECK_COND_OR_FAIL(i < 0, "Invalid argument"); + if (*parsing) { + bool b = (i == 1) ? true : false; + ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &b); + } + break; + } + case SOC_GSSAPIKEXALGORITHMS: + p = ssh_config_get_str_tok(&s, NULL); + CHECK_COND_OR_FAIL(p == NULL, "Missing argument"); + if (*parsing) { + ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, p); + } + break; + case SOC_REQUIRED_RSA_SIZE: + l = ssh_config_get_long(&s, -1); + CHECK_COND_OR_FAIL(l < 0 || l > INT_MAX, "Invalid argument"); + if (*parsing) { + i = (int)l; + ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &i); + } + break; + case SOC_ADDRESSFAMILY: + p = ssh_config_get_str_tok(&s, NULL); + if (p == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "line %d: no argument after keyword \"addressfamily\"", + count); + SAFE_FREE(x); + return SSH_ERROR; + } + if (*parsing) { + int value = -1; + + if (strcasecmp(p, "any") == 0) { + value = SSH_ADDRESS_FAMILY_ANY; + } else if (strcasecmp(p, "inet") == 0) { + value = SSH_ADDRESS_FAMILY_INET; + } else if (strcasecmp(p, "inet6") == 0) { + value = SSH_ADDRESS_FAMILY_INET6; + } else { + SSH_LOG(SSH_LOG_WARNING, + "line %d: invalid argument \"%s\"", + count, + p); + SAFE_FREE(x); + return SSH_ERROR; + } + ssh_options_set(session, SSH_OPTIONS_ADDRESS_FAMILY, &value); + } + break; + default: + ssh_set_error(session, SSH_FATAL, "ERROR - unimplemented opcode: %d", + opcode); + SAFE_FREE(x); + return -1; + break; + } + + SAFE_FREE(x); + return 0; +} + +#undef CHECK_COND_OR_FAIL + +int ssh_config_parse_line(ssh_session session, + const char *line, + unsigned int count, + int *parsing, + unsigned int depth, + bool global) +{ + return ssh_config_parse_line_internal(session, + line, + count, + parsing, + depth, + global, + false, + false); +} + +int ssh_config_parse_line_cli(ssh_session session, const char *line) +{ + int parsing = 1; + return ssh_config_parse_line_internal(session, + line, + 0, + &parsing, + 0, + false, + true, + true); +} + +/* @brief Parse configuration from a file pointer + * + * @params[in] session The ssh session + * @params[in] fp A valid file pointer + * @params[in] global Whether the config is global or not + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_config_parse(ssh_session session, FILE *fp, bool global) +{ + char line[MAX_LINE_SIZE] = {0}; + unsigned int count = 0; + int parsing, rv; + + parsing = 1; + while (fgets(line, sizeof(line), fp)) { + count++; + rv = ssh_config_parse_line(session, line, count, &parsing, 0, global); + if (rv < 0) { + return -1; + } + } + + return 0; +} + +/* @brief Parse configuration file and set the options to the given session + * + * @params[in] session The ssh session + * @params[in] filename The path to the ssh configuration file + * + * @returns 0 on successful parsing the configuration file, -1 on error + */ +int ssh_config_parse_file(ssh_session session, const char *filename) +{ + FILE *fp = NULL; + int rv; + bool global = 0; + + fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (fp == NULL) { + /* The underlying function logs the reasons */ + return 0; + } + + rv = strcmp(filename, GLOBAL_CLIENT_CONFIG); +#ifdef USR_GLOBAL_CLIENT_CONFIG + if (rv != 0) { + rv = strcmp(filename, USR_GLOBAL_CLIENT_CONFIG); + } +#endif + + if (rv == 0) { + global = true; + } + + SSH_LOG(SSH_LOG_PACKET, "Reading configuration data from %s", filename); + + rv = ssh_config_parse(session, fp, global); + + fclose(fp); + return rv; +} + +/* @brief Parse configuration string and set the options to the given session + * + * @params[in] session The ssh session + * @params[in] input Null terminated string containing the configuration + * + * @returns SSH_OK on successful parsing the configuration string, + * SSH_ERROR on error + */ +int ssh_config_parse_string(ssh_session session, const char *input) +{ + char line[MAX_LINE_SIZE] = {0}; + const char *c = input, *line_start = input; + unsigned int line_num = 0; + size_t line_len; + int parsing, rv; + + SSH_LOG(SSH_LOG_DEBUG, "Reading configuration data from string:"); + SSH_LOG(SSH_LOG_DEBUG, "START\n%s\nEND", input); + + parsing = 1; + while (1) { + line_num++; + line_start = c; + c = strchr(line_start, '\n'); + if (c == NULL) { + /* if there is no newline at the end of the string */ + c = strchr(line_start, '\0'); + } + if (c == NULL) { + /* should not happen, would mean a string without trailing '\0' */ + SSH_LOG(SSH_LOG_TRACE, "No trailing '\\0' in config string"); + return SSH_ERROR; + } + line_len = c - line_start; + if (line_len > MAX_LINE_SIZE - 1) { + SSH_LOG(SSH_LOG_TRACE, + "Line %u too long: %zu characters", + line_num, + line_len); + return SSH_ERROR; + } + memcpy(line, line_start, line_len); + line[line_len] = '\0'; + SSH_LOG(SSH_LOG_DEBUG, "Line %u: %s", line_num, line); + rv = ssh_config_parse_line(session, line, line_num, &parsing, 0, false); + if (rv < 0) { + return SSH_ERROR; + } + if (*c == '\0') { + break; + } + c++; + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/config.h b/src/libs/libssh-0.12.2/src/config.h new file mode 100644 index 000000000000..37186b0f1bd9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/config.h @@ -0,0 +1,334 @@ +/* Name of package */ +#define PACKAGE "libssh" + +/* Version number of package */ +#define VERSION "0.11.4" + +#define SYSCONFDIR "etc" +//#define BINARYDIR "C:/Src/libssh-0.9.5/build" +//#define SOURCEDIR "C:/Src/libssh-0.9.5" + +/* Global configuration directory */ +/* #undef USR_GLOBAL_CONF_DIR */ +#ifndef RT_OS_WINDOWS +#define GLOBAL_CONF_DIR "/etc/ssh" +#else +#define GLOBAL_CONF_DIR "C:/ProgramData/ssh" +#endif + +/* Global bind configuration file path */ +#define GLOBAL_BIND_CONFIG "/etc/ssh/libssh_server_config" + +/* Global client configuration file path */ +#define GLOBAL_CLIENT_CONFIG "/etc/ssh/ssh_config" + +/************************** HEADER FILES *************************/ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_ARGP_H */ + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_ARPA_INET_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_GLOB_H 1 +#endif +/* Define to 1 if you have the header file. */ +/* #undef HAVE_VALGRIND_VALGRIND_H */ + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_PTY_H */ + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_UTMP_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UTIL_H 1 +#endif + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_LIBUTIL_H */ + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_SYS_TIME_H +#endif + +/* Define to 1 if you have the header file. */ +//#define HAVE_SYS_UTIME_H 1 + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_IO_H 1 +#else +/* Define to 1 if you have the header file. */ +#define HAVE_TERMIOS_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_UNISTD_H 1 +#endif + +/* Define to 1 if you have the header file. */ +#define HAVE_STDINT_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_OPENSSL_AES_H 1 + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the header file. */ +#define HAVE_WSPIAPI_H 1 +#endif +/* Define to 1 if you have the header file. */ +/* #undef HAVE_OPENSSL_BLOWFISH_H */ + +/* Define to 1 if you have the header file. */ +//#define HAVE_OPENSSL_DES_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_OPENSSL_ECDH_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_OPENSSL_EC_H 1 + +/* Define to 1 if you have the header file. */ +#define HAVE_OPENSSL_ECDSA_H 1 + +/* Define to 1 if you have the header file. */ +/* #undef HAVE_PTHREAD_H */ + +/* Define to 1 if you have eliptic curve cryptography in openssl */ +#define HAVE_OPENSSL_ECC 1 + +/* Define to 1 if you have eliptic curve cryptography in gcrypt */ +/* #undef HAVE_GCRYPT_ECC */ + +/* Define to 1 if you have eliptic curve cryptography */ +#define HAVE_ECC 1 + +/* Define to 1 if you have DSA */ +#define HAVE_DSA 1 + +/* Define to 1 if you have gl_flags as a glob_t sturct member */ +/* #undef HAVE_GLOB_GL_FLAGS_MEMBER */ + +/* Define to 1 if you have OpenSSL with Ed25519 support */ +//#define HAVE_OPENSSL_ED25519 1 + +/* Define to 1 if you have OpenSSL with X25519 support */ +//#define HAVE_OPENSSL_X25519 1 + +/*************************** FUNCTIONS ***************************/ + +/* Define to 1 if you have the `EVP_aes128_ctr' function. */ +#define HAVE_OPENSSL_EVP_AES_CTR 1 + +/* Define to 1 if you have the `EVP_aes128_cbc' function. */ +#define HAVE_OPENSSL_EVP_AES_CBC 1 + +/* Define to 1 if you have the `EVP_aes128_gcm' function. */ +#define HAVE_OPENSSL_EVP_AES_GCM 1 + +/* Define to 1 if you have the `CRYPTO_THREADID_set_callback' function. */ +/* #undef HAVE_OPENSSL_CRYPTO_THREADID_SET_CALLBACK */ + +/* Define to 1 if you have the `CRYPTO_ctr128_encrypt' function. */ +#define HAVE_OPENSSL_CRYPTO_CTR128_ENCRYPT 1 + +/* Define to 1 if you have the `EVP_CIPHER_CTX_new' function. */ +#define HAVE_OPENSSL_EVP_CIPHER_CTX_NEW 1 + +/* Define to 1 if you have the `EVP_KDF_CTX_new_id' function. */ +/* #undef HAVE_OPENSSL_EVP_KDF_CTX_NEW_ID */ + +#ifndef VBOX +/* Define to 1 if you have the `FIPS_mode' function. */ +#define HAVE_OPENSSL_FIPS_MODE 1 +#endif + +/* Define to 1 if you have the `EVP_DigestSign' function. */ +//#define HAVE_OPENSSL_EVP_DIGESTSIGN 1 + +/* Define to 1 if you have the `EVP_DigestVerify' function. */ +//#define HAVE_OPENSSL_EVP_DIGESTVERIFY 1 + +/* Define to 1 if you have the `OPENSSL_ia32cap_loc' function. */ +/* #undef HAVE_OPENSSL_IA32CAP_LOC */ + +/* Define to 1 if you have the `snprintf' function. */ +#define HAVE_SNPRINTF 1 + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the `_snprintf' function. */ +#define HAVE__SNPRINTF 1 + +/* Define to 1 if you have the `_snprintf_s' function. */ +#define HAVE__SNPRINTF_S 1 +#endif +/* Define to 1 if you have the `vsnprintf' function. */ +#define HAVE_VSNPRINTF 1 + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the `_vsnprintf' function. */ +#define HAVE__VSNPRINTF 1 + +/* Define to 1 if you have the `_vsnprintf_s' function. */ +#define HAVE__VSNPRINTF_S 1 +#endif + +/* Define to 1 if you have the `isblank' function. */ +#define HAVE_ISBLANK 1 + +/* Define to 1 if you have the `strncpy' function. */ +#define HAVE_STRNCPY 1 + +#if defined(RT_OS_DARWIN) || defined(RT_OS_LINUX) +/* Define to 1 if you have the `strndup' function. */ +#define HAVE_STRNDUP 1 +#endif + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the `cfmakeraw' function. */ +#define HAVE_CFMAKERAW 1 +#endif + +/* Define to 1 if you have the `getaddrinfo' function. */ +#define HAVE_GETADDRINFO 1 + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the `poll' function. */ +#define HAVE_POLL 1 +#endif + +/* Define to 1 if you have the `select' function. */ +#define HAVE_SELECT 1 + +/* Define to 1 if you have the `clock_gettime' function. */ +/* #undef HAVE_CLOCK_GETTIME */ + +#if defined(RT_OS_WINDOWS) || (defined(RT_OS_DARWIN) && MAC_OS_X_VERSION_MIN_REQUIRED > 1090) +/* Define to 1 if you have the `ntohll' function. */ +#define HAVE_NTOHLL 1 + +/* Define to 1 if you have the `htonll' function. */ +#define HAVE_HTONLL 1 +#endif + +/* Define to 1 if you have the `strtoull' function. */ +#define HAVE_STRTOULL 1 + +/* Define to 1 if you have the `__strtoull' function. */ +/* #undef HAVE___STRTOULL */ + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the `_strtoui64' function. */ +#define HAVE__STRTOUI64 1 +#else +/* Define to 1 if you have the `glob' function. */ +#define HAVE_GLOB 1 +#endif + +/* Define to 1 if you have the `explicit_bzero' function. */ +/* #undef HAVE_EXPLICIT_BZERO */ + +#ifdef RT_OS_DARWIN +/* Define to 1 if you have the `memset_s' function. */ +#define HAVE_MEMSET_S 1 +#endif + +#ifdef RT_OS_WINDOWS +/* Define to 1 if you have the `SecureZeroMemory' function. */ +#define HAVE_SECURE_ZERO_MEMORY 1 +#endif + +/* Define to 1 if you have the `cmocka_set_test_filter' function. */ +/* #undef HAVE_CMOCKA_SET_TEST_FILTER */ + +/*************************** LIBRARIES ***************************/ + +/* Define to 1 if you have the `crypto' library (-lcrypto). */ +#define HAVE_LIBCRYPTO 1 + +/* Define to 1 if you have the `gcrypt' library (-lgcrypt). */ +/* #undef HAVE_LIBGCRYPT */ + +/* Define to 1 if you have the 'mbedTLS' library (-lmbedtls). */ +/* #undef HAVE_LIBMBEDCRYPTO */ + +#ifndef RT_OS_WINDOWS +/* Define to 1 if you have the `pthread' library (-lpthread). */ +#define HAVE_PTHREAD 1 +#endif + +/* Define to 1 if you have the `cmocka' library (-lcmocka). */ +/* #undef HAVE_CMOCKA */ + +/**************************** OPTIONS ****************************/ + +#ifndef RT_OS_WINDOWS +#define HAVE_GCC_THREAD_LOCAL_STORAGE 1 +#else +#define HAVE_MSC_THREAD_LOCAL_STORAGE 1 +#endif + +/* #undef HAVE_FALLTHROUGH_ATTRIBUTE */ +/* #undef HAVE_UNUSED_ATTRIBUTE */ + +/* #undef HAVE_CONSTRUCTOR_ATTRIBUTE */ +/* #undef HAVE_DESTRUCTOR_ATTRIBUTE */ + +/* #undef HAVE_GCC_VOLATILE_MEMORY_PROTECTION */ + +#define HAVE_COMPILER__FUNC__ 1 +#define HAVE_COMPILER__FUNCTION__ 1 + +/* #undef HAVE_GCC_BOUNDED_ATTRIBUTE */ + +/* Define to 1 if you want to enable GSSAPI */ +/* #undef WITH_GSSAPI */ + +/* Define to 1 if you want to enable ZLIB */ +#define WITH_ZLIB 1 + +/* Define to 1 if you want to enable SFTP */ +#define WITH_SFTP 1 + +/* Define to 1 if you want to enable server support */ +//#define WITH_SERVER 1 + +/* Define to 1 if you want to enable DH group exchange algorithms */ +//#define WITH_GEX 1 + +/* Define to 1 if you want to enable blowfish cipher support */ +/* #undef WITH_BLOWFISH_CIPHER */ + +/* Define to 1 if you want to enable debug output for crypto functions */ +/* #undef DEBUG_CRYPTO */ + +/* Define to 1 if you want to enable debug output for packet functions */ +/* #undef DEBUG_PACKET */ + +/* Define to 1 if you want to enable pcap output support (experimental) */ +#define WITH_PCAP 1 + +/* Define to 1 if you want to enable calltrace debug output */ +#define DEBUG_CALLTRACE 1 + +/* Define to 1 if you want to enable NaCl support */ +/* #undef WITH_NACL */ + +/*************************** ENDIAN *****************************/ + +/* Define WORDS_BIGENDIAN to 1 if your processor stores words with the most + significant byte first (like Motorola and SPARC, unlike Intel). */ +/* #undef WORDS_BIGENDIAN */ + +#ifdef RT_OS_WINDOWS +unsigned __int64 htonll(unsigned __int64 Value); +unsigned __int64 ntohll(unsigned __int64 Value); +#endif + +#if defined(RT_OS_LINUX) && !defined(LLONG_MAX) +#define LLONG_MAX 9223372036854775807LL +#endif diff --git a/src/libs/libssh-0.12.2/src/config_parser.c b/src/libs/libssh-0.12.2/src/config_parser.c new file mode 100644 index 000000000000..876f0396a1be --- /dev/null +++ b/src/libs/libssh-0.12.2/src/config_parser.c @@ -0,0 +1,293 @@ +/* + * config_parser.c - Common configuration file parser functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/config_parser.h" +#include "libssh/priv.h" +#include "libssh/misc.h" + +/* Returns the original string after skipping the leading whitespace + * until finding LF. + * This is useful in case we need to get the rest of the line (for example + * external command). + */ +char *ssh_config_get_cmd(char **str) +{ + register char *c = NULL; + char *r = NULL; + + /* Ignore leading spaces */ + for (c = *str; *c; c++) { + if (! isblank(*c)) { + break; + } + } + + for (r = c; *c; c++) { + if (*c == '\n') { + *c = '\0'; + goto out; + } + } + +out: + *str = c + 1; + + return r; +} + +/* Returns the next token delimited by whitespace or equal sign (=) + * respecting the quotes creating separate token (including whitespaces). + */ +char *ssh_config_get_token(char **str) +{ + register char *c = NULL; + bool had_equal = false; + char *r = NULL; + + /* Ignore leading spaces */ + for (c = *str; *c; c++) { + if (! isblank(*c)) { + break; + } + } + + /* If we start with quote, return the whole quoted block */ + if (*c == '\"') { + for (r = ++c; *c; c++) { + if (*c == '\"' || *c == '\n') { + if (*c == '\"' && r != c && *(c - 1) == '\\') { + /* Escaped quote: Move the remaining one char left */ + int remaining_len = strlen(c); + memmove(c - 1, c, remaining_len); + c[remaining_len - 1] = '\0'; + continue; + } + *c = '\0'; + c++; + break; + } + /* XXX Unmatched quotes extend to the end of line */ + } + } else { + /* Otherwise terminate on space, equal or newline */ + for (r = c; *c; c++) { + if (*c == '\0') { + goto out; + } else if (isblank(*c) || *c == '=' || *c == '\n') { + had_equal = (*c == '='); + *c = '\0'; + c++; + break; + } + } + } + + /* Skip any other remaining whitespace */ + while (isblank(*c) || *c == '\n' || (!had_equal && *c == '=')) { + if (*c == '=') { + had_equal = true; + } + c++; + } +out: + *str = c; + return r; +} + +long ssh_config_get_long(char **str, long notfound) +{ + char *p = NULL, *endp = NULL; + long i; + + p = ssh_config_get_token(str); + if (p && *p) { + i = strtol(p, &endp, 10); + if (p == endp) { + return notfound; + } + return i; + } + + return notfound; +} + +const char *ssh_config_get_str_tok(char **str, const char *def) +{ + char *p = NULL; + + p = ssh_config_get_token(str); + if (p && *p) { + return p; + } + + return def; +} + +int ssh_config_get_yesno(char **str, int notfound) +{ + const char *p = NULL; + + p = ssh_config_get_str_tok(str, NULL); + if (p == NULL) { + return notfound; + } + + if (strncasecmp(p, "yes", 3) == 0) { + return 1; + } else if (strncasecmp(p, "no", 2) == 0) { + return 0; + } + + return notfound; +} + +int ssh_config_parse_uri(const char *tok, + char **username, + char **hostname, + char **port, + bool ignore_port) +{ + const char *endp = NULL; + long port_n; + int rc; + + /* Sanitize inputs */ + if (username != NULL) { + *username = NULL; + } + if (hostname != NULL) { + *hostname = NULL; + } + if (port != NULL) { + *port = NULL; + } + + /* Username part (optional) */ + endp = strrchr(tok, '@'); + if (endp != NULL) { + /* Zero-length username is not valid */ + if (tok == endp) { + goto error; + } + if (username != NULL) { + *username = strndup(tok, endp - tok); + if (*username == NULL) { + goto error; + } + rc = ssh_check_username_syntax(*username); + if (rc != SSH_OK) { + goto error; + } + } + tok = endp + 1; + /* If there is second @ character, this does not look like our URI */ + endp = strchr(tok, '@'); + if (endp != NULL) { + goto error; + } + } + + /* Hostname */ + if (*tok == '[') { + /* IPv6 address is enclosed with square brackets */ + tok++; + endp = strchr(tok, ']'); + if (endp == NULL) { + goto error; + } + } else if (!ignore_port) { + /* Hostnames or aliases expand to the last colon (if port is requested) + * or to the end */ + endp = strrchr(tok, ':'); + if (endp == NULL) { + endp = strchr(tok, '\0'); + } + } else { + /* If no port is requested, expand to the end of line + * (to accommodate the IPv6 addresses) */ + endp = strchr(tok, '\0'); + } + if (tok == endp) { + /* Zero-length hostnames are not valid */ + goto error; + } + if (hostname != NULL) { + *hostname = strndup(tok, endp - tok); + if (*hostname == NULL) { + goto error; + } + /* if not an ip, check syntax */ + rc = ssh_is_ipaddr(*hostname); + if (rc == 0) { + rc = ssh_check_hostname_syntax(*hostname); + if (rc != SSH_OK) { + goto error; + } + } + } + /* Skip also the closing bracket */ + if (*endp == ']') { + endp++; + } + + /* Port (optional) */ + if (*endp != '\0') { + char *port_end = NULL; + + /* Verify the port is valid positive number */ + port_n = strtol(endp + 1, &port_end, 10); + if (port_n < 1 || *port_end != '\0') { + SSH_LOG(SSH_LOG_TRACE, "Failed to parse port number." + " The value '%ld' is invalid or there are some" + " trailing characters: '%s'", port_n, port_end); + goto error; + } + if (port != NULL) { + *port = strdup(endp + 1); + if (*port == NULL) { + goto error; + } + } + } + + return SSH_OK; + +error: + if (username != NULL) { + SAFE_FREE(*username); + } + if (hostname != NULL) { + SAFE_FREE(*hostname); + } + if (port != NULL) { + SAFE_FREE(*port); + } + return SSH_ERROR; +} diff --git a/src/libs/libssh-0.12.2/src/connect.c b/src/libs/libssh-0.12.2/src/connect.c new file mode 100644 index 000000000000..0758e468e378 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/connect.c @@ -0,0 +1,472 @@ +/* + * connect.c - handles connections to ssh servers + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ + +#include "libssh/libssh.h" +#include "libssh/misc.h" + +#ifdef _WIN32 +/* + * Only use Windows API functions available on Windows 2000 SP4 or later. + * The available constants are in . + * http://msdn.microsoft.com/en-us/library/aa383745.aspx + * http://blogs.msdn.com/oldnewthing/archive/2007/04/11/2079137.aspx + */ +#undef _WIN32_WINNT +#ifdef HAVE_WSPIAPI_H +#define _WIN32_WINNT 0x0500 /* _WIN32_WINNT_WIN2K */ +#undef NTDDI_VERSION +#define NTDDI_VERSION 0x05000400 /* NTDDI_WIN2KSP4 */ +#else +#define _WIN32_WINNT 0x0501 /* _WIN32_WINNT_WINXP */ +#undef NTDDI_VERSION +#define NTDDI_VERSION 0x05010000 /* NTDDI_WINXP */ +#endif + +#include +#include + +/* is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. */ +#ifdef HAVE_WSPIAPI_H +#include +#endif + +#ifndef EINPROGRESS +#define EINPROGRESS WSAEINPROGRESS +#endif + +#else /* _WIN32 */ + +#include +#include +#include +#include +#include + +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/poll.h" + +#ifndef HAVE_GETADDRINFO +#error "Your system must have getaddrinfo()" +#endif + +#ifdef _WIN32 +#ifndef gai_strerror +char WSAAPI *gai_strerrorA(int code) +{ + static char buf[256]; + + snprintf(buf, sizeof(buf), "Undetermined error code (%d)", code); + + return buf; +} +#endif /* gai_strerror */ +#endif /* _WIN32 */ + +static int ssh_connect_socket_close(socket_t s) +{ +#ifdef _WIN32 + return closesocket(s); +#else + return close(s); +#endif +} + +static int +getai(const char *host, int port, int ai_family, struct addrinfo **ai) +{ + const char *service = NULL; + struct addrinfo hints; + char s_port[10]; + + ZERO_STRUCT(hints); + + hints.ai_protocol = IPPROTO_TCP; + hints.ai_family = ai_family; + hints.ai_socktype = SOCK_STREAM; + + if (port == 0) { + hints.ai_flags = AI_PASSIVE; + } else { + snprintf(s_port, sizeof(s_port), "%hu", (unsigned short)port); + service = s_port; +#ifdef AI_NUMERICSERV + hints.ai_flags = AI_NUMERICSERV; +#endif + } + + if (ssh_is_ipaddr(host) == 1) { + /* this is an IP address */ + SSH_LOG(SSH_LOG_PACKET, "host %s matches an IP address", host); + hints.ai_flags |= AI_NUMERICHOST; + } + + return getaddrinfo(host, service, &hints, ai); +} + +static int set_tcp_nodelay(socket_t socket) +{ + int opt = 1; + + return setsockopt(socket, + IPPROTO_TCP, + TCP_NODELAY, + (void *)&opt, + sizeof(opt)); +} + +/** + * @internal + * + * @brief Launches a nonblocking connect to an IPv4 or IPv6 host + * specified by its IP address or hostname. + * + * @returns A file descriptor, < 0 on error. + * @warning very ugly !!! + */ +socket_t ssh_connect_host_nonblocking(ssh_session session, const char *host, + const char *bind_addr, int port) +{ + socket_t s = -1, first = -1; + int rc; + int ai_family; + static const char *ai_family_str = NULL; + struct addrinfo *ai = NULL; + struct addrinfo *itr = NULL; + char addrname[NI_MAXHOST], portname[NI_MAXSERV]; + + switch (session->opts.address_family) { + case SSH_ADDRESS_FAMILY_INET: + ai_family = PF_INET; + ai_family_str = "inet"; + break; + case SSH_ADDRESS_FAMILY_INET6: + ai_family = PF_INET6; + ai_family_str = "inet6"; + break; + case SSH_ADDRESS_FAMILY_ANY: + default: + ai_family = PF_UNSPEC; + ai_family_str = "any"; + } + SSH_LOG(SSH_LOG_PACKET, + "Resolve target hostname %s port %d (%s)", + host, + port, + ai_family_str); + rc = getai(host, port, ai_family, &ai); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Failed to resolve hostname %s (%s): %s", + host, + ai_family_str, + gai_strerror(rc)); + + return -1; + } + + for (itr = ai; itr != NULL; itr = itr->ai_next) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + /* create socket */ + s = socket(itr->ai_family, itr->ai_socktype, itr->ai_protocol); + if (s < 0) { + ssh_set_error(session, SSH_FATAL, + "Socket create failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + continue; + } + + if (bind_addr) { + struct addrinfo *bind_ai = NULL; + struct addrinfo *bind_itr = NULL; + + SSH_LOG(SSH_LOG_PACKET, + "Resolving bind address %s (%s)", + bind_addr, + ai_family_str); + + rc = getai(bind_addr, 0, ai_family, &bind_ai); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Failed to resolve bind address %s (%s): %s", + bind_addr, + ai_family_str, + gai_strerror(rc)); + ssh_connect_socket_close(s); + s = -1; + break; + } + + for (bind_itr = bind_ai; + bind_itr != NULL; + bind_itr = bind_itr->ai_next) + { + rc = bind(s, bind_itr->ai_addr, bind_itr->ai_addrlen); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Binding local address: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + continue; + } else { + break; + } + } + freeaddrinfo(bind_ai); + + /* Cannot bind to any local addresses */ + if (bind_itr == NULL) { + ssh_connect_socket_close(s); + s = -1; + continue; + } + } + + rc = ssh_socket_set_nonblocking(s); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to set socket non-blocking for %s:%d", + host, port); + ssh_connect_socket_close(s); + s = -1; + continue; + } + + if (session->opts.nodelay) { + /* For winsock, socket options are only effective before connect */ + rc = set_tcp_nodelay(s); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Failed to set TCP_NODELAY on socket: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + ssh_connect_socket_close(s); + s = -1; + continue; + } + } + + rc = getnameinfo(itr->ai_addr, + itr->ai_addrlen, + addrname, + sizeof(addrname), + portname, + sizeof(portname), + NI_NUMERICHOST | NI_NUMERICSERV); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, + "getnameinfo failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + ssh_connect_socket_close(s); + s = -1; + continue; + } + + errno = 0; + SSH_LOG(SSH_LOG_PACKET, + "Connecting to host %s [%s] port %s", + host, + addrname, + portname); + rc = connect(s, itr->ai_addr, itr->ai_addrlen); + if (rc == -1) { + if ((errno != 0) && (errno != EINPROGRESS)) { + ssh_set_error(session, SSH_FATAL, + "Failed to connect: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + ssh_connect_socket_close(s); + s = -1; + } else { + if (first == -1) { + SSH_LOG(SSH_LOG_PACKET, "EINPROGRESS => Store for later."); + first = s; + } else { /* errno == EINPROGRESS */ + /* save only the first "working" socket */ + ssh_connect_socket_close(s); + s = -1; + } + } + continue; + } + + break; + } + + freeaddrinfo(ai); + + /* first let's go through all the addresses looking for immediate + * connection, otherwise return the first address without error or error */ + if (s == -1) { + s = first; + } else if (s != first && first != -1) { + /* Clean up the saved socket if any */ + ssh_connect_socket_close(first); + } + + return s; +} + +/** + * @addtogroup libssh_session + * + * @{ + */ + +static int ssh_select_cb (socket_t fd, int revents, void *userdata) +{ + fd_set *set = (fd_set *)userdata; + if (revents & POLLIN) { + FD_SET(fd, set); + } + return 0; +} + +/** + * @brief A wrapper for the select syscall + * + * This function acts more or less like the select(2) syscall.\n + * There is no support for writing or exceptions.\n + * + * @param[in] channels Arrays of channels pointers terminated by a NULL. + * It is never rewritten. + * + * @param[out] outchannels Arrays of the same size as "channels", there is no + * need to initialize it. + * + * @param[in] maxfd Maximum +1 file descriptor from readfds. + * + * @param[in] readfds A fd_set of file descriptors to be select'ed for + * reading. + * + * @param[in] timeout The timeout in milliseconds. + * + * @return SSH_OK on success, + * SSH_ERROR on error, + * SSH_EINTR if it was interrupted. In that case, + * just restart it. + * + * @warning libssh is not reentrant here. That means that if a signal is caught + * during the processing of this function, you cannot call libssh + * functions on sessions that are busy with ssh_select(). + * + * @see select(2) + */ +int ssh_select(ssh_channel *channels, ssh_channel *outchannels, socket_t maxfd, + fd_set *readfds, struct timeval *timeout) +{ + fd_set origfds; + socket_t fd; + size_t i, j; + int rc; + int base_tm, tm; + struct ssh_timestamp ts; + ssh_event event = ssh_event_new(); + int firstround = 1; + + base_tm = tm = (timeout->tv_sec * 1000) + (timeout->tv_usec / 1000); + for (i = 0 ; channels[i] != NULL; ++i) { + ssh_event_add_session(event, channels[i]->session); + } + + ZERO_STRUCT(origfds); + FD_ZERO(&origfds); + for (fd = 0; fd < maxfd ; fd++) { + if (FD_ISSET(fd, readfds)) { + ssh_event_add_fd(event, fd, POLLIN, ssh_select_cb, readfds); + FD_SET(fd, &origfds); + } + } + outchannels[0] = NULL; + FD_ZERO(readfds); + ssh_timestamp_init(&ts); + do { + /* Poll every channel */ + j = 0; + for (i = 0; channels[i]; i++) { + rc = ssh_channel_poll(channels[i], 0); + if (rc != 0) { + outchannels[j] = channels[i]; + j++; + } else { + rc = ssh_channel_poll(channels[i], 1); + if (rc != 0) { + outchannels[j] = channels[i]; + j++; + } + } + } + + outchannels[j] = NULL; + if (j != 0) { + break; + } + + /* watch if a user socket was triggered */ + for (fd = 0; fd < maxfd; fd++) { + if (FD_ISSET(fd, readfds)) { + goto out; + } + } + + /* If the timeout is elapsed, we should go out */ + if (!firstround && ssh_timeout_elapsed(&ts, base_tm)) { + goto out; + } + + /* since there's nothing, let's fire the polling */ + rc = ssh_event_dopoll(event,tm); + if (rc == SSH_ERROR) { + goto out; + } + + tm = ssh_timeout_update(&ts, base_tm); + firstround = 0; + } while (1); +out: + for (fd = 0; fd < maxfd; fd++) { + if (FD_ISSET(fd, &origfds)) { + ssh_event_remove_fd(event, fd); + } + } + ssh_event_free(event); + return SSH_OK; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/connector.c b/src/libs/libssh-0.12.2/src/connector.c new file mode 100644 index 000000000000..2cdfea475508 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/connector.c @@ -0,0 +1,919 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2015 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/poll.h" +#include "libssh/callbacks.h" +#include "libssh/session.h" +#include +#include +#include +#include + +#ifndef CHUNKSIZE +#define CHUNKSIZE 4096 +#endif + +#ifndef _WIN32 +# include +# include +#endif + +struct ssh_connector_struct { + ssh_session session; + + ssh_channel in_channel; + ssh_channel out_channel; + + socket_t in_fd; + socket_t out_fd; + + bool fd_is_socket; + + ssh_poll_handle in_poll; + ssh_poll_handle out_poll; + + ssh_event event; + + int in_available; + int out_wontblock; + + struct ssh_channel_callbacks_struct in_channel_cb; + struct ssh_channel_callbacks_struct out_channel_cb; + + enum ssh_connector_flags_e in_flags; + enum ssh_connector_flags_e out_flags; +}; + +static int ssh_connector_channel_data_cb(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata); +static int ssh_connector_channel_write_wontblock_cb(ssh_session session, + ssh_channel channel, + uint32_t bytes, + void *userdata); +static ssize_t ssh_connector_fd_read(ssh_connector connector, + void *buffer, + uint32_t len); +static ssize_t ssh_connector_fd_write(ssh_connector connector, + const void *buffer, + uint32_t len); +static bool ssh_connector_fd_is_socket(socket_t socket); + +/** + * @brief Create a new SSH connector. + * + * Allocates and initializes a new connector object for moving data between + * an SSH session and file descriptors. The connector is created with invalid + * file descriptors and callback structures initialized, but not yet attached + * to any channels or sockets. + * + * @param[in] session The SSH session to associate with the connector. + * + * @return A newly allocated connector on success, or NULL if an + * error occurred. On error, an out-of-memory error is + * set on the session. + */ +ssh_connector ssh_connector_new(ssh_session session) +{ + ssh_connector connector; + + connector = calloc(1, sizeof(struct ssh_connector_struct)); + if (connector == NULL){ + ssh_set_error_oom(session); + return NULL; + } + + connector->session = session; + connector->in_fd = SSH_INVALID_SOCKET; + connector->out_fd = SSH_INVALID_SOCKET; + + connector->fd_is_socket = false; + + ssh_callbacks_init(&connector->in_channel_cb); + ssh_callbacks_init(&connector->out_channel_cb); + + connector->in_channel_cb.userdata = connector; + connector->in_channel_cb.channel_data_function = ssh_connector_channel_data_cb; + + connector->out_channel_cb.userdata = connector; + connector->out_channel_cb.channel_write_wontblock_function = + ssh_connector_channel_write_wontblock_cb; + + return connector; +} + +/** + * @brief Free an SSH connector. + * + * Cleans up and deallocates a connector created by ssh_connector_new(). + * Any channel callbacks and poll objects associated with the @p connector + * are removed and freed before the connector structure itself is released. + * + * @param[in] connector The connector to free. + */ +void ssh_connector_free (ssh_connector connector) +{ + if (connector == NULL) { + return; + } + if (connector->in_channel != NULL) { + ssh_remove_channel_callbacks(connector->in_channel, + &connector->in_channel_cb); + } + if (connector->out_channel != NULL) { + ssh_remove_channel_callbacks(connector->out_channel, + &connector->out_channel_cb); + } + + if (connector->event != NULL){ + ssh_connector_remove_event(connector); + } + + if (connector->in_poll != NULL) { + ssh_poll_free(connector->in_poll); + connector->in_poll = NULL; + } + + if (connector->out_poll != NULL) { + ssh_poll_free(connector->out_poll); + connector->out_poll = NULL; + } + + free(connector); +} + +/** + * @brief Set the input channel for a connector. + * + * Associates an SSH channel with the @p connector as its input source and + * installs the internal channel callbacks used for reading data. Any + * configured input file descriptor is disabled and the connector will + * receive data from the given channel only. + * + * If neither `SSH_CONNECTOR_STDOUT` nor `SSH_CONNECTOR_STDERR` is specified + * in @p flags, `SSH_CONNECTOR_STDOUT` is used as the default. + * + * @param[in] connector The connector to configure. + * @param[in] channel The SSH channel to use as input. + * @param[in] flags A combination of ssh_connector_flags_e values + * selecting which channel streams to read from. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure. + */ +int ssh_connector_set_in_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags) +{ + connector->in_channel = channel; + connector->in_fd = SSH_INVALID_SOCKET; + connector->in_flags = flags; + + /* Fallback to default value for invalid flags */ + if (!(flags & SSH_CONNECTOR_STDOUT) && !(flags & SSH_CONNECTOR_STDERR)) { + connector->in_flags = SSH_CONNECTOR_STDOUT; + } + + return ssh_add_channel_callbacks(channel, &connector->in_channel_cb); +} + +/** + * @brief Set the output channel for a connector. + * + * Associates an SSH channel with the @p connector as its output target and + * installs the internal channel callbacks used for writing data. Any + * configured output file descriptor is disabled and the connector will + * send data to the given channel only. + * + * If neither `SSH_CONNECTOR_STDOUT` nor `SSH_CONNECTOR_STDERR` is specified + * in @p flags, `SSH_CONNECTOR_STDOUT` is used as the default. + * + * @param[in] connector The connector to configure. + * @param[in] channel The SSH channel to use as output. + * @param[in] flags A combination of ssh_connector_flags_e values + * selecting which channel streams to write to. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure. + */ +int ssh_connector_set_out_channel(ssh_connector connector, + ssh_channel channel, + enum ssh_connector_flags_e flags) +{ + connector->out_channel = channel; + connector->out_fd = SSH_INVALID_SOCKET; + connector->out_flags = flags; + + /* Fallback to default value for invalid flags */ + if (!(flags & SSH_CONNECTOR_STDOUT) && !(flags & SSH_CONNECTOR_STDERR)) { + connector->out_flags = SSH_CONNECTOR_STDOUT; + } + + return ssh_add_channel_callbacks(channel, &connector->out_channel_cb); +} + +/** + * @brief Set the connector's input file descriptor. + * + * Sets the @p fd (file descriptor) to be used as the input source for the + * @p connector , replacing any previously configured input channel. + * + * @param[in] connector The connector to configure. + * @param[in] fd The file descriptor (socket or regular). + */ +void ssh_connector_set_in_fd(ssh_connector connector, socket_t fd) +{ + connector->in_fd = fd; + connector->fd_is_socket = ssh_connector_fd_is_socket(fd); + connector->in_channel = NULL; +} + +/** + * @brief Set the connector's output file descriptor. + * + * Sets the @p fd (file descriptor) to be used as the output target for the + * @p connector , replacing any previously configured output channel. + * + * @param[in] connector The connector to configure. + * @param[in] fd The file descriptor (socket or regular). + */ +void ssh_connector_set_out_fd(ssh_connector connector, socket_t fd) +{ + connector->out_fd = fd; + connector->fd_is_socket = ssh_connector_fd_is_socket(fd); + connector->out_channel = NULL; +} + +/* TODO */ +static void ssh_connector_except(ssh_connector connector, socket_t fd) +{ + (void) connector; + (void) fd; +} + +/* TODO */ +static void ssh_connector_except_channel(ssh_connector connector, + ssh_channel channel) +{ + (void) connector; + (void) channel; +} + +/** + * @internal + * + * @brief Reset the poll events to be followed for each file descriptor. + */ +static void ssh_connector_reset_pollevents(ssh_connector connector) +{ + if (connector->in_fd != SSH_INVALID_SOCKET) { + if (connector->in_available) { + ssh_poll_remove_events(connector->in_poll, POLLIN); + } else { + ssh_poll_add_events(connector->in_poll, POLLIN); + } + } + + if (connector->out_fd != SSH_INVALID_SOCKET) { + if (connector->out_wontblock) { + ssh_poll_remove_events(connector->out_poll, POLLOUT); + } else { + ssh_poll_add_events(connector->out_poll, POLLOUT); + } + } +} + +/** + * @internal + * + * @brief Update the connector's flags after a read-write io + * operation + * + * This should be called after some data is successfully read from + * connector's input and written to connector's output. + * + * @param[in, out] connector Connector for which the io operation occurred. + * + * @warning This does not consider the case when the io indicated failure + * + * @warning This does not consider the case when the input indicated that + * EOF was encountered. + */ +static void ssh_connector_update_flags_after_io(ssh_connector connector) +{ + /* + * With fds we can afford to mark: + * - in_available as 0 after an fd read (even if more pending data can be + * immediately read from the fd) + * + * - out_wontblock as 0 after an fd write (even if more data can + * be written to the fd without blocking) + * + * since poll events set on the fd will get raised to indicate + * possibility of read/write in case existing situation is apt + * (i.e can read/write occur right now) or if situation becomes + * apt in future (read data becomes available, write becomes + * possible) + */ + + /* + * On the other hand, with channels we need to be more careful + * before claiming read/write not possible because channel callbacks + * are called in limited scenarios. + * + * (e.g. connector callback to indicate read data available on input + * channel is called only when new data is received on channel. It is + * not called when we have some pending data in channel's buffers but + * don't receive any new data on the channel) + * + * Hence, in case of channels, blindly setting flag associated with + * read/write input/output to 0 after a read/write may not be a good + * idea as the callback that sets it back to 1 again may not be ever + * called again. + */ + + uint32_t window_size; + + /* update in_available based on input source (fd or channel) */ + if (connector->in_fd != SSH_INVALID_SOCKET) { + connector->in_available = 0; + } else if (connector->in_channel != NULL) { + if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0) { + connector->in_available = 1; + } else { + connector->in_available = 0; + } + } else { + /* connector input is invalid ! */ + return; + } + + /* update out_wontblock based on output source (fd or channel) */ + if (connector->out_fd != SSH_INVALID_SOCKET) { + connector->out_wontblock = 0; + } else if (connector->out_channel != NULL) { + window_size = ssh_channel_window_size(connector->out_channel); + if (window_size > 0) { + connector->out_wontblock = 1; + } else { + connector->out_wontblock = 0; + } + } else { + /* connector output is invalid ! */ + return; + } +} + +/** + * @internal + * + * @brief Callback called when a poll event is received on an input fd. + */ +static void ssh_connector_fd_in_cb(ssh_connector connector) +{ + unsigned char buffer[CHUNKSIZE]; + uint32_t toread = CHUNKSIZE; + ssize_t r; + ssize_t w; + ssize_t total = 0; + int rc; + + SSH_LOG(SSH_LOG_TRACE, "connector POLLIN event for fd %d", connector->in_fd); + + if (connector->out_wontblock) { + if (connector->out_channel != NULL) { + uint32_t size = ssh_channel_window_size(connector->out_channel); + + /* Don't attempt reading more than the window */ + toread = MIN(size, CHUNKSIZE); + } + + r = ssh_connector_fd_read(connector, buffer, toread); + /* Sanity: Make sure we do not get too large return value to make static + * analysis tools happy */ + if (r < 0 || r > (ssize_t)toread) { + ssh_connector_except(connector, connector->in_fd); + return; + } + + if (connector->out_channel != NULL) { + if (r == 0) { + SSH_LOG(SSH_LOG_TRACE, "input fd %d is EOF", connector->in_fd); + if (connector->out_channel->local_eof == 0) { + rc = ssh_channel_send_eof(connector->out_channel); + (void)rc; /* TODO Handle rc? */ + } + connector->in_available = 1; /* Don't poll on it */ + return; + } else if (r > 0) { + /* loop around ssh_channel_write in case our window reduced due to a race */ + while (total != r){ + if (connector->out_flags & SSH_CONNECTOR_STDOUT) { + w = ssh_channel_write(connector->out_channel, + buffer + total, + (uint32_t)(r - total)); + } else { + w = ssh_channel_write_stderr(connector->out_channel, + buffer + total, + (uint32_t)(r - total)); + } + if (w == SSH_ERROR) { + return; + } + total += w; + } + } + } else if (connector->out_fd != SSH_INVALID_SOCKET) { + if (r == 0){ + close(connector->out_fd); + connector->out_fd = SSH_INVALID_SOCKET; + } else { + /* + * Loop around write in case the write blocks even for CHUNKSIZE + * bytes + */ + while (total < r) { + w = ssh_connector_fd_write(connector, + buffer + total, + (uint32_t)(r - total)); + /* Sanity: Make sure we do not get too large return value + * to make static analysis tools happy */ + if (w < 0 || w > (r - total)) { + ssh_connector_except(connector, connector->out_fd); + return; + } + total += w; + } + } + } else { + ssh_set_error(connector->session, SSH_FATAL, "output socket or channel closed"); + return; + } + + ssh_connector_update_flags_after_io(connector); + } else { + connector->in_available = 1; + } +} + +/** @internal + * @brief Callback called when a poll event is received on an output fd + */ +static void +ssh_connector_fd_out_cb(ssh_connector connector) +{ + unsigned char buffer[CHUNKSIZE]; + ssize_t r; + ssize_t w; + ssize_t total = 0; + SSH_LOG(SSH_LOG_TRACE, "connector POLLOUT event for fd %d", + connector->out_fd); + + if (connector->in_available) { + if (connector->in_channel != NULL) { + r = ssh_channel_read_nonblocking(connector->in_channel, buffer, + CHUNKSIZE, 0); + if (r == SSH_ERROR) { + ssh_connector_except_channel(connector, connector->in_channel); + return; + } else if (r == 0 && ssh_channel_is_eof(connector->in_channel)) { + close(connector->out_fd); + connector->out_fd = SSH_INVALID_SOCKET; + return; + } else if (r > 0) { + /* loop around write in case the write blocks even for CHUNKSIZE bytes */ + while (total != r) { + w = ssh_connector_fd_write(connector, + buffer + total, + (uint32_t)(r - total)); + if (w < 0) { + ssh_connector_except(connector, connector->out_fd); + return; + } + total += w; + } + } + } else if (connector->in_fd != SSH_INVALID_SOCKET) { + /* fallback on the socket input callback */ + connector->out_wontblock = 1; + ssh_connector_fd_in_cb(connector); + } else { + ssh_set_error(connector->session, + SSH_FATAL, + "Output socket or channel closed"); + return; + } + + ssh_connector_update_flags_after_io(connector); + } else { + connector->out_wontblock = 1; + } +} + +/** + * @internal + * + * @brief Callback called when a poll event is received on a file descriptor. + * + * This is for input or output. + * + * @param[in] fd file descriptor receiving the event + * + * @param[in] revents received Poll(2) events + * + * @param[in] userdata connector + * + * @returns 0 + */ +static int ssh_connector_fd_cb(UNUSED_PARAM(ssh_poll_handle p), + socket_t fd, + int revents, + void *userdata) +{ + ssh_connector connector = userdata; + + if (revents & POLLERR) { + ssh_connector_except(connector, fd); + } else if((revents & (POLLIN|POLLHUP)) && fd == connector->in_fd) { + ssh_connector_fd_in_cb(connector); + } else if(((revents & POLLOUT) || (revents & POLLHUP)) && + fd == connector->out_fd) { + ssh_connector_fd_out_cb(connector); + } + ssh_connector_reset_pollevents(connector); + + return 0; +} + +/** + * @internal + * + * @brief Callback called when data is received on channel. + * + * @param[in] session The SSH session + * + * @param[in] channel The channel data came from + * + * @param[in] data Pointer to the data + * + * @param[in] len Length of data + * + * @param[in] is_stderr Set to 1 if the data are out of band + * + * @param[in] userdata The ssh connector + * + * @returns Amount of data bytes consumed + */ +static int ssh_connector_channel_data_cb(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + ssh_connector connector = userdata; + int w; + uint32_t window; + + SSH_LOG(SSH_LOG_TRACE, + "Received data (%" PRIu32 ") on channel (%" PRIu32 ":%" PRIu32 ")", + len, + channel->local_channel, + channel->remote_channel); + + if (is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDERR)) { + /* ignore stderr */ + return 0; + } else if (!is_stderr && !(connector->in_flags & SSH_CONNECTOR_STDOUT)) { + /* ignore stdout */ + return 0; + } else if (len == 0) { + /* ignore empty data */ + return 0; + } + + if (connector->out_wontblock) { + SSH_LOG(SSH_LOG_TRACE, "Writing won't block"); + if (connector->out_channel != NULL) { + uint32_t window_len; + + window = ssh_channel_window_size(connector->out_channel); + window_len = MIN(window, len); + + /* Route the data to the right exception channel */ + if (connector->out_flags & SSH_CONNECTOR_STDOUT && + !(is_stderr && (connector->out_flags & SSH_CONNECTOR_STDERR))) { + w = ssh_channel_write(connector->out_channel, + data, + window_len); + } else { + w = ssh_channel_write_stderr(connector->out_channel, + data, + window_len); + } + if (w == SSH_ERROR) { + ssh_connector_except_channel(connector, connector->out_channel); + } + } else if (connector->out_fd != SSH_INVALID_SOCKET) { + ssize_t ws = ssh_connector_fd_write(connector, data, len); + if (ws < 0) { + ssh_connector_except(connector, connector->out_fd); + } + w = (int)ws; + } else { + ssh_set_error(session, SSH_FATAL, "output socket or channel closed"); + return SSH_ERROR; + } + + ssh_connector_update_flags_after_io(connector); + ssh_connector_reset_pollevents(connector); + + return w; + } else { + SSH_LOG(SSH_LOG_TRACE, "Writing would block: wait?"); + connector->in_available = 1; + + return 0; + } +} + +/** + * @internal + * + * @brief Callback called when the channel is free to write. + * + * @param[in] bytes Amount of bytes that can be written without blocking + * + * @param[in] userdata The ssh connector + * + * @returns Amount of data bytes consumed + */ +static int +ssh_connector_channel_write_wontblock_cb(ssh_session session, + UNUSED_PARAM(ssh_channel channel), + uint32_t bytes, + void *userdata) +{ + ssh_connector connector = userdata; + uint8_t buffer[CHUNKSIZE]; + int r, w; + + (void) channel; + + SSH_LOG(SSH_LOG_TRACE, + "Write won't block (%" PRIu32 ") on channel (%" PRIu32 ":%" PRIu32 ")", + bytes, + channel->local_channel, + channel->remote_channel); + + if (connector->in_available) { + if (connector->in_channel != NULL) { + uint32_t len = MIN(CHUNKSIZE, bytes); + + r = ssh_channel_read_nonblocking(connector->in_channel, + buffer, + len, + 0); + if (r == SSH_ERROR) { + ssh_connector_except_channel(connector, connector->in_channel); + } else if (r == 0 && ssh_channel_is_eof(connector->in_channel)) { + ssh_channel_send_eof(connector->out_channel); + } else if (r > 0) { + w = ssh_channel_write(connector->out_channel, buffer, r); + if (w == SSH_ERROR) { + ssh_connector_except_channel(connector, + connector->out_channel); + } + } + } else if (connector->in_fd != SSH_INVALID_SOCKET) { + /* fallback on on the socket input callback */ + connector->out_wontblock = 1; + ssh_connector_fd_in_cb(connector); + ssh_connector_reset_pollevents(connector); + } else { + ssh_set_error(session, + SSH_FATAL, + "Output socket or channel closed"); + + return 0; + } + + ssh_connector_update_flags_after_io(connector); + } else { + connector->out_wontblock = 1; + } + + return 0; +} + +int ssh_connector_set_event(ssh_connector connector, ssh_event event) +{ + int rc = SSH_OK; + + if ((connector->in_fd == SSH_INVALID_SOCKET && + connector->in_channel == NULL) + || (connector->out_fd == SSH_INVALID_SOCKET && + connector->out_channel == NULL)) { + rc = SSH_ERROR; + ssh_set_error(connector->session,SSH_FATAL,"Connector not complete"); + goto error; + } + + connector->event = event; + if (connector->in_fd != SSH_INVALID_SOCKET) { + if (connector->in_poll == NULL) { + connector->in_poll = ssh_poll_new(connector->in_fd, + POLLIN|POLLERR, + ssh_connector_fd_cb, + connector); + } + rc = ssh_event_add_poll(event, connector->in_poll); + if (rc != SSH_OK) { + goto error; + } + } + + if (connector->out_fd != SSH_INVALID_SOCKET) { + if (connector->out_poll == NULL) { + connector->out_poll = ssh_poll_new(connector->out_fd, + POLLOUT|POLLERR, + ssh_connector_fd_cb, + connector); + } + + rc = ssh_event_add_poll(event, connector->out_poll); + if (rc != SSH_OK) { + goto error; + } + } + if (connector->in_channel != NULL) { + ssh_session session = ssh_channel_get_session(connector->in_channel); + rc = ssh_event_add_session(event, session); + if (rc != SSH_OK) + goto error; + if (ssh_channel_poll_timeout(connector->in_channel, 0, 0) > 0){ + connector->in_available = 1; + } + } + if (connector->out_channel != NULL) { + ssh_session session = ssh_channel_get_session(connector->out_channel); + + rc = ssh_event_add_session(event, session); + if (rc != SSH_OK) { + goto error; + } + if (ssh_channel_window_size(connector->out_channel) > 0) { + connector->out_wontblock = 1; + } + } + +error: + return rc; +} + +int ssh_connector_remove_event(ssh_connector connector) +{ + ssh_session session = NULL; + + if (connector->in_poll != NULL) { + ssh_event_remove_poll(connector->event, connector->in_poll); + ssh_poll_free(connector->in_poll); + connector->in_poll = NULL; + } + + if (connector->out_poll != NULL) { + ssh_event_remove_poll(connector->event, connector->out_poll); + ssh_poll_free(connector->out_poll); + connector->out_poll = NULL; + } + + if (connector->in_channel != NULL) { + session = ssh_channel_get_session(connector->in_channel); + + ssh_event_remove_session(connector->event, session); + } + + if (connector->out_channel != NULL) { + session = ssh_channel_get_session(connector->out_channel); + + ssh_event_remove_session(connector->event, session); + } + connector->event = NULL; + + return SSH_OK; +} + +/** + * @internal + * + * @brief Check the file descriptor to check if it is a Windows socket handle. + * + */ +static bool ssh_connector_fd_is_socket(socket_t s) +{ +#ifdef _WIN32 + struct sockaddr_storage ss; + int len = sizeof(struct sockaddr_storage); + int rc; + + rc = getsockname(s, (struct sockaddr *)&ss, &len); + if (rc == 0) { + return true; + } + + SSH_LOG(SSH_LOG_TRACE, + "Error %i in getsockname() for fd %d", + WSAGetLastError(), + s); + + return false; +#else + struct stat sb; + int rc; + + rc = fstat(s, &sb); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, + "error %i in fstat() for fd %d", + errno, + s); + return false; + } + + /* The descriptor is a socket */ + if (S_ISSOCK(sb.st_mode)) { + return true; + } + + return false; +#endif /* _WIN32 */ +} + +/** + * @internal + * + * @brief read len bytes from socket into buffer + * + */ +static ssize_t ssh_connector_fd_read(ssh_connector connector, + void *buffer, + uint32_t len) +{ + ssize_t nread = -1; + + if (connector->fd_is_socket) { + nread = recv(connector->in_fd,buffer, len, 0); + } else { + nread = read(connector->in_fd,buffer, len); + } + + return nread; +} + +/** + * @internal + * + * @brief brief writes len bytes from buffer to socket + * + */ +static ssize_t ssh_connector_fd_write(ssh_connector connector, + const void *buffer, + uint32_t len) +{ + ssize_t bwritten = -1; + int flags = 0; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + if (connector->fd_is_socket) { + bwritten = send(connector->out_fd,buffer, len, flags); + } else { + bwritten = write(connector->out_fd, buffer, len); + } + + return bwritten; +} diff --git a/src/libs/libssh-0.12.2/src/crypto_common.c b/src/libs/libssh-0.12.2/src/crypto_common.c new file mode 100644 index 000000000000..5dc883f63b01 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/crypto_common.c @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2020 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include "libssh/crypto.h" + +int secure_memcmp(const void *s1, const void *s2, size_t n) +{ + size_t i; + uint8_t status = 0; + const uint8_t *p1 = s1; + const uint8_t *p2 = s2; + + for (i = 0; i < n; i++) { + status |= (p1[i] ^ p2[i]); + } + + return (status != 0); +} diff --git a/src/libs/libssh-0.12.2/src/curve25519.c b/src/libs/libssh-0.12.2/src/curve25519.c new file mode 100644 index 000000000000..c68fbc794e12 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519.c @@ -0,0 +1,370 @@ +/* + * curve25519.c - Curve25519 ECDH functions for key exchange + * curve25519-sha256@libssh.org and curve25519-sha256 + * + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/curve25519.h" +#ifdef HAVE_CURVE25519 + +#include "libssh/bignum.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply); + +static ssh_packet_callback dh_client_callbacks[] = { + ssh_packet_client_curve25519_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_curve25519_client_callbacks = { + .start = SSH2_MSG_KEX_ECDH_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL, +}; + +int ssh_curve25519_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + int rc; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + session->next_crypto->server_kex.cookie, + 16); + ssh_log_hexdump("Session client cookie", + session->next_crypto->client_kex.cookie, + 16); +#endif + + rc = curve25519_do_create_k(session, k); + return rc; +} + +/** @internal + * @brief Starts curve25519-sha256@libssh.org / curve25519-sha256 key exchange + */ +int ssh_client_curve25519_init(ssh_session session) +{ + int rc; + + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_KEX_ECDH_INIT, + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_curve25519_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + rc = ssh_packet_send(session); + + return rc; +} + +void ssh_client_curve25519_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_curve25519_client_callbacks); +} + +int ssh_curve25519_build_k(ssh_session session) +{ + ssh_curve25519_pubkey k; + int rc; + + rc = ssh_curve25519_create_k(session, k); + if (rc != SSH_OK) { + return rc; + } + + bignum_bin2bn(k, + CURVE25519_PUBKEY_SIZE, + &session->next_crypto->shared_secret); + if (session->next_crypto->shared_secret == NULL) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); +#endif + + return SSH_OK; +} + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +static SSH_PACKET_CALLBACK(ssh_packet_client_curve25519_reply) +{ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_client_curve25519_remove_callbacks(session); + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "No public key in packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Failed to import next public key"); + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_S ECC point in packet"); + goto error; + } + if (ssh_string_len(q_s_string) != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server Curve25519 public key: %zu", + ssh_string_len(q_s_string)); + SSH_STRING_FREE(q_s_string); + goto error; + } + memcpy(session->next_crypto->curve25519_server_pubkey, + ssh_string_data(q_s_string), + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_s_string); + + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature = NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ssh_curve25519_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; + +error: + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_curve25519_init, +}; + +static struct ssh_packet_callbacks_struct ssh_curve25519_server_callbacks = { + .start = SSH2_MSG_KEX_ECDH_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL, +}; + +/** @internal + * @brief sets up the curve25519-sha256@libssh.org kex callbacks + */ +void ssh_server_curve25519_init(ssh_session session) +{ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_curve25519_server_callbacks); +} + +/** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +static SSH_PACKET_CALLBACK(ssh_packet_server_curve25519_init) +{ + /* ECDH keys */ + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; + ssh_string server_pubkey_blob = NULL; + + /* SSH host keys (rsa, ed25519 and ecdsa) */ + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_curve25519_server_callbacks); + + /* Extract the client pubkey from the init packet */ + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); + goto error; + } + if (ssh_string_len(q_c_string) != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server Curve25519 public key: %zu", + ssh_string_len(q_c_string)); + goto error; + } + + memcpy(session->next_crypto->curve25519_client_pubkey, + ssh_string_data(q_c_string), + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_c_string); + + /* Build server's key pair */ + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate curve25519 keys"); + goto error; + } + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_REPLY); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* build k and session_id */ + rc = ssh_curve25519_build_k(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + goto error; + } + + /* add host's public key */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob); + SSH_STRING_FREE(server_pubkey_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* add ecdh public key */ + q_s_string = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (q_s_string == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_string_fill(q_s_string, + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Could not copy public key"); + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, q_s_string); + SSH_STRING_FREE(q_s_string); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + /* add signature blob */ + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_ECDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + return SSH_PACKET_USED; +error: + SSH_STRING_FREE(q_c_string); + SSH_STRING_FREE(q_s_string); + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_CURVE25519 */ diff --git a/src/libs/libssh-0.12.2/src/curve25519_crypto.c b/src/libs/libssh-0.12.2/src/curve25519_crypto.c new file mode 100644 index 000000000000..3314f70ed9dd --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519_crypto.c @@ -0,0 +1,164 @@ +/* + * curve25519_crypto.c - Curve25519 ECDH functions for key exchange (OpenSSL) + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + size_t pubkey_len = CURVE25519_PUBKEY_SIZE; + int rc; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_X25519, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 context: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen_init(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen(pctx, &pkey); + EVP_PKEY_CTX_free(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate X25519 keys: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + rc = EVP_PKEY_get_raw_public_key(pkey, *pubkey_loc, &pubkey_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get X25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_free(pkey); + return SSH_ERROR; + } + + /* Free any previously allocated privkey */ + if (session->next_crypto->curve25519_privkey != NULL) { + EVP_PKEY_free(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + } + + session->next_crypto->curve25519_privkey = pkey; + pkey = NULL; + + return SSH_OK; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + int rc, ret = SSH_ERROR; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL, *pubkey = NULL; + size_t shared_key_len = CURVE25519_PUBKEY_SIZE; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + pkey = session->next_crypto->curve25519_privkey; + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create X25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + pctx = EVP_PKEY_CTX_new(pkey, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive_init(pctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize X25519 key derivation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + pubkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_X25519, + NULL, + *peer_pubkey_loc, + CURVE25519_PUBKEY_SIZE); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create X25519 public key EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive_set_peer(pctx, pubkey); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set peer X25519 public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_PKEY_derive(pctx, k, &shared_key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to derive X25519 shared secret: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + ret = SSH_OK; + +out: + EVP_PKEY_free(pubkey); + EVP_PKEY_CTX_free(pctx); + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/curve25519_fallback.c b/src/libs/libssh-0.12.2/src/curve25519_fallback.c new file mode 100644 index 000000000000..e05331d596ef --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519_fallback.c @@ -0,0 +1,73 @@ +/* + * curve25519_fallback.c - Curve25519 ECDH functions for key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#ifdef WITH_NACL +#include "nacl/crypto_scalarmult_curve25519.h" +#endif + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + int rc; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + rc = ssh_get_random(session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE, + 1); + if (rc != 1) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return SSH_ERROR; + } + + crypto_scalarmult_base(*pubkey_loc, + session->next_crypto->curve25519_privkey); + + return SSH_OK; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + crypto_scalarmult(k, + session->next_crypto->curve25519_privkey, + *peer_pubkey_loc); + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/curve25519_gcrypt.c b/src/libs/libssh-0.12.2/src/curve25519_gcrypt.c new file mode 100644 index 000000000000..cd522a1d7da9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519_gcrypt.c @@ -0,0 +1,205 @@ +/* + * curve25519_gcrypt.c - Curve25519 ECDH functions for key exchange (Gcrypt) + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + gcry_error_t gcry_err; + gcry_sexp_t param = NULL, keypair_sexp = NULL; + ssh_string pubkey = NULL; + const char *pubkey_data = NULL; + int ret = SSH_ERROR; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + gcry_err = + gcry_sexp_build(¶m, NULL, "(genkey (ecdh (curve Curve25519)))"); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create keypair sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + gcry_err = gcry_pk_genkey(&keypair_sexp, param); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate keypair: %s", + gcry_strerror(gcry_err)); + goto out; + } + + /* Extract the public key */ + pubkey = ssh_sexp_extract_mpi(keypair_sexp, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to extract public key: %s", + gcry_strerror(gcry_err)); + goto out; + } + + /* Store the public key in the session */ + /* The first byte should be 0x40 indicating that the point is compressed, so + * we skip storing it */ + pubkey_data = (char *)ssh_string_data(pubkey); + if (ssh_string_len(pubkey) != CURVE25519_PUBKEY_SIZE + 1 || + pubkey_data[0] != 0x40) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid public key with length: %zu", + ssh_string_len(pubkey)); + goto out; + } + + memcpy(*pubkey_loc, pubkey_data + 1, CURVE25519_PUBKEY_SIZE); + + /* Free any previously allocated privkey */ + if (session->next_crypto->curve25519_privkey != NULL) { + gcry_sexp_release(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + } + + /* Store the private key */ + session->next_crypto->curve25519_privkey = keypair_sexp; + keypair_sexp = NULL; + ret = SSH_OK; + +out: + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + gcry_sexp_release(param); + gcry_sexp_release(keypair_sexp); + return ret; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + gcry_error_t gcry_err; + gcry_sexp_t pubkey_sexp = NULL, privkey_data_sexp = NULL, + result_sexp = NULL; + ssh_string shared_secret = NULL, privkey = NULL; + char *shared_secret_data = NULL; + int ret = SSH_ERROR; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + gcry_err = gcry_sexp_build( + &pubkey_sexp, + NULL, + "(key-data(public-key (ecdh (curve Curve25519) (q %b))))", + CURVE25519_PUBKEY_SIZE, + *peer_pubkey_loc); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create peer public key sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + privkey = ssh_sexp_extract_mpi(session->next_crypto->curve25519_privkey, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (privkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to extract private key"); + goto out; + } + + gcry_err = gcry_sexp_build(&privkey_data_sexp, + NULL, + "(data(flags raw)(value %b))", + ssh_string_len(privkey), + ssh_string_data(privkey)); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create private key sexp: %s", + gcry_strerror(gcry_err)); + goto out; + } + + gcry_err = gcry_pk_encrypt(&result_sexp, privkey_data_sexp, pubkey_sexp); + if (gcry_err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to compute shared secret: %s", + gcry_strerror(gcry_err)); + goto out; + } + + shared_secret = ssh_sexp_extract_mpi(result_sexp, + "s", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_USG); + if (shared_secret == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to extract shared secret"); + goto out; + } + + /* Copy the shared secret to the output buffer */ + /* The first byte should be 0x40 indicating that it is a compressed point, + * so we skip it */ + shared_secret_data = (char *)ssh_string_data(shared_secret); + if (ssh_string_len(shared_secret) != CURVE25519_PUBKEY_SIZE + 1 || + shared_secret_data[0] != 0x40) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid shared secret with length: %zu", + ssh_string_len(shared_secret)); + goto out; + } + + memcpy(k, shared_secret_data + 1, CURVE25519_PUBKEY_SIZE); + + ret = SSH_OK; + gcry_sexp_release(session->next_crypto->curve25519_privkey); + session->next_crypto->curve25519_privkey = NULL; + +out: + ssh_string_burn(shared_secret); + SSH_STRING_FREE(shared_secret); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + gcry_sexp_release(privkey_data_sexp); + gcry_sexp_release(pubkey_sexp); + gcry_sexp_release(result_sexp); + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/curve25519_mbedcrypto.c b/src/libs/libssh-0.12.2/src/curve25519_mbedcrypto.c new file mode 100644 index 000000000000..f328f7b71305 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/curve25519_mbedcrypto.c @@ -0,0 +1,189 @@ +/* + * curve25519_mbedcrypto.c - Curve25519 ECDH functions for key exchange + * (MbedTLS) + * + * This file is part of the SSH Library + * + * Copyright (c) 2013-2023 by Aris Adamantiadis + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/curve25519.h" + +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "mbedcrypto-compat.h" + +#include +#include + +int ssh_curve25519_init(ssh_session session) +{ + ssh_curve25519_pubkey *pubkey_loc = NULL; + mbedtls_ecdh_context ecdh_ctx; + mbedtls_ecdh_params *ecdh_params = NULL; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + int rc, ret = SSH_ERROR; + char error_buf[128]; + + if (session->server) { + pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + mbedtls_ecdh_init(&ecdh_ctx); + rc = mbedtls_ecdh_setup(&ecdh_ctx, MBEDTLS_ECP_DP_CURVE25519); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup X25519 context: %s", error_buf); + goto out; + } + + ecdh_params = &MBEDTLS_ECDH_PARAMS(ecdh_ctx); + + rc = mbedtls_ecdh_gen_public(&ecdh_params->MBEDTLS_ECDH_PRIVATE(grp), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate X25519 keypair: %s", + error_buf); + goto out; + } + + rc = mbedtls_mpi_write_binary_le(&ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to write X25519 private key: %s", + error_buf); + goto out; + } + + rc = mbedtls_mpi_write_binary_le( + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Q).MBEDTLS_ECDH_PRIVATE(X), + *pubkey_loc, + CURVE25519_PUBKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to write X25519 public key: %s", + error_buf); + goto out; + } + + ret = SSH_OK; + +out: + mbedtls_ecdh_free(&ecdh_ctx); + return ret; +} + +int curve25519_do_create_k(ssh_session session, ssh_curve25519_pubkey k) +{ + ssh_curve25519_pubkey *peer_pubkey_loc = NULL; + int rc, ret = SSH_ERROR; + mbedtls_ecdh_context ecdh_ctx; + mbedtls_ecdh_params *ecdh_params = NULL; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + char error_buf[128]; + + if (session->server) { + peer_pubkey_loc = &session->next_crypto->curve25519_client_pubkey; + } else { + peer_pubkey_loc = &session->next_crypto->curve25519_server_pubkey; + } + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + mbedtls_ecdh_init(&ecdh_ctx); + rc = mbedtls_ecdh_setup(&ecdh_ctx, MBEDTLS_ECP_DP_CURVE25519); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to setup X25519 context: %s", error_buf); + goto out; + } + + ecdh_params = &MBEDTLS_ECDH_PARAMS(ecdh_ctx); + + rc = mbedtls_mpi_read_binary_le(&ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + session->next_crypto->curve25519_privkey, + CURVE25519_PRIVKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to read private key: %s", error_buf); + goto out; + } + + rc = mbedtls_mpi_read_binary_le( + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Qp).MBEDTLS_ECDH_PRIVATE(X), + *peer_pubkey_loc, + CURVE25519_PUBKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to read peer public key: %s", error_buf); + goto out; + } + + rc = mbedtls_mpi_lset( + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Qp).MBEDTLS_ECDH_PRIVATE(Z), + 1); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to set Z coordinate: %s", error_buf); + goto out; + } + + rc = mbedtls_ecdh_compute_shared(&ecdh_params->MBEDTLS_ECDH_PRIVATE(grp), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(z), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(Qp), + &ecdh_params->MBEDTLS_ECDH_PRIVATE(d), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, + "Failed to compute shared secret: %s", + error_buf); + goto out; + } + + rc = mbedtls_mpi_write_binary_le(&ecdh_params->MBEDTLS_ECDH_PRIVATE(z), + k, + CURVE25519_PUBKEY_SIZE); + if (rc != 0) { + mbedtls_strerror(rc, error_buf, sizeof(error_buf)); + SSH_LOG(SSH_LOG_TRACE, "Failed to write shared secret: %s", error_buf); + goto out; + } + + ret = SSH_OK; + +out: + mbedtls_ecdh_free(&ecdh_ctx); + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/dh-gex.c b/src/libs/libssh-0.12.2/src/dh-gex.c new file mode 100644 index 000000000000..888d2b2a4aa5 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/dh-gex.c @@ -0,0 +1,716 @@ +/* + * dh-gex.c - diffie-hellman group exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2016 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/dh-gex.h" +#include "libssh/libssh.h" +#include "libssh/ssh2.h" +#include "libssh/callbacks.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/session.h" + +/* Minimum, recommended and maximum size of DH group */ +#define DH_PMIN 2048 +#define DH_PREQ 2048 +#define DH_PMAX 8192 + +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group); +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply); + +static ssh_packet_callback dhgex_client_callbacks[] = { + ssh_packet_client_dhgex_group, /* SSH_MSG_KEX_DH_GEX_GROUP */ + NULL, /* SSH_MSG_KEX_DH_GEX_INIT */ + ssh_packet_client_dhgex_reply /* SSH_MSG_KEX_DH_GEX_REPLY */ +}; + +static struct ssh_packet_callbacks_struct ssh_dhgex_client_callbacks = { + .start = SSH2_MSG_KEX_DH_GEX_GROUP, + .n_callbacks = 3, + .callbacks = dhgex_client_callbacks, + .user = NULL +}; + +/** @internal + * @brief initiates a diffie-hellman-group-exchange kex + */ +int ssh_client_dhgex_init(ssh_session session) +{ + int rc; + + rc = ssh_dh_init_common(session->next_crypto); + if (rc != SSH_OK){ + goto error; + } + + session->next_crypto->dh_pmin = DH_PMIN; + session->next_crypto->dh_pn = DH_PREQ; + session->next_crypto->dh_pmax = DH_PMAX; + /* Minimum group size, preferred group size, maximum group size */ + rc = ssh_buffer_pack(session->out_buffer, + "bddd", + SSH2_MSG_KEX_DH_GEX_REQUEST, + session->next_crypto->dh_pmin, + session->next_crypto->dh_pn, + session->next_crypto->dh_pmax); + if (rc != SSH_OK) { + goto error; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dhgex_client_callbacks); + session->dh_handshake_state = DH_STATE_REQUEST_SENT; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + return rc; +error: + ssh_dh_cleanup(session->next_crypto); + return SSH_ERROR; +} + +/** @internal + * @brief handle a DH_GEX_GROUP packet, client side. This packet contains + * the group parameters. + */ +SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_group) +{ + int rc; + int blen; + bignum pmin1 = NULL, one = NULL; + bignum_CTX ctx = bignum_ctx_new(); + bignum modulus = NULL, generator = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + (void) type; + (void) user; + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_GROUP received"); + + if (bignum_ctx_invalid(ctx)) { + goto error; + } + + if (session->dh_handshake_state != DH_STATE_REQUEST_SENT) { + ssh_set_error(session, + SSH_FATAL, + "Received DH_GEX_GROUP in invalid state"); + goto error; + } + one = bignum_new(); + pmin1 = bignum_new(); + if (one == NULL || pmin1 == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_unpack(packet, + "BB", + &modulus, + &generator); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_GROUP packet"); + goto error; + } + /* basic checks */ + if (ssh_fips_mode() && + !ssh_dh_is_known_group(modulus, generator)) { + ssh_set_error(session, + SSH_FATAL, + "The received DH group is not FIPS approved"); + goto error; + } + rc = bignum_set_word(one, 1); + if (rc != 1) { + goto error; + } + blen = bignum_num_bits(modulus); + if (blen < DH_PMIN || blen > DH_PMAX) { + ssh_set_error(session, + SSH_FATAL, + "Invalid dh group parameter p: %d not in [%d:%d]", + blen, + DH_PMIN, + DH_PMAX); + goto error; + } + if (bignum_cmp(modulus, one) <= 0) { + /* p must be positive and preferably bigger than one */ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); + goto error; + } + if (!bignum_is_bit_set(modulus, 0)) { + /* p must be a prime and therefore not divisible by 2 */ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter p"); + goto error; + } + bignum_sub(pmin1, modulus, one); + if (bignum_cmp(generator, one) <= 0 || + bignum_cmp(generator, pmin1) > 0) { + /* generator must be at least 2 and smaller than p-1*/ + ssh_set_error(session, SSH_FATAL, "Invalid dh group parameter g"); + goto error; + } + bignum_ctx_free(ctx); + ctx = NULL; + + /* all checks passed, set parameters (the BNs are copied in openssl backend) */ + rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx, + modulus, generator); + if (rc != SSH_OK) { + goto error; + } +#ifdef HAVE_LIBCRYPTO + bignum_safe_free(modulus); + bignum_safe_free(generator); +#endif + modulus = NULL; + generator = NULL; + + /* compute and send DH public parameter */ + rc = ssh_dh_keypair_gen_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, NULL, &pubkey); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bB", + SSH2_MSG_KEX_DH_GEX_INIT, + pubkey); + if (rc != SSH_OK) { + goto error; + } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ + + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_PACKET_USED; + +error: + bignum_safe_free(modulus); + bignum_safe_free(generator); + bignum_safe_free(one); + bignum_safe_free(pmin1); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ + if(!bignum_ctx_invalid(ctx)) { + bignum_ctx_free(ctx); + } + ssh_dh_cleanup(session->next_crypto); + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +void ssh_client_dhgex_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_dhgex_client_callbacks); +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_dhgex_reply) +{ + struct ssh_crypto_struct *crypto=session->next_crypto; + int rc; + ssh_string pubkey_blob = NULL; + bignum server_pubkey = NULL; + (void)type; + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_DH_GEX_REPLY received"); + + ssh_client_dhgex_remove_callbacks(session); + rc = ssh_buffer_unpack(packet, + "SBS", + &pubkey_blob, &server_pubkey, + &crypto->dh_server_signature); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Invalid DH_GEX_REPLY packet"); + goto error; + } + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, + NULL, server_pubkey); + if (rc != SSH_OK) { + bignum_safe_free(server_pubkey); + goto error; + } + /* The ownership was passed to the crypto structure */ + server_pubkey = NULL; + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, DH_SERVER_KEYPAIR, + &session->next_crypto->shared_secret); + ssh_dh_debug_crypto(session->next_crypto); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; +error: + SSH_STRING_FREE(pubkey_blob); + ssh_dh_cleanup(session->next_crypto); + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +#define MODULI_FILE "/etc/ssh/moduli" +/* 2 "Safe" prime; (p-1)/2 is also prime. */ +#define SAFE_PRIME 2 +/* 0x04 Probabilistic Miller-Rabin primality tests. */ +#define PRIM_TEST_REQUIRED 0x04 + +/** + * @internal + * + * @brief Determines if the proposed modulus size is more appropriate than the + * current one. + * + * @returns 1 if it's more appropriate. Returns 0 if same or less appropriate + */ +static bool dhgroup_better_size(uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t current_size, + size_t proposed_size) +{ + if (current_size == proposed_size) { + return false; + } + + if (current_size == pn) { + /* can't do better */ + return false; + } + + if (current_size == 0 && proposed_size >= pmin && proposed_size <= pmax) { + return true; + } + + if (proposed_size < pmin || proposed_size > pmax) { + /* out of bounds */ + return false; + } + + if (current_size == 0) { + /* not in the allowed window */ + return false; + } + + if (proposed_size >= pn && proposed_size < current_size) { + return true; + } + + if (proposed_size <= pn && proposed_size > current_size) { + return true; + } + + if (proposed_size >= pn && current_size < pn) { + return true; + } + + /* We're in the allowed window but a better match already exists. */ + return false; +} + +/** @internal + * @brief returns 1 with 1/n probability + * @returns 1 on with P(1/n), 0 with P(n-1/n). + */ +static bool invn_chance(size_t n) +{ + size_t nounce = 0; + int ok; + + ok = ssh_get_random(&nounce, sizeof(nounce), 0); + if (!ok) { + return false; + } + return (nounce % n) == 0; +} + +/** @internal + * @brief retrieves a DH group from an open moduli file. + */ +static int ssh_retrieve_dhgroup_file(FILE *moduli, + uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t *best_size, + char **best_generator, + char **best_modulus) +{ + char timestamp[32] = {0}; + char generator[32] = {0}; + char modulus[4096] = {0}; + size_t type, tests, tries, size, proposed_size; + int firstbyte; + int rc; + size_t line = 0; + size_t best_nlines = 0; + + *best_size = 0; + for(;;) { + line++; + firstbyte = getc(moduli); + if (firstbyte == '#'){ + do { + firstbyte = getc(moduli); + } while(firstbyte != '\n' && firstbyte != EOF); + if (firstbyte == EOF) { + break; + } + continue; + } + if (firstbyte == EOF) { + break; + } + ungetc(firstbyte, moduli); + rc = fscanf(moduli, + "%31s %zu %zu %zu %zu %31s %4095s\n", + timestamp, + &type, + &tests, + &tries, + &size, + generator, + modulus); + if (rc != 7){ + if (rc == EOF) { + break; + } + SSH_LOG(SSH_LOG_DEBUG, "Invalid moduli entry line %zu", line); + do { + firstbyte = getc(moduli); + } while(firstbyte != '\n' && firstbyte != EOF); + if (firstbyte == EOF) { + break; + } + continue; + } + + /* we only want safe primes that were tested */ + if (type != SAFE_PRIME || !(tests & PRIM_TEST_REQUIRED)) { + continue; + } + + proposed_size = size + 1; + if (proposed_size != *best_size && + dhgroup_better_size(pmin, pn, pmax, *best_size, proposed_size)) { + best_nlines = 1; + *best_size = proposed_size; + } else if (proposed_size == *best_size) { + best_nlines++; + } + + /* Use reservoir sampling algorithm */ + if (proposed_size == *best_size && invn_chance(best_nlines)) { + SAFE_FREE(*best_generator); + SAFE_FREE(*best_modulus); + *best_generator = strdup(generator); + if (*best_generator == NULL) { + return SSH_ERROR; + } + *best_modulus = strdup(modulus); + if (*best_modulus == NULL) { + SAFE_FREE(*best_generator); + return SSH_ERROR; + } + } + } + if (*best_size != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Selected %zu bits modulus out of %zu candidates in %zu lines", + *best_size, + best_nlines - 1, + line); + } else { + SSH_LOG(SSH_LOG_DEBUG, + "No moduli found for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", + pmin, + pn, + pmax); + } + + return SSH_OK; +} + +/** @internal + * @brief retrieves a DH group from the moduli file based on bits len parameters + * @param[in] pmin minimum group size in bits + * @param[in] pn preferred group size + * @param[in] pmax maximum group size + * @param[out] size size of the chosen modulus + * @param[out] p modulus + * @param[out] g generator + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +static int ssh_retrieve_dhgroup(char *moduli_file, + uint32_t pmin, + uint32_t pn, + uint32_t pmax, + size_t *size, + bignum *p, + bignum *g) +{ + FILE *moduli = NULL; + char *generator = NULL; + char *modulus = NULL; + int rc; + + /* In FIPS mode, we can not negotiate arbitrary primes, + * but just the approved ones */ + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "In FIPS mode, using built-in primes"); + return ssh_fallback_group(pmax, p, g); + } + + if (moduli_file != NULL) + moduli = ssh_strict_fopen(moduli_file, SSH_MAX_CONFIG_FILE_SIZE); + else + moduli = ssh_strict_fopen(MODULI_FILE, SSH_MAX_CONFIG_FILE_SIZE); + + if (moduli == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_DEBUG, + "Unable to open moduli file: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return ssh_fallback_group(pmax, p, g); + } + + *size = 0; + *p = NULL; + *g = NULL; + + rc = ssh_retrieve_dhgroup_file(moduli, + pmin, + pn, + pmax, + size, + &generator, + &modulus); + fclose(moduli); + if (rc == SSH_ERROR || *size == 0) { + goto error; + } + rc = bignum_hex2bn(generator, g); + if (rc == 0) { + goto error; + } + rc = bignum_hex2bn(modulus, p); + if (rc == 0) { + goto error; + } + SAFE_FREE(generator); + SAFE_FREE(modulus); + + return SSH_OK; + +error: + bignum_safe_free(*g); + bignum_safe_free(*p); + SAFE_FREE(generator); + SAFE_FREE(modulus); + + return SSH_ERROR; +} + +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request); +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init); + +static ssh_packet_callback dhgex_server_callbacks[] = { + NULL, /* SSH_MSG_KEX_DH_GEX_REQUEST_OLD */ + NULL, /* SSH_MSG_KEX_DH_GEX_GROUP */ + ssh_packet_server_dhgex_init, /* SSH_MSG_KEX_DH_GEX_INIT */ + NULL, /* SSH_MSG_KEX_DH_GEX_REPLY */ + ssh_packet_server_dhgex_request /* SSH_MSG_KEX_DH_GEX_REQUEST */ + +}; + +static struct ssh_packet_callbacks_struct ssh_dhgex_server_callbacks = { + .start = SSH2_MSG_KEX_DH_GEX_REQUEST_OLD, + .n_callbacks = 5, + .callbacks = dhgex_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the diffie-hellman-groupx kex callbacks + */ +void ssh_server_dhgex_init(ssh_session session){ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dhgex_server_callbacks); + ssh_dh_init_common(session->next_crypto); + session->dh_handshake_state = DH_STATE_INIT; +} + +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_request) +{ + bignum modulus = NULL, generator = NULL; + uint32_t pmin, pn, pmax; + size_t size = 0; + int rc; + + (void) type; + (void) user; + + if (session->dh_handshake_state != DH_STATE_INIT) { + ssh_set_error(session, + SSH_FATAL, + "Received DH_GEX_REQUEST in invalid state"); + goto error; + } + + /* Minimum group size, preferred group size, maximum group size */ + rc = ssh_buffer_unpack(packet, "ddd", &pmin, &pn, &pmax); + if (rc != SSH_OK){ + ssh_set_error_invalid(session); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "dh-gex: DHGEX_REQUEST[%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", pmin, pn, pmax); + + if (pmin > pn || pn > pmax || pn > DH_PMAX || pmax < DH_PMIN) { + ssh_set_error(session, + SSH_FATAL, + "Invalid dh-gex arguments [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", + pmin, + pn, + pmax); + goto error; + } + session->next_crypto->dh_pmin = pmin; + session->next_crypto->dh_pn = pn; + session->next_crypto->dh_pmax = pmax; + + /* ensure safe parameters */ + if (pmin < DH_PMIN) { + pmin = DH_PMIN; + if (pn < pmin) { + pn = pmin; + } + } + rc = ssh_retrieve_dhgroup(session->server_opts.moduli_file, + pmin, + pn, + pmax, + &size, + &modulus, + &generator); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Couldn't find DH group for [%" PRIu32 ":%" PRIu32 ":%" PRIu32 "]", + pmin, + pn, + pmax); + goto error; + } + rc = ssh_dh_set_parameters(session->next_crypto->dh_ctx, + modulus, generator); + if (rc != SSH_OK) { + bignum_safe_free(generator); + bignum_safe_free(modulus); + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, + "bBB", + SSH2_MSG_KEX_DH_GEX_GROUP, + modulus, + generator); + +#ifdef HAVE_LIBCRYPTO + bignum_safe_free(generator); + bignum_safe_free(modulus); +#endif + + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + goto error; + } + + session->dh_handshake_state = DH_STATE_GROUP_SENT; + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + +error: + return SSH_PACKET_USED; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEX_DH_GEX_INIT packet and complete + * Diffie-Hellman key exchange + **/ +static SSH_PACKET_CALLBACK(ssh_packet_server_dhgex_init){ + (void) type; + (void) user; + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEX_DHGEX_INIT"); + ssh_packet_remove_callbacks(session, &ssh_dhgex_server_callbacks); + ssh_server_dh_process_init(session, packet); + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/src/dh.c b/src/libs/libssh-0.12.2/src/dh.c new file mode 100644 index 000000000000..c04418dd59bd --- /dev/null +++ b/src/libs/libssh-0.12.2/src/dh.c @@ -0,0 +1,832 @@ +/* + * dh.c - Diffie-Helman algorithm code against SSH 2 + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2018 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2012 by Dmitriy Kuznetsov + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#ifdef WITH_GSSAPI +#include "libssh/gssapi.h" +#include +#endif + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/string.h" + +static unsigned char p_group1_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE6, 0x53, 0x81, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; +#define P_GROUP1_LEN 128 /* Size in bytes of the p number */ + +static unsigned char p_group14_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAC, 0xAA, 0x68, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP14_LEN 256 /* Size in bytes of the p number for group 14 */ + +static unsigned char p_group16_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, + 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, + 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, + 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, + 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, + 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, + 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, + 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, + 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, + 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, + 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, + 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, + 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, + 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, + 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, + 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, + 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, + 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, + 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, + 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, + 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x06, 0x31, 0x99, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP16_LEN 512 /* Size in bytes of the p number for group 16 */ + +static unsigned char p_group18_value[] = { + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xC9, 0x0F, 0xDA, 0xA2, + 0x21, 0x68, 0xC2, 0x34, 0xC4, 0xC6, 0x62, 0x8B, 0x80, 0xDC, 0x1C, 0xD1, + 0x29, 0x02, 0x4E, 0x08, 0x8A, 0x67, 0xCC, 0x74, 0x02, 0x0B, 0xBE, 0xA6, + 0x3B, 0x13, 0x9B, 0x22, 0x51, 0x4A, 0x08, 0x79, 0x8E, 0x34, 0x04, 0xDD, + 0xEF, 0x95, 0x19, 0xB3, 0xCD, 0x3A, 0x43, 0x1B, 0x30, 0x2B, 0x0A, 0x6D, + 0xF2, 0x5F, 0x14, 0x37, 0x4F, 0xE1, 0x35, 0x6D, 0x6D, 0x51, 0xC2, 0x45, + 0xE4, 0x85, 0xB5, 0x76, 0x62, 0x5E, 0x7E, 0xC6, 0xF4, 0x4C, 0x42, 0xE9, + 0xA6, 0x37, 0xED, 0x6B, 0x0B, 0xFF, 0x5C, 0xB6, 0xF4, 0x06, 0xB7, 0xED, + 0xEE, 0x38, 0x6B, 0xFB, 0x5A, 0x89, 0x9F, 0xA5, 0xAE, 0x9F, 0x24, 0x11, + 0x7C, 0x4B, 0x1F, 0xE6, 0x49, 0x28, 0x66, 0x51, 0xEC, 0xE4, 0x5B, 0x3D, + 0xC2, 0x00, 0x7C, 0xB8, 0xA1, 0x63, 0xBF, 0x05, 0x98, 0xDA, 0x48, 0x36, + 0x1C, 0x55, 0xD3, 0x9A, 0x69, 0x16, 0x3F, 0xA8, 0xFD, 0x24, 0xCF, 0x5F, + 0x83, 0x65, 0x5D, 0x23, 0xDC, 0xA3, 0xAD, 0x96, 0x1C, 0x62, 0xF3, 0x56, + 0x20, 0x85, 0x52, 0xBB, 0x9E, 0xD5, 0x29, 0x07, 0x70, 0x96, 0x96, 0x6D, + 0x67, 0x0C, 0x35, 0x4E, 0x4A, 0xBC, 0x98, 0x04, 0xF1, 0x74, 0x6C, 0x08, + 0xCA, 0x18, 0x21, 0x7C, 0x32, 0x90, 0x5E, 0x46, 0x2E, 0x36, 0xCE, 0x3B, + 0xE3, 0x9E, 0x77, 0x2C, 0x18, 0x0E, 0x86, 0x03, 0x9B, 0x27, 0x83, 0xA2, + 0xEC, 0x07, 0xA2, 0x8F, 0xB5, 0xC5, 0x5D, 0xF0, 0x6F, 0x4C, 0x52, 0xC9, + 0xDE, 0x2B, 0xCB, 0xF6, 0x95, 0x58, 0x17, 0x18, 0x39, 0x95, 0x49, 0x7C, + 0xEA, 0x95, 0x6A, 0xE5, 0x15, 0xD2, 0x26, 0x18, 0x98, 0xFA, 0x05, 0x10, + 0x15, 0x72, 0x8E, 0x5A, 0x8A, 0xAA, 0xC4, 0x2D, 0xAD, 0x33, 0x17, 0x0D, + 0x04, 0x50, 0x7A, 0x33, 0xA8, 0x55, 0x21, 0xAB, 0xDF, 0x1C, 0xBA, 0x64, + 0xEC, 0xFB, 0x85, 0x04, 0x58, 0xDB, 0xEF, 0x0A, 0x8A, 0xEA, 0x71, 0x57, + 0x5D, 0x06, 0x0C, 0x7D, 0xB3, 0x97, 0x0F, 0x85, 0xA6, 0xE1, 0xE4, 0xC7, + 0xAB, 0xF5, 0xAE, 0x8C, 0xDB, 0x09, 0x33, 0xD7, 0x1E, 0x8C, 0x94, 0xE0, + 0x4A, 0x25, 0x61, 0x9D, 0xCE, 0xE3, 0xD2, 0x26, 0x1A, 0xD2, 0xEE, 0x6B, + 0xF1, 0x2F, 0xFA, 0x06, 0xD9, 0x8A, 0x08, 0x64, 0xD8, 0x76, 0x02, 0x73, + 0x3E, 0xC8, 0x6A, 0x64, 0x52, 0x1F, 0x2B, 0x18, 0x17, 0x7B, 0x20, 0x0C, + 0xBB, 0xE1, 0x17, 0x57, 0x7A, 0x61, 0x5D, 0x6C, 0x77, 0x09, 0x88, 0xC0, + 0xBA, 0xD9, 0x46, 0xE2, 0x08, 0xE2, 0x4F, 0xA0, 0x74, 0xE5, 0xAB, 0x31, + 0x43, 0xDB, 0x5B, 0xFC, 0xE0, 0xFD, 0x10, 0x8E, 0x4B, 0x82, 0xD1, 0x20, + 0xA9, 0x21, 0x08, 0x01, 0x1A, 0x72, 0x3C, 0x12, 0xA7, 0x87, 0xE6, 0xD7, + 0x88, 0x71, 0x9A, 0x10, 0xBD, 0xBA, 0x5B, 0x26, 0x99, 0xC3, 0x27, 0x18, + 0x6A, 0xF4, 0xE2, 0x3C, 0x1A, 0x94, 0x68, 0x34, 0xB6, 0x15, 0x0B, 0xDA, + 0x25, 0x83, 0xE9, 0xCA, 0x2A, 0xD4, 0x4C, 0xE8, 0xDB, 0xBB, 0xC2, 0xDB, + 0x04, 0xDE, 0x8E, 0xF9, 0x2E, 0x8E, 0xFC, 0x14, 0x1F, 0xBE, 0xCA, 0xA6, + 0x28, 0x7C, 0x59, 0x47, 0x4E, 0x6B, 0xC0, 0x5D, 0x99, 0xB2, 0x96, 0x4F, + 0xA0, 0x90, 0xC3, 0xA2, 0x23, 0x3B, 0xA1, 0x86, 0x51, 0x5B, 0xE7, 0xED, + 0x1F, 0x61, 0x29, 0x70, 0xCE, 0xE2, 0xD7, 0xAF, 0xB8, 0x1B, 0xDD, 0x76, + 0x21, 0x70, 0x48, 0x1C, 0xD0, 0x06, 0x91, 0x27, 0xD5, 0xB0, 0x5A, 0xA9, + 0x93, 0xB4, 0xEA, 0x98, 0x8D, 0x8F, 0xDD, 0xC1, 0x86, 0xFF, 0xB7, 0xDC, + 0x90, 0xA6, 0xC0, 0x8F, 0x4D, 0xF4, 0x35, 0xC9, 0x34, 0x02, 0x84, 0x92, + 0x36, 0xC3, 0xFA, 0xB4, 0xD2, 0x7C, 0x70, 0x26, 0xC1, 0xD4, 0xDC, 0xB2, + 0x60, 0x26, 0x46, 0xDE, 0xC9, 0x75, 0x1E, 0x76, 0x3D, 0xBA, 0x37, 0xBD, + 0xF8, 0xFF, 0x94, 0x06, 0xAD, 0x9E, 0x53, 0x0E, 0xE5, 0xDB, 0x38, 0x2F, + 0x41, 0x30, 0x01, 0xAE, 0xB0, 0x6A, 0x53, 0xED, 0x90, 0x27, 0xD8, 0x31, + 0x17, 0x97, 0x27, 0xB0, 0x86, 0x5A, 0x89, 0x18, 0xDA, 0x3E, 0xDB, 0xEB, + 0xCF, 0x9B, 0x14, 0xED, 0x44, 0xCE, 0x6C, 0xBA, 0xCE, 0xD4, 0xBB, 0x1B, + 0xDB, 0x7F, 0x14, 0x47, 0xE6, 0xCC, 0x25, 0x4B, 0x33, 0x20, 0x51, 0x51, + 0x2B, 0xD7, 0xAF, 0x42, 0x6F, 0xB8, 0xF4, 0x01, 0x37, 0x8C, 0xD2, 0xBF, + 0x59, 0x83, 0xCA, 0x01, 0xC6, 0x4B, 0x92, 0xEC, 0xF0, 0x32, 0xEA, 0x15, + 0xD1, 0x72, 0x1D, 0x03, 0xF4, 0x82, 0xD7, 0xCE, 0x6E, 0x74, 0xFE, 0xF6, + 0xD5, 0x5E, 0x70, 0x2F, 0x46, 0x98, 0x0C, 0x82, 0xB5, 0xA8, 0x40, 0x31, + 0x90, 0x0B, 0x1C, 0x9E, 0x59, 0xE7, 0xC9, 0x7F, 0xBE, 0xC7, 0xE8, 0xF3, + 0x23, 0xA9, 0x7A, 0x7E, 0x36, 0xCC, 0x88, 0xBE, 0x0F, 0x1D, 0x45, 0xB7, + 0xFF, 0x58, 0x5A, 0xC5, 0x4B, 0xD4, 0x07, 0xB2, 0x2B, 0x41, 0x54, 0xAA, + 0xCC, 0x8F, 0x6D, 0x7E, 0xBF, 0x48, 0xE1, 0xD8, 0x14, 0xCC, 0x5E, 0xD2, + 0x0F, 0x80, 0x37, 0xE0, 0xA7, 0x97, 0x15, 0xEE, 0xF2, 0x9B, 0xE3, 0x28, + 0x06, 0xA1, 0xD5, 0x8B, 0xB7, 0xC5, 0xDA, 0x76, 0xF5, 0x50, 0xAA, 0x3D, + 0x8A, 0x1F, 0xBF, 0xF0, 0xEB, 0x19, 0xCC, 0xB1, 0xA3, 0x13, 0xD5, 0x5C, + 0xDA, 0x56, 0xC9, 0xEC, 0x2E, 0xF2, 0x96, 0x32, 0x38, 0x7F, 0xE8, 0xD7, + 0x6E, 0x3C, 0x04, 0x68, 0x04, 0x3E, 0x8F, 0x66, 0x3F, 0x48, 0x60, 0xEE, + 0x12, 0xBF, 0x2D, 0x5B, 0x0B, 0x74, 0x74, 0xD6, 0xE6, 0x94, 0xF9, 0x1E, + 0x6D, 0xBE, 0x11, 0x59, 0x74, 0xA3, 0x92, 0x6F, 0x12, 0xFE, 0xE5, 0xE4, + 0x38, 0x77, 0x7C, 0xB6, 0xA9, 0x32, 0xDF, 0x8C, 0xD8, 0xBE, 0xC4, 0xD0, + 0x73, 0xB9, 0x31, 0xBA, 0x3B, 0xC8, 0x32, 0xB6, 0x8D, 0x9D, 0xD3, 0x00, + 0x74, 0x1F, 0xA7, 0xBF, 0x8A, 0xFC, 0x47, 0xED, 0x25, 0x76, 0xF6, 0x93, + 0x6B, 0xA4, 0x24, 0x66, 0x3A, 0xAB, 0x63, 0x9C, 0x5A, 0xE4, 0xF5, 0x68, + 0x34, 0x23, 0xB4, 0x74, 0x2B, 0xF1, 0xC9, 0x78, 0x23, 0x8F, 0x16, 0xCB, + 0xE3, 0x9D, 0x65, 0x2D, 0xE3, 0xFD, 0xB8, 0xBE, 0xFC, 0x84, 0x8A, 0xD9, + 0x22, 0x22, 0x2E, 0x04, 0xA4, 0x03, 0x7C, 0x07, 0x13, 0xEB, 0x57, 0xA8, + 0x1A, 0x23, 0xF0, 0xC7, 0x34, 0x73, 0xFC, 0x64, 0x6C, 0xEA, 0x30, 0x6B, + 0x4B, 0xCB, 0xC8, 0x86, 0x2F, 0x83, 0x85, 0xDD, 0xFA, 0x9D, 0x4B, 0x7F, + 0xA2, 0xC0, 0x87, 0xE8, 0x79, 0x68, 0x33, 0x03, 0xED, 0x5B, 0xDD, 0x3A, + 0x06, 0x2B, 0x3C, 0xF5, 0xB3, 0xA2, 0x78, 0xA6, 0x6D, 0x2A, 0x13, 0xF8, + 0x3F, 0x44, 0xF8, 0x2D, 0xDF, 0x31, 0x0E, 0xE0, 0x74, 0xAB, 0x6A, 0x36, + 0x45, 0x97, 0xE8, 0x99, 0xA0, 0x25, 0x5D, 0xC1, 0x64, 0xF3, 0x1C, 0xC5, + 0x08, 0x46, 0x85, 0x1D, 0xF9, 0xAB, 0x48, 0x19, 0x5D, 0xED, 0x7E, 0xA1, + 0xB1, 0xD5, 0x10, 0xBD, 0x7E, 0xE7, 0x4D, 0x73, 0xFA, 0xF3, 0x6B, 0xC3, + 0x1E, 0xCF, 0xA2, 0x68, 0x35, 0x90, 0x46, 0xF4, 0xEB, 0x87, 0x9F, 0x92, + 0x40, 0x09, 0x43, 0x8B, 0x48, 0x1C, 0x6C, 0xD7, 0x88, 0x9A, 0x00, 0x2E, + 0xD5, 0xEE, 0x38, 0x2B, 0xC9, 0x19, 0x0D, 0xA6, 0xFC, 0x02, 0x6E, 0x47, + 0x95, 0x58, 0xE4, 0x47, 0x56, 0x77, 0xE9, 0xAA, 0x9E, 0x30, 0x50, 0xE2, + 0x76, 0x56, 0x94, 0xDF, 0xC8, 0x1F, 0x56, 0xE8, 0x80, 0xB9, 0x6E, 0x71, + 0x60, 0xC9, 0x80, 0xDD, 0x98, 0xED, 0xD3, 0xDF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF}; + +#define P_GROUP18_LEN 1024 /* Size in bytes of the p number for group 18 */ + +bignum ssh_dh_generator; +bignum ssh_dh_group1; +bignum ssh_dh_group14; +bignum ssh_dh_group16; +bignum ssh_dh_group18; +static int dh_crypto_initialized; + +/** + * @internal + * @brief Initialize global constants used in DH key agreement + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_dh_init(void) +{ + unsigned long g_int = 2 ; /* G is defined as 2 by the ssh2 standards */ + int rc; + if (dh_crypto_initialized) { + return SSH_OK; + } + dh_crypto_initialized = 1; + + ssh_dh_generator = bignum_new(); + if (ssh_dh_generator == NULL) { + goto error; + } + rc = bignum_set_word(ssh_dh_generator, g_int); + if (rc != 1) { + goto error; + } + + bignum_bin2bn(p_group1_value, P_GROUP1_LEN, &ssh_dh_group1); + if (ssh_dh_group1 == NULL) { + goto error; + } + bignum_bin2bn(p_group14_value, P_GROUP14_LEN, &ssh_dh_group14); + if (ssh_dh_group14 == NULL) { + goto error; + } + bignum_bin2bn(p_group16_value, P_GROUP16_LEN, &ssh_dh_group16); + if (ssh_dh_group16 == NULL) { + goto error; + } + bignum_bin2bn(p_group18_value, P_GROUP18_LEN, &ssh_dh_group18); + if (ssh_dh_group18 == NULL) { + goto error; + } + + return 0; +error: + ssh_dh_finalize(); + return SSH_ERROR; +} + +/** + * @internal + * @brief Finalize and free global constants used in DH key agreement + */ +void ssh_dh_finalize(void) +{ + if (!dh_crypto_initialized) { + return; + } + + bignum_safe_free(ssh_dh_generator); + bignum_safe_free(ssh_dh_group1); + bignum_safe_free(ssh_dh_group14); + bignum_safe_free(ssh_dh_group16); + bignum_safe_free(ssh_dh_group18); + + dh_crypto_initialized = 0; +} + +int ssh_dh_import_next_pubkey_blob(ssh_session session, ssh_string pubkey_blob) +{ + return ssh_pki_import_pubkey_blob(pubkey_blob, + &session->next_crypto->server_pubkey); + +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply); + +static ssh_packet_callback dh_client_callbacks[]= { + ssh_packet_client_dh_reply +}; + +static struct ssh_packet_callbacks_struct ssh_dh_client_callbacks = { + .start = SSH2_MSG_KEXDH_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL +}; + +/** @internal + * @brief Starts diffie-hellman-group1 key exchange + */ +int ssh_client_dh_init(ssh_session session){ + struct ssh_crypto_struct *crypto = session->next_crypto; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum pubkey; +#else + bignum pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + int rc; + + rc = ssh_dh_init_common(crypto); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR); + if (rc == SSH_ERROR){ + goto error; + } + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR, + NULL, &pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, "bB", SSH2_MSG_KEXDH_INIT, pubkey); + if (rc != SSH_OK) { + goto error; + } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + return rc; +error: +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(pubkey); +#endif + ssh_dh_cleanup(crypto); + return SSH_ERROR; +} + +void ssh_client_dh_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_dh_client_callbacks); +} + +SSH_PACKET_CALLBACK(ssh_packet_client_dh_reply){ + struct ssh_crypto_struct *crypto=session->next_crypto; + ssh_string pubkey_blob = NULL; + bignum server_pubkey; + int rc; + + (void)type; + (void)user; + + ssh_client_dh_remove_callbacks(session); + + rc = ssh_buffer_unpack(packet, "SBS", &pubkey_blob, &server_pubkey, + &crypto->dh_server_signature); + if (rc == SSH_ERROR) { + goto error; + } + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, + NULL, server_pubkey); + if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey_blob); + bignum_safe_free(server_pubkey); + goto error; + } + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + rc = ssh_dh_compute_shared_secret(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, DH_SERVER_KEYPAIR, + &session->next_crypto->shared_secret); + ssh_dh_debug_crypto(session->next_crypto); + if (rc == SSH_ERROR){ + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + return SSH_PACKET_USED; +error: + ssh_dh_cleanup(session->next_crypto); + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_dh_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_dh_init, +}; + +static struct ssh_packet_callbacks_struct ssh_dh_server_callbacks = { + .start = SSH2_MSG_KEXDH_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the diffie-hellman-groupx kex callbacks + */ +void ssh_server_dh_init(ssh_session session){ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_dh_server_callbacks); + + ssh_dh_init_common(session->next_crypto); +} + +/** @internal + * @brief processes a SSH_MSG_KEXDH_INIT or SSH_MSG_KEX_DH_GEX_INIT packet and sends + * the appropriate SSH_MSG_KEXDH_REPLY or SSH_MSG_KEX_DH_GEX_REPLY + */ +int ssh_server_dh_process_init(ssh_session session, ssh_buffer packet) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + bignum client_pubkey; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum server_pubkey; +#else + bignum server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + int packet_type; + int rc; + + rc = ssh_buffer_unpack(packet, "B", &client_pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No e number in client request"); + goto error; + } + + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, DH_CLIENT_KEYPAIR, + NULL, client_pubkey); + if (rc != SSH_OK) { + bignum_safe_free(client_pubkey); + goto error; + } + + rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_compute_shared_secret(crypto->dh_ctx, + DH_SERVER_KEYPAIR, DH_CLIENT_KEYPAIR, + &crypto->shared_secret); + ssh_dh_debug_crypto(crypto); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "Could not generate shared secret"); + goto error; + } + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + switch (crypto->kex_type){ + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + packet_type = SSH2_MSG_KEXDH_REPLY; + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + packet_type = SSH2_MSG_KEX_DH_GEX_REPLY; + break; +#endif /* WITH_GEX */ + default: + ssh_set_error(session, SSH_FATAL, "Invalid kex type"); + goto error; + } + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, DH_SERVER_KEYPAIR, + NULL, &server_pubkey); + if (rc != SSH_OK){ + goto error; + } + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, + "bSBS", + packet_type, + pubkey_blob, + server_pubkey, + sig_blob); + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif + if(rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + goto error; + } + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Sent KEX_DH_[GEX]_REPLY"); + + session->dh_handshake_state=DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + return SSH_OK; +error: + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(server_pubkey); +#endif + + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_dh_cleanup(session->next_crypto); + return SSH_ERROR; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEXDH_INIT packet and complete + * Diffie-Hellman key exchange + **/ +static SSH_PACKET_CALLBACK(ssh_packet_server_dh_init){ + (void)type; + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEXDH_INIT"); + ssh_packet_remove_callbacks(session, &ssh_dh_server_callbacks); + ssh_server_dh_process_init(session, packet); + return SSH_PACKET_USED; +} + +/** @internal + * @brief Choose a fallback group for the DH Group exchange if the + * moduli file is not readable + * @param[in] pmax maximum requestsd group size + * @param[out] modulus + * @param[out] generator + * @returns SSH_OK on success, SSH_ERROR otherwise + */ +int ssh_fallback_group(uint32_t pmax, + bignum *modulus, + bignum *generator) +{ + *modulus = NULL; + *generator = NULL; + + if (pmax < 3072) { + bignum_dup(ssh_dh_group14, modulus); + } else if (pmax < 6144) { + bignum_dup(ssh_dh_group16, modulus); + } else { + bignum_dup(ssh_dh_group18, modulus); + } + if (*modulus == NULL) { + return SSH_ERROR; + } + + bignum_dup(ssh_dh_generator, generator); + if (*generator == NULL) { + bignum_safe_free((*modulus)); + return SSH_ERROR; + } + + return SSH_OK; +} + +#endif /* WITH_SERVER */ + +/** + * @addtogroup libssh_session + * + * @{ + */ + +bool ssh_dh_is_known_group(bignum modulus, bignum generator) +{ + int cmp, bits; + bignum m = NULL; + + bits = bignum_num_bits(modulus); + if (bits < 3072) { + m = ssh_dh_group14; + } else if (bits < 6144) { + m = ssh_dh_group16; + } else { + m = ssh_dh_group18; + } + + cmp = bignum_cmp(m, modulus); + if (cmp != 0) { + return false; + } + + cmp = bignum_cmp(ssh_dh_generator, generator); + if (cmp != 0) { + return false; + } + + SSH_LOG(SSH_LOG_TRACE, "The received primes in FIPS are known"); + return true; +} + +ssh_key ssh_dh_get_current_server_publickey(ssh_session session) +{ + if (session->current_crypto == NULL) { + return NULL; + } + + return session->current_crypto->server_pubkey; +} + +/* Caller needs to free the blob */ +int ssh_dh_get_current_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob) +{ + const ssh_key pubkey = ssh_dh_get_current_server_publickey(session); + + return ssh_pki_export_pubkey_blob(pubkey, pubkey_blob); +} + +ssh_key ssh_dh_get_next_server_publickey(ssh_session session) +{ + return session->next_crypto->server_pubkey; +} + +/* Caller needs to free the blob */ +int ssh_dh_get_next_server_publickey_blob(ssh_session session, + ssh_string *pubkey_blob) +{ + const ssh_key pubkey = ssh_dh_get_next_server_publickey(session); + + return ssh_pki_export_pubkey_blob(pubkey, pubkey_blob); +} + +/** + * @internal + * + * @brief Convert a buffer into an unpadded base64 string. + * The caller has to free the memory. + * + * @param hash What should be converted to a base64 string. + * + * @param len Length of the buffer to convert. + * + * @return The base64 string or NULL on error. + * + * @see ssh_string_free_char() + */ +static char *ssh_get_b64_unpadded(const unsigned char *hash, size_t len) +{ + char *b64_padded = NULL; + char *b64_unpadded = NULL; + size_t k; + + b64_padded = (char *)bin_to_base64(hash, len); + if (b64_padded == NULL) { + return NULL; + } + for (k = strlen(b64_padded); k != 0 && b64_padded[k-1] == '='; k--); + + b64_unpadded = strndup(b64_padded, k); + SAFE_FREE(b64_padded); + + return b64_unpadded; +} + +/** + * @brief Get a hash as a human-readable hex- or base64-string. + * + * This gets an allocated fingerprint hash. If it is a SHA sum, it will + * return an unpadded base64 string. If it is a MD5 sum, it will return a hex + * string. Either way, the output is prepended by the hash-type. + * + * @warning Do NOT use MD5 or SHA1! Those hash functions are being deprecated. + * + * @param type Which sort of hash is given, use + * SSH_PUBLICKEY_HASH_SHA256 or better. + * + * @param hash The hash to be converted to fingerprint. + * + * @param len Length of the buffer to convert. + * + * @return Returns the allocated fingerprint hash or NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_get_fingerprint_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len) +{ + const char *prefix = "UNKNOWN"; + char *fingerprint = NULL; + char *str = NULL; + size_t str_len; + int rc; + + switch (type) { + case SSH_PUBLICKEY_HASH_SHA1: + case SSH_PUBLICKEY_HASH_SHA256: + fingerprint = ssh_get_b64_unpadded(hash, len); + break; + case SSH_PUBLICKEY_HASH_MD5: + fingerprint = ssh_get_hexa(hash, len); + break; + } + if (fingerprint == NULL) { + return NULL; + } + + switch (type) { + case SSH_PUBLICKEY_HASH_MD5: + prefix = "MD5"; + break; + case SSH_PUBLICKEY_HASH_SHA1: + prefix = "SHA1"; + break; + case SSH_PUBLICKEY_HASH_SHA256: + prefix = "SHA256"; + break; + } + + str_len = strlen(prefix); + if (str_len + 1 + strlen(fingerprint) + 1 < str_len) { + SAFE_FREE(fingerprint); + return NULL; + } + str_len += 1 + strlen(fingerprint) + 1; + + str = malloc(str_len); + if (str == NULL) { + SAFE_FREE(fingerprint); + return NULL; + } + rc = snprintf(str, str_len, "%s:%s", prefix, fingerprint); + SAFE_FREE(fingerprint); + if (rc < 0 || rc < (int)(str_len - 1)) { + SAFE_FREE(str); + } + + return str; +} + +/** + * @brief Print a hash as a human-readable hex- or base64-string. + * + * This prints an unpadded base64 strings for SHA sums and hex strings for MD5 + * sum. Either way, the output is prepended by the hash-type. + * + * @param type Which sort of hash is given. Use + * SSH_PUBLICKEY_HASH_SHA256 or better. + * + * @param hash The hash to be converted to fingerprint. + * + * @param len Length of the buffer to convert. + * + * @see ssh_get_publickey_hash() + * @see ssh_get_fingerprint_hash() + */ +void ssh_print_hash(enum ssh_publickey_hash_type type, + unsigned char *hash, + size_t len) +{ + char *fingerprint = NULL; + + fingerprint = ssh_get_fingerprint_hash(type, + hash, + len); + if (fingerprint == NULL) { + return; + } + + fprintf(stderr, "%s\n", fingerprint); + + SAFE_FREE(fingerprint); +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/dh_crypto.c b/src/libs/libssh-0.12.2/src/dh_crypto.c new file mode 100644 index 000000000000..647e3bdd45ca --- /dev/null +++ b/src/libs/libssh-0.12.2/src/dh_crypto.c @@ -0,0 +1,615 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Simo Sorce - Red Hat, Inc. + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/session.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#include "openssl/crypto.h" +#include "openssl/dh.h" +#include "libcrypto-compat.h" +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#include +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ + +extern bignum ssh_dh_generator; +extern bignum ssh_dh_group1; +extern bignum ssh_dh_group14; +extern bignum ssh_dh_group16; +extern bignum ssh_dh_group18; + +struct dh_ctx { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DH *keypair[2]; +#else + EVP_PKEY *keypair[2]; +#endif /* OPENSSL_VERSION_NUMBER */ +}; + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) +{ +#ifdef DEBUG_CRYPTO +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum x = NULL, y = NULL, e = NULL, f = NULL; +#else + bignum x = NULL, y = NULL, e = NULL, f = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + ssh_dh_keypair_get_keys(c->dh_ctx, DH_CLIENT_KEYPAIR, &x, &e); + ssh_dh_keypair_get_keys(c->dh_ctx, DH_SERVER_KEYPAIR, &y, &f); + ssh_print_bignum("x", x); + ssh_print_bignum("y", y); + ssh_print_bignum("e", e); + ssh_print_bignum("f", f); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(x); + bignum_safe_free(y); + bignum_safe_free(e); + bignum_safe_free(f); +#endif /* OPENSSL_VERSION_NUMBER */ + + ssh_log_hexdump("Session server cookie", c->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", c->client_kex.cookie, 16); + ssh_print_bignum("k", c->shared_secret); + +#else + (void)c; /* UNUSED_PARAM */ +#endif /* DEBUG_CRYPTO */ +} + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub) +{ + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + + DH_get0_key(ctx->keypair[peer], pub, priv); + + if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + return SSH_ERROR; + } + if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + return SSH_ERROR; + } + + return SSH_OK; +} + +#else +/* If set *priv and *pub should be initialized + * to NULL before calling this function*/ +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + bignum *priv, bignum *pub) +{ + int rc; + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + + if (priv) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PRIV_KEY, + priv); + if (rc != 1) { + return SSH_ERROR; + } + } + if (pub) { + rc = EVP_PKEY_get_bn_param(ctx->keypair[peer], + OSSL_PKEY_PARAM_PUB_KEY, + pub); + if (rc != 1) { + return SSH_ERROR; + } + } + if (priv && (*priv == NULL || bignum_num_bits(*priv) == 0)) { + if (pub && (*pub != NULL && bignum_num_bits(*pub) != 0)) { + bignum_safe_free(*pub); + *pub = NULL; + } + return SSH_ERROR; + } + if (pub && (*pub == NULL || bignum_num_bits(*pub) == 0)) { + if (priv) { + bignum_safe_free(*priv); + *priv = NULL; + } + return SSH_ERROR; + } + + return SSH_OK; +} +#endif /* OPENSSL_VERSION_NUMBER */ + +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + bignum priv, bignum pub) +{ +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + int rc; + OSSL_PARAM *params = NULL, *out_params = NULL, *merged_params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL) || + (ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + (void)DH_set0_key(ctx->keypair[peer], pub, priv); + + return SSH_OK; +#else + rc = EVP_PKEY_todata(ctx->keypair[peer], EVP_PKEY_KEYPAIR, &out_params); + if (rc != 1) { + return SSH_ERROR; + } + + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + rc = SSH_ERROR; + goto out; + } + + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + + if (priv) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, priv); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + } + if (pub) { + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PUB_KEY, pub); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + rc = SSH_ERROR; + goto out; + } + OSSL_PARAM_BLD_free(param_bld); + + merged_params = OSSL_PARAM_merge(out_params, params); + if (merged_params == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[peer]), + EVP_PKEY_PUBLIC_KEY, + merged_params); + if (rc != 1) { + rc = SSH_ERROR; + goto out; + } + + rc = SSH_OK; +out: + bignum_safe_free(priv); + bignum_safe_free(pub); + EVP_PKEY_CTX_free(evp_ctx); + OSSL_PARAM_free(out_params); + OSSL_PARAM_free(params); + OSSL_PARAM_free(merged_params); + + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ +} + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator) +{ + if (ctx == NULL || ctx->keypair[0] == NULL) { + return SSH_ERROR; + } + DH_get0_pqg(ctx->keypair[0], modulus, NULL, generator); + return SSH_OK; +} +#else +int ssh_dh_get_parameters(struct dh_ctx *ctx, + bignum *modulus, bignum *generator) +{ + int rc; + + if (ctx == NULL || ctx->keypair[0] == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_P, (BIGNUM**)modulus); + if (rc != 1) { + return SSH_ERROR; + } + rc = EVP_PKEY_get_bn_param(ctx->keypair[0], OSSL_PKEY_PARAM_FFC_G, (BIGNUM**)generator); + if (rc != 1) { + bignum_safe_free(*modulus); + return SSH_ERROR; + } + + return SSH_OK; +} +#endif /* OPENSSL_VERSION_NUMBER */ + +int ssh_dh_set_parameters(struct dh_ctx *ctx, + const bignum modulus, const bignum generator) +{ + size_t i; + int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM *params = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + if ((ctx == NULL) || (modulus == NULL) || (generator == NULL)) { + return SSH_ERROR; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + evp_ctx = EVP_PKEY_CTX_new_from_name(NULL, "DHX", NULL); +#endif + + for (i = 0; i < 2; i++) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + bignum p = NULL; + bignum g = NULL; + + /* when setting modulus or generator, + * make sure to invalidate existing keys */ + DH_free(ctx->keypair[i]); + ctx->keypair[i] = DH_new(); + if (ctx->keypair[i] == NULL) { + rc = SSH_ERROR; + goto done; + } + + p = BN_dup(modulus); + g = BN_dup(generator); + rc = DH_set0_pqg(ctx->keypair[i], p, NULL, g); + if (rc != 1) { + BN_free(p); + BN_free(g); + rc = SSH_ERROR; + goto done; + } +#else + param_bld = OSSL_PARAM_BLD_new(); + + if (param_bld == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_P, modulus); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_FFC_G, generator); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + OSSL_PARAM_BLD_free(param_bld); + rc = SSH_ERROR; + goto done; + } + OSSL_PARAM_BLD_free(param_bld); + + rc = EVP_PKEY_fromdata_init(evp_ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + /* make sure to invalidate existing keys */ + EVP_PKEY_free(ctx->keypair[i]); + ctx->keypair[i] = NULL; + + rc = EVP_PKEY_fromdata(evp_ctx, + &(ctx->keypair[i]), + EVP_PKEY_KEY_PARAMETERS, + params); + if (rc != 1) { + OSSL_PARAM_free(params); + rc = SSH_ERROR; + goto done; + } + + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ + } + + rc = SSH_OK; +#if OPENSSL_VERSION_NUMBER < 0x30000000L +done: + if (rc != SSH_OK) { + DH_free(ctx->keypair[0]); + DH_free(ctx->keypair[1]); + } +#else +done: + EVP_PKEY_CTX_free(evp_ctx); + + if (rc != SSH_OK) { + EVP_PKEY_free(ctx->keypair[0]); + EVP_PKEY_free(ctx->keypair[1]); + } +#endif /* OPENSSL_VERSION_NUMBER */ + if (rc != SSH_OK) { + ctx->keypair[0] = NULL; + ctx->keypair[1] = NULL; + } + + return rc; +} + +/** + * @internal + * @brief allocate and initialize ephemeral values used in dh kex + */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto) +{ + struct dh_ctx *ctx = NULL; + int rc; + + /* Cleanup any previously allocated dh_ctx */ + if (crypto->dh_ctx != NULL) { + ssh_dh_cleanup(crypto); + } + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return SSH_ERROR; + } + crypto->dh_ctx = ctx; + + switch (crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group1, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group18, ssh_dh_generator); + break; + default: + rc = SSH_OK; + break; + } + + if (rc != SSH_OK) { + ssh_dh_cleanup(crypto); + } + return rc; +} + +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) +{ + if (crypto->dh_ctx != NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + DH_free(crypto->dh_ctx->keypair[0]); + DH_free(crypto->dh_ctx->keypair[1]); +#else + EVP_PKEY_free(crypto->dh_ctx->keypair[0]); + EVP_PKEY_free(crypto->dh_ctx->keypair[1]); +#endif /* OPENSSL_VERSION_NUMBER */ + free(crypto->dh_ctx); + crypto->dh_ctx = NULL; + } +} + +/** @internal + * @brief generates a secret DH parameter of at least DH_SECURITY_BITS + * security as well as the corresponding public key. + * + * @param[out] params a dh_ctx that will hold the new keys. + * @param peer Select either client or server key storage. Valid values are: + * DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) +{ + int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX *evp_ctx = NULL; +#endif + + if ((dh_ctx == NULL) || (dh_ctx->keypair[peer] == NULL)) { + return SSH_ERROR; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + rc = DH_generate_key(dh_ctx->keypair[peer]); + if (rc != 1) { + return SSH_ERROR; + } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[peer], NULL); + if (evp_ctx == NULL) { + return SSH_ERROR; + } + + rc = EVP_PKEY_keygen_init(evp_ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_generate(evp_ctx, &(dh_ctx->keypair[peer])); + if (rc != 1) { + EVP_PKEY_CTX_free(evp_ctx); + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate DH: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + EVP_PKEY_CTX_free(evp_ctx); +#endif /* OPENSSL_VERSION_NUMBER */ + + return SSH_OK; +} + +/** @internal + * @brief generates a shared secret between the local peer and the remote + * peer. The local peer must have been initialized using either the + * ssh_dh_keypair_gen_keys() function or by seetting manually both + * the private and public keys. The remote peer only needs to have + * the remote's peer public key set. + * @param[in] local peer identifier (DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR) + * @param[in] remote peer identifier (DH_CLIENT_KEYPAIR or DH_SERVER_KEYPAIR) + * @param[out] dest a new bignum with the shared secret value is returned. + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, + bignum *dest) +{ + unsigned char *kstring = NULL; + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum pub_key = NULL; + int klen; +#else + size_t klen; + EVP_PKEY_CTX *evp_ctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + if ((dh_ctx == NULL) || + (dh_ctx->keypair[local] == NULL) || + (dh_ctx->keypair[remote] == NULL)) { + return SSH_ERROR; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + kstring = malloc(DH_size(dh_ctx->keypair[local])); + if (kstring == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = ssh_dh_keypair_get_keys(dh_ctx, remote, NULL, &pub_key); + if (rc != SSH_OK) { + rc = SSH_ERROR; + goto done; + } + + klen = DH_compute_key(kstring, pub_key, dh_ctx->keypair[local]); + if (klen == -1) { + rc = SSH_ERROR; + goto done; + } +#else + evp_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, dh_ctx->keypair[local], NULL); + + rc = EVP_PKEY_derive_init(evp_ctx); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive_set_peer(evp_ctx, dh_ctx->keypair[remote]); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set peer key: %s", + ERR_error_string(ERR_get_error(), NULL)); + rc = SSH_ERROR; + goto done; + } + + /* getting the size of the secret */ + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } + + kstring = malloc(klen); + if (kstring == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = EVP_PKEY_derive(evp_ctx, kstring, &klen); + if (rc != 1) { + rc = SSH_ERROR; + goto done; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + *dest = BN_bin2bn(kstring, (int)klen, NULL); + if (*dest == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = SSH_OK; +done: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_PKEY_CTX_free(evp_ctx); +#endif + free(kstring); + return rc; +} diff --git a/src/libs/libssh-0.12.2/src/dh_key.c b/src/libs/libssh-0.12.2/src/dh_key.c new file mode 100644 index 000000000000..cd8b2a85def9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/dh_key.c @@ -0,0 +1,411 @@ +/* + * dh-int.c - Diffie-Helman algorithm code against SSH 2 + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2018 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2012 by Dmitriy Kuznetsov + * Copyright (c) 2019 by Simo Sorce + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +extern bignum ssh_dh_generator; +extern bignum ssh_dh_group1; +extern bignum ssh_dh_group14; +extern bignum ssh_dh_group16; +extern bignum ssh_dh_group18; + +/* + * How many bits of security we want for fast DH. DH private key size must be + * twice that size. + */ +#define DH_SECURITY_BITS 512 + +struct dh_keypair { + bignum priv_key; + bignum pub_key; +}; + +struct dh_ctx { + /* 0 is client, 1 is server */ + struct dh_keypair keypair[2]; + bignum generator; + bignum modulus; +}; + +void ssh_dh_debug_crypto(struct ssh_crypto_struct *c) +{ +#ifdef DEBUG_CRYPTO + const_bignum x = NULL, y = NULL, e = NULL, f = NULL; + + ssh_dh_keypair_get_keys(c->dh_ctx, DH_CLIENT_KEYPAIR, &x, &e); + ssh_dh_keypair_get_keys(c->dh_ctx, DH_SERVER_KEYPAIR, &y, &f); + ssh_print_bignum("p", c->dh_ctx->modulus); + ssh_print_bignum("g", c->dh_ctx->generator); + ssh_print_bignum("x", x); + ssh_print_bignum("y", y); + ssh_print_bignum("e", e); + ssh_print_bignum("f", f); + + ssh_log_hexdump("Session server cookie", c->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", c->client_kex.cookie, 16); + ssh_print_bignum("k", c->shared_secret); +#else + (void)c; /* UNUSED_PARAM */ +#endif +} + +static void ssh_dh_free_modulus(struct dh_ctx *ctx) +{ + if ((ctx->modulus != ssh_dh_group1) && + (ctx->modulus != ssh_dh_group14) && + (ctx->modulus != ssh_dh_group16) && + (ctx->modulus != ssh_dh_group18)) { + bignum_safe_free(ctx->modulus); + } + ctx->modulus = NULL; +} + +static void ssh_dh_free_generator(struct dh_ctx *ctx) +{ + if (ctx->generator != ssh_dh_generator) { + bignum_safe_free(ctx->generator); + } +} + +static void ssh_dh_free_dh_keypair(struct dh_keypair *keypair) +{ + bignum_safe_free(keypair->priv_key); + bignum_safe_free(keypair->pub_key); +} + +static int ssh_dh_init_dh_keypair(struct dh_keypair *keypair) +{ + int rc; + + keypair->priv_key = bignum_new(); + if (keypair->priv_key == NULL) { + rc = SSH_ERROR; + goto done; + } + keypair->pub_key = bignum_new(); + if (keypair->pub_key == NULL) { + rc = SSH_ERROR; + goto done; + } + + rc = SSH_OK; +done: + if (rc != SSH_OK) { + ssh_dh_free_dh_keypair(keypair); + } + return rc; +} + +int ssh_dh_keypair_get_keys(struct dh_ctx *ctx, int peer, + const_bignum *priv, const_bignum *pub) +{ + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL)) { + return SSH_ERROR; + } + + if (priv) { + /* check that we have something in it */ + if (bignum_num_bits(ctx->keypair[peer].priv_key)) { + *priv = ctx->keypair[peer].priv_key; + } else { + return SSH_ERROR; + } + } + + if (pub) { + /* check that we have something in it */ + if (bignum_num_bits(ctx->keypair[peer].pub_key)) { + *pub = ctx->keypair[peer].pub_key; + } else { + return SSH_ERROR; + } + } + + return SSH_OK; +} + +int ssh_dh_keypair_set_keys(struct dh_ctx *ctx, int peer, + bignum priv, bignum pub) +{ + if (((peer != DH_CLIENT_KEYPAIR) && (peer != DH_SERVER_KEYPAIR)) || + ((priv == NULL) && (pub == NULL)) || (ctx == NULL)) { + return SSH_ERROR; + } + + if (priv) { + bignum_safe_free(ctx->keypair[peer].priv_key); + ctx->keypair[peer].priv_key = priv; + } + if (pub) { + int rc; + bignum one = bignum_new(); + bignum pmin1 = bignum_new(); + if (one == NULL || pmin1 == NULL) { + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_ERROR; + } + rc = bignum_set_word(one, 1); + if (rc != 1) { + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_ERROR; + } + bignum_sub(pmin1, ctx->modulus, one); + + /* Validate the peer public key `x` is 1 < x < (modulus - 1) */ + if (bignum_cmp(pub, one) <= 0 || + bignum_cmp(pub, pmin1) >= 0) { + bignum_safe_free(one); + bignum_safe_free(pmin1); + return SSH_ERROR; + } + bignum_safe_free(one); + bignum_safe_free(pmin1); + + bignum_safe_free(ctx->keypair[peer].pub_key); + ctx->keypair[peer].pub_key = pub; + } + return SSH_OK; +} + +int ssh_dh_get_parameters(struct dh_ctx *ctx, + const_bignum *modulus, const_bignum *generator) +{ + if (ctx == NULL) { + return SSH_ERROR; + } + if (modulus) { + *modulus = ctx->modulus; + } + if (generator) { + *generator = ctx->generator; + } + + return SSH_OK; +} + +int ssh_dh_set_parameters(struct dh_ctx *ctx, + bignum modulus, bignum generator) +{ + int rc; + + if ((ctx == NULL) || ((modulus == NULL) && (generator == NULL))) { + return SSH_ERROR; + } + /* when setting modulus or generator, + * make sure to invalidate existing keys */ + ssh_dh_free_dh_keypair(&ctx->keypair[DH_CLIENT_KEYPAIR]); + ssh_dh_free_dh_keypair(&ctx->keypair[DH_SERVER_KEYPAIR]); + + rc = ssh_dh_init_dh_keypair(&ctx->keypair[DH_CLIENT_KEYPAIR]); + if (rc != SSH_OK) { + goto done; + } + rc = ssh_dh_init_dh_keypair(&ctx->keypair[DH_SERVER_KEYPAIR]); + if (rc != SSH_OK) { + goto done; + } + + if (modulus) { + ssh_dh_free_modulus(ctx); + ctx->modulus = modulus; + } + if (generator) { + ssh_dh_free_generator(ctx); + ctx->generator = generator; + } + +done: + return rc; +} + +/** + * @internal + * @brief allocate and initialize ephemeral values used in dh kex + */ +int ssh_dh_init_common(struct ssh_crypto_struct *crypto) +{ + struct dh_ctx *ctx = NULL; + int rc; + + /* Cleanup any previously allocated dh_ctx */ + if (crypto->dh_ctx != NULL) { + ssh_dh_cleanup(crypto); + } + + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return SSH_ERROR; + } + + switch (crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group1, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group14, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group16, ssh_dh_generator); + break; + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_dh_set_parameters(ctx, ssh_dh_group18, ssh_dh_generator); + break; + default: + rc = SSH_OK; + break; + } + + crypto->dh_ctx = ctx; + + if (rc != SSH_OK) { + ssh_dh_cleanup(crypto); + } + return rc; +} + +void ssh_dh_cleanup(struct ssh_crypto_struct *crypto) +{ + struct dh_ctx *ctx = crypto->dh_ctx; + + if (ctx == NULL) { + return; + } + + ssh_dh_free_dh_keypair(&ctx->keypair[DH_CLIENT_KEYPAIR]); + ssh_dh_free_dh_keypair(&ctx->keypair[DH_SERVER_KEYPAIR]); + + ssh_dh_free_modulus(ctx); + ssh_dh_free_generator(ctx); + free(ctx); + crypto->dh_ctx = NULL; +} + +/** @internal + * @brief generates a secret DH parameter of at least DH_SECURITY_BITS + * security as well as the corresponding public key. + * + * @param[out] params a dh_kex parameters structure with preallocated bignum + * where to store the parameters + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_keypair_gen_keys(struct dh_ctx *dh_ctx, int peer) +{ + bignum tmp = NULL; + bignum_CTX ctx = NULL; + int rc = 0; + int bits = 0; + int p_bits = 0; + + ctx = bignum_ctx_new(); + if (bignum_ctx_invalid(ctx)){ + goto error; + } + tmp = bignum_new(); + if (tmp == NULL) { + goto error; + } + p_bits = bignum_num_bits(dh_ctx->modulus); + /* we need at most DH_SECURITY_BITS */ + bits = MIN(DH_SECURITY_BITS * 2, p_bits); + /* ensure we're not too close of p so rnd()%p stays uniform */ + if (bits <= p_bits && bits + 64 > p_bits) { + bits += 64; + } + rc = bignum_rand(tmp, bits); + if (rc != 1) { + goto error; + } + rc = bignum_mod(dh_ctx->keypair[peer].priv_key, tmp, dh_ctx->modulus, ctx); + if (rc != 1) { + goto error; + } + /* Now compute the corresponding public key */ + rc = bignum_mod_exp(dh_ctx->keypair[peer].pub_key, dh_ctx->generator, + dh_ctx->keypair[peer].priv_key, dh_ctx->modulus, ctx); + if (rc != 1) { + goto error; + } + bignum_safe_free(tmp); + bignum_ctx_free(ctx); + return SSH_OK; +error: + bignum_safe_free(tmp); + bignum_ctx_free(ctx); + return SSH_ERROR; +} + +/** @internal + * @brief generates a shared secret between the local peer and the remote peer + * @param[in] local peer identifier + * @param[in] remote peer identifier + * @param[out] dest a preallocated bignum where to store parameter + * @return SSH_OK on success, SSH_ERROR on error + */ +int ssh_dh_compute_shared_secret(struct dh_ctx *dh_ctx, int local, int remote, + bignum *dest) +{ + int rc; + bignum_CTX ctx = bignum_ctx_new(); + if (bignum_ctx_invalid(ctx)) { + return -1; + } + + if (*dest == NULL) { + *dest = bignum_new(); + if (*dest == NULL) { + rc = 0; + goto done; + } + } + + rc = bignum_mod_exp(*dest, dh_ctx->keypair[remote].pub_key, + dh_ctx->keypair[local].priv_key, + dh_ctx->modulus, ctx); + +done: + bignum_ctx_free(ctx); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/ecdh.c b/src/libs/libssh-0.12.2/src/ecdh.c new file mode 100644 index 000000000000..af80beecd53f --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ecdh.c @@ -0,0 +1,130 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#ifdef HAVE_ECDH + +static SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply); + +static ssh_packet_callback ecdh_client_callbacks[]= { + ssh_packet_client_ecdh_reply +}; + +struct ssh_packet_callbacks_struct ssh_ecdh_client_callbacks = { + .start = SSH2_MSG_KEX_ECDH_REPLY, + .n_callbacks = 1, + .callbacks = ecdh_client_callbacks, + .user = NULL +}; + +void ssh_client_ecdh_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_ecdh_client_callbacks); +} + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +SSH_PACKET_CALLBACK(ssh_packet_client_ecdh_reply){ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_client_ecdh_remove_callbacks(session); + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session,SSH_FATAL, "No public key in packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session,SSH_FATAL, "No Q_S ECC point in packet"); + goto error; + } + session->next_crypto->ecdh_server_pubkey = q_s_string; + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature=NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ecdh_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; + +error: + session->session_state=SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static ssh_packet_callback ecdh_server_callbacks[] = { + ssh_packet_server_ecdh_init +}; + +struct ssh_packet_callbacks_struct ssh_ecdh_server_callbacks = { + .start = SSH2_MSG_KEX_ECDH_INIT, + .n_callbacks = 1, + .callbacks = ecdh_server_callbacks, + .user = NULL +}; + +/** @internal + * @brief sets up the ecdh kex callbacks + */ +void ssh_server_ecdh_init(ssh_session session){ + ssh_packet_set_callbacks(session, &ssh_ecdh_server_callbacks); +} + +#endif /* WITH_SERVER */ +#endif /* HAVE_ECDH */ diff --git a/src/libs/libssh-0.12.2/src/ecdh_crypto.c b/src/libs/libssh-0.12.2/src/ecdh_crypto.c new file mode 100644 index 000000000000..6b4e7706cd7d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ecdh_crypto.c @@ -0,0 +1,579 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#ifdef HAVE_ECDH +#include +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#define NISTP256 NID_X9_62_prime256v1 +#define NISTP384 NID_secp384r1 +#define NISTP521 NID_secp521r1 +#else +#include +#include +#include +#include +#include +#include "libcrypto-compat.h" +#endif /* OPENSSL_VERSION_NUMBER */ + +/** @internal + * @brief Map the given key exchange enum value to its curve name. + */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L +static int ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#else +static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) { +#endif /* OPENSSL_VERSION_NUMBER */ + switch (kex_type) { + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + return NISTP256; + case SSH_KEX_ECDH_SHA2_NISTP384: +#if HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + return NISTP384; + case SSH_KEX_ECDH_SHA2_NISTP521: + return NISTP521; + default: +#if OPENSSL_VERSION_NUMBER < 0x30000000L + return SSH_ERROR; +#else + return NULL; +#endif + } +} + +/* @internal + * @brief Generate ECDH key pair for ecdh key exchange and store it in the + * session->next_crypto structure + */ +static ssh_string ssh_ecdh_generate(ssh_session session) +{ + ssh_string pubkey_string = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const EC_POINT *point = NULL; + const EC_GROUP *group = NULL; + EC_KEY *key = NULL; + int curve; +#else + EC_POINT *point = NULL; + EC_GROUP *group = NULL; + const char *curve = NULL; + EVP_PKEY *key = NULL; + OSSL_PARAM *out_params = NULL; + const OSSL_PARAM *pubkey_param = NULL; + const void *pubkey = NULL; + size_t pubkey_len; + int nid; + int rc; +#endif /* OPENSSL_VERSION_NUMBER */ + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + if (curve == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Failed to get curve name"); + return NULL; + } + + key = EC_KEY_new_by_curve_name(curve); +#else + if (curve == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to get curve name"); + return NULL; + } + + key = EVP_EC_gen(curve); +#endif /* OPENSSL_VERSION_NUMBER */ + if (key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to generate key"); + return NULL; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + group = EC_KEY_get0_group(key); + + EC_KEY_generate_key(key); + + point = EC_KEY_get0_public_key(key); + + pubkey_string = pki_key_make_ecpoint_string(group, point); +#else + rc = EVP_PKEY_todata(key, EVP_PKEY_PUBLIC_KEY, &out_params); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export public key"); + EVP_PKEY_free(key); + return NULL; + } + + pubkey_param = OSSL_PARAM_locate_const(out_params, OSSL_PKEY_PARAM_PUB_KEY); + if (pubkey_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to find public key"); + EVP_PKEY_free(key); + OSSL_PARAM_free(out_params); + return NULL; + } + + rc = OSSL_PARAM_get_octet_string_ptr(pubkey_param, + (const void**)&pubkey, + &pubkey_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to read public key"); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + + /* Convert the data to low-level representation */ + nid = pki_key_ecgroup_name_to_nid(curve); + group = EC_GROUP_new_by_curve_name_ex(NULL, NULL, nid); + if (group == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Could not create group: %s", + ERR_error_string(ERR_get_error(), NULL)); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + point = EC_POINT_new(group); + if (point == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Could not create point: %s", + ERR_error_string(ERR_get_error(), NULL)); + EC_GROUP_free(group); + OSSL_PARAM_free(out_params); + EVP_PKEY_free(key); + return NULL; + } + rc = EC_POINT_oct2point(group, point, pubkey, pubkey_len, NULL); + OSSL_PARAM_free(out_params); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export public key"); + EC_GROUP_free(group); + EC_POINT_free(point); + EVP_PKEY_free(key); + return NULL; + } + + pubkey_string = pki_key_make_ecpoint_string(group, point); + EC_GROUP_free(group); + EC_POINT_free(point); +#endif /* OPENSSL_VERSION_NUMBER */ + if (pubkey_string == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to convert public key"); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(key); +#else + EVP_PKEY_free(key); +#endif /* OPENSSL_VERSION_NUMBER */ + return NULL; + } + + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(session->next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(session->next_crypto->ecdh_privkey); +#endif + session->next_crypto->ecdh_privkey = NULL; + } + + session->next_crypto->ecdh_privkey = key; + return pubkey_string; +} + +/** @internal + * @brief Set up a nistp{256,384,521} key pair for ECDH key exchange. + */ +int ssh_ecdh_init(ssh_session session) +{ + ssh_string pubkey = NULL; + ssh_string *pubkey_loc = NULL; + + pubkey = ssh_ecdh_generate(session); + if (pubkey == NULL) { + return SSH_ERROR; + } + + if (session->server) { + pubkey_loc = &session->next_crypto->ecdh_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->ecdh_client_pubkey; + } + + ssh_string_free(*pubkey_loc); + *pubkey_loc = pubkey; + + return SSH_OK; +} + +/** @internal + * @brief Starts ecdh-sha2-nistp256 key exchange + */ +int ssh_client_ecdh_init(ssh_session session) +{ + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, + session->next_crypto->ecdh_client_pubkey); + if (rc < 0) { + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + + return rc; +} + +int ecdh_build_k(ssh_session session) +{ + struct ssh_crypto_struct *next_crypto = session->next_crypto; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const EC_GROUP *group = EC_KEY_get0_group(next_crypto->ecdh_privkey); + EC_POINT *pubkey = NULL; + void *buffer = NULL; + int rc; + int len = (EC_GROUP_get_degree(group) + 7) / 8; + bignum_CTX ctx = bignum_ctx_new(); + if (ctx == NULL) { + return -1; + } + pubkey = EC_POINT_new(group); + if (pubkey == NULL) { + bignum_ctx_free(ctx); + return -1; + } + + if (session->server) { + rc = EC_POINT_oct2point(group, + pubkey, + ssh_string_data(next_crypto->ecdh_client_pubkey), + ssh_string_len(next_crypto->ecdh_client_pubkey), + ctx); + } else { + rc = EC_POINT_oct2point(group, + pubkey, + ssh_string_data(next_crypto->ecdh_server_pubkey), + ssh_string_len(next_crypto->ecdh_server_pubkey), + ctx); + } + bignum_ctx_free(ctx); + if (rc <= 0) { + EC_POINT_clear_free(pubkey); + return -1; + } + + buffer = malloc(len); + if (buffer == NULL) { + EC_POINT_clear_free(pubkey); + return -1; + } + + rc = ECDH_compute_key(buffer, + len, + pubkey, + next_crypto->ecdh_privkey, + NULL); + EC_POINT_clear_free(pubkey); + if (rc <= 0) { + free(buffer); + return -1; + } + + bignum_bin2bn(buffer, len, &next_crypto->shared_secret); + free(buffer); +#else + const char *curve = NULL; + EVP_PKEY *pubkey = NULL; + void *secret = NULL; + size_t secret_len; + int rc; + ssh_string peer_pubkey = NULL; + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + EVP_PKEY_CTX *dh_ctx = EVP_PKEY_CTX_new_from_pkey(NULL, + next_crypto->ecdh_privkey, + NULL); + + if (dh_ctx == NULL || param_bld == NULL) { + ssh_set_error_oom(session); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + rc = EVP_PKEY_derive_init(dh_ctx); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not init PKEY derive: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + if (session->server) { + peer_pubkey = next_crypto->ecdh_client_pubkey; + } else { + peer_pubkey = next_crypto->ecdh_server_pubkey; + } + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(peer_pubkey), + ssh_string_len(peer_pubkey)); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not push the pub key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + curve = ecdh_kex_type_to_curve(next_crypto->kex_type); + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_PKEY_PARAM_GROUP_NAME, + (char *)curve, + strlen(curve)); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not push the group name: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + OSSL_PARAM_BLD_free(param_bld); + return -1; + } + + rc = evp_build_pkey("EC", param_bld, &pubkey, EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Could not build the pkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + rc = EVP_PKEY_derive_set_peer(dh_ctx, pubkey); + EVP_PKEY_free(pubkey); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not set peer pubkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + /* get the max length of the secret */ + rc = EVP_PKEY_derive(dh_ctx, NULL, &secret_len); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not set peer pubkey: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + secret = malloc(secret_len); + if (secret == NULL) { + ssh_set_error_oom(session); + EVP_PKEY_CTX_free(dh_ctx); + return -1; + } + + rc = EVP_PKEY_derive(dh_ctx, secret, &secret_len); + if (rc != 1) { + ssh_set_error(session, + SSH_FATAL, + "Could not derive shared key: %s", + ERR_error_string(ERR_get_error(), NULL)); + EVP_PKEY_CTX_free(dh_ctx); + free(secret); + return -1; + } + + EVP_PKEY_CTX_free(dh_ctx); + + bignum_bin2bn(secret, secret_len, &next_crypto->shared_secret); + free(secret); +#endif /* OPENSSL_VERSION_NUMBER */ + if (next_crypto->shared_secret == NULL) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; + return -1; + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(next_crypto->ecdh_privkey); +#else + EVP_PKEY_free(next_crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ + next_crypto->ecdh_privkey = NULL; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + next_crypto->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", + next_crypto->client_kex.cookie, 16); + ssh_print_bignum("Shared secret key", next_crypto->shared_secret); +#endif /* DEBUG_CRYPTO */ + + return 0; +} + +#ifdef WITH_SERVER + +/** @brief Handle a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init) +{ + /* ECDH keys */ + ssh_string q_c_string = NULL; + /* SSH host keys (rsa, ed25519 and ecdsa) */ + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "Processing SSH_MSG_KEXDH_INIT"); + + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); + /* Extract the client pubkey from the init packet */ + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); + goto error; + } + session->next_crypto->ecdh_client_pubkey = q_c_string; + + rc = ssh_ecdh_init(session); + if (rc < 0) { + goto error; + } + + /* build k and session_id */ + rc = ecdh_build_k(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + SSH_STRING_FREE(sig_blob); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bSSS", + SSH2_MSG_KEXDH_REPLY, + pubkey_blob, /* host's pubkey */ + session->next_crypto->ecdh_server_pubkey, /* ecdh public key */ + sig_blob); /* signature blob */ + + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + return SSH_PACKET_USED; +error: + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_ECDH */ diff --git a/src/libs/libssh-0.12.2/src/ecdh_gcrypt.c b/src/libs/libssh-0.12.2/src/ecdh_gcrypt.c new file mode 100644 index 000000000000..ec7dccfd3cc8 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ecdh_gcrypt.c @@ -0,0 +1,396 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/session.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/libgcrypt.h" + +#ifdef HAVE_ECDH +#include + +/** @internal + * @brief Map the given key exchange enum value to its curve name. + */ +static const char *ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) +{ + switch (kex_type) { + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + return "NIST P-256"; + case SSH_KEX_ECDH_SHA2_NISTP384: +#if HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + return "NIST P-384"; + case SSH_KEX_ECDH_SHA2_NISTP521: + return "NIST P-521"; + default: + return NULL; + } +} + +/** @internal + * @brief Set up a nistp{256,384,521} key pair for ECDH key exchange. + */ +int ssh_ecdh_init(ssh_session session) +{ + int rc = SSH_OK; + const char *curve = NULL; + const char *genstring = NULL; + gpg_error_t err; + gcry_sexp_t param = NULL; + gcry_sexp_t key = NULL; + ssh_string pubkey = NULL; + ssh_string *pubkey_loc = NULL; + + if (session->server) { + pubkey_loc = &session->next_crypto->ecdh_server_pubkey; + genstring = "(genkey(ecdh(curve %s) (flags transient-key)))"; + } else { + pubkey_loc = &session->next_crypto->ecdh_client_pubkey; + genstring = "(genkey(ecdh(curve %s)))"; + } + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == NULL) { + rc = SSH_ERROR; + goto out; + } + + err = gcry_sexp_build(¶m, NULL, genstring, curve); + if (err) { + rc = SSH_ERROR; + goto out; + } + + err = gcry_pk_genkey(&key, param); + if (err) { + rc = SSH_ERROR; + goto out; + } + + pubkey = ssh_sexp_extract_mpi(key, "q", GCRYMPI_FMT_USG, GCRYMPI_FMT_STD); + if (pubkey == NULL) { + rc = SSH_ERROR; + goto out; + } + + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { + gcry_sexp_release(session->next_crypto->ecdh_privkey); + session->next_crypto->ecdh_privkey = NULL; + } + session->next_crypto->ecdh_privkey = key; + key = NULL; + + SSH_STRING_FREE(*pubkey_loc); + *pubkey_loc = pubkey; + pubkey = NULL; + +out: + gcry_sexp_release(param); + gcry_sexp_release(key); + SSH_STRING_FREE(pubkey); + return rc; +} + +/** @internal + * @brief Starts ecdh-sha2-nistp{256,384,521} key exchange. + */ +int ssh_client_ecdh_init(ssh_session session) +{ + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, + session->next_crypto->ecdh_client_pubkey); + if (rc < 0) { + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + return rc; +} + +int ecdh_build_k(ssh_session session) +{ + gpg_error_t err; + gcry_sexp_t data = NULL; + gcry_sexp_t result = NULL; + /* We need to get the x coordinate. Libgcrypt 1.7 and above + offers a suitable API for that. */ +#if (GCRYPT_VERSION_NUMBER >= 0x010700) + gcry_mpi_t s = NULL; + gcry_mpi_point_t point; +#else + size_t k_len = 0; + enum ssh_key_exchange_e kex_type = session->next_crypto->kex_type; + ssh_string s = NULL; +#endif + ssh_string pubkey_raw = NULL; + gcry_sexp_t pubkey = NULL; + ssh_string privkey = NULL; + int rc = SSH_ERROR; + const char *curve = NULL; + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == NULL) { + goto out; + } + + pubkey_raw = session->server + ? session->next_crypto->ecdh_client_pubkey + : session->next_crypto->ecdh_server_pubkey; + + err = gcry_sexp_build(&pubkey, + NULL, + "(key-data(public-key(ecdh(curve %s)(q %b))))", + curve, + ssh_string_len(pubkey_raw), + ssh_string_data(pubkey_raw)); + if (err) { + goto out; + } + + privkey = ssh_sexp_extract_mpi(session->next_crypto->ecdh_privkey, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (privkey == NULL) { + goto out; + } + + err = gcry_sexp_build(&data, NULL, + "(data(flags raw)(value %b))", + ssh_string_len(privkey), + ssh_string_data(privkey)); + if (err) { + goto out; + } + + err = gcry_pk_encrypt(&result, data, pubkey); + if (err) { + goto out; + } + +#if (GCRYPT_VERSION_NUMBER >= 0x010700) + err = gcry_sexp_extract_param(result, "", "s", &s, NULL); + if (err) { + goto out; + } + + point = gcry_mpi_point_new(0); + if (point == NULL) { + gcry_mpi_release(s); + goto out; + } + + err = gcry_mpi_ec_decode_point(point, s, NULL); + gcry_mpi_release(s); + if (err) { + goto out; + } + + session->next_crypto->shared_secret = gcry_mpi_new(0); + gcry_mpi_point_snatch_get(session->next_crypto->shared_secret, + NULL, NULL, point); +#else + s = ssh_sexp_extract_mpi(result, "s", GCRYMPI_FMT_USG, GCRYMPI_FMT_USG); + if (s == NULL) { + goto out; + } + + if (kex_type == SSH_KEX_ECDH_SHA2_NISTP256) { + k_len = 65; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP384) { + k_len = 97; + } else if (kex_type == SSH_KEX_ECDH_SHA2_NISTP521) { + k_len = 133; + } else { + ssh_string_burn(s); + SSH_STRING_FREE(s); + goto out; + } + + if (ssh_string_len(s) != k_len) { + ssh_string_burn(s); + SSH_STRING_FREE(s); + goto out; + } + + err = gcry_mpi_scan(&session->next_crypto->shared_secret, + GCRYMPI_FMT_USG, + (const char *)ssh_string_data(s) + 1, + k_len / 2, + NULL); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (err) { + goto out; + } +#endif + + rc = SSH_OK; + gcry_sexp_release(session->next_crypto->ecdh_privkey); + session->next_crypto->ecdh_privkey = NULL; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Session server cookie", + session->next_crypto->server_kex.cookie, 16); + ssh_log_hexdump("Session client cookie", + session->next_crypto->client_kex.cookie, 16); + ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); +#endif + + out: + gcry_sexp_release(pubkey); + gcry_sexp_release(data); + gcry_sexp_release(result); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + return rc; +} + +#ifdef WITH_SERVER + + +/** @brief Handle a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ + ssh_string q_c_string = NULL; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + int rc = SSH_ERROR; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); + + /* Extract the client pubkey from the init packet */ + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); + goto out; + } + session->next_crypto->ecdh_client_pubkey = q_c_string; + + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate a key pair"); + goto out; + } + + /* build k and session_id */ + rc = ecdh_build_k(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto out; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto out; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + rc = SSH_ERROR; + goto out; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + SSH_STRING_FREE(sig_blob); + goto out; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bSSS", + SSH2_MSG_KEXDH_REPLY, + pubkey_blob, /* host's pubkey */ + session->next_crypto->ecdh_server_pubkey, /* ecdh public key */ + sig_blob); /* signature blob */ + + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + goto out; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto out; + } + + out: + if (rc == SSH_ERROR) { + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + } + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_ECDH */ diff --git a/src/libs/libssh-0.12.2/src/ecdh_mbedcrypto.c b/src/libs/libssh-0.12.2/src/ecdh_mbedcrypto.c new file mode 100644 index 000000000000..7d013b74e433 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ecdh_mbedcrypto.c @@ -0,0 +1,326 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/session.h" +#include "libssh/ecdh.h" +#include "libssh/buffer.h" +#include "libssh/ssh2.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/libmbedcrypto.h" + +#include +#include +#include "mbedcrypto-compat.h" + +#ifdef HAVE_ECDH + +static mbedtls_ecp_group_id +ecdh_kex_type_to_curve(enum ssh_key_exchange_e kex_type) +{ + switch (kex_type) { + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + return MBEDTLS_ECP_DP_SECP256R1; + case SSH_KEX_ECDH_SHA2_NISTP384: + return MBEDTLS_ECP_DP_SECP384R1; + case SSH_KEX_ECDH_SHA2_NISTP521: + return MBEDTLS_ECP_DP_SECP521R1; + default: + return MBEDTLS_ECP_DP_NONE; + } + return MBEDTLS_ECP_DP_NONE; +} + +int ssh_ecdh_init(ssh_session session) +{ + int rc; + mbedtls_ecp_group grp; + mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + ssh_string pubkey = NULL; + ssh_string *pubkey_loc = NULL; + + if (session->server) { + pubkey_loc = &session->next_crypto->ecdh_server_pubkey; + } else { + pubkey_loc = &session->next_crypto->ecdh_client_pubkey; + } + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == MBEDTLS_ECP_DP_NONE) { + return SSH_ERROR; + } + + /* Free any previously allocated privkey */ + if (session->next_crypto->ecdh_privkey != NULL) { + mbedtls_ecp_keypair_free(session->next_crypto->ecdh_privkey); + SAFE_FREE(session->next_crypto->ecdh_privkey); + } + + session->next_crypto->ecdh_privkey = malloc(sizeof(mbedtls_ecp_keypair)); + if (session->next_crypto->ecdh_privkey == NULL) { + return SSH_ERROR; + } + + ecdh_privkey = session->next_crypto->ecdh_privkey; + + mbedtls_ecp_keypair_init(ecdh_privkey); + mbedtls_ecp_group_init(&grp); + + rc = mbedtls_ecp_group_load(&grp, curve); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + rc = mbedtls_ecp_gen_keypair(&grp, + &ecdh_privkey->MBEDTLS_PRIVATE(d), + &ecdh_privkey->MBEDTLS_PRIVATE(Q), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + pubkey = make_ecpoint_string(&grp, &ecdh_privkey->MBEDTLS_PRIVATE(Q)); + if (pubkey == NULL) { + rc = SSH_ERROR; + goto out; + } + + SSH_STRING_FREE(*pubkey_loc); + *pubkey_loc = pubkey; + pubkey = NULL; + +out: + mbedtls_ecp_group_free(&grp); + SSH_STRING_FREE(pubkey); + return rc; +} + +int ssh_client_ecdh_init(ssh_session session) +{ + int rc; + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_INIT); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, + session->next_crypto->ecdh_client_pubkey); + if (rc < 0) { + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_ecdh_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + rc = ssh_packet_send(session); + + return rc; +} + +int ecdh_build_k(ssh_session session) +{ + mbedtls_ecp_group grp; + mbedtls_ecp_point pubkey; + int rc; + mbedtls_ecp_group_id curve; + mbedtls_ctr_drbg_context *ctr_drbg = NULL; + mbedtls_ecp_keypair *ecdh_privkey = NULL; + + ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); + + curve = ecdh_kex_type_to_curve(session->next_crypto->kex_type); + if (curve == MBEDTLS_ECP_DP_NONE) { + return SSH_ERROR; + } + + mbedtls_ecp_group_init(&grp); + mbedtls_ecp_point_init(&pubkey); + + rc = mbedtls_ecp_group_load(&grp, curve); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + if (session->server) { + rc = mbedtls_ecp_point_read_binary(&grp, &pubkey, + ssh_string_data(session->next_crypto->ecdh_client_pubkey), + ssh_string_len(session->next_crypto->ecdh_client_pubkey)); + } else { + rc = mbedtls_ecp_point_read_binary(&grp, &pubkey, + ssh_string_data(session->next_crypto->ecdh_server_pubkey), + ssh_string_len(session->next_crypto->ecdh_server_pubkey)); + } + + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + + session->next_crypto->shared_secret = malloc(sizeof(mbedtls_mpi)); + if (session->next_crypto->shared_secret == NULL) { + rc = SSH_ERROR; + goto out; + } + + mbedtls_mpi_init(session->next_crypto->shared_secret); + + ecdh_privkey = session->next_crypto->ecdh_privkey; + rc = mbedtls_ecdh_compute_shared(&grp, + session->next_crypto->shared_secret, + &pubkey, + &ecdh_privkey->MBEDTLS_PRIVATE(d), + mbedtls_ctr_drbg_random, + ctr_drbg); + if (rc != 0) { + rc = SSH_ERROR; + goto out; + } + +out: + mbedtls_ecp_keypair_free(ecdh_privkey); + SAFE_FREE(session->next_crypto->ecdh_privkey); + mbedtls_ecp_group_free(&grp); + mbedtls_ecp_point_free(&pubkey); + return rc; +} + +#ifdef WITH_SERVER + +SSH_PACKET_CALLBACK(ssh_packet_server_ecdh_init){ + ssh_string q_c_string = NULL; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + ssh_string pubkey_blob = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_ecdh_server_callbacks); + + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No Q_C ECC point in packet"); + return SSH_ERROR; + } + + session->next_crypto->ecdh_client_pubkey = q_c_string; + + rc = ssh_ecdh_init(session); + if (rc < 0) { + return SSH_ERROR; + } + + /* build k and session_id */ + rc = ecdh_build_k(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto out; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + rc = SSH_ERROR; + goto out; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + rc = SSH_ERROR; + goto out; + } + + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + rc = SSH_ERROR; + goto out; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + SSH_STRING_FREE(sig_blob); + goto out; + } + + rc = ssh_buffer_pack(session->out_buffer, "bSSS", + SSH2_MSG_KEXDH_REPLY, + pubkey_blob, /* host's pubkey */ + session->next_crypto->ecdh_server_pubkey, /* ecdh public key */ + sig_blob); /* signature blob */ + + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(pubkey_blob); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEXDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + rc = SSH_ERROR; + goto out; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto out; + } + +out: + if (rc == SSH_ERROR) { + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + } + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ +#endif diff --git a/src/libs/libssh-0.12.2/src/error.c b/src/libs/libssh-0.12.2/src/error.c new file mode 100644 index 000000000000..3f8d78cd8f9d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/error.c @@ -0,0 +1,154 @@ +/* + * error.c - functions for ssh error handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include "libssh/priv.h" +#include "libssh/session.h" + +/** + * @defgroup libssh_error The SSH error functions + * @ingroup libssh + * + * Functions for error handling. + * + * @{ + */ + +/** + * @internal + * + * @brief Registers an error with a description. + * + * @param error The place to store the error. + * + * @param code The class of error. + * + * @param descr The description, which can be a format string. + * + * @param ... The arguments for the format string. + */ +void _ssh_set_error(void *error, + int code, + const char *function, + const char *descr, ...) +{ + struct ssh_common_struct *err = error; + va_list va; + + va_start(va, descr); + vsnprintf(err->error.error_buffer, ERROR_BUFFERLEN, descr, va); + va_end(va); + + err->error.error_code = code; + if (ssh_get_log_level() == SSH_LOG_TRACE) { + ssh_log_function(SSH_LOG_TRACE, + function, + err->error.error_buffer); + } +} + +/** + * @internal + * + * @brief Registers an out of memory error + * + * @param error The place to store the error. + * + */ +void _ssh_set_error_oom(void *error, const char *function) +{ + struct error_struct *err = error; + + snprintf(err->error_buffer, sizeof(err->error_buffer), + "%s: Out of memory", function); + err->error_code = SSH_FATAL; +} + +/** + * @internal + * + * @brief Registers an invalid argument error + * + * @param error The place to store the error. + * + * @param function The function the error happened in. + * + */ +void _ssh_set_error_invalid(void *error, const char *function) +{ + _ssh_set_error(error, SSH_FATAL, function, + "Invalid argument in %s", function); +} + +/** + * @internal + * + * @brief Reset the error code and message + * + * @param error The place to reset the error. + */ +void ssh_reset_error(void *error) +{ + struct ssh_common_struct *err = error; + + ZERO_STRUCT(err->error.error_buffer); + err->error.error_code = 0; +} + +/** + * @brief Retrieve the error text message from the last error. + * + * @param error An ssh_session or ssh_bind. + * + * @return A static string describing the error. + */ +const char *ssh_get_error(void *error) { + struct error_struct *err = error; + + return err->error_buffer; +} + +/** + * @brief Retrieve the error code from the last error. + * + * @param error An ssh_session or ssh_bind. + * + * \return SSH_NO_ERROR No error occurred\n + * SSH_REQUEST_DENIED The last request was denied but situation is + * recoverable\n + * SSH_FATAL A fatal error occurred. This could be an unexpected + * disconnection\n + * + * Other error codes are internal but can be considered the same as + * SSH_FATAL. + */ +int ssh_get_error_code(void *error) { + struct error_struct *err = error; + + return err->error_code; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/external/bcrypt_pbkdf.c b/src/libs/libssh-0.12.2/src/external/bcrypt_pbkdf.c new file mode 100644 index 000000000000..b05b87f21e52 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/bcrypt_pbkdf.c @@ -0,0 +1,191 @@ +/* $OpenBSD: bcrypt_pbkdf.c,v 1.4 2013/07/29 00:55:53 tedu Exp $ */ +/* + * Copyright (c) 2013 Ted Unangst + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +//#include "includes.h" + +#ifndef HAVE_BCRYPT_PBKDF + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/wrapper.h" +#include +#include +#ifdef HAVE_SYS_PARAM_H +#include +#endif + +#include "libssh/blf.h" +#include "libssh/pki_priv.h" +#ifndef SHA512_DIGEST_LENGTH +#define SHA512_DIGEST_LENGTH SHA512_DIGEST_LEN +#endif + +/* + * pkcs #5 pbkdf2 implementation using the "bcrypt" hash + * + * The bcrypt hash function is derived from the bcrypt password hashing + * function with the following modifications: + * 1. The input password and salt are preprocessed with SHA512. + * 2. The output length is expanded to 256 bits. + * 3. Subsequently the magic string to be encrypted is lengthened and modified + * to "OxychromaticBlowfishSwatDynamite" + * 4. The hash function is defined to perform 64 rounds of initial state + * expansion. (More rounds are performed by iterating the hash.) + * + * Note that this implementation pulls the SHA512 operations into the caller + * as a performance optimization. + * + * One modification from official pbkdf2. Instead of outputting key material + * linearly, we mix it. pbkdf2 has a known weakness where if one uses it to + * generate (i.e.) 512 bits of key material for use as two 256 bit keys, an + * attacker can merely run once through the outer loop below, but the user + * always runs it twice. Shuffling output bytes requires computing the + * entirety of the key material to assemble any subkey. This is something a + * wise caller could do; we just do it for you. + */ + +#define BCRYPT_BLOCKS 8 +#define BCRYPT_HASHSIZE (BCRYPT_BLOCKS * 4) + +static void +bcrypt_hash(ssh_blf_ctx *state, uint8_t *sha2pass, uint8_t *sha2salt, uint8_t *out) +{ + uint8_t ciphertext[BCRYPT_HASHSIZE] = + "OxychromaticBlowfishSwatDynamite"; + uint32_t cdata[BCRYPT_BLOCKS]; + int i; + uint16_t j; + uint16_t shalen = SHA512_DIGEST_LENGTH; + + /* key expansion */ + Blowfish_initstate(state); + Blowfish_expandstate(state, sha2salt, shalen, sha2pass, shalen); + for (i = 0; i < 64; i++) { + Blowfish_expand0state(state, sha2salt, shalen); + Blowfish_expand0state(state, sha2pass, shalen); + } + + /* encryption */ + j = 0; + for (i = 0; i < BCRYPT_BLOCKS; i++) + cdata[i] = Blowfish_stream2word(ciphertext, sizeof(ciphertext), + &j); + for (i = 0; i < 64; i++) + ssh_blf_enc(state, cdata, BCRYPT_BLOCKS/2); + + /* copy out */ + for (i = 0; i < BCRYPT_BLOCKS; i++) { + out[4 * i + 3] = (cdata[i] >> 24) & 0xff; + out[4 * i + 2] = (cdata[i] >> 16) & 0xff; + out[4 * i + 1] = (cdata[i] >> 8) & 0xff; + out[4 * i + 0] = cdata[i] & 0xff; + } + + /* zap */ + ssh_burn(ciphertext, sizeof(ciphertext)); + ssh_burn(cdata, sizeof(cdata)); +} + +int +bcrypt_pbkdf(const char *pass, size_t passlen, const uint8_t *salt, size_t saltlen, + uint8_t *key, size_t keylen, unsigned int rounds) +{ + uint8_t sha2pass[SHA512_DIGEST_LENGTH]; + uint8_t sha2salt[SHA512_DIGEST_LENGTH]; + uint8_t out[BCRYPT_HASHSIZE]; + uint8_t tmpout[BCRYPT_HASHSIZE]; + uint8_t *countsalt; + size_t i, j, amt, stride; + uint32_t count; + size_t origkeylen = keylen; + ssh_blf_ctx *state; + SHA512CTX ctx; + + /* nothing crazy */ + if (rounds < 1) + return -1; + if (passlen == 0 || saltlen == 0 || keylen == 0 || + keylen > sizeof(out) * sizeof(out) || saltlen > 1<<20) + return -1; + if ((countsalt = calloc(1, saltlen + 4)) == NULL) + return -1; + stride = (keylen + sizeof(out) - 1) / sizeof(out); + amt = (keylen + stride - 1) / stride; + + memcpy(countsalt, salt, saltlen); + + state = malloc(sizeof(*state)); + if (state == NULL) { + free(countsalt); + return -1; + } + + /* collapse password */ + ctx = sha512_init(); + sha512_update(ctx, pass, passlen); + sha512_final(sha2pass, ctx); + + /* generate key, sizeof(out) at a time */ + for (count = 1; keylen > 0; count++) { + countsalt[saltlen + 0] = (count >> 24) & 0xff; + countsalt[saltlen + 1] = (count >> 16) & 0xff; + countsalt[saltlen + 2] = (count >> 8) & 0xff; + countsalt[saltlen + 3] = count & 0xff; + + /* first round, salt is salt */ + ctx = sha512_init(); + sha512_update(ctx, countsalt, saltlen + 4); + sha512_final(sha2salt, ctx); + + bcrypt_hash(state, sha2pass, sha2salt, tmpout); + memcpy(out, tmpout, sizeof(out)); + + for (i = 1; i < rounds; i++) { + /* subsequent rounds, salt is previous output */ + ctx = sha512_init(); + sha512_update(ctx, tmpout, sizeof(tmpout)); + sha512_final(sha2salt, ctx); + bcrypt_hash(state, sha2pass, sha2salt, tmpout); + for (j = 0; j < sizeof(out); j++) + out[j] ^= tmpout[j]; + } + + /* + * pbkdf2 deviation: output the key material non-linearly. + */ + amt = MIN(amt, keylen); + for (i = 0; i < amt; i++) { + size_t dest = i * stride + (count - 1); + if (dest >= origkeylen) { + break; + } + key[dest] = out[i]; + } + keylen -= i; + } + + /* zap */ + ssh_burn(out, sizeof(out)); + ssh_burn(state, sizeof(*state)); + + free(state); + free(countsalt); + + return 0; +} +#endif /* HAVE_BCRYPT_PBKDF */ diff --git a/src/libs/libssh-0.12.2/src/external/blowfish.c b/src/libs/libssh-0.12.2/src/external/blowfish.c new file mode 100644 index 000000000000..42d5df1eeb3d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/blowfish.c @@ -0,0 +1,691 @@ +/* $OpenBSD: blowfish.c,v 1.20 2021/11/29 01:04:45 djm Exp $ */ +/* + * Blowfish block cipher for OpenBSD + * Copyright 1997 Niels Provos + * All rights reserved. + * + * Implementation advice by David Mazieres . + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * 3. The name of the author may not be used to endorse or promote products + * derived from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +/* + * This code is derived from section 14.3 and the given source + * in section V of Applied Cryptography, second edition. + * Blowfish is an unpatented fast block cipher designed by + * Bruce Schneier. + */ + + +#if !defined(HAVE_BCRYPT_PBKDF) && (!defined(HAVE_BLOWFISH_INITSTATE) || \ + !defined(HAVE_BLOWFISH_EXPAND0STATE) || !defined(HAVE_BLF_ENC)) + +#if 0 +#include /* used for debugging */ +#include +#endif + +#include +#include + +#include "libssh/blf.h" + +#undef inline +#ifdef __GNUC__ +#define inline __inline +#else /* !__GNUC__ */ +#define inline +#endif /* !__GNUC__ */ + +/* Function for Feistel Networks */ + +#define F(s, x) ((((s)[ (((x)>>24)&0xFF)] \ + + (s)[0x100 + (((x)>>16)&0xFF)]) \ + ^ (s)[0x200 + (((x)>> 8)&0xFF)]) \ + + (s)[0x300 + ( (x) &0xFF)]) + +#define BLFRND(s,p,i,j,n) (i ^= F(s,j) ^ (p)[n]) + +void +Blowfish_encipher(ssh_blf_ctx *c, uint32_t *xl, uint32_t *xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t *s = c->S[0]; + uint32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[0]; + BLFRND(s, p, Xr, Xl, 1); BLFRND(s, p, Xl, Xr, 2); + BLFRND(s, p, Xr, Xl, 3); BLFRND(s, p, Xl, Xr, 4); + BLFRND(s, p, Xr, Xl, 5); BLFRND(s, p, Xl, Xr, 6); + BLFRND(s, p, Xr, Xl, 7); BLFRND(s, p, Xl, Xr, 8); + BLFRND(s, p, Xr, Xl, 9); BLFRND(s, p, Xl, Xr, 10); + BLFRND(s, p, Xr, Xl, 11); BLFRND(s, p, Xl, Xr, 12); + BLFRND(s, p, Xr, Xl, 13); BLFRND(s, p, Xl, Xr, 14); + BLFRND(s, p, Xr, Xl, 15); BLFRND(s, p, Xl, Xr, 16); + + *xl = Xr ^ p[17]; + *xr = Xl; +} + +void +Blowfish_decipher(ssh_blf_ctx *c, uint32_t *xl, uint32_t *xr) +{ + uint32_t Xl; + uint32_t Xr; + uint32_t *s = c->S[0]; + uint32_t *p = c->P; + + Xl = *xl; + Xr = *xr; + + Xl ^= p[17]; + BLFRND(s, p, Xr, Xl, 16); BLFRND(s, p, Xl, Xr, 15); + BLFRND(s, p, Xr, Xl, 14); BLFRND(s, p, Xl, Xr, 13); + BLFRND(s, p, Xr, Xl, 12); BLFRND(s, p, Xl, Xr, 11); + BLFRND(s, p, Xr, Xl, 10); BLFRND(s, p, Xl, Xr, 9); + BLFRND(s, p, Xr, Xl, 8); BLFRND(s, p, Xl, Xr, 7); + BLFRND(s, p, Xr, Xl, 6); BLFRND(s, p, Xl, Xr, 5); + BLFRND(s, p, Xr, Xl, 4); BLFRND(s, p, Xl, Xr, 3); + BLFRND(s, p, Xr, Xl, 2); BLFRND(s, p, Xl, Xr, 1); + + *xl = Xr ^ p[0]; + *xr = Xl; +} + +void +Blowfish_initstate(ssh_blf_ctx *c) +{ + /* P-box and S-box tables initialized with digits of Pi */ + + static const ssh_blf_ctx initstate = + { { + { + 0xd1310ba6, 0x98dfb5ac, 0x2ffd72db, 0xd01adfb7, + 0xb8e1afed, 0x6a267e96, 0xba7c9045, 0xf12c7f99, + 0x24a19947, 0xb3916cf7, 0x0801f2e2, 0x858efc16, + 0x636920d8, 0x71574e69, 0xa458fea3, 0xf4933d7e, + 0x0d95748f, 0x728eb658, 0x718bcd58, 0x82154aee, + 0x7b54a41d, 0xc25a59b5, 0x9c30d539, 0x2af26013, + 0xc5d1b023, 0x286085f0, 0xca417918, 0xb8db38ef, + 0x8e79dcb0, 0x603a180e, 0x6c9e0e8b, 0xb01e8a3e, + 0xd71577c1, 0xbd314b27, 0x78af2fda, 0x55605c60, + 0xe65525f3, 0xaa55ab94, 0x57489862, 0x63e81440, + 0x55ca396a, 0x2aab10b6, 0xb4cc5c34, 0x1141e8ce, + 0xa15486af, 0x7c72e993, 0xb3ee1411, 0x636fbc2a, + 0x2ba9c55d, 0x741831f6, 0xce5c3e16, 0x9b87931e, + 0xafd6ba33, 0x6c24cf5c, 0x7a325381, 0x28958677, + 0x3b8f4898, 0x6b4bb9af, 0xc4bfe81b, 0x66282193, + 0x61d809cc, 0xfb21a991, 0x487cac60, 0x5dec8032, + 0xef845d5d, 0xe98575b1, 0xdc262302, 0xeb651b88, + 0x23893e81, 0xd396acc5, 0x0f6d6ff3, 0x83f44239, + 0x2e0b4482, 0xa4842004, 0x69c8f04a, 0x9e1f9b5e, + 0x21c66842, 0xf6e96c9a, 0x670c9c61, 0xabd388f0, + 0x6a51a0d2, 0xd8542f68, 0x960fa728, 0xab5133a3, + 0x6eef0b6c, 0x137a3be4, 0xba3bf050, 0x7efb2a98, + 0xa1f1651d, 0x39af0176, 0x66ca593e, 0x82430e88, + 0x8cee8619, 0x456f9fb4, 0x7d84a5c3, 0x3b8b5ebe, + 0xe06f75d8, 0x85c12073, 0x401a449f, 0x56c16aa6, + 0x4ed3aa62, 0x363f7706, 0x1bfedf72, 0x429b023d, + 0x37d0d724, 0xd00a1248, 0xdb0fead3, 0x49f1c09b, + 0x075372c9, 0x80991b7b, 0x25d479d8, 0xf6e8def7, + 0xe3fe501a, 0xb6794c3b, 0x976ce0bd, 0x04c006ba, + 0xc1a94fb6, 0x409f60c4, 0x5e5c9ec2, 0x196a2463, + 0x68fb6faf, 0x3e6c53b5, 0x1339b2eb, 0x3b52ec6f, + 0x6dfc511f, 0x9b30952c, 0xcc814544, 0xaf5ebd09, + 0xbee3d004, 0xde334afd, 0x660f2807, 0x192e4bb3, + 0xc0cba857, 0x45c8740f, 0xd20b5f39, 0xb9d3fbdb, + 0x5579c0bd, 0x1a60320a, 0xd6a100c6, 0x402c7279, + 0x679f25fe, 0xfb1fa3cc, 0x8ea5e9f8, 0xdb3222f8, + 0x3c7516df, 0xfd616b15, 0x2f501ec8, 0xad0552ab, + 0x323db5fa, 0xfd238760, 0x53317b48, 0x3e00df82, + 0x9e5c57bb, 0xca6f8ca0, 0x1a87562e, 0xdf1769db, + 0xd542a8f6, 0x287effc3, 0xac6732c6, 0x8c4f5573, + 0x695b27b0, 0xbbca58c8, 0xe1ffa35d, 0xb8f011a0, + 0x10fa3d98, 0xfd2183b8, 0x4afcb56c, 0x2dd1d35b, + 0x9a53e479, 0xb6f84565, 0xd28e49bc, 0x4bfb9790, + 0xe1ddf2da, 0xa4cb7e33, 0x62fb1341, 0xcee4c6e8, + 0xef20cada, 0x36774c01, 0xd07e9efe, 0x2bf11fb4, + 0x95dbda4d, 0xae909198, 0xeaad8e71, 0x6b93d5a0, + 0xd08ed1d0, 0xafc725e0, 0x8e3c5b2f, 0x8e7594b7, + 0x8ff6e2fb, 0xf2122b64, 0x8888b812, 0x900df01c, + 0x4fad5ea0, 0x688fc31c, 0xd1cff191, 0xb3a8c1ad, + 0x2f2f2218, 0xbe0e1777, 0xea752dfe, 0x8b021fa1, + 0xe5a0cc0f, 0xb56f74e8, 0x18acf3d6, 0xce89e299, + 0xb4a84fe0, 0xfd13e0b7, 0x7cc43b81, 0xd2ada8d9, + 0x165fa266, 0x80957705, 0x93cc7314, 0x211a1477, + 0xe6ad2065, 0x77b5fa86, 0xc75442f5, 0xfb9d35cf, + 0xebcdaf0c, 0x7b3e89a0, 0xd6411bd3, 0xae1e7e49, + 0x00250e2d, 0x2071b35e, 0x226800bb, 0x57b8e0af, + 0x2464369b, 0xf009b91e, 0x5563911d, 0x59dfa6aa, + 0x78c14389, 0xd95a537f, 0x207d5ba2, 0x02e5b9c5, + 0x83260376, 0x6295cfa9, 0x11c81968, 0x4e734a41, + 0xb3472dca, 0x7b14a94a, 0x1b510052, 0x9a532915, + 0xd60f573f, 0xbc9bc6e4, 0x2b60a476, 0x81e67400, + 0x08ba6fb5, 0x571be91f, 0xf296ec6b, 0x2a0dd915, + 0xb6636521, 0xe7b9f9b6, 0xff34052e, 0xc5855664, + 0x53b02d5d, 0xa99f8fa1, 0x08ba4799, 0x6e85076a}, + { + 0x4b7a70e9, 0xb5b32944, 0xdb75092e, 0xc4192623, + 0xad6ea6b0, 0x49a7df7d, 0x9cee60b8, 0x8fedb266, + 0xecaa8c71, 0x699a17ff, 0x5664526c, 0xc2b19ee1, + 0x193602a5, 0x75094c29, 0xa0591340, 0xe4183a3e, + 0x3f54989a, 0x5b429d65, 0x6b8fe4d6, 0x99f73fd6, + 0xa1d29c07, 0xefe830f5, 0x4d2d38e6, 0xf0255dc1, + 0x4cdd2086, 0x8470eb26, 0x6382e9c6, 0x021ecc5e, + 0x09686b3f, 0x3ebaefc9, 0x3c971814, 0x6b6a70a1, + 0x687f3584, 0x52a0e286, 0xb79c5305, 0xaa500737, + 0x3e07841c, 0x7fdeae5c, 0x8e7d44ec, 0x5716f2b8, + 0xb03ada37, 0xf0500c0d, 0xf01c1f04, 0x0200b3ff, + 0xae0cf51a, 0x3cb574b2, 0x25837a58, 0xdc0921bd, + 0xd19113f9, 0x7ca92ff6, 0x94324773, 0x22f54701, + 0x3ae5e581, 0x37c2dadc, 0xc8b57634, 0x9af3dda7, + 0xa9446146, 0x0fd0030e, 0xecc8c73e, 0xa4751e41, + 0xe238cd99, 0x3bea0e2f, 0x3280bba1, 0x183eb331, + 0x4e548b38, 0x4f6db908, 0x6f420d03, 0xf60a04bf, + 0x2cb81290, 0x24977c79, 0x5679b072, 0xbcaf89af, + 0xde9a771f, 0xd9930810, 0xb38bae12, 0xdccf3f2e, + 0x5512721f, 0x2e6b7124, 0x501adde6, 0x9f84cd87, + 0x7a584718, 0x7408da17, 0xbc9f9abc, 0xe94b7d8c, + 0xec7aec3a, 0xdb851dfa, 0x63094366, 0xc464c3d2, + 0xef1c1847, 0x3215d908, 0xdd433b37, 0x24c2ba16, + 0x12a14d43, 0x2a65c451, 0x50940002, 0x133ae4dd, + 0x71dff89e, 0x10314e55, 0x81ac77d6, 0x5f11199b, + 0x043556f1, 0xd7a3c76b, 0x3c11183b, 0x5924a509, + 0xf28fe6ed, 0x97f1fbfa, 0x9ebabf2c, 0x1e153c6e, + 0x86e34570, 0xeae96fb1, 0x860e5e0a, 0x5a3e2ab3, + 0x771fe71c, 0x4e3d06fa, 0x2965dcb9, 0x99e71d0f, + 0x803e89d6, 0x5266c825, 0x2e4cc978, 0x9c10b36a, + 0xc6150eba, 0x94e2ea78, 0xa5fc3c53, 0x1e0a2df4, + 0xf2f74ea7, 0x361d2b3d, 0x1939260f, 0x19c27960, + 0x5223a708, 0xf71312b6, 0xebadfe6e, 0xeac31f66, + 0xe3bc4595, 0xa67bc883, 0xb17f37d1, 0x018cff28, + 0xc332ddef, 0xbe6c5aa5, 0x65582185, 0x68ab9802, + 0xeecea50f, 0xdb2f953b, 0x2aef7dad, 0x5b6e2f84, + 0x1521b628, 0x29076170, 0xecdd4775, 0x619f1510, + 0x13cca830, 0xeb61bd96, 0x0334fe1e, 0xaa0363cf, + 0xb5735c90, 0x4c70a239, 0xd59e9e0b, 0xcbaade14, + 0xeecc86bc, 0x60622ca7, 0x9cab5cab, 0xb2f3846e, + 0x648b1eaf, 0x19bdf0ca, 0xa02369b9, 0x655abb50, + 0x40685a32, 0x3c2ab4b3, 0x319ee9d5, 0xc021b8f7, + 0x9b540b19, 0x875fa099, 0x95f7997e, 0x623d7da8, + 0xf837889a, 0x97e32d77, 0x11ed935f, 0x16681281, + 0x0e358829, 0xc7e61fd6, 0x96dedfa1, 0x7858ba99, + 0x57f584a5, 0x1b227263, 0x9b83c3ff, 0x1ac24696, + 0xcdb30aeb, 0x532e3054, 0x8fd948e4, 0x6dbc3128, + 0x58ebf2ef, 0x34c6ffea, 0xfe28ed61, 0xee7c3c73, + 0x5d4a14d9, 0xe864b7e3, 0x42105d14, 0x203e13e0, + 0x45eee2b6, 0xa3aaabea, 0xdb6c4f15, 0xfacb4fd0, + 0xc742f442, 0xef6abbb5, 0x654f3b1d, 0x41cd2105, + 0xd81e799e, 0x86854dc7, 0xe44b476a, 0x3d816250, + 0xcf62a1f2, 0x5b8d2646, 0xfc8883a0, 0xc1c7b6a3, + 0x7f1524c3, 0x69cb7492, 0x47848a0b, 0x5692b285, + 0x095bbf00, 0xad19489d, 0x1462b174, 0x23820e00, + 0x58428d2a, 0x0c55f5ea, 0x1dadf43e, 0x233f7061, + 0x3372f092, 0x8d937e41, 0xd65fecf1, 0x6c223bdb, + 0x7cde3759, 0xcbee7460, 0x4085f2a7, 0xce77326e, + 0xa6078084, 0x19f8509e, 0xe8efd855, 0x61d99735, + 0xa969a7aa, 0xc50c06c2, 0x5a04abfc, 0x800bcadc, + 0x9e447a2e, 0xc3453484, 0xfdd56705, 0x0e1e9ec9, + 0xdb73dbd3, 0x105588cd, 0x675fda79, 0xe3674340, + 0xc5c43465, 0x713e38d8, 0x3d28f89e, 0xf16dff20, + 0x153e21e7, 0x8fb03d4a, 0xe6e39f2b, 0xdb83adf7}, + { + 0xe93d5a68, 0x948140f7, 0xf64c261c, 0x94692934, + 0x411520f7, 0x7602d4f7, 0xbcf46b2e, 0xd4a20068, + 0xd4082471, 0x3320f46a, 0x43b7d4b7, 0x500061af, + 0x1e39f62e, 0x97244546, 0x14214f74, 0xbf8b8840, + 0x4d95fc1d, 0x96b591af, 0x70f4ddd3, 0x66a02f45, + 0xbfbc09ec, 0x03bd9785, 0x7fac6dd0, 0x31cb8504, + 0x96eb27b3, 0x55fd3941, 0xda2547e6, 0xabca0a9a, + 0x28507825, 0x530429f4, 0x0a2c86da, 0xe9b66dfb, + 0x68dc1462, 0xd7486900, 0x680ec0a4, 0x27a18dee, + 0x4f3ffea2, 0xe887ad8c, 0xb58ce006, 0x7af4d6b6, + 0xaace1e7c, 0xd3375fec, 0xce78a399, 0x406b2a42, + 0x20fe9e35, 0xd9f385b9, 0xee39d7ab, 0x3b124e8b, + 0x1dc9faf7, 0x4b6d1856, 0x26a36631, 0xeae397b2, + 0x3a6efa74, 0xdd5b4332, 0x6841e7f7, 0xca7820fb, + 0xfb0af54e, 0xd8feb397, 0x454056ac, 0xba489527, + 0x55533a3a, 0x20838d87, 0xfe6ba9b7, 0xd096954b, + 0x55a867bc, 0xa1159a58, 0xcca92963, 0x99e1db33, + 0xa62a4a56, 0x3f3125f9, 0x5ef47e1c, 0x9029317c, + 0xfdf8e802, 0x04272f70, 0x80bb155c, 0x05282ce3, + 0x95c11548, 0xe4c66d22, 0x48c1133f, 0xc70f86dc, + 0x07f9c9ee, 0x41041f0f, 0x404779a4, 0x5d886e17, + 0x325f51eb, 0xd59bc0d1, 0xf2bcc18f, 0x41113564, + 0x257b7834, 0x602a9c60, 0xdff8e8a3, 0x1f636c1b, + 0x0e12b4c2, 0x02e1329e, 0xaf664fd1, 0xcad18115, + 0x6b2395e0, 0x333e92e1, 0x3b240b62, 0xeebeb922, + 0x85b2a20e, 0xe6ba0d99, 0xde720c8c, 0x2da2f728, + 0xd0127845, 0x95b794fd, 0x647d0862, 0xe7ccf5f0, + 0x5449a36f, 0x877d48fa, 0xc39dfd27, 0xf33e8d1e, + 0x0a476341, 0x992eff74, 0x3a6f6eab, 0xf4f8fd37, + 0xa812dc60, 0xa1ebddf8, 0x991be14c, 0xdb6e6b0d, + 0xc67b5510, 0x6d672c37, 0x2765d43b, 0xdcd0e804, + 0xf1290dc7, 0xcc00ffa3, 0xb5390f92, 0x690fed0b, + 0x667b9ffb, 0xcedb7d9c, 0xa091cf0b, 0xd9155ea3, + 0xbb132f88, 0x515bad24, 0x7b9479bf, 0x763bd6eb, + 0x37392eb3, 0xcc115979, 0x8026e297, 0xf42e312d, + 0x6842ada7, 0xc66a2b3b, 0x12754ccc, 0x782ef11c, + 0x6a124237, 0xb79251e7, 0x06a1bbe6, 0x4bfb6350, + 0x1a6b1018, 0x11caedfa, 0x3d25bdd8, 0xe2e1c3c9, + 0x44421659, 0x0a121386, 0xd90cec6e, 0xd5abea2a, + 0x64af674e, 0xda86a85f, 0xbebfe988, 0x64e4c3fe, + 0x9dbc8057, 0xf0f7c086, 0x60787bf8, 0x6003604d, + 0xd1fd8346, 0xf6381fb0, 0x7745ae04, 0xd736fccc, + 0x83426b33, 0xf01eab71, 0xb0804187, 0x3c005e5f, + 0x77a057be, 0xbde8ae24, 0x55464299, 0xbf582e61, + 0x4e58f48f, 0xf2ddfda2, 0xf474ef38, 0x8789bdc2, + 0x5366f9c3, 0xc8b38e74, 0xb475f255, 0x46fcd9b9, + 0x7aeb2661, 0x8b1ddf84, 0x846a0e79, 0x915f95e2, + 0x466e598e, 0x20b45770, 0x8cd55591, 0xc902de4c, + 0xb90bace1, 0xbb8205d0, 0x11a86248, 0x7574a99e, + 0xb77f19b6, 0xe0a9dc09, 0x662d09a1, 0xc4324633, + 0xe85a1f02, 0x09f0be8c, 0x4a99a025, 0x1d6efe10, + 0x1ab93d1d, 0x0ba5a4df, 0xa186f20f, 0x2868f169, + 0xdcb7da83, 0x573906fe, 0xa1e2ce9b, 0x4fcd7f52, + 0x50115e01, 0xa70683fa, 0xa002b5c4, 0x0de6d027, + 0x9af88c27, 0x773f8641, 0xc3604c06, 0x61a806b5, + 0xf0177a28, 0xc0f586e0, 0x006058aa, 0x30dc7d62, + 0x11e69ed7, 0x2338ea63, 0x53c2dd94, 0xc2c21634, + 0xbbcbee56, 0x90bcb6de, 0xebfc7da1, 0xce591d76, + 0x6f05e409, 0x4b7c0188, 0x39720a3d, 0x7c927c24, + 0x86e3725f, 0x724d9db9, 0x1ac15bb4, 0xd39eb8fc, + 0xed545578, 0x08fca5b5, 0xd83d7cd3, 0x4dad0fc4, + 0x1e50ef5e, 0xb161e6f8, 0xa28514d9, 0x6c51133c, + 0x6fd5c7e7, 0x56e14ec4, 0x362abfce, 0xddc6c837, + 0xd79a3234, 0x92638212, 0x670efa8e, 0x406000e0}, + { + 0x3a39ce37, 0xd3faf5cf, 0xabc27737, 0x5ac52d1b, + 0x5cb0679e, 0x4fa33742, 0xd3822740, 0x99bc9bbe, + 0xd5118e9d, 0xbf0f7315, 0xd62d1c7e, 0xc700c47b, + 0xb78c1b6b, 0x21a19045, 0xb26eb1be, 0x6a366eb4, + 0x5748ab2f, 0xbc946e79, 0xc6a376d2, 0x6549c2c8, + 0x530ff8ee, 0x468dde7d, 0xd5730a1d, 0x4cd04dc6, + 0x2939bbdb, 0xa9ba4650, 0xac9526e8, 0xbe5ee304, + 0xa1fad5f0, 0x6a2d519a, 0x63ef8ce2, 0x9a86ee22, + 0xc089c2b8, 0x43242ef6, 0xa51e03aa, 0x9cf2d0a4, + 0x83c061ba, 0x9be96a4d, 0x8fe51550, 0xba645bd6, + 0x2826a2f9, 0xa73a3ae1, 0x4ba99586, 0xef5562e9, + 0xc72fefd3, 0xf752f7da, 0x3f046f69, 0x77fa0a59, + 0x80e4a915, 0x87b08601, 0x9b09e6ad, 0x3b3ee593, + 0xe990fd5a, 0x9e34d797, 0x2cf0b7d9, 0x022b8b51, + 0x96d5ac3a, 0x017da67d, 0xd1cf3ed6, 0x7c7d2d28, + 0x1f9f25cf, 0xadf2b89b, 0x5ad6b472, 0x5a88f54c, + 0xe029ac71, 0xe019a5e6, 0x47b0acfd, 0xed93fa9b, + 0xe8d3c48d, 0x283b57cc, 0xf8d56629, 0x79132e28, + 0x785f0191, 0xed756055, 0xf7960e44, 0xe3d35e8c, + 0x15056dd4, 0x88f46dba, 0x03a16125, 0x0564f0bd, + 0xc3eb9e15, 0x3c9057a2, 0x97271aec, 0xa93a072a, + 0x1b3f6d9b, 0x1e6321f5, 0xf59c66fb, 0x26dcf319, + 0x7533d928, 0xb155fdf5, 0x03563482, 0x8aba3cbb, + 0x28517711, 0xc20ad9f8, 0xabcc5167, 0xccad925f, + 0x4de81751, 0x3830dc8e, 0x379d5862, 0x9320f991, + 0xea7a90c2, 0xfb3e7bce, 0x5121ce64, 0x774fbe32, + 0xa8b6e37e, 0xc3293d46, 0x48de5369, 0x6413e680, + 0xa2ae0810, 0xdd6db224, 0x69852dfd, 0x09072166, + 0xb39a460a, 0x6445c0dd, 0x586cdecf, 0x1c20c8ae, + 0x5bbef7dd, 0x1b588d40, 0xccd2017f, 0x6bb4e3bb, + 0xdda26a7e, 0x3a59ff45, 0x3e350a44, 0xbcb4cdd5, + 0x72eacea8, 0xfa6484bb, 0x8d6612ae, 0xbf3c6f47, + 0xd29be463, 0x542f5d9e, 0xaec2771b, 0xf64e6370, + 0x740e0d8d, 0xe75b1357, 0xf8721671, 0xaf537d5d, + 0x4040cb08, 0x4eb4e2cc, 0x34d2466a, 0x0115af84, + 0xe1b00428, 0x95983a1d, 0x06b89fb4, 0xce6ea048, + 0x6f3f3b82, 0x3520ab82, 0x011a1d4b, 0x277227f8, + 0x611560b1, 0xe7933fdc, 0xbb3a792b, 0x344525bd, + 0xa08839e1, 0x51ce794b, 0x2f32c9b7, 0xa01fbac9, + 0xe01cc87e, 0xbcc7d1f6, 0xcf0111c3, 0xa1e8aac7, + 0x1a908749, 0xd44fbd9a, 0xd0dadecb, 0xd50ada38, + 0x0339c32a, 0xc6913667, 0x8df9317c, 0xe0b12b4f, + 0xf79e59b7, 0x43f5bb3a, 0xf2d519ff, 0x27d9459c, + 0xbf97222c, 0x15e6fc2a, 0x0f91fc71, 0x9b941525, + 0xfae59361, 0xceb69ceb, 0xc2a86459, 0x12baa8d1, + 0xb6c1075e, 0xe3056a0c, 0x10d25065, 0xcb03a442, + 0xe0ec6e0e, 0x1698db3b, 0x4c98a0be, 0x3278e964, + 0x9f1f9532, 0xe0d392df, 0xd3a0342b, 0x8971f21e, + 0x1b0a7441, 0x4ba3348c, 0xc5be7120, 0xc37632d8, + 0xdf359f8d, 0x9b992f2e, 0xe60b6f47, 0x0fe3f11d, + 0xe54cda54, 0x1edad891, 0xce6279cf, 0xcd3e7e6f, + 0x1618b166, 0xfd2c1d05, 0x848fd2c5, 0xf6fb2299, + 0xf523f357, 0xa6327623, 0x93a83531, 0x56cccd02, + 0xacf08162, 0x5a75ebb5, 0x6e163697, 0x88d273cc, + 0xde966292, 0x81b949d0, 0x4c50901b, 0x71c65614, + 0xe6c6c7bd, 0x327a140a, 0x45e1d006, 0xc3f27b9a, + 0xc9aa53fd, 0x62a80f00, 0xbb25bfe2, 0x35bdd2f6, + 0x71126905, 0xb2040222, 0xb6cbcf7c, 0xcd769c2b, + 0x53113ec0, 0x1640e3d3, 0x38abbd60, 0x2547adf0, + 0xba38209c, 0xf746ce76, 0x77afa1c5, 0x20756060, + 0x85cbfe4e, 0x8ae88dd8, 0x7aaaf9b0, 0x4cf9aa7e, + 0x1948c25c, 0x02fb8a8c, 0x01c36ae4, 0xd6ebe1f9, + 0x90d4f869, 0xa65cdea0, 0x3f09252d, 0xc208e69f, + 0xb74e6132, 0xce77e25b, 0x578fdfe3, 0x3ac372e6} + }, + { + 0x243f6a88, 0x85a308d3, 0x13198a2e, 0x03707344, + 0xa4093822, 0x299f31d0, 0x082efa98, 0xec4e6c89, + 0x452821e6, 0x38d01377, 0xbe5466cf, 0x34e90c6c, + 0xc0ac29b7, 0xc97c50dd, 0x3f84d5b5, 0xb5470917, + 0x9216d5d9, 0x8979fb1b + } }; + + *c = initstate; +} + +uint32_t +Blowfish_stream2word(const uint8_t *data, uint16_t databytes, + uint16_t *current) +{ + uint8_t i; + uint16_t j; + uint32_t temp; + + temp = 0x00000000; + j = *current; + + for (i = 0; i < 4; i++, j++) { + if (j >= databytes) + j = 0; + temp = (temp << 8) | data[j]; + } + + *current = j; + return temp; +} + +void +Blowfish_expand0state(ssh_blf_ctx *c, const uint8_t *key, uint16_t keybytes) +{ + uint16_t i; + uint16_t j; + uint16_t k; + uint32_t temp; + uint32_t datal; + uint32_t datar; + + j = 0; + for (i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for (i = 0; i < BLF_N + 2; i += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for (i = 0; i < 4; i++) { + for (k = 0; k < 256; k += 2) { + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } +} + + +void +Blowfish_expandstate(ssh_blf_ctx *c, const uint8_t *data, uint16_t databytes, + const uint8_t *key, uint16_t keybytes) +{ + uint16_t i; + uint16_t j; + uint16_t k; + uint32_t temp; + uint32_t datal; + uint32_t datar; + + j = 0; + for (i = 0; i < BLF_N + 2; i++) { + /* Extract 4 int8 to 1 int32 from keystream */ + temp = Blowfish_stream2word(key, keybytes, &j); + c->P[i] = c->P[i] ^ temp; + } + + j = 0; + datal = 0x00000000; + datar = 0x00000000; + for (i = 0; i < BLF_N + 2; i += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->P[i] = datal; + c->P[i + 1] = datar; + } + + for (i = 0; i < 4; i++) { + for (k = 0; k < 256; k += 2) { + datal ^= Blowfish_stream2word(data, databytes, &j); + datar ^= Blowfish_stream2word(data, databytes, &j); + Blowfish_encipher(c, &datal, &datar); + + c->S[i][k] = datal; + c->S[i][k + 1] = datar; + } + } + +} + +void +ssh_blf_key(ssh_blf_ctx *c, const uint8_t *k, uint16_t len) +{ + /* Initialize S-boxes and subkeys with Pi */ + Blowfish_initstate(c); + + /* Transform S-boxes and subkeys with key */ + Blowfish_expand0state(c, k, len); +} + +void +ssh_blf_enc(ssh_blf_ctx *c, uint32_t *data, uint16_t blocks) +{ + uint32_t *d; + uint16_t i; + + d = data; + for (i = 0; i < blocks; i++) { + Blowfish_encipher(c, d, d + 1); + d += 2; + } +} + +void +ssh_blf_dec(ssh_blf_ctx *c, uint32_t *data, uint16_t blocks) +{ + uint32_t *d; + uint16_t i; + + d = data; + for (i = 0; i < blocks; i++) { + Blowfish_decipher(c, d, d + 1); + d += 2; + } +} + +void +ssh_blf_ecb_encrypt(ssh_blf_ctx *c, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i; + + for (i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +ssh_blf_ecb_decrypt(ssh_blf_ctx *c, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i; + + for (i = 0; i < len; i += 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + data += 8; + } +} + +void +ssh_blf_cbc_encrypt(ssh_blf_ctx *c, uint8_t *iv, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint32_t i, j; + + for (i = 0; i < len; i += 8) { + for (j = 0; j < 8; j++) + data[j] ^= iv[j]; + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_encipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + iv = data; + data += 8; + } +} + +void +ssh_blf_cbc_decrypt(ssh_blf_ctx *c, uint8_t *iva, uint8_t *data, uint32_t len) +{ + uint32_t l, r; + uint8_t *iv; + uint32_t i, j; + + iv = data + len - 16; + data = data + len - 8; + for (i = len - 8; i >= 8; i -= 8) { + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for (j = 0; j < 8; j++) + data[j] ^= iv[j]; + iv -= 8; + data -= 8; + } + l = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + r = data[4] << 24 | data[5] << 16 | data[6] << 8 | data[7]; + Blowfish_decipher(c, &l, &r); + data[0] = l >> 24 & 0xff; + data[1] = l >> 16 & 0xff; + data[2] = l >> 8 & 0xff; + data[3] = l & 0xff; + data[4] = r >> 24 & 0xff; + data[5] = r >> 16 & 0xff; + data[6] = r >> 8 & 0xff; + data[7] = r & 0xff; + for (j = 0; j < 8; j++) + data[j] ^= iva[j]; +} + +#if 0 +void +report(uint32_t data[], uint16_t len) +{ + uint16_t i; + for (i = 0; i < len; i += 2) + printf("Block %0hd: %08lx %08lx.\n", + i / 2, data[i], data[i + 1]); +} +void +main(void) +{ + + ssh_blf_ctx c; + char key[] = "AAAAA"; + char key2[] = "abcdefghijklmnopqrstuvwxyz"; + + uint32_t data[10]; + uint32_t data2[] = + {0x424c4f57l, 0x46495348l}; + + uint16_t i; + + /* First test */ + for (i = 0; i < 10; i++) + data[i] = i; + + ssh_blf_key(&c, (uint8_t *) key, 5); + ssh_blf_enc(&c, data, 5); + ssh_blf_dec(&c, data, 1); + ssh_blf_dec(&c, data + 2, 4); + printf("Should read as 0 - 9.\n"); + report(data, 10); + + /* Second test */ + ssh_blf_key(&c, (uint8_t *) key2, strlen(key2)); + ssh_blf_enc(&c, data2, 1); + printf("\nShould read as: 0x324ed0fe 0xf413a203.\n"); + report(data2, 2); + ssh_blf_dec(&c, data2, 1); + report(data2, 2); +} +#endif + +#endif /* !defined(HAVE_BCRYPT_PBKDF) && (!defined(HAVE_BLOWFISH_INITSTATE) || \ + !defined(HAVE_BLOWFISH_EXPAND0STATE) || !defined(HAVE_BLF_ENC)) */ diff --git a/src/libs/libssh-0.12.2/src/external/chacha.c b/src/libs/libssh-0.12.2/src/external/chacha.c new file mode 100644 index 000000000000..8d1ccca6ed57 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/chacha.c @@ -0,0 +1,216 @@ +/* +chacha-merged.c version 20080118 +D. J. Bernstein +Public domain. + */ + +#include +#include +#include + +#include "libssh/chacha.h" + +typedef struct chacha_ctx chacha_ctx; + +#define U8C(v) (v##U) +#define U32C(v) (v##U) + +#define U8V(v) ((uint8_t)(v) & U8C(0xFF)) +#define U32V(v) ((uint32_t)(v) & U32C(0xFFFFFFFF)) + +#define ROTL32(v, n) \ + (U32V((v) << (n)) | ((v) >> (32 - (n)))) + +#define U8TO32_LITTLE(p) \ + (((uint32_t)((p)[0]) ) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LITTLE(p, v) \ + do { \ + (p)[0] = U8V((v) ); \ + (p)[1] = U8V((v) >> 8); \ + (p)[2] = U8V((v) >> 16); \ + (p)[3] = U8V((v) >> 24); \ + } while (0) + +#define ROTATE(v,c) (ROTL32(v,c)) +#define XOR(v,w) ((v) ^ (w)) +#define PLUS(v,w) (U32V((v) + (w))) +#define PLUSONE(v) (PLUS((v),1)) + +#define QUARTERROUND(a,b,c,d) \ + a = PLUS(a,b); d = ROTATE(XOR(d,a),16); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c),12); \ + a = PLUS(a,b); d = ROTATE(XOR(d,a), 8); \ + c = PLUS(c,d); b = ROTATE(XOR(b,c), 7); + +static const char sigma[16] = "expand 32-byte k"; +static const char tau[16] = "expand 16-byte k"; + +void +chacha_keysetup(chacha_ctx *x,const uint8_t *k,uint32_t kbits) +{ + const char *constants; + + x->input[4] = U8TO32_LITTLE(k + 0); + x->input[5] = U8TO32_LITTLE(k + 4); + x->input[6] = U8TO32_LITTLE(k + 8); + x->input[7] = U8TO32_LITTLE(k + 12); + if (kbits == 256) { /* recommended */ + k += 16; + constants = sigma; + } else { /* kbits == 128 */ + constants = tau; + } + x->input[8] = U8TO32_LITTLE(k + 0); + x->input[9] = U8TO32_LITTLE(k + 4); + x->input[10] = U8TO32_LITTLE(k + 8); + x->input[11] = U8TO32_LITTLE(k + 12); + x->input[0] = U8TO32_LITTLE(constants + 0); + x->input[1] = U8TO32_LITTLE(constants + 4); + x->input[2] = U8TO32_LITTLE(constants + 8); + x->input[3] = U8TO32_LITTLE(constants + 12); +} + +void +chacha_ivsetup(chacha_ctx *x, const uint8_t *iv, const uint8_t *counter) +{ + x->input[12] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 0); + x->input[13] = counter == NULL ? 0 : U8TO32_LITTLE(counter + 4); + x->input[14] = U8TO32_LITTLE(iv + 0); + x->input[15] = U8TO32_LITTLE(iv + 4); +} + +void +chacha_encrypt_bytes(chacha_ctx *x,const uint8_t *m,uint8_t *c,uint32_t bytes) +{ + uint32_t x0, x1, x2, x3, x4, x5, x6, x7, x8, x9, x10, x11, x12, x13, x14, x15; + uint32_t j0, j1, j2, j3, j4, j5, j6, j7, j8, j9, j10, j11, j12, j13, j14, j15; + uint8_t *ctarget = NULL; + uint8_t tmp[64]; + uint32_t i; + + if (!bytes) return; + + j0 = x->input[0]; + j1 = x->input[1]; + j2 = x->input[2]; + j3 = x->input[3]; + j4 = x->input[4]; + j5 = x->input[5]; + j6 = x->input[6]; + j7 = x->input[7]; + j8 = x->input[8]; + j9 = x->input[9]; + j10 = x->input[10]; + j11 = x->input[11]; + j12 = x->input[12]; + j13 = x->input[13]; + j14 = x->input[14]; + j15 = x->input[15]; + + for (;;) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) tmp[i] = m[i]; + m = tmp; + ctarget = c; + c = tmp; + } + x0 = j0; + x1 = j1; + x2 = j2; + x3 = j3; + x4 = j4; + x5 = j5; + x6 = j6; + x7 = j7; + x8 = j8; + x9 = j9; + x10 = j10; + x11 = j11; + x12 = j12; + x13 = j13; + x14 = j14; + x15 = j15; + for (i = 20;i > 0;i -= 2) { + QUARTERROUND( x0, x4, x8,x12) + QUARTERROUND( x1, x5, x9,x13) + QUARTERROUND( x2, x6,x10,x14) + QUARTERROUND( x3, x7,x11,x15) + QUARTERROUND( x0, x5,x10,x15) + QUARTERROUND( x1, x6,x11,x12) + QUARTERROUND( x2, x7, x8,x13) + QUARTERROUND( x3, x4, x9,x14) + } + x0 = PLUS(x0,j0); + x1 = PLUS(x1,j1); + x2 = PLUS(x2,j2); + x3 = PLUS(x3,j3); + x4 = PLUS(x4,j4); + x5 = PLUS(x5,j5); + x6 = PLUS(x6,j6); + x7 = PLUS(x7,j7); + x8 = PLUS(x8,j8); + x9 = PLUS(x9,j9); + x10 = PLUS(x10,j10); + x11 = PLUS(x11,j11); + x12 = PLUS(x12,j12); + x13 = PLUS(x13,j13); + x14 = PLUS(x14,j14); + x15 = PLUS(x15,j15); + + x0 = XOR(x0,U8TO32_LITTLE(m + 0)); + x1 = XOR(x1,U8TO32_LITTLE(m + 4)); + x2 = XOR(x2,U8TO32_LITTLE(m + 8)); + x3 = XOR(x3,U8TO32_LITTLE(m + 12)); + x4 = XOR(x4,U8TO32_LITTLE(m + 16)); + x5 = XOR(x5,U8TO32_LITTLE(m + 20)); + x6 = XOR(x6,U8TO32_LITTLE(m + 24)); + x7 = XOR(x7,U8TO32_LITTLE(m + 28)); + x8 = XOR(x8,U8TO32_LITTLE(m + 32)); + x9 = XOR(x9,U8TO32_LITTLE(m + 36)); + x10 = XOR(x10,U8TO32_LITTLE(m + 40)); + x11 = XOR(x11,U8TO32_LITTLE(m + 44)); + x12 = XOR(x12,U8TO32_LITTLE(m + 48)); + x13 = XOR(x13,U8TO32_LITTLE(m + 52)); + x14 = XOR(x14,U8TO32_LITTLE(m + 56)); + x15 = XOR(x15,U8TO32_LITTLE(m + 60)); + + j12 = PLUSONE(j12); + if (!j12) { + j13 = PLUSONE(j13); + /* stopping at 2^70 bytes per nonce is user's responsibility */ + } + + U32TO8_LITTLE(c + 0,x0); + U32TO8_LITTLE(c + 4,x1); + U32TO8_LITTLE(c + 8,x2); + U32TO8_LITTLE(c + 12,x3); + U32TO8_LITTLE(c + 16,x4); + U32TO8_LITTLE(c + 20,x5); + U32TO8_LITTLE(c + 24,x6); + U32TO8_LITTLE(c + 28,x7); + U32TO8_LITTLE(c + 32,x8); + U32TO8_LITTLE(c + 36,x9); + U32TO8_LITTLE(c + 40,x10); + U32TO8_LITTLE(c + 44,x11); + U32TO8_LITTLE(c + 48,x12); + U32TO8_LITTLE(c + 52,x13); + U32TO8_LITTLE(c + 56,x14); + U32TO8_LITTLE(c + 60,x15); + + if (bytes <= 64) { + if (bytes < 64) { + for (i = 0;i < bytes;++i) ctarget[i] = c[i]; + } + x->input[12] = j12; + x->input[13] = j13; + return; + } + bytes -= 64; + c += 64; + m += 64; + } +} diff --git a/src/libs/libssh-0.12.2/src/external/curve25519_ref.c b/src/libs/libssh-0.12.2/src/external/curve25519_ref.c new file mode 100644 index 000000000000..6ffb14a87fe0 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/curve25519_ref.c @@ -0,0 +1,271 @@ +/* +version 20081011 +Matthew Dempsky +Public domain. +Derived from public domain code by D. J. Bernstein. +*/ + +#include "libssh/curve25519.h" +static const unsigned char base[32] = {9}; + +int crypto_scalarmult_base(unsigned char *q, + const unsigned char *n) +{ + return crypto_scalarmult(q,n,base); +} + +static void add(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 31;++j) { u += a[j] + b[j]; out[j] = u & 255; u >>= 8; } + u += a[31] + b[31]; out[31] = u; +} + +static void sub(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int j; + unsigned int u; + u = 218; + for (j = 0;j < 31;++j) { + u += a[j] + 65280 - b[j]; + out[j] = u & 255; + u >>= 8; + } + u += a[31] - b[31]; + out[31] = u; +} + +static void squeeze(unsigned int a[32]) +{ + unsigned int j; + unsigned int u; + u = 0; + for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; } + u += a[31]; a[31] = u & 127; + u = 19 * (u >> 7); + for (j = 0;j < 31;++j) { u += a[j]; a[j] = u & 255; u >>= 8; } + u += a[31]; a[31] = u; +} + +static const unsigned int minusp[32] = { + 19, 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, 128 +} ; + +static void freeze(unsigned int a[32]) +{ + unsigned int aorig[32]; + unsigned int j; + unsigned int negative; + + for (j = 0;j < 32;++j) aorig[j] = a[j]; + add(a,a,minusp); + negative = -((a[31] >> 7) & 1); + for (j = 0;j < 32;++j) a[j] ^= negative & (aorig[j] ^ a[j]); +} + +static void mult(unsigned int out[32],const unsigned int a[32],const unsigned int b[32]) +{ + unsigned int i; + unsigned int j; + unsigned int u; + + for (i = 0;i < 32;++i) { + u = 0; + for (j = 0;j <= i;++j) u += a[j] * b[i - j]; + for (j = i + 1;j < 32;++j) u += 38 * a[j] * b[i + 32 - j]; + out[i] = u; + } + squeeze(out); +} + +static void mult121665(unsigned int out[32],const unsigned int a[32]) +{ + unsigned int j; + unsigned int u; + + u = 0; + for (j = 0;j < 31;++j) { u += 121665 * a[j]; out[j] = u & 255; u >>= 8; } + u += 121665 * a[31]; out[31] = u & 127; + u = 19 * (u >> 7); + for (j = 0;j < 31;++j) { u += out[j]; out[j] = u & 255; u >>= 8; } + u += out[j]; out[j] = u; +} + +static void square(unsigned int out[32],const unsigned int a[32]) +{ + unsigned int i; + unsigned int j; + unsigned int u; + + for (i = 0;i < 32;++i) { + u = 0; + for (j = 0;j < i - j;++j) u += a[j] * a[i - j]; + for (j = i + 1;j < i + 32 - j;++j) u += 38 * a[j] * a[i + 32 - j]; + u *= 2; + if ((i & 1) == 0) { + u += a[i / 2] * a[i / 2]; + u += 38 * a[i / 2 + 16] * a[i / 2 + 16]; + } + out[i] = u; + } + squeeze(out); +} + +static void c_select(unsigned int p[64],unsigned int q[64],const unsigned int r[64],const unsigned int s[64],unsigned int b) +{ + unsigned int j; + unsigned int t; + unsigned int bminus1; + + bminus1 = b - 1; + for (j = 0;j < 64;++j) { + t = bminus1 & (r[j] ^ s[j]); + p[j] = s[j] ^ t; + q[j] = r[j] ^ t; + } +} + +static void mainloop(unsigned int work[64],const unsigned char e[32]) +{ + unsigned int xzm1[64]; + unsigned int xzm[64]; + unsigned int xzmb[64]; + unsigned int xzm1b[64]; + unsigned int xznb[64]; + unsigned int xzn1b[64]; + unsigned int a0[64]; + unsigned int a1[64]; + unsigned int b0[64]; + unsigned int b1[64]; + unsigned int c1[64]; + unsigned int r[32]; + unsigned int s[32]; + unsigned int t[32]; + unsigned int u[32]; + unsigned int j; + unsigned int b; + int pos; + + for (j = 0;j < 32;++j) xzm1[j] = work[j]; + xzm1[32] = 1; + for (j = 33;j < 64;++j) xzm1[j] = 0; + + xzm[0] = 1; + for (j = 1;j < 64;++j) xzm[j] = 0; + + for (pos = 254;pos >= 0;--pos) { + b = e[pos / 8] >> (pos & 7); + b &= 1; + c_select(xzmb,xzm1b,xzm,xzm1,b); + add(a0,xzmb,xzmb + 32); + sub(a0 + 32,xzmb,xzmb + 32); + add(a1,xzm1b,xzm1b + 32); + sub(a1 + 32,xzm1b,xzm1b + 32); + square(b0,a0); + square(b0 + 32,a0 + 32); + mult(b1,a1,a0 + 32); + mult(b1 + 32,a1 + 32,a0); + add(c1,b1,b1 + 32); + sub(c1 + 32,b1,b1 + 32); + square(r,c1 + 32); + sub(s,b0,b0 + 32); + mult121665(t,s); + add(u,t,b0); + mult(xznb,b0,b0 + 32); + mult(xznb + 32,s,u); + square(xzn1b,c1); + mult(xzn1b + 32,r,work); + c_select(xzm,xzm1,xznb,xzn1b,b); + } + + for (j = 0;j < 64;++j) work[j] = xzm[j]; +} + +static void recip(unsigned int out[32],const unsigned int z[32]) +{ + unsigned int z2[32]; + unsigned int z9[32]; + unsigned int z11[32]; + unsigned int z2_5_0[32]; + unsigned int z2_10_0[32]; + unsigned int z2_20_0[32]; + unsigned int z2_50_0[32]; + unsigned int z2_100_0[32]; + unsigned int t0[32]; + unsigned int t1[32]; + int i; + + /* 2 */ square(z2,z); + /* 4 */ square(t1,z2); + /* 8 */ square(t0,t1); + /* 9 */ mult(z9,t0,z); + /* 11 */ mult(z11,z9,z2); + /* 22 */ square(t0,z11); + /* 2^5 - 2^0 = 31 */ mult(z2_5_0,t0,z9); + + /* 2^6 - 2^1 */ square(t0,z2_5_0); + /* 2^7 - 2^2 */ square(t1,t0); + /* 2^8 - 2^3 */ square(t0,t1); + /* 2^9 - 2^4 */ square(t1,t0); + /* 2^10 - 2^5 */ square(t0,t1); + /* 2^10 - 2^0 */ mult(z2_10_0,t0,z2_5_0); + + /* 2^11 - 2^1 */ square(t0,z2_10_0); + /* 2^12 - 2^2 */ square(t1,t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^20 - 2^0 */ mult(z2_20_0,t1,z2_10_0); + + /* 2^21 - 2^1 */ square(t0,z2_20_0); + /* 2^22 - 2^2 */ square(t1,t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^40 - 2^0 */ mult(t0,t1,z2_20_0); + + /* 2^41 - 2^1 */ square(t1,t0); + /* 2^42 - 2^2 */ square(t0,t1); + /* 2^50 - 2^10 */ for (i = 2;i < 10;i += 2) { square(t1,t0); square(t0,t1); } + /* 2^50 - 2^0 */ mult(z2_50_0,t0,z2_10_0); + + /* 2^51 - 2^1 */ square(t0,z2_50_0); + /* 2^52 - 2^2 */ square(t1,t0); + /* 2^100 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^100 - 2^0 */ mult(z2_100_0,t1,z2_50_0); + + /* 2^101 - 2^1 */ square(t1,z2_100_0); + /* 2^102 - 2^2 */ square(t0,t1); + /* 2^200 - 2^100 */ for (i = 2;i < 100;i += 2) { square(t1,t0); square(t0,t1); } + /* 2^200 - 2^0 */ mult(t1,t0,z2_100_0); + + /* 2^201 - 2^1 */ square(t0,t1); + /* 2^202 - 2^2 */ square(t1,t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { square(t0,t1); square(t1,t0); } + /* 2^250 - 2^0 */ mult(t0,t1,z2_50_0); + + /* 2^251 - 2^1 */ square(t1,t0); + /* 2^252 - 2^2 */ square(t0,t1); + /* 2^253 - 2^3 */ square(t1,t0); + /* 2^254 - 2^4 */ square(t0,t1); + /* 2^255 - 2^5 */ square(t1,t0); + /* 2^255 - 21 */ mult(out,t1,z11); +} + +int crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p) +{ + unsigned int work[96]; + unsigned char e[32]; + unsigned int i; + for (i = 0;i < 32;++i) e[i] = n[i]; + e[0] &= 248; + e[31] &= 127; + e[31] |= 64; + for (i = 0;i < 32;++i) work[i] = p[i]; + mainloop(work,e); + recip(work + 32,work + 32); + mult(work + 64,work,work + 32); + freeze(work + 64); + for (i = 0;i < 32;++i) q[i] = work[64 + i]; + return 0; +} diff --git a/src/libs/libssh-0.12.2/src/external/ed25519.c b/src/libs/libssh-0.12.2/src/external/ed25519.c new file mode 100644 index 000000000000..41b5f289854e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/ed25519.c @@ -0,0 +1,222 @@ +/* $OpenBSD: ed25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ed25519.c + */ + +#include "config.h" + +#include "libssh/libcrypto.h" +#include "libssh/wrapper.h" +#include "libssh/ge25519.h" +#include "libssh/sc25519.h" +#include "libssh/ed25519.h" + +/* + * Public Domain, Author: Daniel J. Bernstein + * Copied from nacl-20110221/crypto_verify/32/ref/verify.c + */ + +static int crypto_verify_32(const unsigned char *x,const unsigned char *y) +{ + unsigned int differentbits = 0; +#define F(i) differentbits |= x[i] ^ y[i]; + F(0) + F(1) + F(2) + F(3) + F(4) + F(5) + F(6) + F(7) + F(8) + F(9) + F(10) + F(11) + F(12) + F(13) + F(14) + F(15) + F(16) + F(17) + F(18) + F(19) + F(20) + F(21) + F(22) + F(23) + F(24) + F(25) + F(26) + F(27) + F(28) + F(29) + F(30) + F(31) + + return (1 & ((differentbits - 1) >> 8)) - 1; +} + +static void get_hram(unsigned char *hram, + const unsigned char *sm, + const unsigned char *pk, + unsigned char *playground, + uint64_t smlen) +{ + uint64_t i; + SHA512CTX ctx; + for (i = 0;i < 32;++i) playground[i] = sm[i]; + for (i = 32;i < 64;++i) playground[i] = pk[i-32]; + for (i = 64;i < smlen;++i) playground[i] = sm[i]; + + ctx = sha512_init(); + sha512_update(ctx, playground, smlen); + sha512_final(hram, ctx); +} + + +int crypto_sign_ed25519_keypair(ed25519_pubkey pk, + ed25519_privkey sk) +{ + sc25519 scsk; + ge25519 gepk; + SHA512CTX ctx; + unsigned char extsk[64]; + int i; + int ok; + + ok = ssh_get_random(sk, 32, 0); + if (!ok) { + return -1; + } + + ctx = sha512_init(); + sha512_update(ctx, sk, 32); + sha512_final(extsk, ctx); + extsk[0] &= 248; + extsk[31] &= 127; + extsk[31] |= 64; + + sc25519_from32bytes(&scsk,extsk); + + ge25519_scalarmult_base(&gepk, &scsk); + ge25519_pack(pk, &gepk); + for(i=0;i<32;i++) { + sk[32 + i] = pk[i]; + } + + return 0; +} + +int crypto_sign_ed25519(unsigned char *sm, + uint64_t *smlen, + const unsigned char *m, + uint64_t mlen, + const ed25519_privkey sk) +{ + sc25519 sck, scs, scsk; + ge25519 ger; + SHA512CTX ctx; + unsigned char r[32]; + unsigned char s[32]; + unsigned char extsk[64]; + uint64_t i; + unsigned char hmg[SHA512_DIGEST_LEN]; + unsigned char hram[SHA512_DIGEST_LEN]; + + ctx = sha512_init(); + sha512_update(ctx, sk, 32); + sha512_final(extsk, ctx); + + extsk[0] &= 248; + extsk[31] &= 127; + extsk[31] |= 64; + + *smlen = mlen + 64; + for (i = 0;i < mlen; i++) { + sm[64 + i] = m[i]; + } + for (i = 0;i < 32; i++) { + sm[32 + i] = extsk[32+i]; + } + + /* Generate k as h(extsk[32],...,extsk[63],m) */ + ctx = sha512_init(); + sha512_update(ctx, sm + 32, mlen + 32); + sha512_final(hmg, ctx); + + /* Computation of R */ + sc25519_from64bytes(&sck, hmg); + ge25519_scalarmult_base(&ger, &sck); + ge25519_pack(r, &ger); + + /* Computation of s */ + for (i = 0; i < 32; i++) { + sm[i] = r[i]; + } + + get_hram(hram, sm, sk+32, sm, mlen+64); + + sc25519_from64bytes(&scs, hram); + sc25519_from32bytes(&scsk, extsk); + sc25519_mul(&scs, &scs, &scsk); + + sc25519_add(&scs, &scs, &sck); + + sc25519_to32bytes(s,&scs); /* cat s */ + for (i = 0;i < 32; i++) { + sm[32 + i] = s[i]; + } + + return 0; +} + +int crypto_sign_ed25519_open(unsigned char *m, + uint64_t *mlen, + const unsigned char *sm, + uint64_t smlen, + const ed25519_pubkey pk) +{ + unsigned int i; + int ret; + unsigned char t2[32]; + ge25519 get1, get2; + sc25519 schram, scs; + unsigned char hram[SHA512_DIGEST_LEN]; + + *mlen = (uint64_t) -1; + if (smlen < 64) return -1; + + if (ge25519_unpackneg_vartime(&get1, pk)) { + return -1; + } + + get_hram(hram,sm,pk,m,smlen); + + sc25519_from64bytes(&schram, hram); + + sc25519_from32bytes(&scs, sm+32); + + ge25519_double_scalarmult_vartime(&get2, + &get1, + &schram, + &ge25519_base, + &scs); + ge25519_pack(t2, &get2); + + ret = crypto_verify_32(sm, t2); + if (ret != 0) { + for (i = 0; i < smlen - 64; i++) { + m[i] = sm[i + 64]; + } + *mlen = smlen-64; + } else { + for (i = 0; i < smlen - 64; i++) { + m[i] = 0; + } + } + + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/external/fe25519.c b/src/libs/libssh-0.12.2/src/external/fe25519.c new file mode 100644 index 000000000000..a7f26c9fcd4e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/fe25519.c @@ -0,0 +1,418 @@ +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/fe25519.c + */ + +#include "config.h" + +#define WINDOWSIZE 1 /* Should be 1,2, or 4 */ +#define WINDOWMASK ((1<>= 31; /* 1: yes; 0: no */ + return x; +} + +static uint32_t ge(uint32_t a,uint32_t b) /* 16-bit inputs */ +{ + unsigned int x = a; + + x -= (unsigned int) b; /* 0..65535: yes; 4294901761..4294967295: no */ + x >>= 31; /* 0: yes; 1: no */ + x ^= 1; /* 1: yes; 0: no */ + + return x; +} + +static uint32_t times19(uint32_t a) +{ + return (a << 4) + (a << 1) + a; +} + +static uint32_t times38(uint32_t a) +{ + return (a << 5) + (a << 2) + (a << 1); +} + +static void reduce_add_sub(fe25519 *r) +{ + uint32_t t; + int i,rep; + + for(rep = 0; rep < 4; rep++) { + t = r->v[31] >> 7; + r->v[31] &= 127; + t = times19(t); + r->v[0] += t; + for(i = 0; i < 31; i++) { + t = r->v[i] >> 8; + r->v[i+1] += t; + r->v[i] &= 255; + } + } +} + +static void reduce_mul(fe25519 *r) +{ + uint32_t t; + int i,rep; + + for(rep = 0; rep < 2; rep++) { + t = r->v[31] >> 7; + r->v[31] &= 127; + t = times19(t); + r->v[0] += t; + for(i = 0; i < 31; i++) { + t = r->v[i] >> 8; + r->v[i+1] += t; + r->v[i] &= 255; + } + } +} + +/* reduction modulo 2^255-19 */ +void fe25519_freeze(fe25519 *r) +{ + int i; + uint32_t m = equal(r->v[31],127); + + for (i = 30; i > 0; i--) { + m &= equal(r->v[i],255); + } + m &= ge(r->v[0],237); + + m = -m; + + r->v[31] -= m&127; + for (i = 30; i > 0; i--) { + r->v[i] -= m&255; + } + r->v[0] -= m&237; +} + +void fe25519_unpack(fe25519 *r, const unsigned char x[32]) +{ + int i; + + for (i = 0;i < 32; i++) { + r->v[i] = x[i]; + } + + r->v[31] &= 127; +} + +/* Assumes input x being reduced below 2^255 */ +void fe25519_pack(unsigned char r[32], const fe25519 *x) +{ + int i; + + fe25519 y = *x; + fe25519_freeze(&y); + + for (i = 0; i < 32; i++) { + r[i] = y.v[i]; + } +} + +uint32_t fe25519_iszero(const fe25519 *x) +{ + int i; + uint32_t r; + + fe25519 t = *x; + fe25519_freeze(&t); + + r = equal(t.v[0],0); + for (i = 1; i < 32; i++) { + r &= equal(t.v[i],0); + } + + return r; +} + +int fe25519_iseq_vartime(const fe25519 *x, const fe25519 *y) +{ + int i; + + fe25519 t1 = *x; + fe25519 t2 = *y; + fe25519_freeze(&t1); + fe25519_freeze(&t2); + + for (i = 0; i < 32; i++) { + if(t1.v[i] != t2.v[i]) { + return 0; + } + } + + return 1; +} + +void fe25519_cmov(fe25519 *r, const fe25519 *x, unsigned char b) +{ + int i; + uint32_t mask = b; + + mask = -mask; + + for (i = 0; i < 32; i++) { + r->v[i] ^= mask & (x->v[i] ^ r->v[i]); + } +} + +unsigned char fe25519_getparity(const fe25519 *x) +{ + fe25519 t = *x; + fe25519_freeze(&t); + + return t.v[0] & 1; +} + +void fe25519_setone(fe25519 *r) +{ + int i; + + r->v[0] = 1; + for (i = 1; i < 32; i++) { + r->v[i]=0; + } +} + +void fe25519_setzero(fe25519 *r) +{ + int i; + + for (i = 0; i < 32; i++) { + r->v[i]=0; + } +} + +void fe25519_neg(fe25519 *r, const fe25519 *x) +{ + fe25519 t; + int i; + + for (i = 0; i < 32; i++) { + t.v[i]=x->v[i]; + } + + fe25519_setzero(r); + fe25519_sub(r, r, &t); +} + +void fe25519_add(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i; + + for (i = 0; i < 32; i++) { + r->v[i] = x->v[i] + y->v[i]; + } + + reduce_add_sub(r); +} + +void fe25519_sub(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i; + uint32_t t[32]; + + t[0] = x->v[0] + 0x1da; + t[31] = x->v[31] + 0xfe; + + for (i = 1; i < 31; i++) { + t[i] = x->v[i] + 0x1fe; + } + + for (i = 0; i < 32; i++) { + r->v[i] = t[i] - y->v[i]; + } + + reduce_add_sub(r); +} + +void fe25519_mul(fe25519 *r, const fe25519 *x, const fe25519 *y) +{ + int i,j; + uint32_t t[63]; + + for (i = 0; i < 63; i++) { + t[i] = 0; + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 32; j++) { + t[i+j] += x->v[i] * y->v[j]; + } + } + + for (i = 32; i < 63; i++) { + r->v[i-32] = t[i-32] + times38(t[i]); + } + r->v[31] = t[31]; /* result now in r[0]...r[31] */ + + reduce_mul(r); +} + +void fe25519_square(fe25519 *r, const fe25519 *x) +{ + fe25519_mul(r, x, x); +} + +void fe25519_invert(fe25519 *r, const fe25519 *x) +{ + fe25519 z2; + fe25519 z9; + fe25519 z11; + fe25519 z2_5_0; + fe25519 z2_10_0; + fe25519 z2_20_0; + fe25519 z2_50_0; + fe25519 z2_100_0; + fe25519 t0; + fe25519 t1; + int i; + + /* 2 */ fe25519_square(&z2, x); + /* 4 */ fe25519_square(&t1, &z2); + /* 8 */ fe25519_square(&t0, &t1); + /* 9 */ fe25519_mul(&z9, &t0, x); + /* 11 */ fe25519_mul(&z11, &z9, &z2); + /* 22 */ fe25519_square(&t0, &z11); + /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0, &t0, &z9); + + /* 2^6 - 2^1 */ fe25519_square(&t0, &z2_5_0); + /* 2^7 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^8 - 2^3 */ fe25519_square(&t0, &t1); + /* 2^9 - 2^4 */ fe25519_square(&t1, &t0); + /* 2^10 - 2^5 */ fe25519_square(&t0, &t1); + /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0, &t0, &z2_5_0); + + /* 2^11 - 2^1 */ fe25519_square(&t0, &z2_10_0); + /* 2^12 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^20 - 2^10 */ for (i = 2;i < 10;i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1, &t0); + } + /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0, &t1, &z2_10_0); + + /* 2^21 - 2^1 */ fe25519_square(&t0, &z2_20_0); + /* 2^22 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^40 - 2^20 */ for (i = 2;i < 20;i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1,&t0); + } + /* 2^40 - 2^0 */ fe25519_mul(&t0, &t1, &z2_20_0); + + /* 2^41 - 2^1 */ fe25519_square(&t1, &t0); + /* 2^42 - 2^2 */ fe25519_square(&t0, &t1); + /* 2^50 - 2^10 */ for (i = 2; i < 10;i += 2) { + fe25519_square(&t1, &t0); + fe25519_square(&t0, &t1); + } + /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0,&t0,&z2_10_0); + + /* 2^51 - 2^1 */ fe25519_square(&t0, &z2_50_0); + /* 2^52 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^100 - 2^50 */ for (i = 2; i < 50; i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1,&t0); + } + /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0, &t1, &z2_50_0); + + /* 2^101 - 2^1 */ fe25519_square(&t1, &z2_100_0); + /* 2^102 - 2^2 */ fe25519_square(&t0, &t1); + /* 2^200 - 2^100 */ for (i = 2; i < 100; i += 2) { + fe25519_square(&t1, &t0); + fe25519_square(&t0,&t1); + } + /* 2^200 - 2^0 */ fe25519_mul(&t1, &t0, &z2_100_0); + + /* 2^201 - 2^1 */ fe25519_square(&t0, &t1); + /* 2^202 - 2^2 */ fe25519_square(&t1, &t0); + /* 2^250 - 2^50 */ for (i = 2;i < 50;i += 2) { + fe25519_square(&t0, &t1); + fe25519_square(&t1,&t0); + } + /* 2^250 - 2^0 */ fe25519_mul(&t0, &t1, &z2_50_0); + + /* 2^251 - 2^1 */ fe25519_square(&t1, &t0); + /* 2^252 - 2^2 */ fe25519_square(&t0, &t1); + /* 2^253 - 2^3 */ fe25519_square(&t1, &t0); + /* 2^254 - 2^4 */ fe25519_square(&t0, &t1); + /* 2^255 - 2^5 */ fe25519_square(&t1, &t0); + /* 2^255 - 21 */ fe25519_mul(r, &t1, &z11); +} + +void fe25519_pow2523(fe25519 *r, const fe25519 *x) +{ + fe25519 z2; + fe25519 z9; + fe25519 z11; + fe25519 z2_5_0; + fe25519 z2_10_0; + fe25519 z2_20_0; + fe25519 z2_50_0; + fe25519 z2_100_0; + fe25519 t; + int i; + + /* 2 */ fe25519_square(&z2, x); + /* 4 */ fe25519_square(&t, &z2); + /* 8 */ fe25519_square(&t, &t); + /* 9 */ fe25519_mul(&z9, &t, x); + /* 11 */ fe25519_mul(&z11, &z9, &z2); + /* 22 */ fe25519_square(&t, &z11); + /* 2^5 - 2^0 = 31 */ fe25519_mul(&z2_5_0, &t, &z9); + + /* 2^6 - 2^1 */ fe25519_square(&t, &z2_5_0); + /* 2^10 - 2^5 */ for (i = 1; i < 5; i++) { + fe25519_square(&t,&t); + } + /* 2^10 - 2^0 */ fe25519_mul(&z2_10_0, &t, &z2_5_0); + + /* 2^11 - 2^1 */ fe25519_square(&t, &z2_10_0); + /* 2^20 - 2^10 */ for (i = 1; i < 10; i++) { + fe25519_square(&t, &t); + } + /* 2^20 - 2^0 */ fe25519_mul(&z2_20_0, &t, &z2_10_0); + + /* 2^21 - 2^1 */ fe25519_square(&t, &z2_20_0); + /* 2^40 - 2^20 */ for (i = 1; i < 20; i++) { + fe25519_square(&t,&t); + } + /* 2^40 - 2^0 */ fe25519_mul(&t, &t, &z2_20_0); + + /* 2^41 - 2^1 */ fe25519_square(&t, &t); + /* 2^50 - 2^10 */ for (i = 1; i < 10; i++) { + fe25519_square(&t,&t); + } + /* 2^50 - 2^0 */ fe25519_mul(&z2_50_0, &t, &z2_10_0); + + /* 2^51 - 2^1 */ fe25519_square(&t, &z2_50_0); + /* 2^100 - 2^50 */ for (i = 1; i < 50; i++) { + fe25519_square(&t, &t); + } + /* 2^100 - 2^0 */ fe25519_mul(&z2_100_0, &t, &z2_50_0); + + /* 2^101 - 2^1 */ fe25519_square(&t, &z2_100_0); + /* 2^200 - 2^100 */ for (i = 1; i < 100; i++) { + fe25519_square(&t, &t); + } + /* 2^200 - 2^0 */ fe25519_mul(&t, &t, &z2_100_0); + + /* 2^201 - 2^1 */ fe25519_square(&t, &t); + /* 2^250 - 2^50 */ for (i = 1; i < 50; i++) { + fe25519_square(&t, &t); + } + /* 2^250 - 2^0 */ fe25519_mul(&t, &t, &z2_50_0); + + /* 2^251 - 2^1 */ fe25519_square(&t, &t); + /* 2^252 - 2^2 */ fe25519_square(&t, &t); + /* 2^252 - 3 */ fe25519_mul(r, &t, x); +} diff --git a/src/libs/libssh-0.12.2/src/external/ge25519.c b/src/libs/libssh-0.12.2/src/external/ge25519.c new file mode 100644 index 000000000000..ffeb1d58a4f0 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/ge25519.c @@ -0,0 +1,369 @@ +/* $OpenBSD: ge25519.c,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519.c + */ + +#include "config.h" + +#include "libssh/fe25519.h" +#include "libssh/sc25519.h" +#include "libssh/ge25519.h" + +/* + * Arithmetic on the twisted Edwards curve -x^2 + y^2 = 1 + dx^2y^2 + * with d = -(121665/121666) = 37095705934669439343138083508754565189542113879843219016388785533085940283555 + * Base point: (15112221349535400772501151409588531511454012693041857206046113283949847762202,46316835694926478169428394003475163141307993866256225615783033603165251855960); + */ + +/* d */ +static const fe25519 ge25519_ecd = { + {0xA3, 0x78, 0x59, 0x13, 0xCA, 0x4D, 0xEB, 0x75, + 0xAB, 0xD8, 0x41, 0x41, 0x4D, 0x0A, 0x70, 0x00, + 0x98, 0xE8, 0x79, 0x77, 0x79, 0x40, 0xC7, 0x8C, + 0x73, 0xFE, 0x6F, 0x2B, 0xEE, 0x6C, 0x03, 0x52} +}; + +/* 2*d */ +static const fe25519 ge25519_ec2d = { + {0x59, 0xF1, 0xB2, 0x26, 0x94, 0x9B, 0xD6, 0xEB, + 0x56, 0xB1, 0x83, 0x82, 0x9A, 0x14, 0xE0, 0x00, + 0x30, 0xD1, 0xF3, 0xEE, 0xF2, 0x80, 0x8E, 0x19, + 0xE7, 0xFC, 0xDF, 0x56, 0xDC, 0xD9, 0x06, 0x24} +}; + +/* sqrt(-1) */ +static const fe25519 ge25519_sqrtm1 = { + {0xB0, 0xA0, 0x0E, 0x4A, 0x27, 0x1B, 0xEE, 0xC4, + 0x78, 0xE4, 0x2F, 0xAD, 0x06, 0x18, 0x43, 0x2F, + 0xA7, 0xD7, 0xFB, 0x3D, 0x99, 0x00, 0x4D, 0x2B, + 0x0B, 0xDF, 0xC1, 0x4F, 0x80, 0x24, 0x83, 0x2B} +}; + +#define ge25519_p3 ge25519 + +typedef struct { + fe25519 x; + fe25519 z; + fe25519 y; + fe25519 t; +} ge25519_p1p1; + +typedef struct { + fe25519 x; + fe25519 y; + fe25519 z; +} ge25519_p2; + +typedef struct { + fe25519 x; + fe25519 y; +} ge25519_aff; + + +/* Packed coordinates of the base point */ +const ge25519 ge25519_base = { + {{0x1A, 0xD5, 0x25, 0x8F, 0x60, 0x2D, 0x56, 0xC9, + 0xB2, 0xA7, 0x25, 0x95, 0x60, 0xC7, 0x2C, 0x69, + 0x5C, 0xDC, 0xD6, 0xFD, 0x31, 0xE2, 0xA4, 0xC0, + 0xFE, 0x53, 0x6E, 0xCD, 0xD3, 0x36, 0x69, 0x21}}, + {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, + 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0xA3, 0xDD, 0xB7, 0xA5, 0xB3, 0x8A, 0xDE, 0x6D, + 0xF5, 0x52, 0x51, 0x77, 0x80, 0x9F, 0xF0, 0x20, + 0x7D, 0xE3, 0xAB, 0x64, 0x8E, 0x4E, 0xEA, 0x66, + 0x65, 0x76, 0x8B, 0xD7, 0x0F, 0x5F, 0x87, 0x67}} +}; + +/* Multiples of the base point in affine representation */ +static const ge25519_aff ge25519_base_multiples_affine[425] = { +#include "ge25519_base.data" +}; + +static void p1p1_to_p2(ge25519_p2 *r, const ge25519_p1p1 *p) +{ + fe25519_mul(&r->x, &p->x, &p->t); + fe25519_mul(&r->y, &p->y, &p->z); + fe25519_mul(&r->z, &p->z, &p->t); +} + +static void p1p1_to_p3(ge25519_p3 *r, const ge25519_p1p1 *p) +{ + p1p1_to_p2((ge25519_p2 *)r, p); + fe25519_mul(&r->t, &p->x, &p->y); +} + +static void ge25519_mixadd2(ge25519_p3 *r, const ge25519_aff *q) +{ + fe25519 a,b,t1,t2,c,d,e,f,g,h,qt; + fe25519_mul(&qt, &q->x, &q->y); + fe25519_sub(&a, &r->y, &r->x); /* A = (Y1-X1)*(Y2-X2) */ + fe25519_add(&b, &r->y, &r->x); /* B = (Y1+X1)*(Y2+X2) */ + fe25519_sub(&t1, &q->y, &q->x); + fe25519_add(&t2, &q->y, &q->x); + fe25519_mul(&a, &a, &t1); + fe25519_mul(&b, &b, &t2); + fe25519_sub(&e, &b, &a); /* E = B-A */ + fe25519_add(&h, &b, &a); /* H = B+A */ + fe25519_mul(&c, &r->t, &qt); /* C = T1*k*T2 */ + fe25519_mul(&c, &c, &ge25519_ec2d); + fe25519_add(&d, &r->z, &r->z); /* D = Z1*2 */ + fe25519_sub(&f, &d, &c); /* F = D-C */ + fe25519_add(&g, &d, &c); /* G = D+C */ + fe25519_mul(&r->x, &e, &f); + fe25519_mul(&r->y, &h, &g); + fe25519_mul(&r->z, &g, &f); + fe25519_mul(&r->t, &e, &h); +} + +static void add_p1p1(ge25519_p1p1 *r, const ge25519_p3 *p, const ge25519_p3 *q) +{ + fe25519 a, b, c, d, t; + + fe25519_sub(&a, &p->y, &p->x); /* A = (Y1-X1)*(Y2-X2) */ + fe25519_sub(&t, &q->y, &q->x); + fe25519_mul(&a, &a, &t); + fe25519_add(&b, &p->x, &p->y); /* B = (Y1+X1)*(Y2+X2) */ + fe25519_add(&t, &q->x, &q->y); + fe25519_mul(&b, &b, &t); + fe25519_mul(&c, &p->t, &q->t); /* C = T1*k*T2 */ + fe25519_mul(&c, &c, &ge25519_ec2d); + fe25519_mul(&d, &p->z, &q->z); /* D = Z1*2*Z2 */ + fe25519_add(&d, &d, &d); + fe25519_sub(&r->x, &b, &a); /* E = B-A */ + fe25519_sub(&r->t, &d, &c); /* F = D-C */ + fe25519_add(&r->z, &d, &c); /* G = D+C */ + fe25519_add(&r->y, &b, &a); /* H = B+A */ +} + +/* See http://www.hyperelliptic.org/EFD/g1p/auto-twisted-extended-1.html#doubling-dbl-2008-hwcd */ +static void dbl_p1p1(ge25519_p1p1 *r, const ge25519_p2 *p) +{ + fe25519 a,b,c,d; + fe25519_square(&a, &p->x); + fe25519_square(&b, &p->y); + fe25519_square(&c, &p->z); + fe25519_add(&c, &c, &c); + fe25519_neg(&d, &a); + + fe25519_add(&r->x, &p->x, &p->y); + fe25519_square(&r->x, &r->x); + fe25519_sub(&r->x, &r->x, &a); + fe25519_sub(&r->x, &r->x, &b); + fe25519_add(&r->z, &d, &b); + fe25519_sub(&r->t, &r->z, &c); + fe25519_sub(&r->y, &d, &b); +} + +/* Constant-time version of: if(b) r = p */ +static void cmov_aff(ge25519_aff *r, const ge25519_aff *p, unsigned char b) +{ + fe25519_cmov(&r->x, &p->x, b); + fe25519_cmov(&r->y, &p->y, b); +} + +static unsigned char equal(signed char b,signed char c) +{ + unsigned char ub = b; + unsigned char uc = c; + unsigned char x = ub ^ uc; /* 0: yes; 1..255: no */ + uint32_t y = x; /* 0: yes; 1..255: no */ + + y -= 1; /* 4294967295: yes; 0..254: no */ + y >>= 31; /* 1: yes; 0: no */ + + return y; +} + +static unsigned char negative(signed char b) +{ + unsigned long long x = b; /* 18446744073709551361..18446744073709551615: yes; 0..255: no */ + + x >>= 63; /* 1: yes; 0: no */ + + return x; +} + +static void choose_t(ge25519_aff *t, unsigned long long pos, signed char b) +{ + /* constant time */ + fe25519 v; + + *t = ge25519_base_multiples_affine[5 * pos + 0]; + + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 1], + equal(b,1) | equal(b,-1)); + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 2], + equal(b,2) | equal(b,-2)); + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 3], + equal(b,3) | equal(b,-3)); + cmov_aff(t, &ge25519_base_multiples_affine[5 * pos + 4], + equal(b,-4)); + + fe25519_neg(&v, &t->x); + fe25519_cmov(&t->x, &v, negative(b)); +} + +static void setneutral(ge25519 *r) +{ + fe25519_setzero(&r->x); + fe25519_setone(&r->y); + fe25519_setone(&r->z); + fe25519_setzero(&r->t); +} + +/* ******************************************************************** + * EXPORTED FUNCTIONS + ******************************************************************** */ + +/* return 0 on success, -1 otherwise */ +int ge25519_unpackneg_vartime(ge25519_p3 *r, const unsigned char p[32]) +{ + unsigned char par; + + fe25519 t, chk, num, den, den2, den4, den6; + fe25519_setone(&r->z); + par = p[31] >> 7; + fe25519_unpack(&r->y, p); + fe25519_square(&num, &r->y); /* x = y^2 */ + fe25519_mul(&den, &num, &ge25519_ecd); /* den = dy^2 */ + fe25519_sub(&num, &num, &r->z); /* x = y^2-1 */ + fe25519_add(&den, &r->z, &den); /* den = dy^2+1 */ + + /* Computation of sqrt(num/den) */ + /* 1.: computation of num^((p-5)/8)*den^((7p-35)/8) = (num*den^7)^((p-5)/8) */ + fe25519_square(&den2, &den); + fe25519_square(&den4, &den2); + fe25519_mul(&den6, &den4, &den2); + fe25519_mul(&t, &den6, &num); + fe25519_mul(&t, &t, &den); + + fe25519_pow2523(&t, &t); + /* 2. computation of r->x = t * num * den^3 */ + fe25519_mul(&t, &t, &num); + fe25519_mul(&t, &t, &den); + fe25519_mul(&t, &t, &den); + fe25519_mul(&r->x, &t, &den); + + /* 3. Check whether sqrt computation gave correct result, multiply by sqrt(-1) if not: */ + fe25519_square(&chk, &r->x); + fe25519_mul(&chk, &chk, &den); + if (!fe25519_iseq_vartime(&chk, &num)) { + fe25519_mul(&r->x, &r->x, &ge25519_sqrtm1); + } + + /* 4. Now we have one of the two square roots, except if input was not a square */ + fe25519_square(&chk, &r->x); + fe25519_mul(&chk, &chk, &den); + if (!fe25519_iseq_vartime(&chk, &num)) { + return -1; + } + + /* 5. Choose the desired square root according to parity: */ + if(fe25519_getparity(&r->x) != (1-par)) { + fe25519_neg(&r->x, &r->x); + } + + fe25519_mul(&r->t, &r->x, &r->y); + + return 0; +} + +void ge25519_pack(unsigned char r[32], const ge25519_p3 *p) +{ + fe25519 tx, ty, zi; + + fe25519_invert(&zi, &p->z); + fe25519_mul(&tx, &p->x, &zi); + fe25519_mul(&ty, &p->y, &zi); + fe25519_pack(r, &ty); + + r[31] ^= fe25519_getparity(&tx) << 7; +} + +int ge25519_isneutral_vartime(const ge25519_p3 *p) +{ + int ret = 1; + + if (!fe25519_iszero(&p->x)) { + ret = 0; + } + + if (!fe25519_iseq_vartime(&p->y, &p->z)) { + ret = 0; + } + + return ret; +} + +/* computes [s1]p1 + [s2]p2 */ +void ge25519_double_scalarmult_vartime(ge25519_p3 *r, const ge25519_p3 *p1, const sc25519 *s1, const ge25519_p3 *p2, const sc25519 *s2) +{ + ge25519_p1p1 tp1p1; + ge25519_p3 pre[16]; + unsigned char b[127]; + int i; + + /* precomputation s2 s1 */ + setneutral(pre); /* 00 00 */ + pre[1] = *p1; /* 00 01 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)p1); p1p1_to_p3( &pre[2], &tp1p1); /* 00 10 */ + add_p1p1(&tp1p1,&pre[1], &pre[2]); p1p1_to_p3( &pre[3], &tp1p1); /* 00 11 */ + pre[4] = *p2; /* 01 00 */ + add_p1p1(&tp1p1,&pre[1], &pre[4]); p1p1_to_p3( &pre[5], &tp1p1); /* 01 01 */ + add_p1p1(&tp1p1,&pre[2], &pre[4]); p1p1_to_p3( &pre[6], &tp1p1); /* 01 10 */ + add_p1p1(&tp1p1,&pre[3], &pre[4]); p1p1_to_p3( &pre[7], &tp1p1); /* 01 11 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)p2); p1p1_to_p3( &pre[8], &tp1p1); /* 10 00 */ + add_p1p1(&tp1p1,&pre[1], &pre[8]); p1p1_to_p3( &pre[9], &tp1p1); /* 10 01 */ + dbl_p1p1(&tp1p1,(ge25519_p2 *)&pre[5]); p1p1_to_p3(&pre[10], &tp1p1); /* 10 10 */ + add_p1p1(&tp1p1,&pre[3], &pre[8]); p1p1_to_p3(&pre[11], &tp1p1); /* 10 11 */ + add_p1p1(&tp1p1,&pre[4], &pre[8]); p1p1_to_p3(&pre[12], &tp1p1); /* 11 00 */ + add_p1p1(&tp1p1,&pre[1],&pre[12]); p1p1_to_p3(&pre[13], &tp1p1); /* 11 01 */ + add_p1p1(&tp1p1,&pre[2],&pre[12]); p1p1_to_p3(&pre[14], &tp1p1); /* 11 10 */ + add_p1p1(&tp1p1,&pre[3],&pre[12]); p1p1_to_p3(&pre[15], &tp1p1); /* 11 11 */ + + sc25519_2interleave2(b,s1,s2); + + /* scalar multiplication */ + *r = pre[b[126]]; + + for (i = 125; i >= 0; i--) { + dbl_p1p1(&tp1p1, (ge25519_p2 *)r); + p1p1_to_p2((ge25519_p2 *) r, &tp1p1); + dbl_p1p1(&tp1p1, (ge25519_p2 *)r); + if(b[i] != 0) { + p1p1_to_p3(r, &tp1p1); + add_p1p1(&tp1p1, r, &pre[b[i]]); + } + if (i != 0) { + p1p1_to_p2((ge25519_p2 *)r, &tp1p1); + } else { + p1p1_to_p3(r, &tp1p1); + } + } +} + +void ge25519_scalarmult_base(ge25519_p3 *r, const sc25519 *s) +{ + signed char b[85]; + int i; + ge25519_aff t; + + sc25519_window3(b,s); + + choose_t((ge25519_aff *)r, 0, b[0]); + fe25519_setone(&r->z); + fe25519_mul(&r->t, &r->x, &r->y); + for (i = 1; i < 85; i++) { + choose_t(&t, (unsigned long long) i, b[i]); + ge25519_mixadd2(r, &t); + } +} diff --git a/src/libs/libssh-0.12.2/src/external/ge25519_base.data b/src/libs/libssh-0.12.2/src/external/ge25519_base.data new file mode 100644 index 000000000000..e6a4227e9ced --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/ge25519_base.data @@ -0,0 +1,858 @@ +/* $OpenBSD: ge25519_base.data,v 1.3 2013/12/09 11:03:45 markus Exp $ */ + +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/ge25519_base.data + */ + +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x1a, 0xd5, 0x25, 0x8f, 0x60, 0x2d, 0x56, 0xc9, 0xb2, 0xa7, 0x25, 0x95, 0x60, 0xc7, 0x2c, 0x69, 0x5c, 0xdc, 0xd6, 0xfd, 0x31, 0xe2, 0xa4, 0xc0, 0xfe, 0x53, 0x6e, 0xcd, 0xd3, 0x36, 0x69, 0x21}} , + {{0x58, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66, 0x66}}}, +{{{0x0e, 0xce, 0x43, 0x28, 0x4e, 0xa1, 0xc5, 0x83, 0x5f, 0xa4, 0xd7, 0x15, 0x45, 0x8e, 0x0d, 0x08, 0xac, 0xe7, 0x33, 0x18, 0x7d, 0x3b, 0x04, 0x3d, 0x6c, 0x04, 0x5a, 0x9f, 0x4c, 0x38, 0xab, 0x36}} , + {{0xc9, 0xa3, 0xf8, 0x6a, 0xae, 0x46, 0x5f, 0x0e, 0x56, 0x51, 0x38, 0x64, 0x51, 0x0f, 0x39, 0x97, 0x56, 0x1f, 0xa2, 0xc9, 0xe8, 0x5e, 0xa2, 0x1d, 0xc2, 0x29, 0x23, 0x09, 0xf3, 0xcd, 0x60, 0x22}}}, +{{{0x5c, 0xe2, 0xf8, 0xd3, 0x5f, 0x48, 0x62, 0xac, 0x86, 0x48, 0x62, 0x81, 0x19, 0x98, 0x43, 0x63, 0x3a, 0xc8, 0xda, 0x3e, 0x74, 0xae, 0xf4, 0x1f, 0x49, 0x8f, 0x92, 0x22, 0x4a, 0x9c, 0xae, 0x67}} , + {{0xd4, 0xb4, 0xf5, 0x78, 0x48, 0x68, 0xc3, 0x02, 0x04, 0x03, 0x24, 0x67, 0x17, 0xec, 0x16, 0x9f, 0xf7, 0x9e, 0x26, 0x60, 0x8e, 0xa1, 0x26, 0xa1, 0xab, 0x69, 0xee, 0x77, 0xd1, 0xb1, 0x67, 0x12}}}, +{{{0x70, 0xf8, 0xc9, 0xc4, 0x57, 0xa6, 0x3a, 0x49, 0x47, 0x15, 0xce, 0x93, 0xc1, 0x9e, 0x73, 0x1a, 0xf9, 0x20, 0x35, 0x7a, 0xb8, 0xd4, 0x25, 0x83, 0x46, 0xf1, 0xcf, 0x56, 0xdb, 0xa8, 0x3d, 0x20}} , + {{0x2f, 0x11, 0x32, 0xca, 0x61, 0xab, 0x38, 0xdf, 0xf0, 0x0f, 0x2f, 0xea, 0x32, 0x28, 0xf2, 0x4c, 0x6c, 0x71, 0xd5, 0x80, 0x85, 0xb8, 0x0e, 0x47, 0xe1, 0x95, 0x15, 0xcb, 0x27, 0xe8, 0xd0, 0x47}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc8, 0x84, 0xa5, 0x08, 0xbc, 0xfd, 0x87, 0x3b, 0x99, 0x8b, 0x69, 0x80, 0x7b, 0xc6, 0x3a, 0xeb, 0x93, 0xcf, 0x4e, 0xf8, 0x5c, 0x2d, 0x86, 0x42, 0xb6, 0x71, 0xd7, 0x97, 0x5f, 0xe1, 0x42, 0x67}} , + {{0xb4, 0xb9, 0x37, 0xfc, 0xa9, 0x5b, 0x2f, 0x1e, 0x93, 0xe4, 0x1e, 0x62, 0xfc, 0x3c, 0x78, 0x81, 0x8f, 0xf3, 0x8a, 0x66, 0x09, 0x6f, 0xad, 0x6e, 0x79, 0x73, 0xe5, 0xc9, 0x00, 0x06, 0xd3, 0x21}}}, +{{{0xf8, 0xf9, 0x28, 0x6c, 0x6d, 0x59, 0xb2, 0x59, 0x74, 0x23, 0xbf, 0xe7, 0x33, 0x8d, 0x57, 0x09, 0x91, 0x9c, 0x24, 0x08, 0x15, 0x2b, 0xe2, 0xb8, 0xee, 0x3a, 0xe5, 0x27, 0x06, 0x86, 0xa4, 0x23}} , + {{0xeb, 0x27, 0x67, 0xc1, 0x37, 0xab, 0x7a, 0xd8, 0x27, 0x9c, 0x07, 0x8e, 0xff, 0x11, 0x6a, 0xb0, 0x78, 0x6e, 0xad, 0x3a, 0x2e, 0x0f, 0x98, 0x9f, 0x72, 0xc3, 0x7f, 0x82, 0xf2, 0x96, 0x96, 0x70}}}, +{{{0x81, 0x6b, 0x88, 0xe8, 0x1e, 0xc7, 0x77, 0x96, 0x0e, 0xa1, 0xa9, 0x52, 0xe0, 0xd8, 0x0e, 0x61, 0x9e, 0x79, 0x2d, 0x95, 0x9c, 0x8d, 0x96, 0xe0, 0x06, 0x40, 0x5d, 0x87, 0x28, 0x5f, 0x98, 0x70}} , + {{0xf1, 0x79, 0x7b, 0xed, 0x4f, 0x44, 0xb2, 0xe7, 0x08, 0x0d, 0xc2, 0x08, 0x12, 0xd2, 0x9f, 0xdf, 0xcd, 0x93, 0x20, 0x8a, 0xcf, 0x33, 0xca, 0x6d, 0x89, 0xb9, 0x77, 0xc8, 0x93, 0x1b, 0x4e, 0x60}}}, +{{{0x26, 0x4f, 0x7e, 0x97, 0xf6, 0x40, 0xdd, 0x4f, 0xfc, 0x52, 0x78, 0xf9, 0x90, 0x31, 0x03, 0xe6, 0x7d, 0x56, 0x39, 0x0b, 0x1d, 0x56, 0x82, 0x85, 0xf9, 0x1a, 0x42, 0x17, 0x69, 0x6c, 0xcf, 0x39}} , + {{0x69, 0xd2, 0x06, 0x3a, 0x4f, 0x39, 0x2d, 0xf9, 0x38, 0x40, 0x8c, 0x4c, 0xe7, 0x05, 0x12, 0xb4, 0x78, 0x8b, 0xf8, 0xc0, 0xec, 0x93, 0xde, 0x7a, 0x6b, 0xce, 0x2c, 0xe1, 0x0e, 0xa9, 0x34, 0x44}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0b, 0xa4, 0x3c, 0xb0, 0x0f, 0x7a, 0x51, 0xf1, 0x78, 0xd6, 0xd9, 0x6a, 0xfd, 0x46, 0xe8, 0xb8, 0xa8, 0x79, 0x1d, 0x87, 0xf9, 0x90, 0xf2, 0x9c, 0x13, 0x29, 0xf8, 0x0b, 0x20, 0x64, 0xfa, 0x05}} , + {{0x26, 0x09, 0xda, 0x17, 0xaf, 0x95, 0xd6, 0xfb, 0x6a, 0x19, 0x0d, 0x6e, 0x5e, 0x12, 0xf1, 0x99, 0x4c, 0xaa, 0xa8, 0x6f, 0x79, 0x86, 0xf4, 0x72, 0x28, 0x00, 0x26, 0xf9, 0xea, 0x9e, 0x19, 0x3d}}}, +{{{0x87, 0xdd, 0xcf, 0xf0, 0x5b, 0x49, 0xa2, 0x5d, 0x40, 0x7a, 0x23, 0x26, 0xa4, 0x7a, 0x83, 0x8a, 0xb7, 0x8b, 0xd2, 0x1a, 0xbf, 0xea, 0x02, 0x24, 0x08, 0x5f, 0x7b, 0xa9, 0xb1, 0xbe, 0x9d, 0x37}} , + {{0xfc, 0x86, 0x4b, 0x08, 0xee, 0xe7, 0xa0, 0xfd, 0x21, 0x45, 0x09, 0x34, 0xc1, 0x61, 0x32, 0x23, 0xfc, 0x9b, 0x55, 0x48, 0x53, 0x99, 0xf7, 0x63, 0xd0, 0x99, 0xce, 0x01, 0xe0, 0x9f, 0xeb, 0x28}}}, +{{{0x47, 0xfc, 0xab, 0x5a, 0x17, 0xf0, 0x85, 0x56, 0x3a, 0x30, 0x86, 0x20, 0x28, 0x4b, 0x8e, 0x44, 0x74, 0x3a, 0x6e, 0x02, 0xf1, 0x32, 0x8f, 0x9f, 0x3f, 0x08, 0x35, 0xe9, 0xca, 0x16, 0x5f, 0x6e}} , + {{0x1c, 0x59, 0x1c, 0x65, 0x5d, 0x34, 0xa4, 0x09, 0xcd, 0x13, 0x9c, 0x70, 0x7d, 0xb1, 0x2a, 0xc5, 0x88, 0xaf, 0x0b, 0x60, 0xc7, 0x9f, 0x34, 0x8d, 0xd6, 0xb7, 0x7f, 0xea, 0x78, 0x65, 0x8d, 0x77}}}, +{{{0x56, 0xa5, 0xc2, 0x0c, 0xdd, 0xbc, 0xb8, 0x20, 0x6d, 0x57, 0x61, 0xb5, 0xfb, 0x78, 0xb5, 0xd4, 0x49, 0x54, 0x90, 0x26, 0xc1, 0xcb, 0xe9, 0xe6, 0xbf, 0xec, 0x1d, 0x4e, 0xed, 0x07, 0x7e, 0x5e}} , + {{0xc7, 0xf6, 0x6c, 0x56, 0x31, 0x20, 0x14, 0x0e, 0xa8, 0xd9, 0x27, 0xc1, 0x9a, 0x3d, 0x1b, 0x7d, 0x0e, 0x26, 0xd3, 0x81, 0xaa, 0xeb, 0xf5, 0x6b, 0x79, 0x02, 0xf1, 0x51, 0x5c, 0x75, 0x55, 0x0f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0a, 0x34, 0xcd, 0x82, 0x3c, 0x33, 0x09, 0x54, 0xd2, 0x61, 0x39, 0x30, 0x9b, 0xfd, 0xef, 0x21, 0x26, 0xd4, 0x70, 0xfa, 0xee, 0xf9, 0x31, 0x33, 0x73, 0x84, 0xd0, 0xb3, 0x81, 0xbf, 0xec, 0x2e}} , + {{0xe8, 0x93, 0x8b, 0x00, 0x64, 0xf7, 0x9c, 0xb8, 0x74, 0xe0, 0xe6, 0x49, 0x48, 0x4d, 0x4d, 0x48, 0xb6, 0x19, 0xa1, 0x40, 0xb7, 0xd9, 0x32, 0x41, 0x7c, 0x82, 0x37, 0xa1, 0x2d, 0xdc, 0xd2, 0x54}}}, +{{{0x68, 0x2b, 0x4a, 0x5b, 0xd5, 0xc7, 0x51, 0x91, 0x1d, 0xe1, 0x2a, 0x4b, 0xc4, 0x47, 0xf1, 0xbc, 0x7a, 0xb3, 0xcb, 0xc8, 0xb6, 0x7c, 0xac, 0x90, 0x05, 0xfd, 0xf3, 0xf9, 0x52, 0x3a, 0x11, 0x6b}} , + {{0x3d, 0xc1, 0x27, 0xf3, 0x59, 0x43, 0x95, 0x90, 0xc5, 0x96, 0x79, 0xf5, 0xf4, 0x95, 0x65, 0x29, 0x06, 0x9c, 0x51, 0x05, 0x18, 0xda, 0xb8, 0x2e, 0x79, 0x7e, 0x69, 0x59, 0x71, 0x01, 0xeb, 0x1a}}}, +{{{0x15, 0x06, 0x49, 0xb6, 0x8a, 0x3c, 0xea, 0x2f, 0x34, 0x20, 0x14, 0xc3, 0xaa, 0xd6, 0xaf, 0x2c, 0x3e, 0xbd, 0x65, 0x20, 0xe2, 0x4d, 0x4b, 0x3b, 0xeb, 0x9f, 0x4a, 0xc3, 0xad, 0xa4, 0x3b, 0x60}} , + {{0xbc, 0x58, 0xe6, 0xc0, 0x95, 0x2a, 0x2a, 0x81, 0x9a, 0x7a, 0xf3, 0xd2, 0x06, 0xbe, 0x48, 0xbc, 0x0c, 0xc5, 0x46, 0xe0, 0x6a, 0xd4, 0xac, 0x0f, 0xd9, 0xcc, 0x82, 0x34, 0x2c, 0xaf, 0xdb, 0x1f}}}, +{{{0xf7, 0x17, 0x13, 0xbd, 0xfb, 0xbc, 0xd2, 0xec, 0x45, 0xb3, 0x15, 0x31, 0xe9, 0xaf, 0x82, 0x84, 0x3d, 0x28, 0xc6, 0xfc, 0x11, 0xf5, 0x41, 0xb5, 0x8b, 0xd3, 0x12, 0x76, 0x52, 0xe7, 0x1a, 0x3c}} , + {{0x4e, 0x36, 0x11, 0x07, 0xa2, 0x15, 0x20, 0x51, 0xc4, 0x2a, 0xc3, 0x62, 0x8b, 0x5e, 0x7f, 0xa6, 0x0f, 0xf9, 0x45, 0x85, 0x6c, 0x11, 0x86, 0xb7, 0x7e, 0xe5, 0xd7, 0xf9, 0xc3, 0x91, 0x1c, 0x05}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xea, 0xd6, 0xde, 0x29, 0x3a, 0x00, 0xb9, 0x02, 0x59, 0xcb, 0x26, 0xc4, 0xba, 0x99, 0xb1, 0x97, 0x2f, 0x8e, 0x00, 0x92, 0x26, 0x4f, 0x52, 0xeb, 0x47, 0x1b, 0x89, 0x8b, 0x24, 0xc0, 0x13, 0x7d}} , + {{0xd5, 0x20, 0x5b, 0x80, 0xa6, 0x80, 0x20, 0x95, 0xc3, 0xe9, 0x9f, 0x8e, 0x87, 0x9e, 0x1e, 0x9e, 0x7a, 0xc7, 0xcc, 0x75, 0x6c, 0xa5, 0xf1, 0x91, 0x1a, 0xa8, 0x01, 0x2c, 0xab, 0x76, 0xa9, 0x59}}}, +{{{0xde, 0xc9, 0xb1, 0x31, 0x10, 0x16, 0xaa, 0x35, 0x14, 0x6a, 0xd4, 0xb5, 0x34, 0x82, 0x71, 0xd2, 0x4a, 0x5d, 0x9a, 0x1f, 0x53, 0x26, 0x3c, 0xe5, 0x8e, 0x8d, 0x33, 0x7f, 0xff, 0xa9, 0xd5, 0x17}} , + {{0x89, 0xaf, 0xf6, 0xa4, 0x64, 0xd5, 0x10, 0xe0, 0x1d, 0xad, 0xef, 0x44, 0xbd, 0xda, 0x83, 0xac, 0x7a, 0xa8, 0xf0, 0x1c, 0x07, 0xf9, 0xc3, 0x43, 0x6c, 0x3f, 0xb7, 0xd3, 0x87, 0x22, 0x02, 0x73}}}, +{{{0x64, 0x1d, 0x49, 0x13, 0x2f, 0x71, 0xec, 0x69, 0x87, 0xd0, 0x42, 0xee, 0x13, 0xec, 0xe3, 0xed, 0x56, 0x7b, 0xbf, 0xbd, 0x8c, 0x2f, 0x7d, 0x7b, 0x9d, 0x28, 0xec, 0x8e, 0x76, 0x2f, 0x6f, 0x08}} , + {{0x22, 0xf5, 0x5f, 0x4d, 0x15, 0xef, 0xfc, 0x4e, 0x57, 0x03, 0x36, 0x89, 0xf0, 0xeb, 0x5b, 0x91, 0xd6, 0xe2, 0xca, 0x01, 0xa5, 0xee, 0x52, 0xec, 0xa0, 0x3c, 0x8f, 0x33, 0x90, 0x5a, 0x94, 0x72}}}, +{{{0x8a, 0x4b, 0xe7, 0x38, 0xbc, 0xda, 0xc2, 0xb0, 0x85, 0xe1, 0x4a, 0xfe, 0x2d, 0x44, 0x84, 0xcb, 0x20, 0x6b, 0x2d, 0xbf, 0x11, 0x9c, 0xd7, 0xbe, 0xd3, 0x3e, 0x5f, 0xbf, 0x68, 0xbc, 0xa8, 0x07}} , + {{0x01, 0x89, 0x28, 0x22, 0x6a, 0x78, 0xaa, 0x29, 0x03, 0xc8, 0x74, 0x95, 0x03, 0x3e, 0xdc, 0xbd, 0x07, 0x13, 0xa8, 0xa2, 0x20, 0x2d, 0xb3, 0x18, 0x70, 0x42, 0xfd, 0x7a, 0xc4, 0xd7, 0x49, 0x72}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x02, 0xff, 0x32, 0x2b, 0x5c, 0x93, 0x54, 0x32, 0xe8, 0x57, 0x54, 0x1a, 0x8b, 0x33, 0x60, 0x65, 0xd3, 0x67, 0xa4, 0xc1, 0x26, 0xc4, 0xa4, 0x34, 0x1f, 0x9b, 0xa7, 0xa9, 0xf4, 0xd9, 0x4f, 0x5b}} , + {{0x46, 0x8d, 0xb0, 0x33, 0x54, 0x26, 0x5b, 0x68, 0xdf, 0xbb, 0xc5, 0xec, 0xc2, 0xf9, 0x3c, 0x5a, 0x37, 0xc1, 0x8e, 0x27, 0x47, 0xaa, 0x49, 0x5a, 0xf8, 0xfb, 0x68, 0x04, 0x23, 0xd1, 0xeb, 0x40}}}, +{{{0x65, 0xa5, 0x11, 0x84, 0x8a, 0x67, 0x9d, 0x9e, 0xd1, 0x44, 0x68, 0x7a, 0x34, 0xe1, 0x9f, 0xa3, 0x54, 0xcd, 0x07, 0xca, 0x79, 0x1f, 0x54, 0x2f, 0x13, 0x70, 0x4e, 0xee, 0xa2, 0xfa, 0xe7, 0x5d}} , + {{0x36, 0xec, 0x54, 0xf8, 0xce, 0xe4, 0x85, 0xdf, 0xf6, 0x6f, 0x1d, 0x90, 0x08, 0xbc, 0xe8, 0xc0, 0x92, 0x2d, 0x43, 0x6b, 0x92, 0xa9, 0x8e, 0xab, 0x0a, 0x2e, 0x1c, 0x1e, 0x64, 0x23, 0x9f, 0x2c}}}, +{{{0xa7, 0xd6, 0x2e, 0xd5, 0xcc, 0xd4, 0xcb, 0x5a, 0x3b, 0xa7, 0xf9, 0x46, 0x03, 0x1d, 0xad, 0x2b, 0x34, 0x31, 0x90, 0x00, 0x46, 0x08, 0x82, 0x14, 0xc4, 0xe0, 0x9c, 0xf0, 0xe3, 0x55, 0x43, 0x31}} , + {{0x60, 0xd6, 0xdd, 0x78, 0xe6, 0xd4, 0x22, 0x42, 0x1f, 0x00, 0xf9, 0xb1, 0x6a, 0x63, 0xe2, 0x92, 0x59, 0xd1, 0x1a, 0xb7, 0x00, 0x54, 0x29, 0xc9, 0xc1, 0xf6, 0x6f, 0x7a, 0xc5, 0x3c, 0x5f, 0x65}}}, +{{{0x27, 0x4f, 0xd0, 0x72, 0xb1, 0x11, 0x14, 0x27, 0x15, 0x94, 0x48, 0x81, 0x7e, 0x74, 0xd8, 0x32, 0xd5, 0xd1, 0x11, 0x28, 0x60, 0x63, 0x36, 0x32, 0x37, 0xb5, 0x13, 0x1c, 0xa0, 0x37, 0xe3, 0x74}} , + {{0xf1, 0x25, 0x4e, 0x11, 0x96, 0x67, 0xe6, 0x1c, 0xc2, 0xb2, 0x53, 0xe2, 0xda, 0x85, 0xee, 0xb2, 0x9f, 0x59, 0xf3, 0xba, 0xbd, 0xfa, 0xcf, 0x6e, 0xf9, 0xda, 0xa4, 0xb3, 0x02, 0x8f, 0x64, 0x08}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x34, 0x94, 0xf2, 0x64, 0x54, 0x47, 0x37, 0x07, 0x40, 0x8a, 0x20, 0xba, 0x4a, 0x55, 0xd7, 0x3f, 0x47, 0xba, 0x25, 0x23, 0x14, 0xb0, 0x2c, 0xe8, 0x55, 0xa8, 0xa6, 0xef, 0x51, 0xbd, 0x6f, 0x6a}} , + {{0x71, 0xd6, 0x16, 0x76, 0xb2, 0x06, 0xea, 0x79, 0xf5, 0xc4, 0xc3, 0x52, 0x7e, 0x61, 0xd1, 0xe1, 0xad, 0x70, 0x78, 0x1d, 0x16, 0x11, 0xf8, 0x7c, 0x2b, 0xfc, 0x55, 0x9f, 0x52, 0xf8, 0xf5, 0x16}}}, +{{{0x34, 0x96, 0x9a, 0xf6, 0xc5, 0xe0, 0x14, 0x03, 0x24, 0x0e, 0x4c, 0xad, 0x9e, 0x9a, 0x70, 0x23, 0x96, 0xb2, 0xf1, 0x2e, 0x9d, 0xc3, 0x32, 0x9b, 0x54, 0xa5, 0x73, 0xde, 0x88, 0xb1, 0x3e, 0x24}} , + {{0xf6, 0xe2, 0x4c, 0x1f, 0x5b, 0xb2, 0xaf, 0x82, 0xa5, 0xcf, 0x81, 0x10, 0x04, 0xef, 0xdb, 0xa2, 0xcc, 0x24, 0xb2, 0x7e, 0x0b, 0x7a, 0xeb, 0x01, 0xd8, 0x52, 0xf4, 0x51, 0x89, 0x29, 0x79, 0x37}}}, +{{{0x74, 0xde, 0x12, 0xf3, 0x68, 0xb7, 0x66, 0xc3, 0xee, 0x68, 0xdc, 0x81, 0xb5, 0x55, 0x99, 0xab, 0xd9, 0x28, 0x63, 0x6d, 0x8b, 0x40, 0x69, 0x75, 0x6c, 0xcd, 0x5c, 0x2a, 0x7e, 0x32, 0x7b, 0x29}} , + {{0x02, 0xcc, 0x22, 0x74, 0x4d, 0x19, 0x07, 0xc0, 0xda, 0xb5, 0x76, 0x51, 0x2a, 0xaa, 0xa6, 0x0a, 0x5f, 0x26, 0xd4, 0xbc, 0xaf, 0x48, 0x88, 0x7f, 0x02, 0xbc, 0xf2, 0xe1, 0xcf, 0xe9, 0xdd, 0x15}}}, +{{{0xed, 0xb5, 0x9a, 0x8c, 0x9a, 0xdd, 0x27, 0xf4, 0x7f, 0x47, 0xd9, 0x52, 0xa7, 0xcd, 0x65, 0xa5, 0x31, 0x22, 0xed, 0xa6, 0x63, 0x5b, 0x80, 0x4a, 0xad, 0x4d, 0xed, 0xbf, 0xee, 0x49, 0xb3, 0x06}} , + {{0xf8, 0x64, 0x8b, 0x60, 0x90, 0xe9, 0xde, 0x44, 0x77, 0xb9, 0x07, 0x36, 0x32, 0xc2, 0x50, 0xf5, 0x65, 0xdf, 0x48, 0x4c, 0x37, 0xaa, 0x68, 0xab, 0x9a, 0x1f, 0x3e, 0xff, 0x89, 0x92, 0xa0, 0x07}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x7d, 0x4f, 0x9c, 0x19, 0xc0, 0x4a, 0x31, 0xec, 0xf9, 0xaa, 0xeb, 0xb2, 0x16, 0x9c, 0xa3, 0x66, 0x5f, 0xd1, 0xd4, 0xed, 0xb8, 0x92, 0x1c, 0xab, 0xda, 0xea, 0xd9, 0x57, 0xdf, 0x4c, 0x2a, 0x48}} , + {{0x4b, 0xb0, 0x4e, 0x6e, 0x11, 0x3b, 0x51, 0xbd, 0x6a, 0xfd, 0xe4, 0x25, 0xa5, 0x5f, 0x11, 0x3f, 0x98, 0x92, 0x51, 0x14, 0xc6, 0x5f, 0x3c, 0x0b, 0xa8, 0xf7, 0xc2, 0x81, 0x43, 0xde, 0x91, 0x73}}}, +{{{0x3c, 0x8f, 0x9f, 0x33, 0x2a, 0x1f, 0x43, 0x33, 0x8f, 0x68, 0xff, 0x1f, 0x3d, 0x73, 0x6b, 0xbf, 0x68, 0xcc, 0x7d, 0x13, 0x6c, 0x24, 0x4b, 0xcc, 0x4d, 0x24, 0x0d, 0xfe, 0xde, 0x86, 0xad, 0x3b}} , + {{0x79, 0x51, 0x81, 0x01, 0xdc, 0x73, 0x53, 0xe0, 0x6e, 0x9b, 0xea, 0x68, 0x3f, 0x5c, 0x14, 0x84, 0x53, 0x8d, 0x4b, 0xc0, 0x9f, 0x9f, 0x89, 0x2b, 0x8c, 0xba, 0x86, 0xfa, 0xf2, 0xcd, 0xe3, 0x2d}}}, +{{{0x06, 0xf9, 0x29, 0x5a, 0xdb, 0x3d, 0x84, 0x52, 0xab, 0xcc, 0x6b, 0x60, 0x9d, 0xb7, 0x4a, 0x0e, 0x36, 0x63, 0x91, 0xad, 0xa0, 0x95, 0xb0, 0x97, 0x89, 0x4e, 0xcf, 0x7d, 0x3c, 0xe5, 0x7c, 0x28}} , + {{0x2e, 0x69, 0x98, 0xfd, 0xc6, 0xbd, 0xcc, 0xca, 0xdf, 0x9a, 0x44, 0x7e, 0x9d, 0xca, 0x89, 0x6d, 0xbf, 0x27, 0xc2, 0xf8, 0xcd, 0x46, 0x00, 0x2b, 0xb5, 0x58, 0x4e, 0xb7, 0x89, 0x09, 0xe9, 0x2d}}}, +{{{0x54, 0xbe, 0x75, 0xcb, 0x05, 0xb0, 0x54, 0xb7, 0xe7, 0x26, 0x86, 0x4a, 0xfc, 0x19, 0xcf, 0x27, 0x46, 0xd4, 0x22, 0x96, 0x5a, 0x11, 0xe8, 0xd5, 0x1b, 0xed, 0x71, 0xc5, 0x5d, 0xc8, 0xaf, 0x45}} , + {{0x40, 0x7b, 0x77, 0x57, 0x49, 0x9e, 0x80, 0x39, 0x23, 0xee, 0x81, 0x0b, 0x22, 0xcf, 0xdb, 0x7a, 0x2f, 0x14, 0xb8, 0x57, 0x8f, 0xa1, 0x39, 0x1e, 0x77, 0xfc, 0x0b, 0xa6, 0xbf, 0x8a, 0x0c, 0x6c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x77, 0x3a, 0xd4, 0xd8, 0x27, 0xcf, 0xe8, 0xa1, 0x72, 0x9d, 0xca, 0xdd, 0x0d, 0x96, 0xda, 0x79, 0xed, 0x56, 0x42, 0x15, 0x60, 0xc7, 0x1c, 0x6b, 0x26, 0x30, 0xf6, 0x6a, 0x95, 0x67, 0xf3, 0x0a}} , + {{0xc5, 0x08, 0xa4, 0x2b, 0x2f, 0xbd, 0x31, 0x81, 0x2a, 0xa6, 0xb6, 0xe4, 0x00, 0x91, 0xda, 0x3d, 0xb2, 0xb0, 0x96, 0xce, 0x8a, 0xd2, 0x8d, 0x70, 0xb3, 0xd3, 0x34, 0x01, 0x90, 0x8d, 0x10, 0x21}}}, +{{{0x33, 0x0d, 0xe7, 0xba, 0x4f, 0x07, 0xdf, 0x8d, 0xea, 0x7d, 0xa0, 0xc5, 0xd6, 0xb1, 0xb0, 0xe5, 0x57, 0x1b, 0x5b, 0xf5, 0x45, 0x13, 0x14, 0x64, 0x5a, 0xeb, 0x5c, 0xfc, 0x54, 0x01, 0x76, 0x2b}} , + {{0x02, 0x0c, 0xc2, 0xaf, 0x96, 0x36, 0xfe, 0x4a, 0xe2, 0x54, 0x20, 0x6a, 0xeb, 0xb2, 0x9f, 0x62, 0xd7, 0xce, 0xa2, 0x3f, 0x20, 0x11, 0x34, 0x37, 0xe0, 0x42, 0xed, 0x6f, 0xf9, 0x1a, 0xc8, 0x7d}}}, +{{{0xd8, 0xb9, 0x11, 0xe8, 0x36, 0x3f, 0x42, 0xc1, 0xca, 0xdc, 0xd3, 0xf1, 0xc8, 0x23, 0x3d, 0x4f, 0x51, 0x7b, 0x9d, 0x8d, 0xd8, 0xe4, 0xa0, 0xaa, 0xf3, 0x04, 0xd6, 0x11, 0x93, 0xc8, 0x35, 0x45}} , + {{0x61, 0x36, 0xd6, 0x08, 0x90, 0xbf, 0xa7, 0x7a, 0x97, 0x6c, 0x0f, 0x84, 0xd5, 0x33, 0x2d, 0x37, 0xc9, 0x6a, 0x80, 0x90, 0x3d, 0x0a, 0xa2, 0xaa, 0xe1, 0xb8, 0x84, 0xba, 0x61, 0x36, 0xdd, 0x69}}}, +{{{0x6b, 0xdb, 0x5b, 0x9c, 0xc6, 0x92, 0xbc, 0x23, 0xaf, 0xc5, 0xb8, 0x75, 0xf8, 0x42, 0xfa, 0xd6, 0xb6, 0x84, 0x94, 0x63, 0x98, 0x93, 0x48, 0x78, 0x38, 0xcd, 0xbb, 0x18, 0x34, 0xc3, 0xdb, 0x67}} , + {{0x96, 0xf3, 0x3a, 0x09, 0x56, 0xb0, 0x6f, 0x7c, 0x51, 0x1e, 0x1b, 0x39, 0x48, 0xea, 0xc9, 0x0c, 0x25, 0xa2, 0x7a, 0xca, 0xe7, 0x92, 0xfc, 0x59, 0x30, 0xa3, 0x89, 0x85, 0xdf, 0x6f, 0x43, 0x38}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x79, 0x84, 0x44, 0x19, 0xbd, 0xe9, 0x54, 0xc4, 0xc0, 0x6e, 0x2a, 0xa8, 0xa8, 0x9b, 0x43, 0xd5, 0x71, 0x22, 0x5f, 0xdc, 0x01, 0xfa, 0xdf, 0xb3, 0xb8, 0x47, 0x4b, 0x0a, 0xa5, 0x44, 0xea, 0x29}} , + {{0x05, 0x90, 0x50, 0xaf, 0x63, 0x5f, 0x9d, 0x9e, 0xe1, 0x9d, 0x38, 0x97, 0x1f, 0x6c, 0xac, 0x30, 0x46, 0xb2, 0x6a, 0x19, 0xd1, 0x4b, 0xdb, 0xbb, 0x8c, 0xda, 0x2e, 0xab, 0xc8, 0x5a, 0x77, 0x6c}}}, +{{{0x2b, 0xbe, 0xaf, 0xa1, 0x6d, 0x2f, 0x0b, 0xb1, 0x8f, 0xe3, 0xe0, 0x38, 0xcd, 0x0b, 0x41, 0x1b, 0x4a, 0x15, 0x07, 0xf3, 0x6f, 0xdc, 0xb8, 0xe9, 0xde, 0xb2, 0xa3, 0x40, 0x01, 0xa6, 0x45, 0x1e}} , + {{0x76, 0x0a, 0xda, 0x8d, 0x2c, 0x07, 0x3f, 0x89, 0x7d, 0x04, 0xad, 0x43, 0x50, 0x6e, 0xd2, 0x47, 0xcb, 0x8a, 0xe6, 0x85, 0x1a, 0x24, 0xf3, 0xd2, 0x60, 0xfd, 0xdf, 0x73, 0xa4, 0x0d, 0x73, 0x0e}}}, +{{{0xfd, 0x67, 0x6b, 0x71, 0x9b, 0x81, 0x53, 0x39, 0x39, 0xf4, 0xb8, 0xd5, 0xc3, 0x30, 0x9b, 0x3b, 0x7c, 0xa3, 0xf0, 0xd0, 0x84, 0x21, 0xd6, 0xbf, 0xb7, 0x4c, 0x87, 0x13, 0x45, 0x2d, 0xa7, 0x55}} , + {{0x5d, 0x04, 0xb3, 0x40, 0x28, 0x95, 0x2d, 0x30, 0x83, 0xec, 0x5e, 0xe4, 0xff, 0x75, 0xfe, 0x79, 0x26, 0x9d, 0x1d, 0x36, 0xcd, 0x0a, 0x15, 0xd2, 0x24, 0x14, 0x77, 0x71, 0xd7, 0x8a, 0x1b, 0x04}}}, +{{{0x5d, 0x93, 0xc9, 0xbe, 0xaa, 0x90, 0xcd, 0x9b, 0xfb, 0x73, 0x7e, 0xb0, 0x64, 0x98, 0x57, 0x44, 0x42, 0x41, 0xb1, 0xaf, 0xea, 0xc1, 0xc3, 0x22, 0xff, 0x60, 0x46, 0xcb, 0x61, 0x81, 0x70, 0x61}} , + {{0x0d, 0x82, 0xb9, 0xfe, 0x21, 0xcd, 0xc4, 0xf5, 0x98, 0x0c, 0x4e, 0x72, 0xee, 0x87, 0x49, 0xf8, 0xa1, 0x95, 0xdf, 0x8f, 0x2d, 0xbd, 0x21, 0x06, 0x7c, 0x15, 0xe8, 0x12, 0x6d, 0x93, 0xd6, 0x38}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x91, 0xf7, 0x51, 0xd9, 0xef, 0x7d, 0x42, 0x01, 0x13, 0xe9, 0xb8, 0x7f, 0xa6, 0x49, 0x17, 0x64, 0x21, 0x80, 0x83, 0x2c, 0x63, 0x4c, 0x60, 0x09, 0x59, 0x91, 0x92, 0x77, 0x39, 0x51, 0xf4, 0x48}} , + {{0x60, 0xd5, 0x22, 0x83, 0x08, 0x2f, 0xff, 0x99, 0x3e, 0x69, 0x6d, 0x88, 0xda, 0xe7, 0x5b, 0x52, 0x26, 0x31, 0x2a, 0xe5, 0x89, 0xde, 0x68, 0x90, 0xb6, 0x22, 0x5a, 0xbd, 0xd3, 0x85, 0x53, 0x31}}}, +{{{0xd8, 0xce, 0xdc, 0xf9, 0x3c, 0x4b, 0xa2, 0x1d, 0x2c, 0x2f, 0x36, 0xbe, 0x7a, 0xfc, 0xcd, 0xbc, 0xdc, 0xf9, 0x30, 0xbd, 0xff, 0x05, 0xc7, 0xe4, 0x8e, 0x17, 0x62, 0xf8, 0x4d, 0xa0, 0x56, 0x79}} , + {{0x82, 0xe7, 0xf6, 0xba, 0x53, 0x84, 0x0a, 0xa3, 0x34, 0xff, 0x3c, 0xa3, 0x6a, 0xa1, 0x37, 0xea, 0xdd, 0xb6, 0x95, 0xb3, 0x78, 0x19, 0x76, 0x1e, 0x55, 0x2f, 0x77, 0x2e, 0x7f, 0xc1, 0xea, 0x5e}}}, +{{{0x83, 0xe1, 0x6e, 0xa9, 0x07, 0x33, 0x3e, 0x83, 0xff, 0xcb, 0x1c, 0x9f, 0xb1, 0xa3, 0xb4, 0xc9, 0xe1, 0x07, 0x97, 0xff, 0xf8, 0x23, 0x8f, 0xce, 0x40, 0xfd, 0x2e, 0x5e, 0xdb, 0x16, 0x43, 0x2d}} , + {{0xba, 0x38, 0x02, 0xf7, 0x81, 0x43, 0x83, 0xa3, 0x20, 0x4f, 0x01, 0x3b, 0x8a, 0x04, 0x38, 0x31, 0xc6, 0x0f, 0xc8, 0xdf, 0xd7, 0xfa, 0x2f, 0x88, 0x3f, 0xfc, 0x0c, 0x76, 0xc4, 0xa6, 0x45, 0x72}}}, +{{{0xbb, 0x0c, 0xbc, 0x6a, 0xa4, 0x97, 0x17, 0x93, 0x2d, 0x6f, 0xde, 0x72, 0x10, 0x1c, 0x08, 0x2c, 0x0f, 0x80, 0x32, 0x68, 0x27, 0xd4, 0xab, 0xdd, 0xc5, 0x58, 0x61, 0x13, 0x6d, 0x11, 0x1e, 0x4d}} , + {{0x1a, 0xb9, 0xc9, 0x10, 0xfb, 0x1e, 0x4e, 0xf4, 0x84, 0x4b, 0x8a, 0x5e, 0x7b, 0x4b, 0xe8, 0x43, 0x8c, 0x8f, 0x00, 0xb5, 0x54, 0x13, 0xc5, 0x5c, 0xb6, 0x35, 0x4e, 0x9d, 0xe4, 0x5b, 0x41, 0x6d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x15, 0x7d, 0x12, 0x48, 0x82, 0x14, 0x42, 0xcd, 0x32, 0xd4, 0x4b, 0xc1, 0x72, 0x61, 0x2a, 0x8c, 0xec, 0xe2, 0xf8, 0x24, 0x45, 0x94, 0xe3, 0xbe, 0xdd, 0x67, 0xa8, 0x77, 0x5a, 0xae, 0x5b, 0x4b}} , + {{0xcb, 0x77, 0x9a, 0x20, 0xde, 0xb8, 0x23, 0xd9, 0xa0, 0x0f, 0x8c, 0x7b, 0xa5, 0xcb, 0xae, 0xb6, 0xec, 0x42, 0x67, 0x0e, 0x58, 0xa4, 0x75, 0x98, 0x21, 0x71, 0x84, 0xb3, 0xe0, 0x76, 0x94, 0x73}}}, +{{{0xdf, 0xfc, 0x69, 0x28, 0x23, 0x3f, 0x5b, 0xf8, 0x3b, 0x24, 0x37, 0xf3, 0x1d, 0xd5, 0x22, 0x6b, 0xd0, 0x98, 0xa8, 0x6c, 0xcf, 0xff, 0x06, 0xe1, 0x13, 0xdf, 0xb9, 0xc1, 0x0c, 0xa9, 0xbf, 0x33}} , + {{0xd9, 0x81, 0xda, 0xb2, 0x4f, 0x82, 0x9d, 0x43, 0x81, 0x09, 0xf1, 0xd2, 0x01, 0xef, 0xac, 0xf4, 0x2d, 0x7d, 0x01, 0x09, 0xf1, 0xff, 0xa5, 0x9f, 0xe5, 0xca, 0x27, 0x63, 0xdb, 0x20, 0xb1, 0x53}}}, +{{{0x67, 0x02, 0xe8, 0xad, 0xa9, 0x34, 0xd4, 0xf0, 0x15, 0x81, 0xaa, 0xc7, 0x4d, 0x87, 0x94, 0xea, 0x75, 0xe7, 0x4c, 0x94, 0x04, 0x0e, 0x69, 0x87, 0xe7, 0x51, 0x91, 0x10, 0x03, 0xc7, 0xbe, 0x56}} , + {{0x32, 0xfb, 0x86, 0xec, 0x33, 0x6b, 0x2e, 0x51, 0x2b, 0xc8, 0xfa, 0x6c, 0x70, 0x47, 0x7e, 0xce, 0x05, 0x0c, 0x71, 0xf3, 0xb4, 0x56, 0xa6, 0xdc, 0xcc, 0x78, 0x07, 0x75, 0xd0, 0xdd, 0xb2, 0x6a}}}, +{{{0xc6, 0xef, 0xb9, 0xc0, 0x2b, 0x22, 0x08, 0x1e, 0x71, 0x70, 0xb3, 0x35, 0x9c, 0x7a, 0x01, 0x92, 0x44, 0x9a, 0xf6, 0xb0, 0x58, 0x95, 0xc1, 0x9b, 0x02, 0xed, 0x2d, 0x7c, 0x34, 0x29, 0x49, 0x44}} , + {{0x45, 0x62, 0x1d, 0x2e, 0xff, 0x2a, 0x1c, 0x21, 0xa4, 0x25, 0x7b, 0x0d, 0x8c, 0x15, 0x39, 0xfc, 0x8f, 0x7c, 0xa5, 0x7d, 0x1e, 0x25, 0xa3, 0x45, 0xd6, 0xab, 0xbd, 0xcb, 0xc5, 0x5e, 0x78, 0x77}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0xd3, 0x42, 0xed, 0x1d, 0x00, 0x3c, 0x15, 0x2c, 0x9c, 0x77, 0x81, 0xd2, 0x73, 0xd1, 0x06, 0xd5, 0xc4, 0x7f, 0x94, 0xbb, 0x92, 0x2d, 0x2c, 0x4b, 0x45, 0x4b, 0xe9, 0x2a, 0x89, 0x6b, 0x2b}} , + {{0xd2, 0x0c, 0x88, 0xc5, 0x48, 0x4d, 0xea, 0x0d, 0x4a, 0xc9, 0x52, 0x6a, 0x61, 0x79, 0xe9, 0x76, 0xf3, 0x85, 0x52, 0x5c, 0x1b, 0x2c, 0xe1, 0xd6, 0xc4, 0x0f, 0x18, 0x0e, 0x4e, 0xf6, 0x1c, 0x7f}}}, +{{{0xb4, 0x04, 0x2e, 0x42, 0xcb, 0x1f, 0x2b, 0x11, 0x51, 0x7b, 0x08, 0xac, 0xaa, 0x3e, 0x9e, 0x52, 0x60, 0xb7, 0xc2, 0x61, 0x57, 0x8c, 0x84, 0xd5, 0x18, 0xa6, 0x19, 0xfc, 0xb7, 0x75, 0x91, 0x1b}} , + {{0xe8, 0x68, 0xca, 0x44, 0xc8, 0x38, 0x38, 0xcc, 0x53, 0x0a, 0x32, 0x35, 0xcc, 0x52, 0xcb, 0x0e, 0xf7, 0xc5, 0xe7, 0xec, 0x3d, 0x85, 0xcc, 0x58, 0xe2, 0x17, 0x47, 0xff, 0x9f, 0xa5, 0x30, 0x17}}}, +{{{0xe3, 0xae, 0xc8, 0xc1, 0x71, 0x75, 0x31, 0x00, 0x37, 0x41, 0x5c, 0x0e, 0x39, 0xda, 0x73, 0xa0, 0xc7, 0x97, 0x36, 0x6c, 0x5b, 0xf2, 0xee, 0x64, 0x0a, 0x3d, 0x89, 0x1e, 0x1d, 0x49, 0x8c, 0x37}} , + {{0x4c, 0xe6, 0xb0, 0xc1, 0xa5, 0x2a, 0x82, 0x09, 0x08, 0xad, 0x79, 0x9c, 0x56, 0xf6, 0xf9, 0xc1, 0xd7, 0x7c, 0x39, 0x7f, 0x93, 0xca, 0x11, 0x55, 0xbf, 0x07, 0x1b, 0x82, 0x29, 0x69, 0x95, 0x5c}}}, +{{{0x87, 0xee, 0xa6, 0x56, 0x9e, 0xc2, 0x9a, 0x56, 0x24, 0x42, 0x85, 0x4d, 0x98, 0x31, 0x1e, 0x60, 0x4d, 0x87, 0x85, 0x04, 0xae, 0x46, 0x12, 0xf9, 0x8e, 0x7f, 0xe4, 0x7f, 0xf6, 0x1c, 0x37, 0x01}} , + {{0x73, 0x4c, 0xb6, 0xc5, 0xc4, 0xe9, 0x6c, 0x85, 0x48, 0x4a, 0x5a, 0xac, 0xd9, 0x1f, 0x43, 0xf8, 0x62, 0x5b, 0xee, 0x98, 0x2a, 0x33, 0x8e, 0x79, 0xce, 0x61, 0x06, 0x35, 0xd8, 0xd7, 0xca, 0x71}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x72, 0xd3, 0xae, 0xa6, 0xca, 0x8f, 0xcd, 0xcc, 0x78, 0x8e, 0x19, 0x4d, 0xa7, 0xd2, 0x27, 0xe9, 0xa4, 0x3c, 0x16, 0x5b, 0x84, 0x80, 0xf9, 0xd0, 0xcc, 0x6a, 0x1e, 0xca, 0x1e, 0x67, 0xbd, 0x63}} , + {{0x7b, 0x6e, 0x2a, 0xd2, 0x87, 0x48, 0xff, 0xa1, 0xca, 0xe9, 0x15, 0x85, 0xdc, 0xdb, 0x2c, 0x39, 0x12, 0x91, 0xa9, 0x20, 0xaa, 0x4f, 0x29, 0xf4, 0x15, 0x7a, 0xd2, 0xf5, 0x32, 0xcc, 0x60, 0x04}}}, +{{{0xe5, 0x10, 0x47, 0x3b, 0xfa, 0x90, 0xfc, 0x30, 0xb5, 0xea, 0x6f, 0x56, 0x8f, 0xfb, 0x0e, 0xa7, 0x3b, 0xc8, 0xb2, 0xff, 0x02, 0x7a, 0x33, 0x94, 0x93, 0x2a, 0x03, 0xe0, 0x96, 0x3a, 0x6c, 0x0f}} , + {{0x5a, 0x63, 0x67, 0xe1, 0x9b, 0x47, 0x78, 0x9f, 0x38, 0x79, 0xac, 0x97, 0x66, 0x1d, 0x5e, 0x51, 0xee, 0x24, 0x42, 0xe8, 0x58, 0x4b, 0x8a, 0x03, 0x75, 0x86, 0x37, 0x86, 0xe2, 0x97, 0x4e, 0x3d}}}, +{{{0x3f, 0x75, 0x8e, 0xb4, 0xff, 0xd8, 0xdd, 0xd6, 0x37, 0x57, 0x9d, 0x6d, 0x3b, 0xbd, 0xd5, 0x60, 0x88, 0x65, 0x9a, 0xb9, 0x4a, 0x68, 0x84, 0xa2, 0x67, 0xdd, 0x17, 0x25, 0x97, 0x04, 0x8b, 0x5e}} , + {{0xbb, 0x40, 0x5e, 0xbc, 0x16, 0x92, 0x05, 0xc4, 0xc0, 0x4e, 0x72, 0x90, 0x0e, 0xab, 0xcf, 0x8a, 0xed, 0xef, 0xb9, 0x2d, 0x3b, 0xf8, 0x43, 0x5b, 0xba, 0x2d, 0xeb, 0x2f, 0x52, 0xd2, 0xd1, 0x5a}}}, +{{{0x40, 0xb4, 0xab, 0xe6, 0xad, 0x9f, 0x46, 0x69, 0x4a, 0xb3, 0x8e, 0xaa, 0xea, 0x9c, 0x8a, 0x20, 0x16, 0x5d, 0x8c, 0x13, 0xbd, 0xf6, 0x1d, 0xc5, 0x24, 0xbd, 0x90, 0x2a, 0x1c, 0xc7, 0x13, 0x3b}} , + {{0x54, 0xdc, 0x16, 0x0d, 0x18, 0xbe, 0x35, 0x64, 0x61, 0x52, 0x02, 0x80, 0xaf, 0x05, 0xf7, 0xa6, 0x42, 0xd3, 0x8f, 0x2e, 0x79, 0x26, 0xa8, 0xbb, 0xb2, 0x17, 0x48, 0xb2, 0x7a, 0x0a, 0x89, 0x14}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x20, 0xa8, 0x88, 0xe3, 0x91, 0xc0, 0x6e, 0xbb, 0x8a, 0x27, 0x82, 0x51, 0x83, 0xb2, 0x28, 0xa9, 0x83, 0xeb, 0xa6, 0xa9, 0x4d, 0x17, 0x59, 0x22, 0x54, 0x00, 0x50, 0x45, 0xcb, 0x48, 0x4b, 0x18}} , + {{0x33, 0x7c, 0xe7, 0x26, 0xba, 0x4d, 0x32, 0xfe, 0x53, 0xf4, 0xfa, 0x83, 0xe3, 0xa5, 0x79, 0x66, 0x73, 0xef, 0x80, 0x23, 0x68, 0xc2, 0x60, 0xdd, 0xa9, 0x33, 0xdc, 0x03, 0x7a, 0xe0, 0xe0, 0x3e}}}, +{{{0x34, 0x5c, 0x13, 0xfb, 0xc0, 0xe3, 0x78, 0x2b, 0x54, 0x58, 0x22, 0x9b, 0x76, 0x81, 0x7f, 0x93, 0x9c, 0x25, 0x3c, 0xd2, 0xe9, 0x96, 0x21, 0x26, 0x08, 0xf5, 0xed, 0x95, 0x11, 0xae, 0x04, 0x5a}} , + {{0xb9, 0xe8, 0xc5, 0x12, 0x97, 0x1f, 0x83, 0xfe, 0x3e, 0x94, 0x99, 0xd4, 0x2d, 0xf9, 0x52, 0x59, 0x5c, 0x82, 0xa6, 0xf0, 0x75, 0x7e, 0xe8, 0xec, 0xcc, 0xac, 0x18, 0x21, 0x09, 0x67, 0x66, 0x67}}}, +{{{0xb3, 0x40, 0x29, 0xd1, 0xcb, 0x1b, 0x08, 0x9e, 0x9c, 0xb7, 0x53, 0xb9, 0x3b, 0x71, 0x08, 0x95, 0x12, 0x1a, 0x58, 0xaf, 0x7e, 0x82, 0x52, 0x43, 0x4f, 0x11, 0x39, 0xf4, 0x93, 0x1a, 0x26, 0x05}} , + {{0x6e, 0x44, 0xa3, 0xf9, 0x64, 0xaf, 0xe7, 0x6d, 0x7d, 0xdf, 0x1e, 0xac, 0x04, 0xea, 0x3b, 0x5f, 0x9b, 0xe8, 0x24, 0x9d, 0x0e, 0xe5, 0x2e, 0x3e, 0xdf, 0xa9, 0xf7, 0xd4, 0x50, 0x71, 0xf0, 0x78}}}, +{{{0x3e, 0xa8, 0x38, 0xc2, 0x57, 0x56, 0x42, 0x9a, 0xb1, 0xe2, 0xf8, 0x45, 0xaa, 0x11, 0x48, 0x5f, 0x17, 0xc4, 0x54, 0x27, 0xdc, 0x5d, 0xaa, 0xdd, 0x41, 0xbc, 0xdf, 0x81, 0xb9, 0x53, 0xee, 0x52}} , + {{0xc3, 0xf1, 0xa7, 0x6d, 0xb3, 0x5f, 0x92, 0x6f, 0xcc, 0x91, 0xb8, 0x95, 0x05, 0xdf, 0x3c, 0x64, 0x57, 0x39, 0x61, 0x51, 0xad, 0x8c, 0x38, 0x7b, 0xc8, 0xde, 0x00, 0x34, 0xbe, 0xa1, 0xb0, 0x7e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0x24, 0x1d, 0x8a, 0x67, 0x20, 0xee, 0x42, 0xeb, 0x38, 0xed, 0x0b, 0x8b, 0xcd, 0x46, 0x9d, 0x5e, 0x6b, 0x1e, 0x24, 0x9d, 0x12, 0x05, 0x1a, 0xcc, 0x05, 0x4e, 0x92, 0x38, 0xe1, 0x1f, 0x50}} , + {{0x4e, 0xee, 0x1c, 0x91, 0xe6, 0x11, 0xbd, 0x8e, 0x55, 0x1a, 0x18, 0x75, 0x66, 0xaf, 0x4d, 0x7b, 0x0f, 0xae, 0x6d, 0x85, 0xca, 0x82, 0x58, 0x21, 0x9c, 0x18, 0xe0, 0xed, 0xec, 0x22, 0x80, 0x2f}}}, +{{{0x68, 0x3b, 0x0a, 0x39, 0x1d, 0x6a, 0x15, 0x57, 0xfc, 0xf0, 0x63, 0x54, 0xdb, 0x39, 0xdb, 0xe8, 0x5c, 0x64, 0xff, 0xa0, 0x09, 0x4f, 0x3b, 0xb7, 0x32, 0x60, 0x99, 0x94, 0xfd, 0x94, 0x82, 0x2d}} , + {{0x24, 0xf6, 0x5a, 0x44, 0xf1, 0x55, 0x2c, 0xdb, 0xea, 0x7c, 0x84, 0x7c, 0x01, 0xac, 0xe3, 0xfd, 0xc9, 0x27, 0xc1, 0x5a, 0xb9, 0xde, 0x4f, 0x5a, 0x90, 0xdd, 0xc6, 0x67, 0xaa, 0x6f, 0x8a, 0x3a}}}, +{{{0x78, 0x52, 0x87, 0xc9, 0x97, 0x63, 0xb1, 0xdd, 0x54, 0x5f, 0xc1, 0xf8, 0xf1, 0x06, 0xa6, 0xa8, 0xa3, 0x88, 0x82, 0xd4, 0xcb, 0xa6, 0x19, 0xdd, 0xd1, 0x11, 0x87, 0x08, 0x17, 0x4c, 0x37, 0x2a}} , + {{0xa1, 0x0c, 0xf3, 0x08, 0x43, 0xd9, 0x24, 0x1e, 0x83, 0xa7, 0xdf, 0x91, 0xca, 0xbd, 0x69, 0x47, 0x8d, 0x1b, 0xe2, 0xb9, 0x4e, 0xb5, 0xe1, 0x76, 0xb3, 0x1c, 0x93, 0x03, 0xce, 0x5f, 0xb3, 0x5a}}}, +{{{0x1d, 0xda, 0xe4, 0x61, 0x03, 0x50, 0xa9, 0x8b, 0x68, 0x18, 0xef, 0xb2, 0x1c, 0x84, 0x3b, 0xa2, 0x44, 0x95, 0xa3, 0x04, 0x3b, 0xd6, 0x99, 0x00, 0xaf, 0x76, 0x42, 0x67, 0x02, 0x7d, 0x85, 0x56}} , + {{0xce, 0x72, 0x0e, 0x29, 0x84, 0xb2, 0x7d, 0xd2, 0x45, 0xbe, 0x57, 0x06, 0xed, 0x7f, 0xcf, 0xed, 0xcd, 0xef, 0x19, 0xd6, 0xbc, 0x15, 0x79, 0x64, 0xd2, 0x18, 0xe3, 0x20, 0x67, 0x3a, 0x54, 0x0b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x52, 0xfd, 0x04, 0xc5, 0xfb, 0x99, 0xe7, 0xe8, 0xfb, 0x8c, 0xe1, 0x42, 0x03, 0xef, 0x9d, 0xd9, 0x9e, 0x4d, 0xf7, 0x80, 0xcf, 0x2e, 0xcc, 0x9b, 0x45, 0xc9, 0x7b, 0x7a, 0xbc, 0x37, 0xa8, 0x52}} , + {{0x96, 0x11, 0x41, 0x8a, 0x47, 0x91, 0xfe, 0xb6, 0xda, 0x7a, 0x54, 0x63, 0xd1, 0x14, 0x35, 0x05, 0x86, 0x8c, 0xa9, 0x36, 0x3f, 0xf2, 0x85, 0x54, 0x4e, 0x92, 0xd8, 0x85, 0x01, 0x46, 0xd6, 0x50}}}, +{{{0x53, 0xcd, 0xf3, 0x86, 0x40, 0xe6, 0x39, 0x42, 0x95, 0xd6, 0xcb, 0x45, 0x1a, 0x20, 0xc8, 0x45, 0x4b, 0x32, 0x69, 0x04, 0xb1, 0xaf, 0x20, 0x46, 0xc7, 0x6b, 0x23, 0x5b, 0x69, 0xee, 0x30, 0x3f}} , + {{0x70, 0x83, 0x47, 0xc0, 0xdb, 0x55, 0x08, 0xa8, 0x7b, 0x18, 0x6d, 0xf5, 0x04, 0x5a, 0x20, 0x0c, 0x4a, 0x8c, 0x60, 0xae, 0xae, 0x0f, 0x64, 0x55, 0x55, 0x2e, 0xd5, 0x1d, 0x53, 0x31, 0x42, 0x41}}}, +{{{0xca, 0xfc, 0x88, 0x6b, 0x96, 0x78, 0x0a, 0x8b, 0x83, 0xdc, 0xbc, 0xaf, 0x40, 0xb6, 0x8d, 0x7f, 0xef, 0xb4, 0xd1, 0x3f, 0xcc, 0xa2, 0x74, 0xc9, 0xc2, 0x92, 0x55, 0x00, 0xab, 0xdb, 0xbf, 0x4f}} , + {{0x93, 0x1c, 0x06, 0x2d, 0x66, 0x65, 0x02, 0xa4, 0x97, 0x18, 0xfd, 0x00, 0xe7, 0xab, 0x03, 0xec, 0xce, 0xc1, 0xbf, 0x37, 0xf8, 0x13, 0x53, 0xa5, 0xe5, 0x0c, 0x3a, 0xa8, 0x55, 0xb9, 0xff, 0x68}}}, +{{{0xe4, 0xe6, 0x6d, 0x30, 0x7d, 0x30, 0x35, 0xc2, 0x78, 0x87, 0xf9, 0xfc, 0x6b, 0x5a, 0xc3, 0xb7, 0x65, 0xd8, 0x2e, 0xc7, 0xa5, 0x0c, 0xc6, 0xdc, 0x12, 0xaa, 0xd6, 0x4f, 0xc5, 0x38, 0xbc, 0x0e}} , + {{0xe2, 0x3c, 0x76, 0x86, 0x38, 0xf2, 0x7b, 0x2c, 0x16, 0x78, 0x8d, 0xf5, 0xa4, 0x15, 0xda, 0xdb, 0x26, 0x85, 0xa0, 0x56, 0xdd, 0x1d, 0xe3, 0xb3, 0xfd, 0x40, 0xef, 0xf2, 0xd9, 0xa1, 0xb3, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xdb, 0x49, 0x0e, 0xe6, 0x58, 0x10, 0x7a, 0x52, 0xda, 0xb5, 0x7d, 0x37, 0x6a, 0x3e, 0xa1, 0x78, 0xce, 0xc7, 0x1c, 0x24, 0x23, 0xdb, 0x7d, 0xfb, 0x8c, 0x8d, 0xdc, 0x30, 0x67, 0x69, 0x75, 0x3b}} , + {{0xa9, 0xea, 0x6d, 0x16, 0x16, 0x60, 0xf4, 0x60, 0x87, 0x19, 0x44, 0x8c, 0x4a, 0x8b, 0x3e, 0xfb, 0x16, 0x00, 0x00, 0x54, 0xa6, 0x9e, 0x9f, 0xef, 0xcf, 0xd9, 0xd2, 0x4c, 0x74, 0x31, 0xd0, 0x34}}}, +{{{0xa4, 0xeb, 0x04, 0xa4, 0x8c, 0x8f, 0x71, 0x27, 0x95, 0x85, 0x5d, 0x55, 0x4b, 0xb1, 0x26, 0x26, 0xc8, 0xae, 0x6a, 0x7d, 0xa2, 0x21, 0xca, 0xce, 0x38, 0xab, 0x0f, 0xd0, 0xd5, 0x2b, 0x6b, 0x00}} , + {{0xe5, 0x67, 0x0c, 0xf1, 0x3a, 0x9a, 0xea, 0x09, 0x39, 0xef, 0xd1, 0x30, 0xbc, 0x33, 0xba, 0xb1, 0x6a, 0xc5, 0x27, 0x08, 0x7f, 0x54, 0x80, 0x3d, 0xab, 0xf6, 0x15, 0x7a, 0xc2, 0x40, 0x73, 0x72}}}, +{{{0x84, 0x56, 0x82, 0xb6, 0x12, 0x70, 0x7f, 0xf7, 0xf0, 0xbd, 0x5b, 0xa9, 0xd5, 0xc5, 0x5f, 0x59, 0xbf, 0x7f, 0xb3, 0x55, 0x22, 0x02, 0xc9, 0x44, 0x55, 0x87, 0x8f, 0x96, 0x98, 0x64, 0x6d, 0x15}} , + {{0xb0, 0x8b, 0xaa, 0x1e, 0xec, 0xc7, 0xa5, 0x8f, 0x1f, 0x92, 0x04, 0xc6, 0x05, 0xf6, 0xdf, 0xa1, 0xcc, 0x1f, 0x81, 0xf5, 0x0e, 0x9c, 0x57, 0xdc, 0xe3, 0xbb, 0x06, 0x87, 0x1e, 0xfe, 0x23, 0x6c}}}, +{{{0xd8, 0x2b, 0x5b, 0x16, 0xea, 0x20, 0xf1, 0xd3, 0x68, 0x8f, 0xae, 0x5b, 0xd0, 0xa9, 0x1a, 0x19, 0xa8, 0x36, 0xfb, 0x2b, 0x57, 0x88, 0x7d, 0x90, 0xd5, 0xa6, 0xf3, 0xdc, 0x38, 0x89, 0x4e, 0x1f}} , + {{0xcc, 0x19, 0xda, 0x9b, 0x3b, 0x43, 0x48, 0x21, 0x2e, 0x23, 0x4d, 0x3d, 0xae, 0xf8, 0x8c, 0xfc, 0xdd, 0xa6, 0x74, 0x37, 0x65, 0xca, 0xee, 0x1a, 0x19, 0x8e, 0x9f, 0x64, 0x6f, 0x0c, 0x8b, 0x5a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0xb9, 0xc2, 0xf0, 0x72, 0xb8, 0x15, 0x16, 0xcc, 0x8d, 0x3c, 0x6f, 0x25, 0xed, 0xf4, 0x46, 0x2e, 0x0c, 0x60, 0x0f, 0xe2, 0x84, 0x34, 0x55, 0x89, 0x59, 0x34, 0x1b, 0xf5, 0x8d, 0xfe, 0x08}} , + {{0xf8, 0xab, 0x93, 0xbc, 0x44, 0xba, 0x1b, 0x75, 0x4b, 0x49, 0x6f, 0xd0, 0x54, 0x2e, 0x63, 0xba, 0xb5, 0xea, 0xed, 0x32, 0x14, 0xc9, 0x94, 0xd8, 0xc5, 0xce, 0xf4, 0x10, 0x68, 0xe0, 0x38, 0x27}}}, +{{{0x74, 0x1c, 0x14, 0x9b, 0xd4, 0x64, 0x61, 0x71, 0x5a, 0xb6, 0x21, 0x33, 0x4f, 0xf7, 0x8e, 0xba, 0xa5, 0x48, 0x9a, 0xc7, 0xfa, 0x9a, 0xf0, 0xb4, 0x62, 0xad, 0xf2, 0x5e, 0xcc, 0x03, 0x24, 0x1a}} , + {{0xf5, 0x76, 0xfd, 0xe4, 0xaf, 0xb9, 0x03, 0x59, 0xce, 0x63, 0xd2, 0x3b, 0x1f, 0xcd, 0x21, 0x0c, 0xad, 0x44, 0xa5, 0x97, 0xac, 0x80, 0x11, 0x02, 0x9b, 0x0c, 0xe5, 0x8b, 0xcd, 0xfb, 0x79, 0x77}}}, +{{{0x15, 0xbe, 0x9a, 0x0d, 0xba, 0x38, 0x72, 0x20, 0x8a, 0xf5, 0xbe, 0x59, 0x93, 0x79, 0xb7, 0xf6, 0x6a, 0x0c, 0x38, 0x27, 0x1a, 0x60, 0xf4, 0x86, 0x3b, 0xab, 0x5a, 0x00, 0xa0, 0xce, 0x21, 0x7d}} , + {{0x6c, 0xba, 0x14, 0xc5, 0xea, 0x12, 0x9e, 0x2e, 0x82, 0x63, 0xce, 0x9b, 0x4a, 0xe7, 0x1d, 0xec, 0xf1, 0x2e, 0x51, 0x1c, 0xf4, 0xd0, 0x69, 0x15, 0x42, 0x9d, 0xa3, 0x3f, 0x0e, 0xbf, 0xe9, 0x5c}}}, +{{{0xe4, 0x0d, 0xf4, 0xbd, 0xee, 0x31, 0x10, 0xed, 0xcb, 0x12, 0x86, 0xad, 0xd4, 0x2f, 0x90, 0x37, 0x32, 0xc3, 0x0b, 0x73, 0xec, 0x97, 0x85, 0xa4, 0x01, 0x1c, 0x76, 0x35, 0xfe, 0x75, 0xdd, 0x71}} , + {{0x11, 0xa4, 0x88, 0x9f, 0x3e, 0x53, 0x69, 0x3b, 0x1b, 0xe0, 0xf7, 0xba, 0x9b, 0xad, 0x4e, 0x81, 0x5f, 0xb5, 0x5c, 0xae, 0xbe, 0x67, 0x86, 0x37, 0x34, 0x8e, 0x07, 0x32, 0x45, 0x4a, 0x67, 0x39}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x90, 0x70, 0x58, 0x20, 0x03, 0x1e, 0x67, 0xb2, 0xc8, 0x9b, 0x58, 0xc5, 0xb1, 0xeb, 0x2d, 0x4a, 0xde, 0x82, 0x8c, 0xf2, 0xd2, 0x14, 0xb8, 0x70, 0x61, 0x4e, 0x73, 0xd6, 0x0b, 0x6b, 0x0d, 0x30}} , + {{0x81, 0xfc, 0x55, 0x5c, 0xbf, 0xa7, 0xc4, 0xbd, 0xe2, 0xf0, 0x4b, 0x8f, 0xe9, 0x7d, 0x99, 0xfa, 0xd3, 0xab, 0xbc, 0xc7, 0x83, 0x2b, 0x04, 0x7f, 0x0c, 0x19, 0x43, 0x03, 0x3d, 0x07, 0xca, 0x40}}}, +{{{0xf9, 0xc8, 0xbe, 0x8c, 0x16, 0x81, 0x39, 0x96, 0xf6, 0x17, 0x58, 0xc8, 0x30, 0x58, 0xfb, 0xc2, 0x03, 0x45, 0xd2, 0x52, 0x76, 0xe0, 0x6a, 0x26, 0x28, 0x5c, 0x88, 0x59, 0x6a, 0x5a, 0x54, 0x42}} , + {{0x07, 0xb5, 0x2e, 0x2c, 0x67, 0x15, 0x9b, 0xfb, 0x83, 0x69, 0x1e, 0x0f, 0xda, 0xd6, 0x29, 0xb1, 0x60, 0xe0, 0xb2, 0xba, 0x69, 0xa2, 0x9e, 0xbd, 0xbd, 0xe0, 0x1c, 0xbd, 0xcd, 0x06, 0x64, 0x70}}}, +{{{0x41, 0xfa, 0x8c, 0xe1, 0x89, 0x8f, 0x27, 0xc8, 0x25, 0x8f, 0x6f, 0x5f, 0x55, 0xf8, 0xde, 0x95, 0x6d, 0x2f, 0x75, 0x16, 0x2b, 0x4e, 0x44, 0xfd, 0x86, 0x6e, 0xe9, 0x70, 0x39, 0x76, 0x97, 0x7e}} , + {{0x17, 0x62, 0x6b, 0x14, 0xa1, 0x7c, 0xd0, 0x79, 0x6e, 0xd8, 0x8a, 0xa5, 0x6d, 0x8c, 0x93, 0xd2, 0x3f, 0xec, 0x44, 0x8d, 0x6e, 0x91, 0x01, 0x8c, 0x8f, 0xee, 0x01, 0x8f, 0xc0, 0xb4, 0x85, 0x0e}}}, +{{{0x02, 0x3a, 0x70, 0x41, 0xe4, 0x11, 0x57, 0x23, 0xac, 0xe6, 0xfc, 0x54, 0x7e, 0xcd, 0xd7, 0x22, 0xcb, 0x76, 0x9f, 0x20, 0xce, 0xa0, 0x73, 0x76, 0x51, 0x3b, 0xa4, 0xf8, 0xe3, 0x62, 0x12, 0x6c}} , + {{0x7f, 0x00, 0x9c, 0x26, 0x0d, 0x6f, 0x48, 0x7f, 0x3a, 0x01, 0xed, 0xc5, 0x96, 0xb0, 0x1f, 0x4f, 0xa8, 0x02, 0x62, 0x27, 0x8a, 0x50, 0x8d, 0x9a, 0x8b, 0x52, 0x0f, 0x1e, 0xcf, 0x41, 0x38, 0x19}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf5, 0x6c, 0xd4, 0x2f, 0x0f, 0x69, 0x0f, 0x87, 0x3f, 0x61, 0x65, 0x1e, 0x35, 0x34, 0x85, 0xba, 0x02, 0x30, 0xac, 0x25, 0x3d, 0xe2, 0x62, 0xf1, 0xcc, 0xe9, 0x1b, 0xc2, 0xef, 0x6a, 0x42, 0x57}} , + {{0x34, 0x1f, 0x2e, 0xac, 0xd1, 0xc7, 0x04, 0x52, 0x32, 0x66, 0xb2, 0x33, 0x73, 0x21, 0x34, 0x54, 0xf7, 0x71, 0xed, 0x06, 0xb0, 0xff, 0xa6, 0x59, 0x6f, 0x8a, 0x4e, 0xfb, 0x02, 0xb0, 0x45, 0x6b}}}, +{{{0xf5, 0x48, 0x0b, 0x03, 0xc5, 0x22, 0x7d, 0x80, 0x08, 0x53, 0xfe, 0x32, 0xb1, 0xa1, 0x8a, 0x74, 0x6f, 0xbd, 0x3f, 0x85, 0xf4, 0xcf, 0xf5, 0x60, 0xaf, 0x41, 0x7e, 0x3e, 0x46, 0xa3, 0x5a, 0x20}} , + {{0xaa, 0x35, 0x87, 0x44, 0x63, 0x66, 0x97, 0xf8, 0x6e, 0x55, 0x0c, 0x04, 0x3e, 0x35, 0x50, 0xbf, 0x93, 0x69, 0xd2, 0x8b, 0x05, 0x55, 0x99, 0xbe, 0xe2, 0x53, 0x61, 0xec, 0xe8, 0x08, 0x0b, 0x32}}}, +{{{0xb3, 0x10, 0x45, 0x02, 0x69, 0x59, 0x2e, 0x97, 0xd9, 0x64, 0xf8, 0xdb, 0x25, 0x80, 0xdc, 0xc4, 0xd5, 0x62, 0x3c, 0xed, 0x65, 0x91, 0xad, 0xd1, 0x57, 0x81, 0x94, 0xaa, 0xa1, 0x29, 0xfc, 0x68}} , + {{0xdd, 0xb5, 0x7d, 0xab, 0x5a, 0x21, 0x41, 0x53, 0xbb, 0x17, 0x79, 0x0d, 0xd1, 0xa8, 0x0c, 0x0c, 0x20, 0x88, 0x09, 0xe9, 0x84, 0xe8, 0x25, 0x11, 0x67, 0x7a, 0x8b, 0x1a, 0xe4, 0x5d, 0xe1, 0x5d}}}, +{{{0x37, 0xea, 0xfe, 0x65, 0x3b, 0x25, 0xe8, 0xe1, 0xc2, 0xc5, 0x02, 0xa4, 0xbe, 0x98, 0x0a, 0x2b, 0x61, 0xc1, 0x9b, 0xe2, 0xd5, 0x92, 0xe6, 0x9e, 0x7d, 0x1f, 0xca, 0x43, 0x88, 0x8b, 0x2c, 0x59}} , + {{0xe0, 0xb5, 0x00, 0x1d, 0x2a, 0x6f, 0xaf, 0x79, 0x86, 0x2f, 0xa6, 0x5a, 0x93, 0xd1, 0xfe, 0xae, 0x3a, 0xee, 0xdb, 0x7c, 0x61, 0xbe, 0x7c, 0x01, 0xf9, 0xfe, 0x52, 0xdc, 0xd8, 0x52, 0xa3, 0x42}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x22, 0xaf, 0x13, 0x37, 0xbd, 0x37, 0x71, 0xac, 0x04, 0x46, 0x63, 0xac, 0xa4, 0x77, 0xed, 0x25, 0x38, 0xe0, 0x15, 0xa8, 0x64, 0x00, 0x0d, 0xce, 0x51, 0x01, 0xa9, 0xbc, 0x0f, 0x03, 0x1c, 0x04}} , + {{0x89, 0xf9, 0x80, 0x07, 0xcf, 0x3f, 0xb3, 0xe9, 0xe7, 0x45, 0x44, 0x3d, 0x2a, 0x7c, 0xe9, 0xe4, 0x16, 0x5c, 0x5e, 0x65, 0x1c, 0xc7, 0x7d, 0xc6, 0x7a, 0xfb, 0x43, 0xee, 0x25, 0x76, 0x46, 0x72}}}, +{{{0x02, 0xa2, 0xed, 0xf4, 0x8f, 0x6b, 0x0b, 0x3e, 0xeb, 0x35, 0x1a, 0xd5, 0x7e, 0xdb, 0x78, 0x00, 0x96, 0x8a, 0xa0, 0xb4, 0xcf, 0x60, 0x4b, 0xd4, 0xd5, 0xf9, 0x2d, 0xbf, 0x88, 0xbd, 0x22, 0x62}} , + {{0x13, 0x53, 0xe4, 0x82, 0x57, 0xfa, 0x1e, 0x8f, 0x06, 0x2b, 0x90, 0xba, 0x08, 0xb6, 0x10, 0x54, 0x4f, 0x7c, 0x1b, 0x26, 0xed, 0xda, 0x6b, 0xdd, 0x25, 0xd0, 0x4e, 0xea, 0x42, 0xbb, 0x25, 0x03}}}, +{{{0x51, 0x16, 0x50, 0x7c, 0xd5, 0x5d, 0xf6, 0x99, 0xe8, 0x77, 0x72, 0x4e, 0xfa, 0x62, 0xcb, 0x76, 0x75, 0x0c, 0xe2, 0x71, 0x98, 0x92, 0xd5, 0xfa, 0x45, 0xdf, 0x5c, 0x6f, 0x1e, 0x9e, 0x28, 0x69}} , + {{0x0d, 0xac, 0x66, 0x6d, 0xc3, 0x8b, 0xba, 0x16, 0xb5, 0xe2, 0xa0, 0x0d, 0x0c, 0xbd, 0xa4, 0x8e, 0x18, 0x6c, 0xf2, 0xdc, 0xf9, 0xdc, 0x4a, 0x86, 0x25, 0x95, 0x14, 0xcb, 0xd8, 0x1a, 0x04, 0x0f}}}, +{{{0x97, 0xa5, 0xdb, 0x8b, 0x2d, 0xaa, 0x42, 0x11, 0x09, 0xf2, 0x93, 0xbb, 0xd9, 0x06, 0x84, 0x4e, 0x11, 0xa8, 0xa0, 0x25, 0x2b, 0xa6, 0x5f, 0xae, 0xc4, 0xb4, 0x4c, 0xc8, 0xab, 0xc7, 0x3b, 0x02}} , + {{0xee, 0xc9, 0x29, 0x0f, 0xdf, 0x11, 0x85, 0xed, 0xce, 0x0d, 0x62, 0x2c, 0x8f, 0x4b, 0xf9, 0x04, 0xe9, 0x06, 0x72, 0x1d, 0x37, 0x20, 0x50, 0xc9, 0x14, 0xeb, 0xec, 0x39, 0xa7, 0x97, 0x2b, 0x4d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x69, 0xd1, 0x39, 0xbd, 0xfb, 0x33, 0xbe, 0xc4, 0xf0, 0x5c, 0xef, 0xf0, 0x56, 0x68, 0xfc, 0x97, 0x47, 0xc8, 0x72, 0xb6, 0x53, 0xa4, 0x0a, 0x98, 0xa5, 0xb4, 0x37, 0x71, 0xcf, 0x66, 0x50, 0x6d}} , + {{0x17, 0xa4, 0x19, 0x52, 0x11, 0x47, 0xb3, 0x5c, 0x5b, 0xa9, 0x2e, 0x22, 0xb4, 0x00, 0x52, 0xf9, 0x57, 0x18, 0xb8, 0xbe, 0x5a, 0xe3, 0xab, 0x83, 0xc8, 0x87, 0x0a, 0x2a, 0xd8, 0x8c, 0xbb, 0x54}}}, +{{{0xa9, 0x62, 0x93, 0x85, 0xbe, 0xe8, 0x73, 0x4a, 0x0e, 0xb0, 0xb5, 0x2d, 0x94, 0x50, 0xaa, 0xd3, 0xb2, 0xea, 0x9d, 0x62, 0x76, 0x3b, 0x07, 0x34, 0x4e, 0x2d, 0x70, 0xc8, 0x9a, 0x15, 0x66, 0x6b}} , + {{0xc5, 0x96, 0xca, 0xc8, 0x22, 0x1a, 0xee, 0x5f, 0xe7, 0x31, 0x60, 0x22, 0x83, 0x08, 0x63, 0xce, 0xb9, 0x32, 0x44, 0x58, 0x5d, 0x3a, 0x9b, 0xe4, 0x04, 0xd5, 0xef, 0x38, 0xef, 0x4b, 0xdd, 0x19}}}, +{{{0x4d, 0xc2, 0x17, 0x75, 0xa1, 0x68, 0xcd, 0xc3, 0xc6, 0x03, 0x44, 0xe3, 0x78, 0x09, 0x91, 0x47, 0x3f, 0x0f, 0xe4, 0x92, 0x58, 0xfa, 0x7d, 0x1f, 0x20, 0x94, 0x58, 0x5e, 0xbc, 0x19, 0x02, 0x6f}} , + {{0x20, 0xd6, 0xd8, 0x91, 0x54, 0xa7, 0xf3, 0x20, 0x4b, 0x34, 0x06, 0xfa, 0x30, 0xc8, 0x6f, 0x14, 0x10, 0x65, 0x74, 0x13, 0x4e, 0xf0, 0x69, 0x26, 0xce, 0xcf, 0x90, 0xf4, 0xd0, 0xc5, 0xc8, 0x64}}}, +{{{0x26, 0xa2, 0x50, 0x02, 0x24, 0x72, 0xf1, 0xf0, 0x4e, 0x2d, 0x93, 0xd5, 0x08, 0xe7, 0xae, 0x38, 0xf7, 0x18, 0xa5, 0x32, 0x34, 0xc2, 0xf0, 0xa6, 0xec, 0xb9, 0x61, 0x7b, 0x64, 0x99, 0xac, 0x71}} , + {{0x25, 0xcf, 0x74, 0x55, 0x1b, 0xaa, 0xa9, 0x38, 0x41, 0x40, 0xd5, 0x95, 0x95, 0xab, 0x1c, 0x5e, 0xbc, 0x41, 0x7e, 0x14, 0x30, 0xbe, 0x13, 0x89, 0xf4, 0xe5, 0xeb, 0x28, 0xc0, 0xc2, 0x96, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2b, 0x77, 0x45, 0xec, 0x67, 0x76, 0x32, 0x4c, 0xb9, 0xdf, 0x25, 0x32, 0x6b, 0xcb, 0xe7, 0x14, 0x61, 0x43, 0xee, 0xba, 0x9b, 0x71, 0xef, 0xd2, 0x48, 0x65, 0xbb, 0x1b, 0x8a, 0x13, 0x1b, 0x22}} , + {{0x84, 0xad, 0x0c, 0x18, 0x38, 0x5a, 0xba, 0xd0, 0x98, 0x59, 0xbf, 0x37, 0xb0, 0x4f, 0x97, 0x60, 0x20, 0xb3, 0x9b, 0x97, 0xf6, 0x08, 0x6c, 0xa4, 0xff, 0xfb, 0xb7, 0xfa, 0x95, 0xb2, 0x51, 0x79}}}, +{{{0x28, 0x5c, 0x3f, 0xdb, 0x6b, 0x18, 0x3b, 0x5c, 0xd1, 0x04, 0x28, 0xde, 0x85, 0x52, 0x31, 0xb5, 0xbb, 0xf6, 0xa9, 0xed, 0xbe, 0x28, 0x4f, 0xb3, 0x7e, 0x05, 0x6a, 0xdb, 0x95, 0x0d, 0x1b, 0x1c}} , + {{0xd5, 0xc5, 0xc3, 0x9a, 0x0a, 0xd0, 0x31, 0x3e, 0x07, 0x36, 0x8e, 0xc0, 0x8a, 0x62, 0xb1, 0xca, 0xd6, 0x0e, 0x1e, 0x9d, 0xef, 0xab, 0x98, 0x4d, 0xbb, 0x6c, 0x05, 0xe0, 0xe4, 0x5d, 0xbd, 0x57}}}, +{{{0xcc, 0x21, 0x27, 0xce, 0xfd, 0xa9, 0x94, 0x8e, 0xe1, 0xab, 0x49, 0xe0, 0x46, 0x26, 0xa1, 0xa8, 0x8c, 0xa1, 0x99, 0x1d, 0xb4, 0x27, 0x6d, 0x2d, 0xc8, 0x39, 0x30, 0x5e, 0x37, 0x52, 0xc4, 0x6e}} , + {{0xa9, 0x85, 0xf4, 0xe7, 0xb0, 0x15, 0x33, 0x84, 0x1b, 0x14, 0x1a, 0x02, 0xd9, 0x3b, 0xad, 0x0f, 0x43, 0x6c, 0xea, 0x3e, 0x0f, 0x7e, 0xda, 0xdd, 0x6b, 0x4c, 0x7f, 0x6e, 0xd4, 0x6b, 0xbf, 0x0f}}}, +{{{0x47, 0x9f, 0x7c, 0x56, 0x7c, 0x43, 0x91, 0x1c, 0xbb, 0x4e, 0x72, 0x3e, 0x64, 0xab, 0xa0, 0xa0, 0xdf, 0xb4, 0xd8, 0x87, 0x3a, 0xbd, 0xa8, 0x48, 0xc9, 0xb8, 0xef, 0x2e, 0xad, 0x6f, 0x84, 0x4f}} , + {{0x2d, 0x2d, 0xf0, 0x1b, 0x7e, 0x2a, 0x6c, 0xf8, 0xa9, 0x6a, 0xe1, 0xf0, 0x99, 0xa1, 0x67, 0x9a, 0xd4, 0x13, 0xca, 0xca, 0xba, 0x27, 0x92, 0xaa, 0xa1, 0x5d, 0x50, 0xde, 0xcc, 0x40, 0x26, 0x0a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9f, 0x3e, 0xf2, 0xb2, 0x90, 0xce, 0xdb, 0x64, 0x3e, 0x03, 0xdd, 0x37, 0x36, 0x54, 0x70, 0x76, 0x24, 0xb5, 0x69, 0x03, 0xfc, 0xa0, 0x2b, 0x74, 0xb2, 0x05, 0x0e, 0xcc, 0xd8, 0x1f, 0x6a, 0x1f}} , + {{0x19, 0x5e, 0x60, 0x69, 0x58, 0x86, 0xa0, 0x31, 0xbd, 0x32, 0xe9, 0x2c, 0x5c, 0xd2, 0x85, 0xba, 0x40, 0x64, 0xa8, 0x74, 0xf8, 0x0e, 0x1c, 0xb3, 0xa9, 0x69, 0xe8, 0x1e, 0x40, 0x64, 0x99, 0x77}}}, +{{{0x6c, 0x32, 0x4f, 0xfd, 0xbb, 0x5c, 0xbb, 0x8d, 0x64, 0x66, 0x4a, 0x71, 0x1f, 0x79, 0xa3, 0xad, 0x8d, 0xf9, 0xd4, 0xec, 0xcf, 0x67, 0x70, 0xfa, 0x05, 0x4a, 0x0f, 0x6e, 0xaf, 0x87, 0x0a, 0x6f}} , + {{0xc6, 0x36, 0x6e, 0x6c, 0x8c, 0x24, 0x09, 0x60, 0xbe, 0x26, 0xd2, 0x4c, 0x5e, 0x17, 0xca, 0x5f, 0x1d, 0xcc, 0x87, 0xe8, 0x42, 0x6a, 0xcb, 0xcb, 0x7d, 0x92, 0x05, 0x35, 0x81, 0x13, 0x60, 0x6b}}}, +{{{0xf4, 0x15, 0xcd, 0x0f, 0x0a, 0xaf, 0x4e, 0x6b, 0x51, 0xfd, 0x14, 0xc4, 0x2e, 0x13, 0x86, 0x74, 0x44, 0xcb, 0x66, 0x6b, 0xb6, 0x9d, 0x74, 0x56, 0x32, 0xac, 0x8d, 0x8e, 0x8c, 0x8c, 0x8c, 0x39}} , + {{0xca, 0x59, 0x74, 0x1a, 0x11, 0xef, 0x6d, 0xf7, 0x39, 0x5c, 0x3b, 0x1f, 0xfa, 0xe3, 0x40, 0x41, 0x23, 0x9e, 0xf6, 0xd1, 0x21, 0xa2, 0xbf, 0xad, 0x65, 0x42, 0x6b, 0x59, 0x8a, 0xe8, 0xc5, 0x7f}}}, +{{{0x64, 0x05, 0x7a, 0x84, 0x4a, 0x13, 0xc3, 0xf6, 0xb0, 0x6e, 0x9a, 0x6b, 0x53, 0x6b, 0x32, 0xda, 0xd9, 0x74, 0x75, 0xc4, 0xba, 0x64, 0x3d, 0x3b, 0x08, 0xdd, 0x10, 0x46, 0xef, 0xc7, 0x90, 0x1f}} , + {{0x7b, 0x2f, 0x3a, 0xce, 0xc8, 0xa1, 0x79, 0x3c, 0x30, 0x12, 0x44, 0x28, 0xf6, 0xbc, 0xff, 0xfd, 0xf4, 0xc0, 0x97, 0xb0, 0xcc, 0xc3, 0x13, 0x7a, 0xb9, 0x9a, 0x16, 0xe4, 0xcb, 0x4c, 0x34, 0x63}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x07, 0x4e, 0xd3, 0x2d, 0x09, 0x33, 0x0e, 0xd2, 0x0d, 0xbe, 0x3e, 0xe7, 0xe4, 0xaa, 0xb7, 0x00, 0x8b, 0xe8, 0xad, 0xaa, 0x7a, 0x8d, 0x34, 0x28, 0xa9, 0x81, 0x94, 0xc5, 0xe7, 0x42, 0xac, 0x47}} , + {{0x24, 0x89, 0x7a, 0x8f, 0xb5, 0x9b, 0xf0, 0xc2, 0x03, 0x64, 0xd0, 0x1e, 0xf5, 0xa4, 0xb2, 0xf3, 0x74, 0xe9, 0x1a, 0x16, 0xfd, 0xcb, 0x15, 0xea, 0xeb, 0x10, 0x6c, 0x35, 0xd1, 0xc1, 0xa6, 0x28}}}, +{{{0xcc, 0xd5, 0x39, 0xfc, 0xa5, 0xa4, 0xad, 0x32, 0x15, 0xce, 0x19, 0xe8, 0x34, 0x2b, 0x1c, 0x60, 0x91, 0xfc, 0x05, 0xa9, 0xb3, 0xdc, 0x80, 0x29, 0xc4, 0x20, 0x79, 0x06, 0x39, 0xc0, 0xe2, 0x22}} , + {{0xbb, 0xa8, 0xe1, 0x89, 0x70, 0x57, 0x18, 0x54, 0x3c, 0xf6, 0x0d, 0x82, 0x12, 0x05, 0x87, 0x96, 0x06, 0x39, 0xe3, 0xf8, 0xb3, 0x95, 0xe5, 0xd7, 0x26, 0xbf, 0x09, 0x5a, 0x94, 0xf9, 0x1c, 0x63}}}, +{{{0x2b, 0x8c, 0x2d, 0x9a, 0x8b, 0x84, 0xf2, 0x56, 0xfb, 0xad, 0x2e, 0x7f, 0xb7, 0xfc, 0x30, 0xe1, 0x35, 0x89, 0xba, 0x4d, 0xa8, 0x6d, 0xce, 0x8c, 0x8b, 0x30, 0xe0, 0xda, 0x29, 0x18, 0x11, 0x17}} , + {{0x19, 0xa6, 0x5a, 0x65, 0x93, 0xc3, 0xb5, 0x31, 0x22, 0x4f, 0xf3, 0xf6, 0x0f, 0xeb, 0x28, 0xc3, 0x7c, 0xeb, 0xce, 0x86, 0xec, 0x67, 0x76, 0x6e, 0x35, 0x45, 0x7b, 0xd8, 0x6b, 0x92, 0x01, 0x65}}}, +{{{0x3d, 0xd5, 0x9a, 0x64, 0x73, 0x36, 0xb1, 0xd6, 0x86, 0x98, 0x42, 0x3f, 0x8a, 0xf1, 0xc7, 0xf5, 0x42, 0xa8, 0x9c, 0x52, 0xa8, 0xdc, 0xf9, 0x24, 0x3f, 0x4a, 0xa1, 0xa4, 0x5b, 0xe8, 0x62, 0x1a}} , + {{0xc5, 0xbd, 0xc8, 0x14, 0xd5, 0x0d, 0xeb, 0xe1, 0xa5, 0xe6, 0x83, 0x11, 0x09, 0x00, 0x1d, 0x55, 0x83, 0x51, 0x7e, 0x75, 0x00, 0x81, 0xb9, 0xcb, 0xd8, 0xc5, 0xe5, 0xa1, 0xd9, 0x17, 0x6d, 0x1f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xea, 0xf9, 0xe4, 0xe9, 0xe1, 0x52, 0x3f, 0x51, 0x19, 0x0d, 0xdd, 0xd9, 0x9d, 0x93, 0x31, 0x87, 0x23, 0x09, 0xd5, 0x83, 0xeb, 0x92, 0x09, 0x76, 0x6e, 0xe3, 0xf8, 0xc0, 0xa2, 0x66, 0xb5, 0x36}} , + {{0x3a, 0xbb, 0x39, 0xed, 0x32, 0x02, 0xe7, 0x43, 0x7a, 0x38, 0x14, 0x84, 0xe3, 0x44, 0xd2, 0x5e, 0x94, 0xdd, 0x78, 0x89, 0x55, 0x4c, 0x73, 0x9e, 0xe1, 0xe4, 0x3e, 0x43, 0xd0, 0x4a, 0xde, 0x1b}}}, +{{{0xb2, 0xe7, 0x8f, 0xe3, 0xa3, 0xc5, 0xcb, 0x72, 0xee, 0x79, 0x41, 0xf8, 0xdf, 0xee, 0x65, 0xc5, 0x45, 0x77, 0x27, 0x3c, 0xbd, 0x58, 0xd3, 0x75, 0xe2, 0x04, 0x4b, 0xbb, 0x65, 0xf3, 0xc8, 0x0f}} , + {{0x24, 0x7b, 0x93, 0x34, 0xb5, 0xe2, 0x74, 0x48, 0xcd, 0xa0, 0x0b, 0x92, 0x97, 0x66, 0x39, 0xf4, 0xb0, 0xe2, 0x5d, 0x39, 0x6a, 0x5b, 0x45, 0x17, 0x78, 0x1e, 0xdb, 0x91, 0x81, 0x1c, 0xf9, 0x16}}}, +{{{0x16, 0xdf, 0xd1, 0x5a, 0xd5, 0xe9, 0x4e, 0x58, 0x95, 0x93, 0x5f, 0x51, 0x09, 0xc3, 0x2a, 0xc9, 0xd4, 0x55, 0x48, 0x79, 0xa4, 0xa3, 0xb2, 0xc3, 0x62, 0xaa, 0x8c, 0xe8, 0xad, 0x47, 0x39, 0x1b}} , + {{0x46, 0xda, 0x9e, 0x51, 0x3a, 0xe6, 0xd1, 0xa6, 0xbb, 0x4d, 0x7b, 0x08, 0xbe, 0x8c, 0xd5, 0xf3, 0x3f, 0xfd, 0xf7, 0x44, 0x80, 0x2d, 0x53, 0x4b, 0xd0, 0x87, 0x68, 0xc1, 0xb5, 0xd8, 0xf7, 0x07}}}, +{{{0xf4, 0x10, 0x46, 0xbe, 0xb7, 0xd2, 0xd1, 0xce, 0x5e, 0x76, 0xa2, 0xd7, 0x03, 0xdc, 0xe4, 0x81, 0x5a, 0xf6, 0x3c, 0xde, 0xae, 0x7a, 0x9d, 0x21, 0x34, 0xa5, 0xf6, 0xa9, 0x73, 0xe2, 0x8d, 0x60}} , + {{0xfa, 0x44, 0x71, 0xf6, 0x41, 0xd8, 0xc6, 0x58, 0x13, 0x37, 0xeb, 0x84, 0x0f, 0x96, 0xc7, 0xdc, 0xc8, 0xa9, 0x7a, 0x83, 0xb2, 0x2f, 0x31, 0xb1, 0x1a, 0xd8, 0x98, 0x3f, 0x11, 0xd0, 0x31, 0x3b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x81, 0xd5, 0x34, 0x16, 0x01, 0xa3, 0x93, 0xea, 0x52, 0x94, 0xec, 0x93, 0xb7, 0x81, 0x11, 0x2d, 0x58, 0xf9, 0xb5, 0x0a, 0xaa, 0x4f, 0xf6, 0x2e, 0x3f, 0x36, 0xbf, 0x33, 0x5a, 0xe7, 0xd1, 0x08}} , + {{0x1a, 0xcf, 0x42, 0xae, 0xcc, 0xb5, 0x77, 0x39, 0xc4, 0x5b, 0x5b, 0xd0, 0x26, 0x59, 0x27, 0xd0, 0x55, 0x71, 0x12, 0x9d, 0x88, 0x3d, 0x9c, 0xea, 0x41, 0x6a, 0xf0, 0x50, 0x93, 0x93, 0xdd, 0x47}}}, +{{{0x6f, 0xc9, 0x51, 0x6d, 0x1c, 0xaa, 0xf5, 0xa5, 0x90, 0x3f, 0x14, 0xe2, 0x6e, 0x8e, 0x64, 0xfd, 0xac, 0xe0, 0x4e, 0x22, 0xe5, 0xc1, 0xbc, 0x29, 0x0a, 0x6a, 0x9e, 0xa1, 0x60, 0xcb, 0x2f, 0x0b}} , + {{0xdc, 0x39, 0x32, 0xf3, 0xa1, 0x44, 0xe9, 0xc5, 0xc3, 0x78, 0xfb, 0x95, 0x47, 0x34, 0x35, 0x34, 0xe8, 0x25, 0xde, 0x93, 0xc6, 0xb4, 0x76, 0x6d, 0x86, 0x13, 0xc6, 0xe9, 0x68, 0xb5, 0x01, 0x63}}}, +{{{0x1f, 0x9a, 0x52, 0x64, 0x97, 0xd9, 0x1c, 0x08, 0x51, 0x6f, 0x26, 0x9d, 0xaa, 0x93, 0x33, 0x43, 0xfa, 0x77, 0xe9, 0x62, 0x9b, 0x5d, 0x18, 0x75, 0xeb, 0x78, 0xf7, 0x87, 0x8f, 0x41, 0xb4, 0x4d}} , + {{0x13, 0xa8, 0x82, 0x3e, 0xe9, 0x13, 0xad, 0xeb, 0x01, 0xca, 0xcf, 0xda, 0xcd, 0xf7, 0x6c, 0xc7, 0x7a, 0xdc, 0x1e, 0x6e, 0xc8, 0x4e, 0x55, 0x62, 0x80, 0xea, 0x78, 0x0c, 0x86, 0xb9, 0x40, 0x51}}}, +{{{0x27, 0xae, 0xd3, 0x0d, 0x4c, 0x8f, 0x34, 0xea, 0x7d, 0x3c, 0xe5, 0x8a, 0xcf, 0x5b, 0x92, 0xd8, 0x30, 0x16, 0xb4, 0xa3, 0x75, 0xff, 0xeb, 0x27, 0xc8, 0x5c, 0x6c, 0xc2, 0xee, 0x6c, 0x21, 0x0b}} , + {{0xc3, 0xba, 0x12, 0x53, 0x2a, 0xaa, 0x77, 0xad, 0x19, 0x78, 0x55, 0x8a, 0x2e, 0x60, 0x87, 0xc2, 0x6e, 0x91, 0x38, 0x91, 0x3f, 0x7a, 0xc5, 0x24, 0x8f, 0x51, 0xc5, 0xde, 0xb0, 0x53, 0x30, 0x56}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x02, 0xfe, 0x54, 0x12, 0x18, 0xca, 0x7d, 0xa5, 0x68, 0x43, 0xa3, 0x6d, 0x14, 0x2a, 0x6a, 0xa5, 0x8e, 0x32, 0xe7, 0x63, 0x4f, 0xe3, 0xc6, 0x44, 0x3e, 0xab, 0x63, 0xca, 0x17, 0x86, 0x74, 0x3f}} , + {{0x1e, 0x64, 0xc1, 0x7d, 0x52, 0xdc, 0x13, 0x5a, 0xa1, 0x9c, 0x4e, 0xee, 0x99, 0x28, 0xbb, 0x4c, 0xee, 0xac, 0xa9, 0x1b, 0x89, 0xa2, 0x38, 0x39, 0x7b, 0xc4, 0x0f, 0x42, 0xe6, 0x89, 0xed, 0x0f}}}, +{{{0xf3, 0x3c, 0x8c, 0x80, 0x83, 0x10, 0x8a, 0x37, 0x50, 0x9c, 0xb4, 0xdf, 0x3f, 0x8c, 0xf7, 0x23, 0x07, 0xd6, 0xff, 0xa0, 0x82, 0x6c, 0x75, 0x3b, 0xe4, 0xb5, 0xbb, 0xe4, 0xe6, 0x50, 0xf0, 0x08}} , + {{0x62, 0xee, 0x75, 0x48, 0x92, 0x33, 0xf2, 0xf4, 0xad, 0x15, 0x7a, 0xa1, 0x01, 0x46, 0xa9, 0x32, 0x06, 0x88, 0xb6, 0x36, 0x47, 0x35, 0xb9, 0xb4, 0x42, 0x85, 0x76, 0xf0, 0x48, 0x00, 0x90, 0x38}}}, +{{{0x51, 0x15, 0x9d, 0xc3, 0x95, 0xd1, 0x39, 0xbb, 0x64, 0x9d, 0x15, 0x81, 0xc1, 0x68, 0xd0, 0xb6, 0xa4, 0x2c, 0x7d, 0x5e, 0x02, 0x39, 0x00, 0xe0, 0x3b, 0xa4, 0xcc, 0xca, 0x1d, 0x81, 0x24, 0x10}} , + {{0xe7, 0x29, 0xf9, 0x37, 0xd9, 0x46, 0x5a, 0xcd, 0x70, 0xfe, 0x4d, 0x5b, 0xbf, 0xa5, 0xcf, 0x91, 0xf4, 0xef, 0xee, 0x8a, 0x29, 0xd0, 0xe7, 0xc4, 0x25, 0x92, 0x8a, 0xff, 0x36, 0xfc, 0xe4, 0x49}}}, +{{{0xbd, 0x00, 0xb9, 0x04, 0x7d, 0x35, 0xfc, 0xeb, 0xd0, 0x0b, 0x05, 0x32, 0x52, 0x7a, 0x89, 0x24, 0x75, 0x50, 0xe1, 0x63, 0x02, 0x82, 0x8e, 0xe7, 0x85, 0x0c, 0xf2, 0x56, 0x44, 0x37, 0x83, 0x25}} , + {{0x8f, 0xa1, 0xce, 0xcb, 0x60, 0xda, 0x12, 0x02, 0x1e, 0x29, 0x39, 0x2a, 0x03, 0xb7, 0xeb, 0x77, 0x40, 0xea, 0xc9, 0x2b, 0x2c, 0xd5, 0x7d, 0x7e, 0x2c, 0xc7, 0x5a, 0xfd, 0xff, 0xc4, 0xd1, 0x62}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x1d, 0x88, 0x98, 0x5b, 0x4e, 0xfc, 0x41, 0x24, 0x05, 0xe6, 0x50, 0x2b, 0xae, 0x96, 0x51, 0xd9, 0x6b, 0x72, 0xb2, 0x33, 0x42, 0x98, 0x68, 0xbb, 0x10, 0x5a, 0x7a, 0x8c, 0x9d, 0x07, 0xb4, 0x05}} , + {{0x2f, 0x61, 0x9f, 0xd7, 0xa8, 0x3f, 0x83, 0x8c, 0x10, 0x69, 0x90, 0xe6, 0xcf, 0xd2, 0x63, 0xa3, 0xe4, 0x54, 0x7e, 0xe5, 0x69, 0x13, 0x1c, 0x90, 0x57, 0xaa, 0xe9, 0x53, 0x22, 0x43, 0x29, 0x23}}}, +{{{0xe5, 0x1c, 0xf8, 0x0a, 0xfd, 0x2d, 0x7e, 0xf5, 0xf5, 0x70, 0x7d, 0x41, 0x6b, 0x11, 0xfe, 0xbe, 0x99, 0xd1, 0x55, 0x29, 0x31, 0xbf, 0xc0, 0x97, 0x6c, 0xd5, 0x35, 0xcc, 0x5e, 0x8b, 0xd9, 0x69}} , + {{0x8e, 0x4e, 0x9f, 0x25, 0xf8, 0x81, 0x54, 0x2d, 0x0e, 0xd5, 0x54, 0x81, 0x9b, 0xa6, 0x92, 0xce, 0x4b, 0xe9, 0x8f, 0x24, 0x3b, 0xca, 0xe0, 0x44, 0xab, 0x36, 0xfe, 0xfb, 0x87, 0xd4, 0x26, 0x3e}}}, +{{{0x0f, 0x93, 0x9c, 0x11, 0xe7, 0xdb, 0xf1, 0xf0, 0x85, 0x43, 0x28, 0x15, 0x37, 0xdd, 0xde, 0x27, 0xdf, 0xad, 0x3e, 0x49, 0x4f, 0xe0, 0x5b, 0xf6, 0x80, 0x59, 0x15, 0x3c, 0x85, 0xb7, 0x3e, 0x12}} , + {{0xf5, 0xff, 0xcc, 0xf0, 0xb4, 0x12, 0x03, 0x5f, 0xc9, 0x84, 0xcb, 0x1d, 0x17, 0xe0, 0xbc, 0xcc, 0x03, 0x62, 0xa9, 0x8b, 0x94, 0xa6, 0xaa, 0x18, 0xcb, 0x27, 0x8d, 0x49, 0xa6, 0x17, 0x15, 0x07}}}, +{{{0xd9, 0xb6, 0xd4, 0x9d, 0xd4, 0x6a, 0xaf, 0x70, 0x07, 0x2c, 0x10, 0x9e, 0xbd, 0x11, 0xad, 0xe4, 0x26, 0x33, 0x70, 0x92, 0x78, 0x1c, 0x74, 0x9f, 0x75, 0x60, 0x56, 0xf4, 0x39, 0xa8, 0xa8, 0x62}} , + {{0x3b, 0xbf, 0x55, 0x35, 0x61, 0x8b, 0x44, 0x97, 0xe8, 0x3a, 0x55, 0xc1, 0xc8, 0x3b, 0xfd, 0x95, 0x29, 0x11, 0x60, 0x96, 0x1e, 0xcb, 0x11, 0x9d, 0xc2, 0x03, 0x8a, 0x1b, 0xc6, 0xd6, 0x45, 0x3d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x7e, 0x0e, 0x50, 0xb2, 0xcc, 0x0d, 0x6b, 0xa6, 0x71, 0x5b, 0x42, 0xed, 0xbd, 0xaf, 0xac, 0xf0, 0xfc, 0x12, 0xa2, 0x3f, 0x4e, 0xda, 0xe8, 0x11, 0xf3, 0x23, 0xe1, 0x04, 0x62, 0x03, 0x1c, 0x4e}} , + {{0xc8, 0xb1, 0x1b, 0x6f, 0x73, 0x61, 0x3d, 0x27, 0x0d, 0x7d, 0x7a, 0x25, 0x5f, 0x73, 0x0e, 0x2f, 0x93, 0xf6, 0x24, 0xd8, 0x4f, 0x90, 0xac, 0xa2, 0x62, 0x0a, 0xf0, 0x61, 0xd9, 0x08, 0x59, 0x6a}}}, +{{{0x6f, 0x2d, 0x55, 0xf8, 0x2f, 0x8e, 0xf0, 0x18, 0x3b, 0xea, 0xdd, 0x26, 0x72, 0xd1, 0xf5, 0xfe, 0xe5, 0xb8, 0xe6, 0xd3, 0x10, 0x48, 0x46, 0x49, 0x3a, 0x9f, 0x5e, 0x45, 0x6b, 0x90, 0xe8, 0x7f}} , + {{0xd3, 0x76, 0x69, 0x33, 0x7b, 0xb9, 0x40, 0x70, 0xee, 0xa6, 0x29, 0x6b, 0xdd, 0xd0, 0x5d, 0x8d, 0xc1, 0x3e, 0x4a, 0xea, 0x37, 0xb1, 0x03, 0x02, 0x03, 0x35, 0xf1, 0x28, 0x9d, 0xff, 0x00, 0x13}}}, +{{{0x7a, 0xdb, 0x12, 0xd2, 0x8a, 0x82, 0x03, 0x1b, 0x1e, 0xaf, 0xf9, 0x4b, 0x9c, 0xbe, 0xae, 0x7c, 0xe4, 0x94, 0x2a, 0x23, 0xb3, 0x62, 0x86, 0xe7, 0xfd, 0x23, 0xaa, 0x99, 0xbd, 0x2b, 0x11, 0x6c}} , + {{0x8d, 0xa6, 0xd5, 0xac, 0x9d, 0xcc, 0x68, 0x75, 0x7f, 0xc3, 0x4d, 0x4b, 0xdd, 0x6c, 0xbb, 0x11, 0x5a, 0x60, 0xe5, 0xbd, 0x7d, 0x27, 0x8b, 0xda, 0xb4, 0x95, 0xf6, 0x03, 0x27, 0xa4, 0x92, 0x3f}}}, +{{{0x22, 0xd6, 0xb5, 0x17, 0x84, 0xbf, 0x12, 0xcc, 0x23, 0x14, 0x4a, 0xdf, 0x14, 0x31, 0xbc, 0xa1, 0xac, 0x6e, 0xab, 0xfa, 0x57, 0x11, 0x53, 0xb3, 0x27, 0xe6, 0xf9, 0x47, 0x33, 0x44, 0x34, 0x1e}} , + {{0x79, 0xfc, 0xa6, 0xb4, 0x0b, 0x35, 0x20, 0xc9, 0x4d, 0x22, 0x84, 0xc4, 0xa9, 0x20, 0xec, 0x89, 0x94, 0xba, 0x66, 0x56, 0x48, 0xb9, 0x87, 0x7f, 0xca, 0x1e, 0x06, 0xed, 0xa5, 0x55, 0x59, 0x29}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x56, 0xe1, 0xf5, 0xf1, 0xd5, 0xab, 0xa8, 0x2b, 0xae, 0x89, 0xf3, 0xcf, 0x56, 0x9f, 0xf2, 0x4b, 0x31, 0xbc, 0x18, 0xa9, 0x06, 0x5b, 0xbe, 0xb4, 0x61, 0xf8, 0xb2, 0x06, 0x9c, 0x81, 0xab, 0x4c}} , + {{0x1f, 0x68, 0x76, 0x01, 0x16, 0x38, 0x2b, 0x0f, 0x77, 0x97, 0x92, 0x67, 0x4e, 0x86, 0x6a, 0x8b, 0xe5, 0xe8, 0x0c, 0xf7, 0x36, 0x39, 0xb5, 0x33, 0xe6, 0xcf, 0x5e, 0xbd, 0x18, 0xfb, 0x10, 0x1f}}}, +{{{0x83, 0xf0, 0x0d, 0x63, 0xef, 0x53, 0x6b, 0xb5, 0x6b, 0xf9, 0x83, 0xcf, 0xde, 0x04, 0x22, 0x9b, 0x2c, 0x0a, 0xe0, 0xa5, 0xd8, 0xc7, 0x9c, 0xa5, 0xa3, 0xf6, 0x6f, 0xcf, 0x90, 0x6b, 0x68, 0x7c}} , + {{0x33, 0x15, 0xd7, 0x7f, 0x1a, 0xd5, 0x21, 0x58, 0xc4, 0x18, 0xa5, 0xf0, 0xcc, 0x73, 0xa8, 0xfd, 0xfa, 0x18, 0xd1, 0x03, 0x91, 0x8d, 0x52, 0xd2, 0xa3, 0xa4, 0xd3, 0xb1, 0xea, 0x1d, 0x0f, 0x00}}}, +{{{0xcc, 0x48, 0x83, 0x90, 0xe5, 0xfd, 0x3f, 0x84, 0xaa, 0xf9, 0x8b, 0x82, 0x59, 0x24, 0x34, 0x68, 0x4f, 0x1c, 0x23, 0xd9, 0xcc, 0x71, 0xe1, 0x7f, 0x8c, 0xaf, 0xf1, 0xee, 0x00, 0xb6, 0xa0, 0x77}} , + {{0xf5, 0x1a, 0x61, 0xf7, 0x37, 0x9d, 0x00, 0xf4, 0xf2, 0x69, 0x6f, 0x4b, 0x01, 0x85, 0x19, 0x45, 0x4d, 0x7f, 0x02, 0x7c, 0x6a, 0x05, 0x47, 0x6c, 0x1f, 0x81, 0x20, 0xd4, 0xe8, 0x50, 0x27, 0x72}}}, +{{{0x2c, 0x3a, 0xe5, 0xad, 0xf4, 0xdd, 0x2d, 0xf7, 0x5c, 0x44, 0xb5, 0x5b, 0x21, 0xa3, 0x89, 0x5f, 0x96, 0x45, 0xca, 0x4d, 0xa4, 0x21, 0x99, 0x70, 0xda, 0xc4, 0xc4, 0xa0, 0xe5, 0xf4, 0xec, 0x0a}} , + {{0x07, 0x68, 0x21, 0x65, 0xe9, 0x08, 0xa0, 0x0b, 0x6a, 0x4a, 0xba, 0xb5, 0x80, 0xaf, 0xd0, 0x1b, 0xc5, 0xf5, 0x4b, 0x73, 0x50, 0x60, 0x2d, 0x71, 0x69, 0x61, 0x0e, 0xc0, 0x20, 0x40, 0x30, 0x19}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0x75, 0x57, 0x3b, 0xeb, 0x5c, 0x14, 0x56, 0x50, 0xc9, 0x4f, 0xb8, 0xb8, 0x1e, 0xa3, 0xf4, 0xab, 0xf5, 0xa9, 0x20, 0x15, 0x94, 0x82, 0xda, 0x96, 0x1c, 0x9b, 0x59, 0x8c, 0xff, 0xf4, 0x51}} , + {{0xc1, 0x3a, 0x86, 0xd7, 0xb0, 0x06, 0x84, 0x7f, 0x1b, 0xbd, 0xd4, 0x07, 0x78, 0x80, 0x2e, 0xb1, 0xb4, 0xee, 0x52, 0x38, 0xee, 0x9a, 0xf9, 0xf6, 0xf3, 0x41, 0x6e, 0xd4, 0x88, 0x95, 0xac, 0x35}}}, +{{{0x41, 0x97, 0xbf, 0x71, 0x6a, 0x9b, 0x72, 0xec, 0xf3, 0xf8, 0x6b, 0xe6, 0x0e, 0x6c, 0x69, 0xa5, 0x2f, 0x68, 0x52, 0xd8, 0x61, 0x81, 0xc0, 0x63, 0x3f, 0xa6, 0x3c, 0x13, 0x90, 0xe6, 0x8d, 0x56}} , + {{0xe8, 0x39, 0x30, 0x77, 0x23, 0xb1, 0xfd, 0x1b, 0x3d, 0x3e, 0x74, 0x4d, 0x7f, 0xae, 0x5b, 0x3a, 0xb4, 0x65, 0x0e, 0x3a, 0x43, 0xdc, 0xdc, 0x41, 0x47, 0xe6, 0xe8, 0x92, 0x09, 0x22, 0x48, 0x4c}}}, +{{{0x85, 0x57, 0x9f, 0xb5, 0xc8, 0x06, 0xb2, 0x9f, 0x47, 0x3f, 0xf0, 0xfa, 0xe6, 0xa9, 0xb1, 0x9b, 0x6f, 0x96, 0x7d, 0xf9, 0xa4, 0x65, 0x09, 0x75, 0x32, 0xa6, 0x6c, 0x7f, 0x47, 0x4b, 0x2f, 0x4f}} , + {{0x34, 0xe9, 0x59, 0x93, 0x9d, 0x26, 0x80, 0x54, 0xf2, 0xcc, 0x3c, 0xc2, 0x25, 0x85, 0xe3, 0x6a, 0xc1, 0x62, 0x04, 0xa7, 0x08, 0x32, 0x6d, 0xa1, 0x39, 0x84, 0x8a, 0x3b, 0x87, 0x5f, 0x11, 0x13}}}, +{{{0xda, 0x03, 0x34, 0x66, 0xc4, 0x0c, 0x73, 0x6e, 0xbc, 0x24, 0xb5, 0xf9, 0x70, 0x81, 0x52, 0xe9, 0xf4, 0x7c, 0x23, 0xdd, 0x9f, 0xb8, 0x46, 0xef, 0x1d, 0x22, 0x55, 0x7d, 0x71, 0xc4, 0x42, 0x33}} , + {{0xc5, 0x37, 0x69, 0x5b, 0xa8, 0xc6, 0x9d, 0xa4, 0xfc, 0x61, 0x6e, 0x68, 0x46, 0xea, 0xd7, 0x1c, 0x67, 0xd2, 0x7d, 0xfa, 0xf1, 0xcc, 0x54, 0x8d, 0x36, 0x35, 0xc9, 0x00, 0xdf, 0x6c, 0x67, 0x50}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9a, 0x4d, 0x42, 0x29, 0x5d, 0xa4, 0x6b, 0x6f, 0xa8, 0x8a, 0x4d, 0x91, 0x7b, 0xd2, 0xdf, 0x36, 0xef, 0x01, 0x22, 0xc5, 0xcc, 0x8d, 0xeb, 0x58, 0x3d, 0xb3, 0x50, 0xfc, 0x8b, 0x97, 0x96, 0x33}} , + {{0x93, 0x33, 0x07, 0xc8, 0x4a, 0xca, 0xd0, 0xb1, 0xab, 0xbd, 0xdd, 0xa7, 0x7c, 0xac, 0x3e, 0x45, 0xcb, 0xcc, 0x07, 0x91, 0xbf, 0x35, 0x9d, 0xcb, 0x7d, 0x12, 0x3c, 0x11, 0x59, 0x13, 0xcf, 0x5c}}}, +{{{0x45, 0xb8, 0x41, 0xd7, 0xab, 0x07, 0x15, 0x00, 0x8e, 0xce, 0xdf, 0xb2, 0x43, 0x5c, 0x01, 0xdc, 0xf4, 0x01, 0x51, 0x95, 0x10, 0x5a, 0xf6, 0x24, 0x24, 0xa0, 0x19, 0x3a, 0x09, 0x2a, 0xaa, 0x3f}} , + {{0xdc, 0x8e, 0xeb, 0xc6, 0xbf, 0xdd, 0x11, 0x7b, 0xe7, 0x47, 0xe6, 0xce, 0xe7, 0xb6, 0xc5, 0xe8, 0x8a, 0xdc, 0x4b, 0x57, 0x15, 0x3b, 0x66, 0xca, 0x89, 0xa3, 0xfd, 0xac, 0x0d, 0xe1, 0x1d, 0x7a}}}, +{{{0x89, 0xef, 0xbf, 0x03, 0x75, 0xd0, 0x29, 0x50, 0xcb, 0x7d, 0xd6, 0xbe, 0xad, 0x5f, 0x7b, 0x00, 0x32, 0xaa, 0x98, 0xed, 0x3f, 0x8f, 0x92, 0xcb, 0x81, 0x56, 0x01, 0x63, 0x64, 0xa3, 0x38, 0x39}} , + {{0x8b, 0xa4, 0xd6, 0x50, 0xb4, 0xaa, 0x5d, 0x64, 0x64, 0x76, 0x2e, 0xa1, 0xa6, 0xb3, 0xb8, 0x7c, 0x7a, 0x56, 0xf5, 0x5c, 0x4e, 0x84, 0x5c, 0xfb, 0xdd, 0xca, 0x48, 0x8b, 0x48, 0xb9, 0xba, 0x34}}}, +{{{0xc5, 0xe3, 0xe8, 0xae, 0x17, 0x27, 0xe3, 0x64, 0x60, 0x71, 0x47, 0x29, 0x02, 0x0f, 0x92, 0x5d, 0x10, 0x93, 0xc8, 0x0e, 0xa1, 0xed, 0xba, 0xa9, 0x96, 0x1c, 0xc5, 0x76, 0x30, 0xcd, 0xf9, 0x30}} , + {{0x95, 0xb0, 0xbd, 0x8c, 0xbc, 0xa7, 0x4f, 0x7e, 0xfd, 0x4e, 0x3a, 0xbf, 0x5f, 0x04, 0x79, 0x80, 0x2b, 0x5a, 0x9f, 0x4f, 0x68, 0x21, 0x19, 0x71, 0xc6, 0x20, 0x01, 0x42, 0xaa, 0xdf, 0xae, 0x2c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x90, 0x6e, 0x7e, 0x4b, 0x71, 0x93, 0xc0, 0x72, 0xed, 0xeb, 0x71, 0x24, 0x97, 0x26, 0x9c, 0xfe, 0xcb, 0x3e, 0x59, 0x19, 0xa8, 0x0f, 0x75, 0x7d, 0xbe, 0x18, 0xe6, 0x96, 0x1e, 0x95, 0x70, 0x60}} , + {{0x89, 0x66, 0x3e, 0x1d, 0x4c, 0x5f, 0xfe, 0xc0, 0x04, 0x43, 0xd6, 0x44, 0x19, 0xb5, 0xad, 0xc7, 0x22, 0xdc, 0x71, 0x28, 0x64, 0xde, 0x41, 0x38, 0x27, 0x8f, 0x2c, 0x6b, 0x08, 0xb8, 0xb8, 0x7b}}}, +{{{0x3d, 0x70, 0x27, 0x9d, 0xd9, 0xaf, 0xb1, 0x27, 0xaf, 0xe3, 0x5d, 0x1e, 0x3a, 0x30, 0x54, 0x61, 0x60, 0xe8, 0xc3, 0x26, 0x3a, 0xbc, 0x7e, 0xf5, 0x81, 0xdd, 0x64, 0x01, 0x04, 0xeb, 0xc0, 0x1e}} , + {{0xda, 0x2c, 0xa4, 0xd1, 0xa1, 0xc3, 0x5c, 0x6e, 0x32, 0x07, 0x1f, 0xb8, 0x0e, 0x19, 0x9e, 0x99, 0x29, 0x33, 0x9a, 0xae, 0x7a, 0xed, 0x68, 0x42, 0x69, 0x7c, 0x07, 0xb3, 0x38, 0x2c, 0xf6, 0x3d}}}, +{{{0x64, 0xaa, 0xb5, 0x88, 0x79, 0x65, 0x38, 0x8c, 0x94, 0xd6, 0x62, 0x37, 0x7d, 0x64, 0xcd, 0x3a, 0xeb, 0xff, 0xe8, 0x81, 0x09, 0xc7, 0x6a, 0x50, 0x09, 0x0d, 0x28, 0x03, 0x0d, 0x9a, 0x93, 0x0a}} , + {{0x42, 0xa3, 0xf1, 0xc5, 0xb4, 0x0f, 0xd8, 0xc8, 0x8d, 0x15, 0x31, 0xbd, 0xf8, 0x07, 0x8b, 0xcd, 0x08, 0x8a, 0xfb, 0x18, 0x07, 0xfe, 0x8e, 0x52, 0x86, 0xef, 0xbe, 0xec, 0x49, 0x52, 0x99, 0x08}}}, +{{{0x0f, 0xa9, 0xd5, 0x01, 0xaa, 0x48, 0x4f, 0x28, 0x66, 0x32, 0x1a, 0xba, 0x7c, 0xea, 0x11, 0x80, 0x17, 0x18, 0x9b, 0x56, 0x88, 0x25, 0x06, 0x69, 0x12, 0x2c, 0xea, 0x56, 0x69, 0x41, 0x24, 0x19}} , + {{0xde, 0x21, 0xf0, 0xda, 0x8a, 0xfb, 0xb1, 0xb8, 0xcd, 0xc8, 0x6a, 0x82, 0x19, 0x73, 0xdb, 0xc7, 0xcf, 0x88, 0xeb, 0x96, 0xee, 0x6f, 0xfb, 0x06, 0xd2, 0xcd, 0x7d, 0x7b, 0x12, 0x28, 0x8e, 0x0c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x93, 0x44, 0x97, 0xce, 0x28, 0xff, 0x3a, 0x40, 0xc4, 0xf5, 0xf6, 0x9b, 0xf4, 0x6b, 0x07, 0x84, 0xfb, 0x98, 0xd8, 0xec, 0x8c, 0x03, 0x57, 0xec, 0x49, 0xed, 0x63, 0xb6, 0xaa, 0xff, 0x98, 0x28}} , + {{0x3d, 0x16, 0x35, 0xf3, 0x46, 0xbc, 0xb3, 0xf4, 0xc6, 0xb6, 0x4f, 0xfa, 0xf4, 0xa0, 0x13, 0xe6, 0x57, 0x45, 0x93, 0xb9, 0xbc, 0xd6, 0x59, 0xe7, 0x77, 0x94, 0x6c, 0xab, 0x96, 0x3b, 0x4f, 0x09}}}, +{{{0x5a, 0xf7, 0x6b, 0x01, 0x12, 0x4f, 0x51, 0xc1, 0x70, 0x84, 0x94, 0x47, 0xb2, 0x01, 0x6c, 0x71, 0xd7, 0xcc, 0x17, 0x66, 0x0f, 0x59, 0x5d, 0x5d, 0x10, 0x01, 0x57, 0x11, 0xf5, 0xdd, 0xe2, 0x34}} , + {{0x26, 0xd9, 0x1f, 0x5c, 0x58, 0xac, 0x8b, 0x03, 0xd2, 0xc3, 0x85, 0x0f, 0x3a, 0xc3, 0x7f, 0x6d, 0x8e, 0x86, 0xcd, 0x52, 0x74, 0x8f, 0x55, 0x77, 0x17, 0xb7, 0x8e, 0xb7, 0x88, 0xea, 0xda, 0x1b}}}, +{{{0xb6, 0xea, 0x0e, 0x40, 0x93, 0x20, 0x79, 0x35, 0x6a, 0x61, 0x84, 0x5a, 0x07, 0x6d, 0xf9, 0x77, 0x6f, 0xed, 0x69, 0x1c, 0x0d, 0x25, 0x76, 0xcc, 0xf0, 0xdb, 0xbb, 0xc5, 0xad, 0xe2, 0x26, 0x57}} , + {{0xcf, 0xe8, 0x0e, 0x6b, 0x96, 0x7d, 0xed, 0x27, 0xd1, 0x3c, 0xa9, 0xd9, 0x50, 0xa9, 0x98, 0x84, 0x5e, 0x86, 0xef, 0xd6, 0xf0, 0xf8, 0x0e, 0x89, 0x05, 0x2f, 0xd9, 0x5f, 0x15, 0x5f, 0x73, 0x79}}}, +{{{0xc8, 0x5c, 0x16, 0xfe, 0xed, 0x9f, 0x26, 0x56, 0xf6, 0x4b, 0x9f, 0xa7, 0x0a, 0x85, 0xfe, 0xa5, 0x8c, 0x87, 0xdd, 0x98, 0xce, 0x4e, 0xc3, 0x58, 0x55, 0xb2, 0x7b, 0x3d, 0xd8, 0x6b, 0xb5, 0x4c}} , + {{0x65, 0x38, 0xa0, 0x15, 0xfa, 0xa7, 0xb4, 0x8f, 0xeb, 0xc4, 0x86, 0x9b, 0x30, 0xa5, 0x5e, 0x4d, 0xea, 0x8a, 0x9a, 0x9f, 0x1a, 0xd8, 0x5b, 0x53, 0x14, 0x19, 0x25, 0x63, 0xb4, 0x6f, 0x1f, 0x5d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xac, 0x8f, 0xbc, 0x1e, 0x7d, 0x8b, 0x5a, 0x0b, 0x8d, 0xaf, 0x76, 0x2e, 0x71, 0xe3, 0x3b, 0x6f, 0x53, 0x2f, 0x3e, 0x90, 0x95, 0xd4, 0x35, 0x14, 0x4f, 0x8c, 0x3c, 0xce, 0x57, 0x1c, 0x76, 0x49}} , + {{0xa8, 0x50, 0xe1, 0x61, 0x6b, 0x57, 0x35, 0xeb, 0x44, 0x0b, 0x0c, 0x6e, 0xf9, 0x25, 0x80, 0x74, 0xf2, 0x8f, 0x6f, 0x7a, 0x3e, 0x7f, 0x2d, 0xf3, 0x4e, 0x09, 0x65, 0x10, 0x5e, 0x03, 0x25, 0x32}}}, +{{{0xa9, 0x60, 0xdc, 0x0f, 0x64, 0xe5, 0x1d, 0xe2, 0x8d, 0x4f, 0x79, 0x2f, 0x0e, 0x24, 0x02, 0x00, 0x05, 0x77, 0x43, 0x25, 0x3d, 0x6a, 0xc7, 0xb7, 0xbf, 0x04, 0x08, 0x65, 0xf4, 0x39, 0x4b, 0x65}} , + {{0x96, 0x19, 0x12, 0x6b, 0x6a, 0xb7, 0xe3, 0xdc, 0x45, 0x9b, 0xdb, 0xb4, 0xa8, 0xae, 0xdc, 0xa8, 0x14, 0x44, 0x65, 0x62, 0xce, 0x34, 0x9a, 0x84, 0x18, 0x12, 0x01, 0xf1, 0xe2, 0x7b, 0xce, 0x50}}}, +{{{0x41, 0x21, 0x30, 0x53, 0x1b, 0x47, 0x01, 0xb7, 0x18, 0xd8, 0x82, 0x57, 0xbd, 0xa3, 0x60, 0xf0, 0x32, 0xf6, 0x5b, 0xf0, 0x30, 0x88, 0x91, 0x59, 0xfd, 0x90, 0xa2, 0xb9, 0x55, 0x93, 0x21, 0x34}} , + {{0x97, 0x67, 0x9e, 0xeb, 0x6a, 0xf9, 0x6e, 0xd6, 0x73, 0xe8, 0x6b, 0x29, 0xec, 0x63, 0x82, 0x00, 0xa8, 0x99, 0x1c, 0x1d, 0x30, 0xc8, 0x90, 0x52, 0x90, 0xb6, 0x6a, 0x80, 0x4e, 0xff, 0x4b, 0x51}}}, +{{{0x0f, 0x7d, 0x63, 0x8c, 0x6e, 0x5c, 0xde, 0x30, 0xdf, 0x65, 0xfa, 0x2e, 0xb0, 0xa3, 0x25, 0x05, 0x54, 0xbd, 0x25, 0xba, 0x06, 0xae, 0xdf, 0x8b, 0xd9, 0x1b, 0xea, 0x38, 0xb3, 0x05, 0x16, 0x09}} , + {{0xc7, 0x8c, 0xbf, 0x64, 0x28, 0xad, 0xf8, 0xa5, 0x5a, 0x6f, 0xc9, 0xba, 0xd5, 0x7f, 0xd5, 0xd6, 0xbd, 0x66, 0x2f, 0x3d, 0xaa, 0x54, 0xf6, 0xba, 0x32, 0x22, 0x9a, 0x1e, 0x52, 0x05, 0xf4, 0x1d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xaa, 0x1f, 0xbb, 0xeb, 0xfe, 0xe4, 0x87, 0xfc, 0xb1, 0x2c, 0xb7, 0x88, 0xf4, 0xc6, 0xb9, 0xf5, 0x24, 0x46, 0xf2, 0xa5, 0x9f, 0x8f, 0x8a, 0x93, 0x70, 0x69, 0xd4, 0x56, 0xec, 0xfd, 0x06, 0x46}} , + {{0x4e, 0x66, 0xcf, 0x4e, 0x34, 0xce, 0x0c, 0xd9, 0xa6, 0x50, 0xd6, 0x5e, 0x95, 0xaf, 0xe9, 0x58, 0xfa, 0xee, 0x9b, 0xb8, 0xa5, 0x0f, 0x35, 0xe0, 0x43, 0x82, 0x6d, 0x65, 0xe6, 0xd9, 0x00, 0x0f}}}, +{{{0x7b, 0x75, 0x3a, 0xfc, 0x64, 0xd3, 0x29, 0x7e, 0xdd, 0x49, 0x9a, 0x59, 0x53, 0xbf, 0xb4, 0xa7, 0x52, 0xb3, 0x05, 0xab, 0xc3, 0xaf, 0x16, 0x1a, 0x85, 0x42, 0x32, 0xa2, 0x86, 0xfa, 0x39, 0x43}} , + {{0x0e, 0x4b, 0xa3, 0x63, 0x8a, 0xfe, 0xa5, 0x58, 0xf1, 0x13, 0xbd, 0x9d, 0xaa, 0x7f, 0x76, 0x40, 0x70, 0x81, 0x10, 0x75, 0x99, 0xbb, 0xbe, 0x0b, 0x16, 0xe9, 0xba, 0x62, 0x34, 0xcc, 0x07, 0x6d}}}, +{{{0xc3, 0xf1, 0xc6, 0x93, 0x65, 0xee, 0x0b, 0xbc, 0xea, 0x14, 0xf0, 0xc1, 0xf8, 0x84, 0x89, 0xc2, 0xc9, 0xd7, 0xea, 0x34, 0xca, 0xa7, 0xc4, 0x99, 0xd5, 0x50, 0x69, 0xcb, 0xd6, 0x21, 0x63, 0x7c}} , + {{0x99, 0xeb, 0x7c, 0x31, 0x73, 0x64, 0x67, 0x7f, 0x0c, 0x66, 0xaa, 0x8c, 0x69, 0x91, 0xe2, 0x26, 0xd3, 0x23, 0xe2, 0x76, 0x5d, 0x32, 0x52, 0xdf, 0x5d, 0xc5, 0x8f, 0xb7, 0x7c, 0x84, 0xb3, 0x70}}}, +{{{0xeb, 0x01, 0xc7, 0x36, 0x97, 0x4e, 0xb6, 0xab, 0x5f, 0x0d, 0x2c, 0xba, 0x67, 0x64, 0x55, 0xde, 0xbc, 0xff, 0xa6, 0xec, 0x04, 0xd3, 0x8d, 0x39, 0x56, 0x5e, 0xee, 0xf8, 0xe4, 0x2e, 0x33, 0x62}} , + {{0x65, 0xef, 0xb8, 0x9f, 0xc8, 0x4b, 0xa7, 0xfd, 0x21, 0x49, 0x9b, 0x92, 0x35, 0x82, 0xd6, 0x0a, 0x9b, 0xf2, 0x79, 0xf1, 0x47, 0x2f, 0x6a, 0x7e, 0x9f, 0xcf, 0x18, 0x02, 0x3c, 0xfb, 0x1b, 0x3e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2f, 0x8b, 0xc8, 0x40, 0x51, 0xd1, 0xac, 0x1a, 0x0b, 0xe4, 0xa9, 0xa2, 0x42, 0x21, 0x19, 0x2f, 0x7b, 0x97, 0xbf, 0xf7, 0x57, 0x6d, 0x3f, 0x3d, 0x4f, 0x0f, 0xe2, 0xb2, 0x81, 0x00, 0x9e, 0x7b}} , + {{0x8c, 0x85, 0x2b, 0xc4, 0xfc, 0xf1, 0xab, 0xe8, 0x79, 0x22, 0xc4, 0x84, 0x17, 0x3a, 0xfa, 0x86, 0xa6, 0x7d, 0xf9, 0xf3, 0x6f, 0x03, 0x57, 0x20, 0x4d, 0x79, 0xf9, 0x6e, 0x71, 0x54, 0x38, 0x09}}}, +{{{0x40, 0x29, 0x74, 0xa8, 0x2f, 0x5e, 0xf9, 0x79, 0xa4, 0xf3, 0x3e, 0xb9, 0xfd, 0x33, 0x31, 0xac, 0x9a, 0x69, 0x88, 0x1e, 0x77, 0x21, 0x2d, 0xf3, 0x91, 0x52, 0x26, 0x15, 0xb2, 0xa6, 0xcf, 0x7e}} , + {{0xc6, 0x20, 0x47, 0x6c, 0xa4, 0x7d, 0xcb, 0x63, 0xea, 0x5b, 0x03, 0xdf, 0x3e, 0x88, 0x81, 0x6d, 0xce, 0x07, 0x42, 0x18, 0x60, 0x7e, 0x7b, 0x55, 0xfe, 0x6a, 0xf3, 0xda, 0x5c, 0x8b, 0x95, 0x10}}}, +{{{0x62, 0xe4, 0x0d, 0x03, 0xb4, 0xd7, 0xcd, 0xfa, 0xbd, 0x46, 0xdf, 0x93, 0x71, 0x10, 0x2c, 0xa8, 0x3b, 0xb6, 0x09, 0x05, 0x70, 0x84, 0x43, 0x29, 0xa8, 0x59, 0xf5, 0x8e, 0x10, 0xe4, 0xd7, 0x20}} , + {{0x57, 0x82, 0x1c, 0xab, 0xbf, 0x62, 0x70, 0xe8, 0xc4, 0xcf, 0xf0, 0x28, 0x6e, 0x16, 0x3c, 0x08, 0x78, 0x89, 0x85, 0x46, 0x0f, 0xf6, 0x7f, 0xcf, 0xcb, 0x7e, 0xb8, 0x25, 0xe9, 0x5a, 0xfa, 0x03}}}, +{{{0xfb, 0x95, 0x92, 0x63, 0x50, 0xfc, 0x62, 0xf0, 0xa4, 0x5e, 0x8c, 0x18, 0xc2, 0x17, 0x24, 0xb7, 0x78, 0xc2, 0xa9, 0xe7, 0x6a, 0x32, 0xd6, 0x29, 0x85, 0xaf, 0xcb, 0x8d, 0x91, 0x13, 0xda, 0x6b}} , + {{0x36, 0x0a, 0xc2, 0xb6, 0x4b, 0xa5, 0x5d, 0x07, 0x17, 0x41, 0x31, 0x5f, 0x62, 0x46, 0xf8, 0x92, 0xf9, 0x66, 0x48, 0x73, 0xa6, 0x97, 0x0d, 0x7d, 0x88, 0xee, 0x62, 0xb1, 0x03, 0xa8, 0x3f, 0x2c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x4a, 0xb1, 0x70, 0x8a, 0xa9, 0xe8, 0x63, 0x79, 0x00, 0xe2, 0x25, 0x16, 0xca, 0x4b, 0x0f, 0xa4, 0x66, 0xad, 0x19, 0x9f, 0x88, 0x67, 0x0c, 0x8b, 0xc2, 0x4a, 0x5b, 0x2b, 0x6d, 0x95, 0xaf, 0x19}} , + {{0x8b, 0x9d, 0xb6, 0xcc, 0x60, 0xb4, 0x72, 0x4f, 0x17, 0x69, 0x5a, 0x4a, 0x68, 0x34, 0xab, 0xa1, 0x45, 0x32, 0x3c, 0x83, 0x87, 0x72, 0x30, 0x54, 0x77, 0x68, 0xae, 0xfb, 0xb5, 0x8b, 0x22, 0x5e}}}, +{{{0xf1, 0xb9, 0x87, 0x35, 0xc5, 0xbb, 0xb9, 0xcf, 0xf5, 0xd6, 0xcd, 0xd5, 0x0c, 0x7c, 0x0e, 0xe6, 0x90, 0x34, 0xfb, 0x51, 0x42, 0x1e, 0x6d, 0xac, 0x9a, 0x46, 0xc4, 0x97, 0x29, 0x32, 0xbf, 0x45}} , + {{0x66, 0x9e, 0xc6, 0x24, 0xc0, 0xed, 0xa5, 0x5d, 0x88, 0xd4, 0xf0, 0x73, 0x97, 0x7b, 0xea, 0x7f, 0x42, 0xff, 0x21, 0xa0, 0x9b, 0x2f, 0x9a, 0xfd, 0x53, 0x57, 0x07, 0x84, 0x48, 0x88, 0x9d, 0x52}}}, +{{{0xc6, 0x96, 0x48, 0x34, 0x2a, 0x06, 0xaf, 0x94, 0x3d, 0xf4, 0x1a, 0xcf, 0xf2, 0xc0, 0x21, 0xc2, 0x42, 0x5e, 0xc8, 0x2f, 0x35, 0xa2, 0x3e, 0x29, 0xfa, 0x0c, 0x84, 0xe5, 0x89, 0x72, 0x7c, 0x06}} , + {{0x32, 0x65, 0x03, 0xe5, 0x89, 0xa6, 0x6e, 0xb3, 0x5b, 0x8e, 0xca, 0xeb, 0xfe, 0x22, 0x56, 0x8b, 0x5d, 0x14, 0x4b, 0x4d, 0xf9, 0xbe, 0xb5, 0xf5, 0xe6, 0x5c, 0x7b, 0x8b, 0xf4, 0x13, 0x11, 0x34}}}, +{{{0x07, 0xc6, 0x22, 0x15, 0xe2, 0x9c, 0x60, 0xa2, 0x19, 0xd9, 0x27, 0xae, 0x37, 0x4e, 0xa6, 0xc9, 0x80, 0xa6, 0x91, 0x8f, 0x12, 0x49, 0xe5, 0x00, 0x18, 0x47, 0xd1, 0xd7, 0x28, 0x22, 0x63, 0x39}} , + {{0xe8, 0xe2, 0x00, 0x7e, 0xf2, 0x9e, 0x1e, 0x99, 0x39, 0x95, 0x04, 0xbd, 0x1e, 0x67, 0x7b, 0xb2, 0x26, 0xac, 0xe6, 0xaa, 0xe2, 0x46, 0xd5, 0xe4, 0xe8, 0x86, 0xbd, 0xab, 0x7c, 0x55, 0x59, 0x6f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x24, 0x64, 0x6e, 0x9b, 0x35, 0x71, 0x78, 0xce, 0x33, 0x03, 0x21, 0x33, 0x36, 0xf1, 0x73, 0x9b, 0xb9, 0x15, 0x8b, 0x2c, 0x69, 0xcf, 0x4d, 0xed, 0x4f, 0x4d, 0x57, 0x14, 0x13, 0x82, 0xa4, 0x4d}} , + {{0x65, 0x6e, 0x0a, 0xa4, 0x59, 0x07, 0x17, 0xf2, 0x6b, 0x4a, 0x1f, 0x6e, 0xf6, 0xb5, 0xbc, 0x62, 0xe4, 0xb6, 0xda, 0xa2, 0x93, 0xbc, 0x29, 0x05, 0xd2, 0xd2, 0x73, 0x46, 0x03, 0x16, 0x40, 0x31}}}, +{{{0x4c, 0x73, 0x6d, 0x15, 0xbd, 0xa1, 0x4d, 0x5c, 0x13, 0x0b, 0x24, 0x06, 0x98, 0x78, 0x1c, 0x5b, 0xeb, 0x1f, 0x18, 0x54, 0x43, 0xd9, 0x55, 0x66, 0xda, 0x29, 0x21, 0xe8, 0xb8, 0x3c, 0x42, 0x22}} , + {{0xb4, 0xcd, 0x08, 0x6f, 0x15, 0x23, 0x1a, 0x0b, 0x22, 0xed, 0xd1, 0xf1, 0xa7, 0xc7, 0x73, 0x45, 0xf3, 0x9e, 0xce, 0x76, 0xb7, 0xf6, 0x39, 0xb6, 0x8e, 0x79, 0xbe, 0xe9, 0x9b, 0xcf, 0x7d, 0x62}}}, +{{{0x92, 0x5b, 0xfc, 0x72, 0xfd, 0xba, 0xf1, 0xfd, 0xa6, 0x7c, 0x95, 0xe3, 0x61, 0x3f, 0xe9, 0x03, 0xd4, 0x2b, 0xd4, 0x20, 0xd9, 0xdb, 0x4d, 0x32, 0x3e, 0xf5, 0x11, 0x64, 0xe3, 0xb4, 0xbe, 0x32}} , + {{0x86, 0x17, 0x90, 0xe7, 0xc9, 0x1f, 0x10, 0xa5, 0x6a, 0x2d, 0x39, 0xd0, 0x3b, 0xc4, 0xa6, 0xe9, 0x59, 0x13, 0xda, 0x1a, 0xe6, 0xa0, 0xb9, 0x3c, 0x50, 0xb8, 0x40, 0x7c, 0x15, 0x36, 0x5a, 0x42}}}, +{{{0xb4, 0x0b, 0x32, 0xab, 0xdc, 0x04, 0x51, 0x55, 0x21, 0x1e, 0x0b, 0x75, 0x99, 0x89, 0x73, 0x35, 0x3a, 0x91, 0x2b, 0xfe, 0xe7, 0x49, 0xea, 0x76, 0xc1, 0xf9, 0x46, 0xb9, 0x53, 0x02, 0x23, 0x04}} , + {{0xfc, 0x5a, 0x1e, 0x1d, 0x74, 0x58, 0x95, 0xa6, 0x8f, 0x7b, 0x97, 0x3e, 0x17, 0x3b, 0x79, 0x2d, 0xa6, 0x57, 0xef, 0x45, 0x02, 0x0b, 0x4d, 0x6e, 0x9e, 0x93, 0x8d, 0x2f, 0xd9, 0x9d, 0xdb, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc0, 0xd7, 0x56, 0x97, 0x58, 0x91, 0xde, 0x09, 0x4f, 0x9f, 0xbe, 0x63, 0xb0, 0x83, 0x86, 0x43, 0x5d, 0xbc, 0xe0, 0xf3, 0xc0, 0x75, 0xbf, 0x8b, 0x8e, 0xaa, 0xf7, 0x8b, 0x64, 0x6e, 0xb0, 0x63}} , + {{0x16, 0xae, 0x8b, 0xe0, 0x9b, 0x24, 0x68, 0x5c, 0x44, 0xc2, 0xd0, 0x08, 0xb7, 0x7b, 0x62, 0xfd, 0x7f, 0xd8, 0xd4, 0xb7, 0x50, 0xfd, 0x2c, 0x1b, 0xbf, 0x41, 0x95, 0xd9, 0x8e, 0xd8, 0x17, 0x1b}}}, +{{{0x86, 0x55, 0x37, 0x8e, 0xc3, 0x38, 0x48, 0x14, 0xb5, 0x97, 0xd2, 0xa7, 0x54, 0x45, 0xf1, 0x35, 0x44, 0x38, 0x9e, 0xf1, 0x1b, 0xb6, 0x34, 0x00, 0x3c, 0x96, 0xee, 0x29, 0x00, 0xea, 0x2c, 0x0b}} , + {{0xea, 0xda, 0x99, 0x9e, 0x19, 0x83, 0x66, 0x6d, 0xe9, 0x76, 0x87, 0x50, 0xd1, 0xfd, 0x3c, 0x60, 0x87, 0xc6, 0x41, 0xd9, 0x8e, 0xdb, 0x5e, 0xde, 0xaa, 0x9a, 0xd3, 0x28, 0xda, 0x95, 0xea, 0x47}}}, +{{{0xd0, 0x80, 0xba, 0x19, 0xae, 0x1d, 0xa9, 0x79, 0xf6, 0x3f, 0xac, 0x5d, 0x6f, 0x96, 0x1f, 0x2a, 0xce, 0x29, 0xb2, 0xff, 0x37, 0xf1, 0x94, 0x8f, 0x0c, 0xb5, 0x28, 0xba, 0x9a, 0x21, 0xf6, 0x66}} , + {{0x02, 0xfb, 0x54, 0xb8, 0x05, 0xf3, 0x81, 0x52, 0x69, 0x34, 0x46, 0x9d, 0x86, 0x76, 0x8f, 0xd7, 0xf8, 0x6a, 0x66, 0xff, 0xe6, 0xa7, 0x90, 0xf7, 0x5e, 0xcd, 0x6a, 0x9b, 0x55, 0xfc, 0x9d, 0x48}}}, +{{{0xbd, 0xaa, 0x13, 0xe6, 0xcd, 0x45, 0x4a, 0xa4, 0x59, 0x0a, 0x64, 0xb1, 0x98, 0xd6, 0x34, 0x13, 0x04, 0xe6, 0x97, 0x94, 0x06, 0xcb, 0xd4, 0x4e, 0xbb, 0x96, 0xcd, 0xd1, 0x57, 0xd1, 0xe3, 0x06}} , + {{0x7a, 0x6c, 0x45, 0x27, 0xc4, 0x93, 0x7f, 0x7d, 0x7c, 0x62, 0x50, 0x38, 0x3a, 0x6b, 0xb5, 0x88, 0xc6, 0xd9, 0xf1, 0x78, 0x19, 0xb9, 0x39, 0x93, 0x3d, 0xc9, 0xe0, 0x9c, 0x3c, 0xce, 0xf5, 0x72}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x24, 0xea, 0x23, 0x7d, 0x56, 0x2c, 0xe2, 0x59, 0x0e, 0x85, 0x60, 0x04, 0x88, 0x5a, 0x74, 0x1e, 0x4b, 0xef, 0x13, 0xda, 0x4c, 0xff, 0x83, 0x45, 0x85, 0x3f, 0x08, 0x95, 0x2c, 0x20, 0x13, 0x1f}} , + {{0x48, 0x5f, 0x27, 0x90, 0x5c, 0x02, 0x42, 0xad, 0x78, 0x47, 0x5c, 0xb5, 0x7e, 0x08, 0x85, 0x00, 0xfa, 0x7f, 0xfd, 0xfd, 0xe7, 0x09, 0x11, 0xf2, 0x7e, 0x1b, 0x38, 0x6c, 0x35, 0x6d, 0x33, 0x66}}}, +{{{0x93, 0x03, 0x36, 0x81, 0xac, 0xe4, 0x20, 0x09, 0x35, 0x4c, 0x45, 0xb2, 0x1e, 0x4c, 0x14, 0x21, 0xe6, 0xe9, 0x8a, 0x7b, 0x8d, 0xfe, 0x1e, 0xc6, 0x3e, 0xc1, 0x35, 0xfa, 0xe7, 0x70, 0x4e, 0x1d}} , + {{0x61, 0x2e, 0xc2, 0xdd, 0x95, 0x57, 0xd1, 0xab, 0x80, 0xe8, 0x63, 0x17, 0xb5, 0x48, 0xe4, 0x8a, 0x11, 0x9e, 0x72, 0xbe, 0x85, 0x8d, 0x51, 0x0a, 0xf2, 0x9f, 0xe0, 0x1c, 0xa9, 0x07, 0x28, 0x7b}}}, +{{{0xbb, 0x71, 0x14, 0x5e, 0x26, 0x8c, 0x3d, 0xc8, 0xe9, 0x7c, 0xd3, 0xd6, 0xd1, 0x2f, 0x07, 0x6d, 0xe6, 0xdf, 0xfb, 0x79, 0xd6, 0x99, 0x59, 0x96, 0x48, 0x40, 0x0f, 0x3a, 0x7b, 0xb2, 0xa0, 0x72}} , + {{0x4e, 0x3b, 0x69, 0xc8, 0x43, 0x75, 0x51, 0x6c, 0x79, 0x56, 0xe4, 0xcb, 0xf7, 0xa6, 0x51, 0xc2, 0x2c, 0x42, 0x0b, 0xd4, 0x82, 0x20, 0x1c, 0x01, 0x08, 0x66, 0xd7, 0xbf, 0x04, 0x56, 0xfc, 0x02}}}, +{{{0x24, 0xe8, 0xb7, 0x60, 0xae, 0x47, 0x80, 0xfc, 0xe5, 0x23, 0xe7, 0xc2, 0xc9, 0x85, 0xe6, 0x98, 0xa0, 0x29, 0x4e, 0xe1, 0x84, 0x39, 0x2d, 0x95, 0x2c, 0xf3, 0x45, 0x3c, 0xff, 0xaf, 0x27, 0x4c}} , + {{0x6b, 0xa6, 0xf5, 0x4b, 0x11, 0xbd, 0xba, 0x5b, 0x9e, 0xc4, 0xa4, 0x51, 0x1e, 0xbe, 0xd0, 0x90, 0x3a, 0x9c, 0xc2, 0x26, 0xb6, 0x1e, 0xf1, 0x95, 0x7d, 0xc8, 0x6d, 0x52, 0xe6, 0x99, 0x2c, 0x5f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x85, 0xe0, 0x24, 0x32, 0xb4, 0xd1, 0xef, 0xfc, 0x69, 0xa2, 0xbf, 0x8f, 0x72, 0x2c, 0x95, 0xf6, 0xe4, 0x6e, 0x7d, 0x90, 0xf7, 0x57, 0x81, 0xa0, 0xf7, 0xda, 0xef, 0x33, 0x07, 0xe3, 0x6b, 0x78}} , + {{0x36, 0x27, 0x3e, 0xc6, 0x12, 0x07, 0xab, 0x4e, 0xbe, 0x69, 0x9d, 0xb3, 0xbe, 0x08, 0x7c, 0x2a, 0x47, 0x08, 0xfd, 0xd4, 0xcd, 0x0e, 0x27, 0x34, 0x5b, 0x98, 0x34, 0x2f, 0x77, 0x5f, 0x3a, 0x65}}}, +{{{0x13, 0xaa, 0x2e, 0x4c, 0xf0, 0x22, 0xb8, 0x6c, 0xb3, 0x19, 0x4d, 0xeb, 0x6b, 0xd0, 0xa4, 0xc6, 0x9c, 0xdd, 0xc8, 0x5b, 0x81, 0x57, 0x89, 0xdf, 0x33, 0xa9, 0x68, 0x49, 0x80, 0xe4, 0xfe, 0x21}} , + {{0x00, 0x17, 0x90, 0x30, 0xe9, 0xd3, 0x60, 0x30, 0x31, 0xc2, 0x72, 0x89, 0x7a, 0x36, 0xa5, 0xbd, 0x39, 0x83, 0x85, 0x50, 0xa1, 0x5d, 0x6c, 0x41, 0x1d, 0xb5, 0x2c, 0x07, 0x40, 0x77, 0x0b, 0x50}}}, +{{{0x64, 0x34, 0xec, 0xc0, 0x9e, 0x44, 0x41, 0xaf, 0xa0, 0x36, 0x05, 0x6d, 0xea, 0x30, 0x25, 0x46, 0x35, 0x24, 0x9d, 0x86, 0xbd, 0x95, 0xf1, 0x6a, 0x46, 0xd7, 0x94, 0x54, 0xf9, 0x3b, 0xbd, 0x5d}} , + {{0x77, 0x5b, 0xe2, 0x37, 0xc7, 0xe1, 0x7c, 0x13, 0x8c, 0x9f, 0x7b, 0x7b, 0x2a, 0xce, 0x42, 0xa3, 0xb9, 0x2a, 0x99, 0xa8, 0xc0, 0xd8, 0x3c, 0x86, 0xb0, 0xfb, 0xe9, 0x76, 0x77, 0xf7, 0xf5, 0x56}}}, +{{{0xdf, 0xb3, 0x46, 0x11, 0x6e, 0x13, 0xb7, 0x28, 0x4e, 0x56, 0xdd, 0xf1, 0xac, 0xad, 0x58, 0xc3, 0xf8, 0x88, 0x94, 0x5e, 0x06, 0x98, 0xa1, 0xe4, 0x6a, 0xfb, 0x0a, 0x49, 0x5d, 0x8a, 0xfe, 0x77}} , + {{0x46, 0x02, 0xf5, 0xa5, 0xaf, 0xc5, 0x75, 0x6d, 0xba, 0x45, 0x35, 0x0a, 0xfe, 0xc9, 0xac, 0x22, 0x91, 0x8d, 0x21, 0x95, 0x33, 0x03, 0xc0, 0x8a, 0x16, 0xf3, 0x39, 0xe0, 0x01, 0x0f, 0x53, 0x3c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x34, 0x75, 0x37, 0x1f, 0x34, 0x4e, 0xa9, 0x1d, 0x68, 0x67, 0xf8, 0x49, 0x98, 0x96, 0xfc, 0x4c, 0x65, 0x97, 0xf7, 0x02, 0x4a, 0x52, 0x6c, 0x01, 0xbd, 0x48, 0xbb, 0x1b, 0xed, 0xa4, 0xe2, 0x53}} , + {{0x59, 0xd5, 0x9b, 0x5a, 0xa2, 0x90, 0xd3, 0xb8, 0x37, 0x4c, 0x55, 0x82, 0x28, 0x08, 0x0f, 0x7f, 0xaa, 0x81, 0x65, 0xe0, 0x0c, 0x52, 0xc9, 0xa3, 0x32, 0x27, 0x64, 0xda, 0xfd, 0x34, 0x23, 0x5a}}}, +{{{0xb5, 0xb0, 0x0c, 0x4d, 0xb3, 0x7b, 0x23, 0xc8, 0x1f, 0x8a, 0x39, 0x66, 0xe6, 0xba, 0x4c, 0x10, 0x37, 0xca, 0x9c, 0x7c, 0x05, 0x9e, 0xff, 0xc0, 0xf8, 0x8e, 0xb1, 0x8f, 0x6f, 0x67, 0x18, 0x26}} , + {{0x4b, 0x41, 0x13, 0x54, 0x23, 0x1a, 0xa4, 0x4e, 0xa9, 0x8b, 0x1e, 0x4b, 0xfc, 0x15, 0x24, 0xbb, 0x7e, 0xcb, 0xb6, 0x1e, 0x1b, 0xf5, 0xf2, 0xc8, 0x56, 0xec, 0x32, 0xa2, 0x60, 0x5b, 0xa0, 0x2a}}}, +{{{0xa4, 0x29, 0x47, 0x86, 0x2e, 0x92, 0x4f, 0x11, 0x4f, 0xf3, 0xb2, 0x5c, 0xd5, 0x3e, 0xa6, 0xb9, 0xc8, 0xe2, 0x33, 0x11, 0x1f, 0x01, 0x8f, 0xb0, 0x9b, 0xc7, 0xa5, 0xff, 0x83, 0x0f, 0x1e, 0x28}} , + {{0x1d, 0x29, 0x7a, 0xa1, 0xec, 0x8e, 0xb5, 0xad, 0xea, 0x02, 0x68, 0x60, 0x74, 0x29, 0x1c, 0xa5, 0xcf, 0xc8, 0x3b, 0x7d, 0x8b, 0x2b, 0x7c, 0xad, 0xa4, 0x40, 0x17, 0x51, 0x59, 0x7c, 0x2e, 0x5d}}}, +{{{0x0a, 0x6c, 0x4f, 0xbc, 0x3e, 0x32, 0xe7, 0x4a, 0x1a, 0x13, 0xc1, 0x49, 0x38, 0xbf, 0xf7, 0xc2, 0xd3, 0x8f, 0x6b, 0xad, 0x52, 0xf7, 0xcf, 0xbc, 0x27, 0xcb, 0x40, 0x67, 0x76, 0xcd, 0x6d, 0x56}} , + {{0xe5, 0xb0, 0x27, 0xad, 0xbe, 0x9b, 0xf2, 0xb5, 0x63, 0xde, 0x3a, 0x23, 0x95, 0xb7, 0x0a, 0x7e, 0xf3, 0x9e, 0x45, 0x6f, 0x19, 0x39, 0x75, 0x8f, 0x39, 0x3d, 0x0f, 0xc0, 0x9f, 0xf1, 0xe9, 0x51}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x88, 0xaa, 0x14, 0x24, 0x86, 0x94, 0x11, 0x12, 0x3e, 0x1a, 0xb5, 0xcc, 0xbb, 0xe0, 0x9c, 0xd5, 0x9c, 0x6d, 0xba, 0x58, 0x72, 0x8d, 0xfb, 0x22, 0x7b, 0x9f, 0x7c, 0x94, 0x30, 0xb3, 0x51, 0x21}} , + {{0xf6, 0x74, 0x3d, 0xf2, 0xaf, 0xd0, 0x1e, 0x03, 0x7c, 0x23, 0x6b, 0xc9, 0xfc, 0x25, 0x70, 0x90, 0xdc, 0x9a, 0xa4, 0xfb, 0x49, 0xfc, 0x3d, 0x0a, 0x35, 0x38, 0x6f, 0xe4, 0x7e, 0x50, 0x01, 0x2a}}}, +{{{0xd6, 0xe3, 0x96, 0x61, 0x3a, 0xfd, 0xef, 0x9b, 0x1f, 0x90, 0xa4, 0x24, 0x14, 0x5b, 0xc8, 0xde, 0x50, 0xb1, 0x1d, 0xaf, 0xe8, 0x55, 0x8a, 0x87, 0x0d, 0xfe, 0xaa, 0x3b, 0x82, 0x2c, 0x8d, 0x7b}} , + {{0x85, 0x0c, 0xaf, 0xf8, 0x83, 0x44, 0x49, 0xd9, 0x45, 0xcf, 0xf7, 0x48, 0xd9, 0x53, 0xb4, 0xf1, 0x65, 0xa0, 0xe1, 0xc3, 0xb3, 0x15, 0xed, 0x89, 0x9b, 0x4f, 0x62, 0xb3, 0x57, 0xa5, 0x45, 0x1c}}}, +{{{0x8f, 0x12, 0xea, 0xaf, 0xd1, 0x1f, 0x79, 0x10, 0x0b, 0xf6, 0xa3, 0x7b, 0xea, 0xac, 0x8b, 0x57, 0x32, 0x62, 0xe7, 0x06, 0x12, 0x51, 0xa0, 0x3b, 0x43, 0x5e, 0xa4, 0x20, 0x78, 0x31, 0xce, 0x0d}} , + {{0x84, 0x7c, 0xc2, 0xa6, 0x91, 0x23, 0xce, 0xbd, 0xdc, 0xf9, 0xce, 0xd5, 0x75, 0x30, 0x22, 0xe6, 0xf9, 0x43, 0x62, 0x0d, 0xf7, 0x75, 0x9d, 0x7f, 0x8c, 0xff, 0x7d, 0xe4, 0x72, 0xac, 0x9f, 0x1c}}}, +{{{0x88, 0xc1, 0x99, 0xd0, 0x3c, 0x1c, 0x5d, 0xb4, 0xef, 0x13, 0x0f, 0x90, 0xb9, 0x36, 0x2f, 0x95, 0x95, 0xc6, 0xdc, 0xde, 0x0a, 0x51, 0xe2, 0x8d, 0xf3, 0xbc, 0x51, 0xec, 0xdf, 0xb1, 0xa2, 0x5f}} , + {{0x2e, 0x68, 0xa1, 0x23, 0x7d, 0x9b, 0x40, 0x69, 0x85, 0x7b, 0x42, 0xbf, 0x90, 0x4b, 0xd6, 0x40, 0x2f, 0xd7, 0x52, 0x52, 0xb2, 0x21, 0xde, 0x64, 0xbd, 0x88, 0xc3, 0x6d, 0xa5, 0xfa, 0x81, 0x3f}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xfb, 0xfd, 0x47, 0x7b, 0x8a, 0x66, 0x9e, 0x79, 0x2e, 0x64, 0x82, 0xef, 0xf7, 0x21, 0xec, 0xf6, 0xd8, 0x86, 0x09, 0x31, 0x7c, 0xdd, 0x03, 0x6a, 0x58, 0xa0, 0x77, 0xb7, 0x9b, 0x8c, 0x87, 0x1f}} , + {{0x55, 0x47, 0xe4, 0xa8, 0x3d, 0x55, 0x21, 0x34, 0xab, 0x1d, 0xae, 0xe0, 0xf4, 0xea, 0xdb, 0xc5, 0xb9, 0x58, 0xbf, 0xc4, 0x2a, 0x89, 0x31, 0x1a, 0xf4, 0x2d, 0xe1, 0xca, 0x37, 0x99, 0x47, 0x59}}}, +{{{0xc7, 0xca, 0x63, 0xc1, 0x49, 0xa9, 0x35, 0x45, 0x55, 0x7e, 0xda, 0x64, 0x32, 0x07, 0x50, 0xf7, 0x32, 0xac, 0xde, 0x75, 0x58, 0x9b, 0x11, 0xb2, 0x3a, 0x1f, 0xf5, 0xf7, 0x79, 0x04, 0xe6, 0x08}} , + {{0x46, 0xfa, 0x22, 0x4b, 0xfa, 0xe1, 0xfe, 0x96, 0xfc, 0x67, 0xba, 0x67, 0x97, 0xc4, 0xe7, 0x1b, 0x86, 0x90, 0x5f, 0xee, 0xf4, 0x5b, 0x11, 0xb2, 0xcd, 0xad, 0xee, 0xc2, 0x48, 0x6c, 0x2b, 0x1b}}}, +{{{0xe3, 0x39, 0x62, 0xb4, 0x4f, 0x31, 0x04, 0xc9, 0xda, 0xd5, 0x73, 0x51, 0x57, 0xc5, 0xb8, 0xf3, 0xa3, 0x43, 0x70, 0xe4, 0x61, 0x81, 0x84, 0xe2, 0xbb, 0xbf, 0x4f, 0x9e, 0xa4, 0x5e, 0x74, 0x06}} , + {{0x29, 0xac, 0xff, 0x27, 0xe0, 0x59, 0xbe, 0x39, 0x9c, 0x0d, 0x83, 0xd7, 0x10, 0x0b, 0x15, 0xb7, 0xe1, 0xc2, 0x2c, 0x30, 0x73, 0x80, 0x3a, 0x7d, 0x5d, 0xab, 0x58, 0x6b, 0xc1, 0xf0, 0xf4, 0x22}}}, +{{{0xfe, 0x7f, 0xfb, 0x35, 0x7d, 0xc6, 0x01, 0x23, 0x28, 0xc4, 0x02, 0xac, 0x1f, 0x42, 0xb4, 0x9d, 0xfc, 0x00, 0x94, 0xa5, 0xee, 0xca, 0xda, 0x97, 0x09, 0x41, 0x77, 0x87, 0x5d, 0x7b, 0x87, 0x78}} , + {{0xf5, 0xfb, 0x90, 0x2d, 0x81, 0x19, 0x9e, 0x2f, 0x6d, 0x85, 0x88, 0x8c, 0x40, 0x5c, 0x77, 0x41, 0x4d, 0x01, 0x19, 0x76, 0x60, 0xe8, 0x4c, 0x48, 0xe4, 0x33, 0x83, 0x32, 0x6c, 0xb4, 0x41, 0x03}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xff, 0x10, 0xc2, 0x09, 0x4f, 0x6e, 0xf4, 0xd2, 0xdf, 0x7e, 0xca, 0x7b, 0x1c, 0x1d, 0xba, 0xa3, 0xb6, 0xda, 0x67, 0x33, 0xd4, 0x87, 0x36, 0x4b, 0x11, 0x20, 0x05, 0xa6, 0x29, 0xc1, 0x87, 0x17}} , + {{0xf6, 0x96, 0xca, 0x2f, 0xda, 0x38, 0xa7, 0x1b, 0xfc, 0xca, 0x7d, 0xfe, 0x08, 0x89, 0xe2, 0x47, 0x2b, 0x6a, 0x5d, 0x4b, 0xfa, 0xa1, 0xb4, 0xde, 0xb6, 0xc2, 0x31, 0x51, 0xf5, 0xe0, 0xa4, 0x0b}}}, +{{{0x5c, 0xe5, 0xc6, 0x04, 0x8e, 0x2b, 0x57, 0xbe, 0x38, 0x85, 0x23, 0xcb, 0xb7, 0xbe, 0x4f, 0xa9, 0xd3, 0x6e, 0x12, 0xaa, 0xd5, 0xb2, 0x2e, 0x93, 0x29, 0x9a, 0x4a, 0x88, 0x18, 0x43, 0xf5, 0x01}} , + {{0x50, 0xfc, 0xdb, 0xa2, 0x59, 0x21, 0x8d, 0xbd, 0x7e, 0x33, 0xae, 0x2f, 0x87, 0x1a, 0xd0, 0x97, 0xc7, 0x0d, 0x4d, 0x63, 0x01, 0xef, 0x05, 0x84, 0xec, 0x40, 0xdd, 0xa8, 0x0a, 0x4f, 0x70, 0x0b}}}, +{{{0x41, 0x69, 0x01, 0x67, 0x5c, 0xd3, 0x8a, 0xc5, 0xcf, 0x3f, 0xd1, 0x57, 0xd1, 0x67, 0x3e, 0x01, 0x39, 0xb5, 0xcb, 0x81, 0x56, 0x96, 0x26, 0xb6, 0xc2, 0xe7, 0x5c, 0xfb, 0x63, 0x97, 0x58, 0x06}} , + {{0x0c, 0x0e, 0xf3, 0xba, 0xf0, 0xe5, 0xba, 0xb2, 0x57, 0x77, 0xc6, 0x20, 0x9b, 0x89, 0x24, 0xbe, 0xf2, 0x9c, 0x8a, 0xba, 0x69, 0xc1, 0xf1, 0xb0, 0x4f, 0x2a, 0x05, 0x9a, 0xee, 0x10, 0x7e, 0x36}}}, +{{{0x3f, 0x26, 0xe9, 0x40, 0xe9, 0x03, 0xad, 0x06, 0x69, 0x91, 0xe0, 0xd1, 0x89, 0x60, 0x84, 0x79, 0xde, 0x27, 0x6d, 0xe6, 0x76, 0xbd, 0xea, 0xe6, 0xae, 0x48, 0xc3, 0x67, 0xc0, 0x57, 0xcd, 0x2f}} , + {{0x7f, 0xc1, 0xdc, 0xb9, 0xc7, 0xbc, 0x86, 0x3d, 0x55, 0x4b, 0x28, 0x7a, 0xfb, 0x4d, 0xc7, 0xf8, 0xbc, 0x67, 0x2a, 0x60, 0x4d, 0x8f, 0x07, 0x0b, 0x1a, 0x17, 0xbf, 0xfa, 0xac, 0xa7, 0x3d, 0x1a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x91, 0x3f, 0xed, 0x5e, 0x18, 0x78, 0x3f, 0x23, 0x2c, 0x0d, 0x8c, 0x44, 0x00, 0xe8, 0xfb, 0xe9, 0x8e, 0xd6, 0xd1, 0x36, 0x58, 0x57, 0x9e, 0xae, 0x4b, 0x5c, 0x0b, 0x07, 0xbc, 0x6b, 0x55, 0x2b}} , + {{0x6f, 0x4d, 0x17, 0xd7, 0xe1, 0x84, 0xd9, 0x78, 0xb1, 0x90, 0xfd, 0x2e, 0xb3, 0xb5, 0x19, 0x3f, 0x1b, 0xfa, 0xc0, 0x68, 0xb3, 0xdd, 0x00, 0x2e, 0x89, 0xbd, 0x7e, 0x80, 0x32, 0x13, 0xa0, 0x7b}}}, +{{{0x1a, 0x6f, 0x40, 0xaf, 0x44, 0x44, 0xb0, 0x43, 0x8f, 0x0d, 0xd0, 0x1e, 0xc4, 0x0b, 0x19, 0x5d, 0x8e, 0xfe, 0xc1, 0xf3, 0xc5, 0x5c, 0x91, 0xf8, 0x04, 0x4e, 0xbe, 0x90, 0xb4, 0x47, 0x5c, 0x3f}} , + {{0xb0, 0x3b, 0x2c, 0xf3, 0xfe, 0x32, 0x71, 0x07, 0x3f, 0xaa, 0xba, 0x45, 0x60, 0xa8, 0x8d, 0xea, 0x54, 0xcb, 0x39, 0x10, 0xb4, 0xf2, 0x8b, 0xd2, 0x14, 0x82, 0x42, 0x07, 0x8e, 0xe9, 0x7c, 0x53}}}, +{{{0xb0, 0xae, 0xc1, 0x8d, 0xc9, 0x8f, 0xb9, 0x7a, 0x77, 0xef, 0xba, 0x79, 0xa0, 0x3c, 0xa8, 0xf5, 0x6a, 0xe2, 0x3f, 0x5d, 0x00, 0xe3, 0x4b, 0x45, 0x24, 0x7b, 0x43, 0x78, 0x55, 0x1d, 0x2b, 0x1e}} , + {{0x01, 0xb8, 0xd6, 0x16, 0x67, 0xa0, 0x15, 0xb9, 0xe1, 0x58, 0xa4, 0xa7, 0x31, 0x37, 0x77, 0x2f, 0x8b, 0x12, 0x9f, 0xf4, 0x3f, 0xc7, 0x36, 0x66, 0xd2, 0xa8, 0x56, 0xf7, 0x7f, 0x74, 0xc6, 0x41}}}, +{{{0x5d, 0xf8, 0xb4, 0xa8, 0x30, 0xdd, 0xcc, 0x38, 0xa5, 0xd3, 0xca, 0xd8, 0xd1, 0xf8, 0xb2, 0x31, 0x91, 0xd4, 0x72, 0x05, 0x57, 0x4a, 0x3b, 0x82, 0x4a, 0xc6, 0x68, 0x20, 0xe2, 0x18, 0x41, 0x61}} , + {{0x19, 0xd4, 0x8d, 0x47, 0x29, 0x12, 0x65, 0xb0, 0x11, 0x78, 0x47, 0xb5, 0xcb, 0xa3, 0xa5, 0xfa, 0x05, 0x85, 0x54, 0xa9, 0x33, 0x97, 0x8d, 0x2b, 0xc2, 0xfe, 0x99, 0x35, 0x28, 0xe5, 0xeb, 0x63}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xb1, 0x3f, 0x3f, 0xef, 0xd8, 0xf4, 0xfc, 0xb3, 0xa0, 0x60, 0x50, 0x06, 0x2b, 0x29, 0x52, 0x70, 0x15, 0x0b, 0x24, 0x24, 0xf8, 0x5f, 0x79, 0x18, 0xcc, 0xff, 0x89, 0x99, 0x84, 0xa1, 0xae, 0x13}} , + {{0x44, 0x1f, 0xb8, 0xc2, 0x01, 0xc1, 0x30, 0x19, 0x55, 0x05, 0x60, 0x10, 0xa4, 0x6c, 0x2d, 0x67, 0x70, 0xe5, 0x25, 0x1b, 0xf2, 0xbf, 0xdd, 0xfb, 0x70, 0x2b, 0xa1, 0x8c, 0x9c, 0x94, 0x84, 0x08}}}, +{{{0xe7, 0xc4, 0x43, 0x4d, 0xc9, 0x2b, 0x69, 0x5d, 0x1d, 0x3c, 0xaf, 0xbb, 0x43, 0x38, 0x4e, 0x98, 0x3d, 0xed, 0x0d, 0x21, 0x03, 0xfd, 0xf0, 0x99, 0x47, 0x04, 0xb0, 0x98, 0x69, 0x55, 0x72, 0x0f}} , + {{0x5e, 0xdf, 0x15, 0x53, 0x3b, 0x86, 0x80, 0xb0, 0xf1, 0x70, 0x68, 0x8f, 0x66, 0x7c, 0x0e, 0x49, 0x1a, 0xd8, 0x6b, 0xfe, 0x4e, 0xef, 0xca, 0x47, 0xd4, 0x03, 0xc1, 0x37, 0x50, 0x9c, 0xc1, 0x16}}}, +{{{0xcd, 0x24, 0xc6, 0x3e, 0x0c, 0x82, 0x9b, 0x91, 0x2b, 0x61, 0x4a, 0xb2, 0x0f, 0x88, 0x55, 0x5f, 0x5a, 0x57, 0xff, 0xe5, 0x74, 0x0b, 0x13, 0x43, 0x00, 0xd8, 0x6b, 0xcf, 0xd2, 0x15, 0x03, 0x2c}} , + {{0xdc, 0xff, 0x15, 0x61, 0x2f, 0x4a, 0x2f, 0x62, 0xf2, 0x04, 0x2f, 0xb5, 0x0c, 0xb7, 0x1e, 0x3f, 0x74, 0x1a, 0x0f, 0xd7, 0xea, 0xcd, 0xd9, 0x7d, 0xf6, 0x12, 0x0e, 0x2f, 0xdb, 0x5a, 0x3b, 0x16}}}, +{{{0x1b, 0x37, 0x47, 0xe3, 0xf5, 0x9e, 0xea, 0x2c, 0x2a, 0xe7, 0x82, 0x36, 0xf4, 0x1f, 0x81, 0x47, 0x92, 0x4b, 0x69, 0x0e, 0x11, 0x8c, 0x5d, 0x53, 0x5b, 0x81, 0x27, 0x08, 0xbc, 0xa0, 0xae, 0x25}} , + {{0x69, 0x32, 0xa1, 0x05, 0x11, 0x42, 0x00, 0xd2, 0x59, 0xac, 0x4d, 0x62, 0x8b, 0x13, 0xe2, 0x50, 0x5d, 0xa0, 0x9d, 0x9b, 0xfd, 0xbb, 0x12, 0x41, 0x75, 0x41, 0x9e, 0xcc, 0xdc, 0xc7, 0xdc, 0x5d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd9, 0xe3, 0x38, 0x06, 0x46, 0x70, 0x82, 0x5e, 0x28, 0x49, 0x79, 0xff, 0x25, 0xd2, 0x4e, 0x29, 0x8d, 0x06, 0xb0, 0x23, 0xae, 0x9b, 0x66, 0xe4, 0x7d, 0xc0, 0x70, 0x91, 0xa3, 0xfc, 0xec, 0x4e}} , + {{0x62, 0x12, 0x37, 0x6a, 0x30, 0xf6, 0x1e, 0xfb, 0x14, 0x5c, 0x0d, 0x0e, 0xb7, 0x81, 0x6a, 0xe7, 0x08, 0x05, 0xac, 0xaa, 0x38, 0x46, 0xe2, 0x73, 0xea, 0x4b, 0x07, 0x81, 0x43, 0x7c, 0x9e, 0x5e}}}, +{{{0xfc, 0xf9, 0x21, 0x4f, 0x2e, 0x76, 0x9b, 0x1f, 0x28, 0x60, 0x77, 0x43, 0x32, 0x9d, 0xbe, 0x17, 0x30, 0x2a, 0xc6, 0x18, 0x92, 0x66, 0x62, 0x30, 0x98, 0x40, 0x11, 0xa6, 0x7f, 0x18, 0x84, 0x28}} , + {{0x3f, 0xab, 0xd3, 0xf4, 0x8a, 0x76, 0xa1, 0x3c, 0xca, 0x2d, 0x49, 0xc3, 0xea, 0x08, 0x0b, 0x85, 0x17, 0x2a, 0xc3, 0x6c, 0x08, 0xfd, 0x57, 0x9f, 0x3d, 0x5f, 0xdf, 0x67, 0x68, 0x42, 0x00, 0x32}}}, +{{{0x51, 0x60, 0x1b, 0x06, 0x4f, 0x8a, 0x21, 0xba, 0x38, 0xa8, 0xba, 0xd6, 0x40, 0xf6, 0xe9, 0x9b, 0x76, 0x4d, 0x56, 0x21, 0x5b, 0x0a, 0x9b, 0x2e, 0x4f, 0x3d, 0x81, 0x32, 0x08, 0x9f, 0x97, 0x5b}} , + {{0xe5, 0x44, 0xec, 0x06, 0x9d, 0x90, 0x79, 0x9f, 0xd3, 0xe0, 0x79, 0xaf, 0x8f, 0x10, 0xfd, 0xdd, 0x04, 0xae, 0x27, 0x97, 0x46, 0x33, 0x79, 0xea, 0xb8, 0x4e, 0xca, 0x5a, 0x59, 0x57, 0xe1, 0x0e}}}, +{{{0x1a, 0xda, 0xf3, 0xa5, 0x41, 0x43, 0x28, 0xfc, 0x7e, 0xe7, 0x71, 0xea, 0xc6, 0x3b, 0x59, 0xcc, 0x2e, 0xd3, 0x40, 0xec, 0xb3, 0x13, 0x6f, 0x44, 0xcd, 0x13, 0xb2, 0x37, 0xf2, 0x6e, 0xd9, 0x1c}} , + {{0xe3, 0xdb, 0x60, 0xcd, 0x5c, 0x4a, 0x18, 0x0f, 0xef, 0x73, 0x36, 0x71, 0x8c, 0xf6, 0x11, 0xb4, 0xd8, 0xce, 0x17, 0x5e, 0x4f, 0x26, 0x77, 0x97, 0x5f, 0xcb, 0xef, 0x91, 0xeb, 0x6a, 0x62, 0x7a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x18, 0x4a, 0xa2, 0x97, 0x08, 0x81, 0x2d, 0x83, 0xc4, 0xcc, 0xf0, 0x83, 0x7e, 0xec, 0x0d, 0x95, 0x4c, 0x5b, 0xfb, 0xfa, 0x98, 0x80, 0x4a, 0x66, 0x56, 0x0c, 0x51, 0xb3, 0xf2, 0x04, 0x5d, 0x27}} , + {{0x3b, 0xb9, 0xb8, 0x06, 0x5a, 0x2e, 0xfe, 0xc3, 0x82, 0x37, 0x9c, 0xa3, 0x11, 0x1f, 0x9c, 0xa6, 0xda, 0x63, 0x48, 0x9b, 0xad, 0xde, 0x2d, 0xa6, 0xbc, 0x6e, 0x32, 0xda, 0x27, 0x65, 0xdd, 0x57}}}, +{{{0x84, 0x4f, 0x37, 0x31, 0x7d, 0x2e, 0xbc, 0xad, 0x87, 0x07, 0x2a, 0x6b, 0x37, 0xfc, 0x5f, 0xeb, 0x4e, 0x75, 0x35, 0xa6, 0xde, 0xab, 0x0a, 0x19, 0x3a, 0xb7, 0xb1, 0xef, 0x92, 0x6a, 0x3b, 0x3c}} , + {{0x3b, 0xb2, 0x94, 0x6d, 0x39, 0x60, 0xac, 0xee, 0xe7, 0x81, 0x1a, 0x3b, 0x76, 0x87, 0x5c, 0x05, 0x94, 0x2a, 0x45, 0xb9, 0x80, 0xe9, 0x22, 0xb1, 0x07, 0xcb, 0x40, 0x9e, 0x70, 0x49, 0x6d, 0x12}}}, +{{{0xfd, 0x18, 0x78, 0x84, 0xa8, 0x4c, 0x7d, 0x6e, 0x59, 0xa6, 0xe5, 0x74, 0xf1, 0x19, 0xa6, 0x84, 0x2e, 0x51, 0xc1, 0x29, 0x13, 0xf2, 0x14, 0x6b, 0x5d, 0x53, 0x51, 0xf7, 0xef, 0xbf, 0x01, 0x22}} , + {{0xa4, 0x4b, 0x62, 0x4c, 0xe6, 0xfd, 0x72, 0x07, 0xf2, 0x81, 0xfc, 0xf2, 0xbd, 0x12, 0x7c, 0x68, 0x76, 0x2a, 0xba, 0xf5, 0x65, 0xb1, 0x1f, 0x17, 0x0a, 0x38, 0xb0, 0xbf, 0xc0, 0xf8, 0xf4, 0x2a}}}, +{{{0x55, 0x60, 0x55, 0x5b, 0xe4, 0x1d, 0x71, 0x4c, 0x9d, 0x5b, 0x9f, 0x70, 0xa6, 0x85, 0x9a, 0x2c, 0xa0, 0xe2, 0x32, 0x48, 0xce, 0x9e, 0x2a, 0xa5, 0x07, 0x3b, 0xc7, 0x6c, 0x86, 0x77, 0xde, 0x3c}} , + {{0xf7, 0x18, 0x7a, 0x96, 0x7e, 0x43, 0x57, 0xa9, 0x55, 0xfc, 0x4e, 0xb6, 0x72, 0x00, 0xf2, 0xe4, 0xd7, 0x52, 0xd3, 0xd3, 0xb6, 0x85, 0xf6, 0x71, 0xc7, 0x44, 0x3f, 0x7f, 0xd7, 0xb3, 0xf2, 0x79}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x46, 0xca, 0xa7, 0x55, 0x7b, 0x79, 0xf3, 0xca, 0x5a, 0x65, 0xf6, 0xed, 0x50, 0x14, 0x7b, 0xe4, 0xc4, 0x2a, 0x65, 0x9e, 0xe2, 0xf9, 0xca, 0xa7, 0x22, 0x26, 0x53, 0xcb, 0x21, 0x5b, 0xa7, 0x31}} , + {{0x90, 0xd7, 0xc5, 0x26, 0x08, 0xbd, 0xb0, 0x53, 0x63, 0x58, 0xc3, 0x31, 0x5e, 0x75, 0x46, 0x15, 0x91, 0xa6, 0xf8, 0x2f, 0x1a, 0x08, 0x65, 0x88, 0x2f, 0x98, 0x04, 0xf1, 0x7c, 0x6e, 0x00, 0x77}}}, +{{{0x81, 0x21, 0x61, 0x09, 0xf6, 0x4e, 0xf1, 0x92, 0xee, 0x63, 0x61, 0x73, 0x87, 0xc7, 0x54, 0x0e, 0x42, 0x4b, 0xc9, 0x47, 0xd1, 0xb8, 0x7e, 0x91, 0x75, 0x37, 0x99, 0x28, 0xb8, 0xdd, 0x7f, 0x50}} , + {{0x89, 0x8f, 0xc0, 0xbe, 0x5d, 0xd6, 0x9f, 0xa0, 0xf0, 0x9d, 0x81, 0xce, 0x3a, 0x7b, 0x98, 0x58, 0xbb, 0xd7, 0x78, 0xc8, 0x3f, 0x13, 0xf1, 0x74, 0x19, 0xdf, 0xf8, 0x98, 0x89, 0x5d, 0xfa, 0x5f}}}, +{{{0x9e, 0x35, 0x85, 0x94, 0x47, 0x1f, 0x90, 0x15, 0x26, 0xd0, 0x84, 0xed, 0x8a, 0x80, 0xf7, 0x63, 0x42, 0x86, 0x27, 0xd7, 0xf4, 0x75, 0x58, 0xdc, 0x9c, 0xc0, 0x22, 0x7e, 0x20, 0x35, 0xfd, 0x1f}} , + {{0x68, 0x0e, 0x6f, 0x97, 0xba, 0x70, 0xbb, 0xa3, 0x0e, 0xe5, 0x0b, 0x12, 0xf4, 0xa2, 0xdc, 0x47, 0xf8, 0xe6, 0xd0, 0x23, 0x6c, 0x33, 0xa8, 0x99, 0x46, 0x6e, 0x0f, 0x44, 0xba, 0x76, 0x48, 0x0f}}}, +{{{0xa3, 0x2a, 0x61, 0x37, 0xe2, 0x59, 0x12, 0x0e, 0x27, 0xba, 0x64, 0x43, 0xae, 0xc0, 0x42, 0x69, 0x79, 0xa4, 0x1e, 0x29, 0x8b, 0x15, 0xeb, 0xf8, 0xaf, 0xd4, 0xa2, 0x68, 0x33, 0xb5, 0x7a, 0x24}} , + {{0x2c, 0x19, 0x33, 0xdd, 0x1b, 0xab, 0xec, 0x01, 0xb0, 0x23, 0xf8, 0x42, 0x2b, 0x06, 0x88, 0xea, 0x3d, 0x2d, 0x00, 0x2a, 0x78, 0x45, 0x4d, 0x38, 0xed, 0x2e, 0x2e, 0x44, 0x49, 0xed, 0xcb, 0x33}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xa0, 0x68, 0xe8, 0x41, 0x8f, 0x91, 0xf8, 0x11, 0x13, 0x90, 0x2e, 0xa7, 0xab, 0x30, 0xef, 0xad, 0xa0, 0x61, 0x00, 0x88, 0xef, 0xdb, 0xce, 0x5b, 0x5c, 0xbb, 0x62, 0xc8, 0x56, 0xf9, 0x00, 0x73}} , + {{0x3f, 0x60, 0xc1, 0x82, 0x2d, 0xa3, 0x28, 0x58, 0x24, 0x9e, 0x9f, 0xe3, 0x70, 0xcc, 0x09, 0x4e, 0x1a, 0x3f, 0x11, 0x11, 0x15, 0x07, 0x3c, 0xa4, 0x41, 0xe0, 0x65, 0xa3, 0x0a, 0x41, 0x6d, 0x11}}}, +{{{0x31, 0x40, 0x01, 0x52, 0x56, 0x94, 0x5b, 0x28, 0x8a, 0xaa, 0x52, 0xee, 0xd8, 0x0a, 0x05, 0x8d, 0xcd, 0xb5, 0xaa, 0x2e, 0x38, 0xaa, 0xb7, 0x87, 0xf7, 0x2b, 0xfb, 0x04, 0xcb, 0x84, 0x3d, 0x54}} , + {{0x20, 0xef, 0x59, 0xde, 0xa4, 0x2b, 0x93, 0x6e, 0x2e, 0xec, 0x42, 0x9a, 0xd4, 0x2d, 0xf4, 0x46, 0x58, 0x27, 0x2b, 0x18, 0x8f, 0x83, 0x3d, 0x69, 0x9e, 0xd4, 0x3e, 0xb6, 0xc5, 0xfd, 0x58, 0x03}}}, +{{{0x33, 0x89, 0xc9, 0x63, 0x62, 0x1c, 0x17, 0xb4, 0x60, 0xc4, 0x26, 0x68, 0x09, 0xc3, 0x2e, 0x37, 0x0f, 0x7b, 0xb4, 0x9c, 0xb6, 0xf9, 0xfb, 0xd4, 0x51, 0x78, 0xc8, 0x63, 0xea, 0x77, 0x47, 0x07}} , + {{0x32, 0xb4, 0x18, 0x47, 0x79, 0xcb, 0xd4, 0x5a, 0x07, 0x14, 0x0f, 0xa0, 0xd5, 0xac, 0xd0, 0x41, 0x40, 0xab, 0x61, 0x23, 0xe5, 0x2a, 0x2a, 0x6f, 0xf7, 0xa8, 0xd4, 0x76, 0xef, 0xe7, 0x45, 0x6c}}}, +{{{0xa1, 0x5e, 0x60, 0x4f, 0xfb, 0xe1, 0x70, 0x6a, 0x1f, 0x55, 0x4f, 0x09, 0xb4, 0x95, 0x33, 0x36, 0xc6, 0x81, 0x01, 0x18, 0x06, 0x25, 0x27, 0xa4, 0xb4, 0x24, 0xa4, 0x86, 0x03, 0x4c, 0xac, 0x02}} , + {{0x77, 0x38, 0xde, 0xd7, 0x60, 0x48, 0x07, 0xf0, 0x74, 0xa8, 0xff, 0x54, 0xe5, 0x30, 0x43, 0xff, 0x77, 0xfb, 0x21, 0x07, 0xff, 0xb2, 0x07, 0x6b, 0xe4, 0xe5, 0x30, 0xfc, 0x19, 0x6c, 0xa3, 0x01}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x13, 0xc5, 0x2c, 0xac, 0xd3, 0x83, 0x82, 0x7c, 0x29, 0xf7, 0x05, 0xa5, 0x00, 0xb6, 0x1f, 0x86, 0x55, 0xf4, 0xd6, 0x2f, 0x0c, 0x99, 0xd0, 0x65, 0x9b, 0x6b, 0x46, 0x0d, 0x43, 0xf8, 0x16, 0x28}} , + {{0x1e, 0x7f, 0xb4, 0x74, 0x7e, 0xb1, 0x89, 0x4f, 0x18, 0x5a, 0xab, 0x64, 0x06, 0xdf, 0x45, 0x87, 0xe0, 0x6a, 0xc6, 0xf0, 0x0e, 0xc9, 0x24, 0x35, 0x38, 0xea, 0x30, 0x54, 0xb4, 0xc4, 0x52, 0x54}}}, +{{{0xe9, 0x9f, 0xdc, 0x3f, 0xc1, 0x89, 0x44, 0x74, 0x27, 0xe4, 0xc1, 0x90, 0xff, 0x4a, 0xa7, 0x3c, 0xee, 0xcd, 0xf4, 0x1d, 0x25, 0x94, 0x7f, 0x63, 0x16, 0x48, 0xbc, 0x64, 0xfe, 0x95, 0xc4, 0x0c}} , + {{0x8b, 0x19, 0x75, 0x6e, 0x03, 0x06, 0x5e, 0x6a, 0x6f, 0x1a, 0x8c, 0xe3, 0xd3, 0x28, 0xf2, 0xe0, 0xb9, 0x7a, 0x43, 0x69, 0xe6, 0xd3, 0xc0, 0xfe, 0x7e, 0x97, 0xab, 0x6c, 0x7b, 0x8e, 0x13, 0x42}}}, +{{{0xd4, 0xca, 0x70, 0x3d, 0xab, 0xfb, 0x5f, 0x5e, 0x00, 0x0c, 0xcc, 0x77, 0x22, 0xf8, 0x78, 0x55, 0xae, 0x62, 0x35, 0xfb, 0x9a, 0xc6, 0x03, 0xe4, 0x0c, 0xee, 0xab, 0xc7, 0xc0, 0x89, 0x87, 0x54}} , + {{0x32, 0xad, 0xae, 0x85, 0x58, 0x43, 0xb8, 0xb1, 0xe6, 0x3e, 0x00, 0x9c, 0x78, 0x88, 0x56, 0xdb, 0x9c, 0xfc, 0x79, 0xf6, 0xf9, 0x41, 0x5f, 0xb7, 0xbc, 0x11, 0xf9, 0x20, 0x36, 0x1c, 0x53, 0x2b}}}, +{{{0x5a, 0x20, 0x5b, 0xa1, 0xa5, 0x44, 0x91, 0x24, 0x02, 0x63, 0x12, 0x64, 0xb8, 0x55, 0xf6, 0xde, 0x2c, 0xdb, 0x47, 0xb8, 0xc6, 0x0a, 0xc3, 0x00, 0x78, 0x93, 0xd8, 0xf5, 0xf5, 0x18, 0x28, 0x0a}} , + {{0xd6, 0x1b, 0x9a, 0x6c, 0xe5, 0x46, 0xea, 0x70, 0x96, 0x8d, 0x4e, 0x2a, 0x52, 0x21, 0x26, 0x4b, 0xb1, 0xbb, 0x0f, 0x7c, 0xa9, 0x9b, 0x04, 0xbb, 0x51, 0x08, 0xf1, 0x9a, 0xa4, 0x76, 0x7c, 0x18}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xfa, 0x94, 0xf7, 0x40, 0xd0, 0xd7, 0xeb, 0xa9, 0x82, 0x36, 0xd5, 0x15, 0xb9, 0x33, 0x7a, 0xbf, 0x8a, 0xf2, 0x63, 0xaa, 0x37, 0xf5, 0x59, 0xac, 0xbd, 0xbb, 0x32, 0x36, 0xbe, 0x73, 0x99, 0x38}} , + {{0x2c, 0xb3, 0xda, 0x7a, 0xd8, 0x3d, 0x99, 0xca, 0xd2, 0xf4, 0xda, 0x99, 0x8e, 0x4f, 0x98, 0xb7, 0xf4, 0xae, 0x3e, 0x9f, 0x8e, 0x35, 0x60, 0xa4, 0x33, 0x75, 0xa4, 0x04, 0x93, 0xb1, 0x6b, 0x4d}}}, +{{{0x97, 0x9d, 0xa8, 0xcd, 0x97, 0x7b, 0x9d, 0xb9, 0xe7, 0xa5, 0xef, 0xfd, 0xa8, 0x42, 0x6b, 0xc3, 0x62, 0x64, 0x7d, 0xa5, 0x1b, 0xc9, 0x9e, 0xd2, 0x45, 0xb9, 0xee, 0x03, 0xb0, 0xbf, 0xc0, 0x68}} , + {{0xed, 0xb7, 0x84, 0x2c, 0xf6, 0xd3, 0xa1, 0x6b, 0x24, 0x6d, 0x87, 0x56, 0x97, 0x59, 0x79, 0x62, 0x9f, 0xac, 0xed, 0xf3, 0xc9, 0x89, 0x21, 0x2e, 0x04, 0xb3, 0xcc, 0x2f, 0xbe, 0xd6, 0x0a, 0x4b}}}, +{{{0x39, 0x61, 0x05, 0xed, 0x25, 0x89, 0x8b, 0x5d, 0x1b, 0xcb, 0x0c, 0x55, 0xf4, 0x6a, 0x00, 0x8a, 0x46, 0xe8, 0x1e, 0xc6, 0x83, 0xc8, 0x5a, 0x76, 0xdb, 0xcc, 0x19, 0x7a, 0xcc, 0x67, 0x46, 0x0b}} , + {{0x53, 0xcf, 0xc2, 0xa1, 0xad, 0x6a, 0xf3, 0xcd, 0x8f, 0xc9, 0xde, 0x1c, 0xf8, 0x6c, 0x8f, 0xf8, 0x76, 0x42, 0xe7, 0xfe, 0xb2, 0x72, 0x21, 0x0a, 0x66, 0x74, 0x8f, 0xb7, 0xeb, 0xe4, 0x6f, 0x01}}}, +{{{0x22, 0x8c, 0x6b, 0xbe, 0xfc, 0x4d, 0x70, 0x62, 0x6e, 0x52, 0x77, 0x99, 0x88, 0x7e, 0x7b, 0x57, 0x7a, 0x0d, 0xfe, 0xdc, 0x72, 0x92, 0xf1, 0x68, 0x1d, 0x97, 0xd7, 0x7c, 0x8d, 0x53, 0x10, 0x37}} , + {{0x53, 0x88, 0x77, 0x02, 0xca, 0x27, 0xa8, 0xe5, 0x45, 0xe2, 0xa8, 0x48, 0x2a, 0xab, 0x18, 0xca, 0xea, 0x2d, 0x2a, 0x54, 0x17, 0x37, 0x32, 0x09, 0xdc, 0xe0, 0x4a, 0xb7, 0x7d, 0x82, 0x10, 0x7d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x8a, 0x64, 0x1e, 0x14, 0x0a, 0x57, 0xd4, 0xda, 0x5c, 0x96, 0x9b, 0x01, 0x4c, 0x67, 0xbf, 0x8b, 0x30, 0xfe, 0x08, 0xdb, 0x0d, 0xd5, 0xa8, 0xd7, 0x09, 0x11, 0x85, 0xa2, 0xd3, 0x45, 0xfb, 0x7e}} , + {{0xda, 0x8c, 0xc2, 0xd0, 0xac, 0x18, 0xe8, 0x52, 0x36, 0xd4, 0x21, 0xa3, 0xdd, 0x57, 0x22, 0x79, 0xb7, 0xf8, 0x71, 0x9d, 0xc6, 0x91, 0x70, 0x86, 0x56, 0xbf, 0xa1, 0x11, 0x8b, 0x19, 0xe1, 0x0f}}}, +{{{0x18, 0x32, 0x98, 0x2c, 0x8f, 0x91, 0xae, 0x12, 0xf0, 0x8c, 0xea, 0xf3, 0x3c, 0xb9, 0x5d, 0xe4, 0x69, 0xed, 0xb2, 0x47, 0x18, 0xbd, 0xce, 0x16, 0x52, 0x5c, 0x23, 0xe2, 0xa5, 0x25, 0x52, 0x5d}} , + {{0xb9, 0xb1, 0xe7, 0x5d, 0x4e, 0xbc, 0xee, 0xbb, 0x40, 0x81, 0x77, 0x82, 0x19, 0xab, 0xb5, 0xc6, 0xee, 0xab, 0x5b, 0x6b, 0x63, 0x92, 0x8a, 0x34, 0x8d, 0xcd, 0xee, 0x4f, 0x49, 0xe5, 0xc9, 0x7e}}}, +{{{0x21, 0xac, 0x8b, 0x22, 0xcd, 0xc3, 0x9a, 0xe9, 0x5e, 0x78, 0xbd, 0xde, 0xba, 0xad, 0xab, 0xbf, 0x75, 0x41, 0x09, 0xc5, 0x58, 0xa4, 0x7d, 0x92, 0xb0, 0x7f, 0xf2, 0xa1, 0xd1, 0xc0, 0xb3, 0x6d}} , + {{0x62, 0x4f, 0xd0, 0x75, 0x77, 0xba, 0x76, 0x77, 0xd7, 0xb8, 0xd8, 0x92, 0x6f, 0x98, 0x34, 0x3d, 0xd6, 0x4e, 0x1c, 0x0f, 0xf0, 0x8f, 0x2e, 0xf1, 0xb3, 0xbd, 0xb1, 0xb9, 0xec, 0x99, 0xb4, 0x07}}}, +{{{0x60, 0x57, 0x2e, 0x9a, 0x72, 0x1d, 0x6b, 0x6e, 0x58, 0x33, 0x24, 0x8c, 0x48, 0x39, 0x46, 0x8e, 0x89, 0x6a, 0x88, 0x51, 0x23, 0x62, 0xb5, 0x32, 0x09, 0x36, 0xe3, 0x57, 0xf5, 0x98, 0xde, 0x6f}} , + {{0x8b, 0x2c, 0x00, 0x48, 0x4a, 0xf9, 0x5b, 0x87, 0x69, 0x52, 0xe5, 0x5b, 0xd1, 0xb1, 0xe5, 0x25, 0x25, 0xe0, 0x9c, 0xc2, 0x13, 0x44, 0xe8, 0xb9, 0x0a, 0x70, 0xad, 0xbd, 0x0f, 0x51, 0x94, 0x69}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xa2, 0xdc, 0xab, 0xa9, 0x25, 0x2d, 0xac, 0x5f, 0x03, 0x33, 0x08, 0xe7, 0x7e, 0xfe, 0x95, 0x36, 0x3c, 0x5b, 0x3a, 0xd3, 0x05, 0x82, 0x1c, 0x95, 0x2d, 0xd8, 0x77, 0x7e, 0x02, 0xd9, 0x5b, 0x70}} , + {{0xc2, 0xfe, 0x1b, 0x0c, 0x67, 0xcd, 0xd6, 0xe0, 0x51, 0x8e, 0x2c, 0xe0, 0x79, 0x88, 0xf0, 0xcf, 0x41, 0x4a, 0xad, 0x23, 0xd4, 0x46, 0xca, 0x94, 0xa1, 0xc3, 0xeb, 0x28, 0x06, 0xfa, 0x17, 0x14}}}, +{{{0x7b, 0xaa, 0x70, 0x0a, 0x4b, 0xfb, 0xf5, 0xbf, 0x80, 0xc5, 0xcf, 0x08, 0x7a, 0xdd, 0xa1, 0xf4, 0x9d, 0x54, 0x50, 0x53, 0x23, 0x77, 0x23, 0xf5, 0x34, 0xa5, 0x22, 0xd1, 0x0d, 0x96, 0x2e, 0x47}} , + {{0xcc, 0xb7, 0x32, 0x89, 0x57, 0xd0, 0x98, 0x75, 0xe4, 0x37, 0x99, 0xa9, 0xe8, 0xba, 0xed, 0xba, 0xeb, 0xc7, 0x4f, 0x15, 0x76, 0x07, 0x0c, 0x4c, 0xef, 0x9f, 0x52, 0xfc, 0x04, 0x5d, 0x58, 0x10}}}, +{{{0xce, 0x82, 0xf0, 0x8f, 0x79, 0x02, 0xa8, 0xd1, 0xda, 0x14, 0x09, 0x48, 0xee, 0x8a, 0x40, 0x98, 0x76, 0x60, 0x54, 0x5a, 0xde, 0x03, 0x24, 0xf5, 0xe6, 0x2f, 0xe1, 0x03, 0xbf, 0x68, 0x82, 0x7f}} , + {{0x64, 0xe9, 0x28, 0xc7, 0xa4, 0xcf, 0x2a, 0xf9, 0x90, 0x64, 0x72, 0x2c, 0x8b, 0xeb, 0xec, 0xa0, 0xf2, 0x7d, 0x35, 0xb5, 0x90, 0x4d, 0x7f, 0x5b, 0x4a, 0x49, 0xe4, 0xb8, 0x3b, 0xc8, 0xa1, 0x2f}}}, +{{{0x8b, 0xc5, 0xcc, 0x3d, 0x69, 0xa6, 0xa1, 0x18, 0x44, 0xbc, 0x4d, 0x77, 0x37, 0xc7, 0x86, 0xec, 0x0c, 0xc9, 0xd6, 0x44, 0xa9, 0x23, 0x27, 0xb9, 0x03, 0x34, 0xa7, 0x0a, 0xd5, 0xc7, 0x34, 0x37}} , + {{0xf9, 0x7e, 0x3e, 0x66, 0xee, 0xf9, 0x99, 0x28, 0xff, 0xad, 0x11, 0xd8, 0xe2, 0x66, 0xc5, 0xcd, 0x0f, 0x0d, 0x0b, 0x6a, 0xfc, 0x7c, 0x24, 0xa8, 0x4f, 0xa8, 0x5e, 0x80, 0x45, 0x8b, 0x6c, 0x41}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xef, 0x1e, 0xec, 0xf7, 0x8d, 0x77, 0xf2, 0xea, 0xdb, 0x60, 0x03, 0x21, 0xc0, 0xff, 0x5e, 0x67, 0xc3, 0x71, 0x0b, 0x21, 0xb4, 0x41, 0xa0, 0x68, 0x38, 0xc6, 0x01, 0xa3, 0xd3, 0x51, 0x3c, 0x3c}} , + {{0x92, 0xf8, 0xd6, 0x4b, 0xef, 0x42, 0x13, 0xb2, 0x4a, 0xc4, 0x2e, 0x72, 0x3f, 0xc9, 0x11, 0xbd, 0x74, 0x02, 0x0e, 0xf5, 0x13, 0x9d, 0x83, 0x1a, 0x1b, 0xd5, 0x54, 0xde, 0xc4, 0x1e, 0x16, 0x6c}}}, +{{{0x27, 0x52, 0xe4, 0x63, 0xaa, 0x94, 0xe6, 0xc3, 0x28, 0x9c, 0xc6, 0x56, 0xac, 0xfa, 0xb6, 0xbd, 0xe2, 0xcc, 0x76, 0xc6, 0x27, 0x27, 0xa2, 0x8e, 0x78, 0x2b, 0x84, 0x72, 0x10, 0xbd, 0x4e, 0x2a}} , + {{0xea, 0xa7, 0x23, 0xef, 0x04, 0x61, 0x80, 0x50, 0xc9, 0x6e, 0xa5, 0x96, 0xd1, 0xd1, 0xc8, 0xc3, 0x18, 0xd7, 0x2d, 0xfd, 0x26, 0xbd, 0xcb, 0x7b, 0x92, 0x51, 0x0e, 0x4a, 0x65, 0x57, 0xb8, 0x49}}}, +{{{0xab, 0x55, 0x36, 0xc3, 0xec, 0x63, 0x55, 0x11, 0x55, 0xf6, 0xa5, 0xc7, 0x01, 0x5f, 0xfe, 0x79, 0xd8, 0x0a, 0xf7, 0x03, 0xd8, 0x98, 0x99, 0xf5, 0xd0, 0x00, 0x54, 0x6b, 0x66, 0x28, 0xf5, 0x25}} , + {{0x7a, 0x8d, 0xa1, 0x5d, 0x70, 0x5d, 0x51, 0x27, 0xee, 0x30, 0x65, 0x56, 0x95, 0x46, 0xde, 0xbd, 0x03, 0x75, 0xb4, 0x57, 0x59, 0x89, 0xeb, 0x02, 0x9e, 0xcc, 0x89, 0x19, 0xa7, 0xcb, 0x17, 0x67}}}, +{{{0x6a, 0xeb, 0xfc, 0x9a, 0x9a, 0x10, 0xce, 0xdb, 0x3a, 0x1c, 0x3c, 0x6a, 0x9d, 0xea, 0x46, 0xbc, 0x45, 0x49, 0xac, 0xe3, 0x41, 0x12, 0x7c, 0xf0, 0xf7, 0x4f, 0xf9, 0xf7, 0xff, 0x2c, 0x89, 0x04}} , + {{0x30, 0x31, 0x54, 0x1a, 0x46, 0xca, 0xe6, 0xc6, 0xcb, 0xe2, 0xc3, 0xc1, 0x8b, 0x75, 0x81, 0xbe, 0xee, 0xf8, 0xa3, 0x11, 0x1c, 0x25, 0xa3, 0xa7, 0x35, 0x51, 0x55, 0xe2, 0x25, 0xaa, 0xe2, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xb4, 0x48, 0x10, 0x9f, 0x8a, 0x09, 0x76, 0xfa, 0xf0, 0x7a, 0xb0, 0x70, 0xf7, 0x83, 0x80, 0x52, 0x84, 0x2b, 0x26, 0xa2, 0xc4, 0x5d, 0x4f, 0xba, 0xb1, 0xc8, 0x40, 0x0d, 0x78, 0x97, 0xc4, 0x60}} , + {{0xd4, 0xb1, 0x6c, 0x08, 0xc7, 0x40, 0x38, 0x73, 0x5f, 0x0b, 0xf3, 0x76, 0x5d, 0xb2, 0xa5, 0x2f, 0x57, 0x57, 0x07, 0xed, 0x08, 0xa2, 0x6c, 0x4f, 0x08, 0x02, 0xb5, 0x0e, 0xee, 0x44, 0xfa, 0x22}}}, +{{{0x0f, 0x00, 0x3f, 0xa6, 0x04, 0x19, 0x56, 0x65, 0x31, 0x7f, 0x8b, 0xeb, 0x0d, 0xe1, 0x47, 0x89, 0x97, 0x16, 0x53, 0xfa, 0x81, 0xa7, 0xaa, 0xb2, 0xbf, 0x67, 0xeb, 0x72, 0x60, 0x81, 0x0d, 0x48}} , + {{0x7e, 0x13, 0x33, 0xcd, 0xa8, 0x84, 0x56, 0x1e, 0x67, 0xaf, 0x6b, 0x43, 0xac, 0x17, 0xaf, 0x16, 0xc0, 0x52, 0x99, 0x49, 0x5b, 0x87, 0x73, 0x7e, 0xb5, 0x43, 0xda, 0x6b, 0x1d, 0x0f, 0x2d, 0x55}}}, +{{{0xe9, 0x58, 0x1f, 0xff, 0x84, 0x3f, 0x93, 0x1c, 0xcb, 0xe1, 0x30, 0x69, 0xa5, 0x75, 0x19, 0x7e, 0x14, 0x5f, 0xf8, 0xfc, 0x09, 0xdd, 0xa8, 0x78, 0x9d, 0xca, 0x59, 0x8b, 0xd1, 0x30, 0x01, 0x13}} , + {{0xff, 0x76, 0x03, 0xc5, 0x4b, 0x89, 0x99, 0x70, 0x00, 0x59, 0x70, 0x9c, 0xd5, 0xd9, 0x11, 0x89, 0x5a, 0x46, 0xfe, 0xef, 0xdc, 0xd9, 0x55, 0x2b, 0x45, 0xa7, 0xb0, 0x2d, 0xfb, 0x24, 0xc2, 0x29}}}, +{{{0x38, 0x06, 0xf8, 0x0b, 0xac, 0x82, 0xc4, 0x97, 0x2b, 0x90, 0xe0, 0xf7, 0xa8, 0xab, 0x6c, 0x08, 0x80, 0x66, 0x90, 0x46, 0xf7, 0x26, 0x2d, 0xf8, 0xf1, 0xc4, 0x6b, 0x4a, 0x82, 0x98, 0x8e, 0x37}} , + {{0x8e, 0xb4, 0xee, 0xb8, 0xd4, 0x3f, 0xb2, 0x1b, 0xe0, 0x0a, 0x3d, 0x75, 0x34, 0x28, 0xa2, 0x8e, 0xc4, 0x92, 0x7b, 0xfe, 0x60, 0x6e, 0x6d, 0xb8, 0x31, 0x1d, 0x62, 0x0d, 0x78, 0x14, 0x42, 0x11}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x5e, 0xa8, 0xd8, 0x04, 0x9b, 0x73, 0xc9, 0xc9, 0xdc, 0x0d, 0x73, 0xbf, 0x0a, 0x0a, 0x73, 0xff, 0x18, 0x1f, 0x9c, 0x51, 0xaa, 0xc6, 0xf1, 0x83, 0x25, 0xfd, 0xab, 0xa3, 0x11, 0xd3, 0x01, 0x24}} , + {{0x4d, 0xe3, 0x7e, 0x38, 0x62, 0x5e, 0x64, 0xbb, 0x2b, 0x53, 0xb5, 0x03, 0x68, 0xc4, 0xf2, 0x2b, 0x5a, 0x03, 0x32, 0x99, 0x4a, 0x41, 0x9a, 0xe1, 0x1a, 0xae, 0x8c, 0x48, 0xf3, 0x24, 0x32, 0x65}}}, +{{{0xe8, 0xdd, 0xad, 0x3a, 0x8c, 0xea, 0xf4, 0xb3, 0xb2, 0xe5, 0x73, 0xf2, 0xed, 0x8b, 0xbf, 0xed, 0xb1, 0x0c, 0x0c, 0xfb, 0x2b, 0xf1, 0x01, 0x48, 0xe8, 0x26, 0x03, 0x8e, 0x27, 0x4d, 0x96, 0x72}} , + {{0xc8, 0x09, 0x3b, 0x60, 0xc9, 0x26, 0x4d, 0x7c, 0xf2, 0x9c, 0xd4, 0xa1, 0x3b, 0x26, 0xc2, 0x04, 0x33, 0x44, 0x76, 0x3c, 0x02, 0xbb, 0x11, 0x42, 0x0c, 0x22, 0xb7, 0xc6, 0xe1, 0xac, 0xb4, 0x0e}}}, +{{{0x6f, 0x85, 0xe7, 0xef, 0xde, 0x67, 0x30, 0xfc, 0xbf, 0x5a, 0xe0, 0x7b, 0x7a, 0x2a, 0x54, 0x6b, 0x5d, 0x62, 0x85, 0xa1, 0xf8, 0x16, 0x88, 0xec, 0x61, 0xb9, 0x96, 0xb5, 0xef, 0x2d, 0x43, 0x4d}} , + {{0x7c, 0x31, 0x33, 0xcc, 0xe4, 0xcf, 0x6c, 0xff, 0x80, 0x47, 0x77, 0xd1, 0xd8, 0xe9, 0x69, 0x97, 0x98, 0x7f, 0x20, 0x57, 0x1d, 0x1d, 0x4f, 0x08, 0x27, 0xc8, 0x35, 0x57, 0x40, 0xc6, 0x21, 0x0c}}}, +{{{0xd2, 0x8e, 0x9b, 0xfa, 0x42, 0x8e, 0xdf, 0x8f, 0xc7, 0x86, 0xf9, 0xa4, 0xca, 0x70, 0x00, 0x9d, 0x21, 0xbf, 0xec, 0x57, 0x62, 0x30, 0x58, 0x8c, 0x0d, 0x35, 0xdb, 0x5d, 0x8b, 0x6a, 0xa0, 0x5a}} , + {{0xc1, 0x58, 0x7c, 0x0d, 0x20, 0xdd, 0x11, 0x26, 0x5f, 0x89, 0x3b, 0x97, 0x58, 0xf8, 0x8b, 0xe3, 0xdf, 0x32, 0xe2, 0xfc, 0xd8, 0x67, 0xf2, 0xa5, 0x37, 0x1e, 0x6d, 0xec, 0x7c, 0x27, 0x20, 0x79}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xd0, 0xe9, 0xc0, 0xfa, 0x95, 0x45, 0x23, 0x96, 0xf1, 0x2c, 0x79, 0x25, 0x14, 0xce, 0x40, 0x14, 0x44, 0x2c, 0x36, 0x50, 0xd9, 0x63, 0x56, 0xb7, 0x56, 0x3b, 0x9e, 0xa7, 0xef, 0x89, 0xbb, 0x0e}} , + {{0xce, 0x7f, 0xdc, 0x0a, 0xcc, 0x82, 0x1c, 0x0a, 0x78, 0x71, 0xe8, 0x74, 0x8d, 0x01, 0x30, 0x0f, 0xa7, 0x11, 0x4c, 0xdf, 0x38, 0xd7, 0xa7, 0x0d, 0xf8, 0x48, 0x52, 0x00, 0x80, 0x7b, 0x5f, 0x0e}}}, +{{{0x25, 0x83, 0xe6, 0x94, 0x7b, 0x81, 0xb2, 0x91, 0xae, 0x0e, 0x05, 0xc9, 0xa3, 0x68, 0x2d, 0xd9, 0x88, 0x25, 0x19, 0x2a, 0x61, 0x61, 0x21, 0x97, 0x15, 0xa1, 0x35, 0xa5, 0x46, 0xc8, 0xa2, 0x0e}} , + {{0x1b, 0x03, 0x0d, 0x8b, 0x5a, 0x1b, 0x97, 0x4b, 0xf2, 0x16, 0x31, 0x3d, 0x1f, 0x33, 0xa0, 0x50, 0x3a, 0x18, 0xbe, 0x13, 0xa1, 0x76, 0xc1, 0xba, 0x1b, 0xf1, 0x05, 0x7b, 0x33, 0xa8, 0x82, 0x3b}}}, +{{{0xba, 0x36, 0x7b, 0x6d, 0xa9, 0xea, 0x14, 0x12, 0xc5, 0xfa, 0x91, 0x00, 0xba, 0x9b, 0x99, 0xcc, 0x56, 0x02, 0xe9, 0xa0, 0x26, 0x40, 0x66, 0x8c, 0xc4, 0xf8, 0x85, 0x33, 0x68, 0xe7, 0x03, 0x20}} , + {{0x50, 0x5b, 0xff, 0xa9, 0xb2, 0xf1, 0xf1, 0x78, 0xcf, 0x14, 0xa4, 0xa9, 0xfc, 0x09, 0x46, 0x94, 0x54, 0x65, 0x0d, 0x9c, 0x5f, 0x72, 0x21, 0xe2, 0x97, 0xa5, 0x2d, 0x81, 0xce, 0x4a, 0x5f, 0x79}}}, +{{{0x3d, 0x5f, 0x5c, 0xd2, 0xbc, 0x7d, 0x77, 0x0e, 0x2a, 0x6d, 0x22, 0x45, 0x84, 0x06, 0xc4, 0xdd, 0xc6, 0xa6, 0xc6, 0xd7, 0x49, 0xad, 0x6d, 0x87, 0x91, 0x0e, 0x3a, 0x67, 0x1d, 0x2c, 0x1d, 0x56}} , + {{0xfe, 0x7a, 0x74, 0xcf, 0xd4, 0xd2, 0xe5, 0x19, 0xde, 0xd0, 0xdb, 0x70, 0x23, 0x69, 0xe6, 0x6d, 0xec, 0xec, 0xcc, 0x09, 0x33, 0x6a, 0x77, 0xdc, 0x6b, 0x22, 0x76, 0x5d, 0x92, 0x09, 0xac, 0x2d}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x23, 0x15, 0x17, 0xeb, 0xd3, 0xdb, 0x12, 0x5e, 0x01, 0xf0, 0x91, 0xab, 0x2c, 0x41, 0xce, 0xac, 0xed, 0x1b, 0x4b, 0x2d, 0xbc, 0xdb, 0x17, 0x66, 0x89, 0x46, 0xad, 0x4b, 0x1e, 0x6f, 0x0b, 0x14}} , + {{0x11, 0xce, 0xbf, 0xb6, 0x77, 0x2d, 0x48, 0x22, 0x18, 0x4f, 0xa3, 0x5d, 0x4a, 0xb0, 0x70, 0x12, 0x3e, 0x54, 0xd7, 0xd8, 0x0e, 0x2b, 0x27, 0xdc, 0x53, 0xff, 0xca, 0x8c, 0x59, 0xb3, 0x4e, 0x44}}}, +{{{0x07, 0x76, 0x61, 0x0f, 0x66, 0xb2, 0x21, 0x39, 0x7e, 0xc0, 0xec, 0x45, 0x28, 0x82, 0xa1, 0x29, 0x32, 0x44, 0x35, 0x13, 0x5e, 0x61, 0x5e, 0x54, 0xcb, 0x7c, 0xef, 0xf6, 0x41, 0xcf, 0x9f, 0x0a}} , + {{0xdd, 0xf9, 0xda, 0x84, 0xc3, 0xe6, 0x8a, 0x9f, 0x24, 0xd2, 0x96, 0x5d, 0x39, 0x6f, 0x58, 0x8c, 0xc1, 0x56, 0x93, 0xab, 0xb5, 0x79, 0x3b, 0xd2, 0xa8, 0x73, 0x16, 0xed, 0xfa, 0xb4, 0x2f, 0x73}}}, +{{{0x8b, 0xb1, 0x95, 0xe5, 0x92, 0x50, 0x35, 0x11, 0x76, 0xac, 0xf4, 0x4d, 0x24, 0xc3, 0x32, 0xe6, 0xeb, 0xfe, 0x2c, 0x87, 0xc4, 0xf1, 0x56, 0xc4, 0x75, 0x24, 0x7a, 0x56, 0x85, 0x5a, 0x3a, 0x13}} , + {{0x0d, 0x16, 0xac, 0x3c, 0x4a, 0x58, 0x86, 0x3a, 0x46, 0x7f, 0x6c, 0xa3, 0x52, 0x6e, 0x37, 0xe4, 0x96, 0x9c, 0xe9, 0x5c, 0x66, 0x41, 0x67, 0xe4, 0xfb, 0x79, 0x0c, 0x05, 0xf6, 0x64, 0xd5, 0x7c}}}, +{{{0x28, 0xc1, 0xe1, 0x54, 0x73, 0xf2, 0xbf, 0x76, 0x74, 0x19, 0x19, 0x1b, 0xe4, 0xb9, 0xa8, 0x46, 0x65, 0x73, 0xf3, 0x77, 0x9b, 0x29, 0x74, 0x5b, 0xc6, 0x89, 0x6c, 0x2c, 0x7c, 0xf8, 0xb3, 0x0f}} , + {{0xf7, 0xd5, 0xe9, 0x74, 0x5d, 0xb8, 0x25, 0x16, 0xb5, 0x30, 0xbc, 0x84, 0xc5, 0xf0, 0xad, 0xca, 0x12, 0x28, 0xbc, 0x9d, 0xd4, 0xfa, 0x82, 0xe6, 0xe3, 0xbf, 0xa2, 0x15, 0x2c, 0xd4, 0x34, 0x10}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x61, 0xb1, 0x46, 0xba, 0x0e, 0x31, 0xa5, 0x67, 0x6c, 0x7f, 0xd6, 0xd9, 0x27, 0x85, 0x0f, 0x79, 0x14, 0xc8, 0x6c, 0x2f, 0x5f, 0x5b, 0x9c, 0x35, 0x3d, 0x38, 0x86, 0x77, 0x65, 0x55, 0x6a, 0x7b}} , + {{0xd3, 0xb0, 0x3a, 0x66, 0x60, 0x1b, 0x43, 0xf1, 0x26, 0x58, 0x99, 0x09, 0x8f, 0x2d, 0xa3, 0x14, 0x71, 0x85, 0xdb, 0xed, 0xf6, 0x26, 0xd5, 0x61, 0x9a, 0x73, 0xac, 0x0e, 0xea, 0xac, 0xb7, 0x0c}}}, +{{{0x5e, 0xf4, 0xe5, 0x17, 0x0e, 0x10, 0x9f, 0xe7, 0x43, 0x5f, 0x67, 0x5c, 0xac, 0x4b, 0xe5, 0x14, 0x41, 0xd2, 0xbf, 0x48, 0xf5, 0x14, 0xb0, 0x71, 0xc6, 0x61, 0xc1, 0xb2, 0x70, 0x58, 0xd2, 0x5a}} , + {{0x2d, 0xba, 0x16, 0x07, 0x92, 0x94, 0xdc, 0xbd, 0x50, 0x2b, 0xc9, 0x7f, 0x42, 0x00, 0xba, 0x61, 0xed, 0xf8, 0x43, 0xed, 0xf5, 0xf9, 0x40, 0x60, 0xb2, 0xb0, 0x82, 0xcb, 0xed, 0x75, 0xc7, 0x65}}}, +{{{0x80, 0xba, 0x0d, 0x09, 0x40, 0xa7, 0x39, 0xa6, 0x67, 0x34, 0x7e, 0x66, 0xbe, 0x56, 0xfb, 0x53, 0x78, 0xc4, 0x46, 0xe8, 0xed, 0x68, 0x6c, 0x7f, 0xce, 0xe8, 0x9f, 0xce, 0xa2, 0x64, 0x58, 0x53}} , + {{0xe8, 0xc1, 0xa9, 0xc2, 0x7b, 0x59, 0x21, 0x33, 0xe2, 0x43, 0x73, 0x2b, 0xac, 0x2d, 0xc1, 0x89, 0x3b, 0x15, 0xe2, 0xd5, 0xc0, 0x97, 0x8a, 0xfd, 0x6f, 0x36, 0x33, 0xb7, 0xb9, 0xc3, 0x88, 0x09}}}, +{{{0xd0, 0xb6, 0x56, 0x30, 0x5c, 0xae, 0xb3, 0x75, 0x44, 0xa4, 0x83, 0x51, 0x6e, 0x01, 0x65, 0xef, 0x45, 0x76, 0xe6, 0xf5, 0xa2, 0x0d, 0xd4, 0x16, 0x3b, 0x58, 0x2f, 0xf2, 0x2f, 0x36, 0x18, 0x3f}} , + {{0xfd, 0x2f, 0xe0, 0x9b, 0x1e, 0x8c, 0xc5, 0x18, 0xa9, 0xca, 0xd4, 0x2b, 0x35, 0xb6, 0x95, 0x0a, 0x9f, 0x7e, 0xfb, 0xc4, 0xef, 0x88, 0x7b, 0x23, 0x43, 0xec, 0x2f, 0x0d, 0x0f, 0x7a, 0xfc, 0x5c}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x8d, 0xd2, 0xda, 0xc7, 0x44, 0xd6, 0x7a, 0xdb, 0x26, 0x7d, 0x1d, 0xb8, 0xe1, 0xde, 0x9d, 0x7a, 0x7d, 0x17, 0x7e, 0x1c, 0x37, 0x04, 0x8d, 0x2d, 0x7c, 0x5e, 0x18, 0x38, 0x1e, 0xaf, 0xc7, 0x1b}} , + {{0x33, 0x48, 0x31, 0x00, 0x59, 0xf6, 0xf2, 0xca, 0x0f, 0x27, 0x1b, 0x63, 0x12, 0x7e, 0x02, 0x1d, 0x49, 0xc0, 0x5d, 0x79, 0x87, 0xef, 0x5e, 0x7a, 0x2f, 0x1f, 0x66, 0x55, 0xd8, 0x09, 0xd9, 0x61}}}, +{{{0x54, 0x83, 0x02, 0x18, 0x82, 0x93, 0x99, 0x07, 0xd0, 0xa7, 0xda, 0xd8, 0x75, 0x89, 0xfa, 0xf2, 0xd9, 0xa3, 0xb8, 0x6b, 0x5a, 0x35, 0x28, 0xd2, 0x6b, 0x59, 0xc2, 0xf8, 0x45, 0xe2, 0xbc, 0x06}} , + {{0x65, 0xc0, 0xa3, 0x88, 0x51, 0x95, 0xfc, 0x96, 0x94, 0x78, 0xe8, 0x0d, 0x8b, 0x41, 0xc9, 0xc2, 0x58, 0x48, 0x75, 0x10, 0x2f, 0xcd, 0x2a, 0xc9, 0xa0, 0x6d, 0x0f, 0xdd, 0x9c, 0x98, 0x26, 0x3d}}}, +{{{0x2f, 0x66, 0x29, 0x1b, 0x04, 0x89, 0xbd, 0x7e, 0xee, 0x6e, 0xdd, 0xb7, 0x0e, 0xef, 0xb0, 0x0c, 0xb4, 0xfc, 0x7f, 0xc2, 0xc9, 0x3a, 0x3c, 0x64, 0xef, 0x45, 0x44, 0xaf, 0x8a, 0x90, 0x65, 0x76}} , + {{0xa1, 0x4c, 0x70, 0x4b, 0x0e, 0xa0, 0x83, 0x70, 0x13, 0xa4, 0xaf, 0xb8, 0x38, 0x19, 0x22, 0x65, 0x09, 0xb4, 0x02, 0x4f, 0x06, 0xf8, 0x17, 0xce, 0x46, 0x45, 0xda, 0x50, 0x7c, 0x8a, 0xd1, 0x4e}}}, +{{{0xf7, 0xd4, 0x16, 0x6c, 0x4e, 0x95, 0x9d, 0x5d, 0x0f, 0x91, 0x2b, 0x52, 0xfe, 0x5c, 0x34, 0xe5, 0x30, 0xe6, 0xa4, 0x3b, 0xf3, 0xf3, 0x34, 0x08, 0xa9, 0x4a, 0xa0, 0xb5, 0x6e, 0xb3, 0x09, 0x0a}} , + {{0x26, 0xd9, 0x5e, 0xa3, 0x0f, 0xeb, 0xa2, 0xf3, 0x20, 0x3b, 0x37, 0xd4, 0xe4, 0x9e, 0xce, 0x06, 0x3d, 0x53, 0xed, 0xae, 0x2b, 0xeb, 0xb6, 0x24, 0x0a, 0x11, 0xa3, 0x0f, 0xd6, 0x7f, 0xa4, 0x3a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xdb, 0x9f, 0x2c, 0xfc, 0xd6, 0xb2, 0x1e, 0x2e, 0x52, 0x7a, 0x06, 0x87, 0x2d, 0x86, 0x72, 0x2b, 0x6d, 0x90, 0x77, 0x46, 0x43, 0xb5, 0x7a, 0xf8, 0x60, 0x7d, 0x91, 0x60, 0x5b, 0x9d, 0x9e, 0x07}} , + {{0x97, 0x87, 0xc7, 0x04, 0x1c, 0x38, 0x01, 0x39, 0x58, 0xc7, 0x85, 0xa3, 0xfc, 0x64, 0x00, 0x64, 0x25, 0xa2, 0xbf, 0x50, 0x94, 0xca, 0x26, 0x31, 0x45, 0x0a, 0x24, 0xd2, 0x51, 0x29, 0x51, 0x16}}}, +{{{0x4d, 0x4a, 0xd7, 0x98, 0x71, 0x57, 0xac, 0x7d, 0x8b, 0x37, 0xbd, 0x63, 0xff, 0x87, 0xb1, 0x49, 0x95, 0x20, 0x7c, 0xcf, 0x7c, 0x59, 0xc4, 0x91, 0x9c, 0xef, 0xd0, 0xdb, 0x60, 0x09, 0x9d, 0x46}} , + {{0xcb, 0x78, 0x94, 0x90, 0xe4, 0x45, 0xb3, 0xf6, 0xd9, 0xf6, 0x57, 0x74, 0xd5, 0xf8, 0x83, 0x4f, 0x39, 0xc9, 0xbd, 0x88, 0xc2, 0x57, 0x21, 0x1f, 0x24, 0x32, 0x68, 0xf8, 0xc7, 0x21, 0x5f, 0x0b}}}, +{{{0x2a, 0x36, 0x68, 0xfc, 0x5f, 0xb6, 0x4f, 0xa5, 0xe3, 0x9d, 0x24, 0x2f, 0xc0, 0x93, 0x61, 0xcf, 0xf8, 0x0a, 0xed, 0xe1, 0xdb, 0x27, 0xec, 0x0e, 0x14, 0x32, 0x5f, 0x8e, 0xa1, 0x62, 0x41, 0x16}} , + {{0x95, 0x21, 0x01, 0xce, 0x95, 0x5b, 0x0e, 0x57, 0xc7, 0xb9, 0x62, 0xb5, 0x28, 0xca, 0x11, 0xec, 0xb4, 0x46, 0x06, 0x73, 0x26, 0xff, 0xfb, 0x66, 0x7d, 0xee, 0x5f, 0xb2, 0x56, 0xfd, 0x2a, 0x08}}}, +{{{0x92, 0x67, 0x77, 0x56, 0xa1, 0xff, 0xc4, 0xc5, 0x95, 0xf0, 0xe3, 0x3a, 0x0a, 0xca, 0x94, 0x4d, 0x9e, 0x7e, 0x3d, 0xb9, 0x6e, 0xb6, 0xb0, 0xce, 0xa4, 0x30, 0x89, 0x99, 0xe9, 0xad, 0x11, 0x59}} , + {{0xf6, 0x48, 0x95, 0xa1, 0x6f, 0x5f, 0xb7, 0xa5, 0xbb, 0x30, 0x00, 0x1c, 0xd2, 0x8a, 0xd6, 0x25, 0x26, 0x1b, 0xb2, 0x0d, 0x37, 0x6a, 0x05, 0xf4, 0x9d, 0x3e, 0x17, 0x2a, 0x43, 0xd2, 0x3a, 0x06}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x32, 0x99, 0x93, 0xd1, 0x9a, 0x72, 0xf3, 0xa9, 0x16, 0xbd, 0xb4, 0x4c, 0xdd, 0xf9, 0xd4, 0xb2, 0x64, 0x9a, 0xd3, 0x05, 0xe4, 0xa3, 0x73, 0x1c, 0xcb, 0x7e, 0x57, 0x67, 0xff, 0x04, 0xb3, 0x10}} , + {{0xb9, 0x4b, 0xa4, 0xad, 0xd0, 0x6d, 0x61, 0x23, 0xb4, 0xaf, 0x34, 0xa9, 0xaa, 0x65, 0xec, 0xd9, 0x69, 0xe3, 0x85, 0xcd, 0xcc, 0xe7, 0xb0, 0x9b, 0x41, 0xc1, 0x1c, 0xf9, 0xa0, 0xfa, 0xb7, 0x13}}}, +{{{0x04, 0xfd, 0x88, 0x3c, 0x0c, 0xd0, 0x09, 0x52, 0x51, 0x4f, 0x06, 0x19, 0xcc, 0xc3, 0xbb, 0xde, 0x80, 0xc5, 0x33, 0xbc, 0xf9, 0xf3, 0x17, 0x36, 0xdd, 0xc6, 0xde, 0xe8, 0x9b, 0x5d, 0x79, 0x1b}} , + {{0x65, 0x0a, 0xbe, 0x51, 0x57, 0xad, 0x50, 0x79, 0x08, 0x71, 0x9b, 0x07, 0x95, 0x8f, 0xfb, 0xae, 0x4b, 0x38, 0xba, 0xcf, 0x53, 0x2a, 0x86, 0x1e, 0xc0, 0x50, 0x5c, 0x67, 0x1b, 0xf6, 0x87, 0x6c}}}, +{{{0x4f, 0x00, 0xb2, 0x66, 0x55, 0xed, 0x4a, 0xed, 0x8d, 0xe1, 0x66, 0x18, 0xb2, 0x14, 0x74, 0x8d, 0xfd, 0x1a, 0x36, 0x0f, 0x26, 0x5c, 0x8b, 0x89, 0xf3, 0xab, 0xf2, 0xf3, 0x24, 0x67, 0xfd, 0x70}} , + {{0xfd, 0x4e, 0x2a, 0xc1, 0x3a, 0xca, 0x8f, 0x00, 0xd8, 0xec, 0x74, 0x67, 0xef, 0x61, 0xe0, 0x28, 0xd0, 0x96, 0xf4, 0x48, 0xde, 0x81, 0xe3, 0xef, 0xdc, 0xaa, 0x7d, 0xf3, 0xb6, 0x55, 0xa6, 0x65}}}, +{{{0xeb, 0xcb, 0xc5, 0x70, 0x91, 0x31, 0x10, 0x93, 0x0d, 0xc8, 0xd0, 0xef, 0x62, 0xe8, 0x6f, 0x82, 0xe3, 0x69, 0x3d, 0x91, 0x7f, 0x31, 0xe1, 0x26, 0x35, 0x3c, 0x4a, 0x2f, 0xab, 0xc4, 0x9a, 0x5e}} , + {{0xab, 0x1b, 0xb5, 0xe5, 0x2b, 0xc3, 0x0e, 0x29, 0xb0, 0xd0, 0x73, 0xe6, 0x4f, 0x64, 0xf2, 0xbc, 0xe4, 0xe4, 0xe1, 0x9a, 0x52, 0x33, 0x2f, 0xbd, 0xcc, 0x03, 0xee, 0x8a, 0xfa, 0x00, 0x5f, 0x50}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf6, 0xdb, 0x0d, 0x22, 0x3d, 0xb5, 0x14, 0x75, 0x31, 0xf0, 0x81, 0xe2, 0xb9, 0x37, 0xa2, 0xa9, 0x84, 0x11, 0x9a, 0x07, 0xb5, 0x53, 0x89, 0x78, 0xa9, 0x30, 0x27, 0xa1, 0xf1, 0x4e, 0x5c, 0x2e}} , + {{0x8b, 0x00, 0x54, 0xfb, 0x4d, 0xdc, 0xcb, 0x17, 0x35, 0x40, 0xff, 0xb7, 0x8c, 0xfe, 0x4a, 0xe4, 0x4e, 0x99, 0x4e, 0xa8, 0x74, 0x54, 0x5d, 0x5c, 0x96, 0xa3, 0x12, 0x55, 0x36, 0x31, 0x17, 0x5c}}}, +{{{0xce, 0x24, 0xef, 0x7b, 0x86, 0xf2, 0x0f, 0x77, 0xe8, 0x5c, 0x7d, 0x87, 0x38, 0x2d, 0xef, 0xaf, 0xf2, 0x8c, 0x72, 0x2e, 0xeb, 0xb6, 0x55, 0x4b, 0x6e, 0xf1, 0x4e, 0x8a, 0x0e, 0x9a, 0x6c, 0x4c}} , + {{0x25, 0xea, 0x86, 0xc2, 0xd1, 0x4f, 0xb7, 0x3e, 0xa8, 0x5c, 0x8d, 0x66, 0x81, 0x25, 0xed, 0xc5, 0x4c, 0x05, 0xb9, 0xd8, 0xd6, 0x70, 0xbe, 0x73, 0x82, 0xe8, 0xa1, 0xe5, 0x1e, 0x71, 0xd5, 0x26}}}, +{{{0x4e, 0x6d, 0xc3, 0xa7, 0x4f, 0x22, 0x45, 0x26, 0xa2, 0x7e, 0x16, 0xf7, 0xf7, 0x63, 0xdc, 0x86, 0x01, 0x2a, 0x71, 0x38, 0x5c, 0x33, 0xc3, 0xce, 0x30, 0xff, 0xf9, 0x2c, 0x91, 0x71, 0x8a, 0x72}} , + {{0x8c, 0x44, 0x09, 0x28, 0xd5, 0x23, 0xc9, 0x8f, 0xf3, 0x84, 0x45, 0xc6, 0x9a, 0x5e, 0xff, 0xd2, 0xc7, 0x57, 0x93, 0xa3, 0xc1, 0x69, 0xdd, 0x62, 0x0f, 0xda, 0x5c, 0x30, 0x59, 0x5d, 0xe9, 0x4c}}}, +{{{0x92, 0x7e, 0x50, 0x27, 0x72, 0xd7, 0x0c, 0xd6, 0x69, 0x96, 0x81, 0x35, 0x84, 0x94, 0x35, 0x8b, 0x6c, 0xaa, 0x62, 0x86, 0x6e, 0x1c, 0x15, 0xf3, 0x6c, 0xb3, 0xff, 0x65, 0x1b, 0xa2, 0x9b, 0x59}} , + {{0xe2, 0xa9, 0x65, 0x88, 0xc4, 0x50, 0xfa, 0xbb, 0x3b, 0x6e, 0x5f, 0x44, 0x01, 0xca, 0x97, 0xd4, 0xdd, 0xf6, 0xcd, 0x3f, 0x3f, 0xe5, 0x97, 0x67, 0x2b, 0x8c, 0x66, 0x0f, 0x35, 0x9b, 0xf5, 0x07}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xf1, 0x59, 0x27, 0xd8, 0xdb, 0x5a, 0x11, 0x5e, 0x82, 0xf3, 0x38, 0xff, 0x1c, 0xed, 0xfe, 0x3f, 0x64, 0x54, 0x3f, 0x7f, 0xd1, 0x81, 0xed, 0xef, 0x65, 0xc5, 0xcb, 0xfd, 0xe1, 0x80, 0xcd, 0x11}} , + {{0xe0, 0xdb, 0x22, 0x28, 0xe6, 0xff, 0x61, 0x9d, 0x41, 0x14, 0x2d, 0x3b, 0x26, 0x22, 0xdf, 0xf1, 0x34, 0x81, 0xe9, 0x45, 0xee, 0x0f, 0x98, 0x8b, 0xa6, 0x3f, 0xef, 0xf7, 0x43, 0x19, 0xf1, 0x43}}}, +{{{0xee, 0xf3, 0x00, 0xa1, 0x50, 0xde, 0xc0, 0xb6, 0x01, 0xe3, 0x8c, 0x3c, 0x4d, 0x31, 0xd2, 0xb0, 0x58, 0xcd, 0xed, 0x10, 0x4a, 0x7a, 0xef, 0x80, 0xa9, 0x19, 0x32, 0xf3, 0xd8, 0x33, 0x8c, 0x06}} , + {{0xcb, 0x7d, 0x4f, 0xff, 0x30, 0xd8, 0x12, 0x3b, 0x39, 0x1c, 0x06, 0xf9, 0x4c, 0x34, 0x35, 0x71, 0xb5, 0x16, 0x94, 0x67, 0xdf, 0xee, 0x11, 0xde, 0xa4, 0x1d, 0x88, 0x93, 0x35, 0xa9, 0x32, 0x10}}}, +{{{0xe9, 0xc3, 0xbc, 0x7b, 0x5c, 0xfc, 0xb2, 0xf9, 0xc9, 0x2f, 0xe5, 0xba, 0x3a, 0x0b, 0xab, 0x64, 0x38, 0x6f, 0x5b, 0x4b, 0x93, 0xda, 0x64, 0xec, 0x4d, 0x3d, 0xa0, 0xf5, 0xbb, 0xba, 0x47, 0x48}} , + {{0x60, 0xbc, 0x45, 0x1f, 0x23, 0xa2, 0x3b, 0x70, 0x76, 0xe6, 0x97, 0x99, 0x4f, 0x77, 0x54, 0x67, 0x30, 0x9a, 0xe7, 0x66, 0xd6, 0xcd, 0x2e, 0x51, 0x24, 0x2c, 0x42, 0x4a, 0x11, 0xfe, 0x6f, 0x7e}}}, +{{{0x87, 0xc0, 0xb1, 0xf0, 0xa3, 0x6f, 0x0c, 0x93, 0xa9, 0x0a, 0x72, 0xef, 0x5c, 0xbe, 0x65, 0x35, 0xa7, 0x6a, 0x4e, 0x2c, 0xbf, 0x21, 0x23, 0xe8, 0x2f, 0x97, 0xc7, 0x3e, 0xc8, 0x17, 0xac, 0x1e}} , + {{0x7b, 0xef, 0x21, 0xe5, 0x40, 0xcc, 0x1e, 0xdc, 0xd6, 0xbd, 0x97, 0x7a, 0x7c, 0x75, 0x86, 0x7a, 0x25, 0x5a, 0x6e, 0x7c, 0xe5, 0x51, 0x3c, 0x1b, 0x5b, 0x82, 0x9a, 0x07, 0x60, 0xa1, 0x19, 0x04}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x96, 0x88, 0xa6, 0xab, 0x8f, 0xe3, 0x3a, 0x49, 0xf8, 0xfe, 0x34, 0xe7, 0x6a, 0xb2, 0xfe, 0x40, 0x26, 0x74, 0x57, 0x4c, 0xf6, 0xd4, 0x99, 0xce, 0x5d, 0x7b, 0x2f, 0x67, 0xd6, 0x5a, 0xe4, 0x4e}} , + {{0x5c, 0x82, 0xb3, 0xbd, 0x55, 0x25, 0xf6, 0x6a, 0x93, 0xa4, 0x02, 0xc6, 0x7d, 0x5c, 0xb1, 0x2b, 0x5b, 0xff, 0xfb, 0x56, 0xf8, 0x01, 0x41, 0x90, 0xc6, 0xb6, 0xac, 0x4f, 0xfe, 0xa7, 0x41, 0x70}}}, +{{{0xdb, 0xfa, 0x9b, 0x2c, 0xd4, 0x23, 0x67, 0x2c, 0x8a, 0x63, 0x6c, 0x07, 0x26, 0x48, 0x4f, 0xc2, 0x03, 0xd2, 0x53, 0x20, 0x28, 0xed, 0x65, 0x71, 0x47, 0xa9, 0x16, 0x16, 0x12, 0xbc, 0x28, 0x33}} , + {{0x39, 0xc0, 0xfa, 0xfa, 0xcd, 0x33, 0x43, 0xc7, 0x97, 0x76, 0x9b, 0x93, 0x91, 0x72, 0xeb, 0xc5, 0x18, 0x67, 0x4c, 0x11, 0xf0, 0xf4, 0xe5, 0x73, 0xb2, 0x5c, 0x1b, 0xc2, 0x26, 0x3f, 0xbf, 0x2b}}}, +{{{0x86, 0xe6, 0x8c, 0x1d, 0xdf, 0xca, 0xfc, 0xd5, 0xf8, 0x3a, 0xc3, 0x44, 0x72, 0xe6, 0x78, 0x9d, 0x2b, 0x97, 0xf8, 0x28, 0x45, 0xb4, 0x20, 0xc9, 0x2a, 0x8c, 0x67, 0xaa, 0x11, 0xc5, 0x5b, 0x2f}} , + {{0x17, 0x0f, 0x86, 0x52, 0xd7, 0x9d, 0xc3, 0x44, 0x51, 0x76, 0x32, 0x65, 0xb4, 0x37, 0x81, 0x99, 0x46, 0x37, 0x62, 0xed, 0xcf, 0x64, 0x9d, 0x72, 0x40, 0x7a, 0x4c, 0x0b, 0x76, 0x2a, 0xfb, 0x56}}}, +{{{0x33, 0xa7, 0x90, 0x7c, 0xc3, 0x6f, 0x17, 0xa5, 0xa0, 0x67, 0x72, 0x17, 0xea, 0x7e, 0x63, 0x14, 0x83, 0xde, 0xc1, 0x71, 0x2d, 0x41, 0x32, 0x7a, 0xf3, 0xd1, 0x2b, 0xd8, 0x2a, 0xa6, 0x46, 0x36}} , + {{0xac, 0xcc, 0x6b, 0x7c, 0xf9, 0xb8, 0x8b, 0x08, 0x5c, 0xd0, 0x7d, 0x8f, 0x73, 0xea, 0x20, 0xda, 0x86, 0xca, 0x00, 0xc7, 0xad, 0x73, 0x4d, 0xe9, 0xe8, 0xa9, 0xda, 0x1f, 0x03, 0x06, 0xdd, 0x24}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x9c, 0xb2, 0x61, 0x0a, 0x98, 0x2a, 0xa5, 0xd7, 0xee, 0xa9, 0xac, 0x65, 0xcb, 0x0a, 0x1e, 0xe2, 0xbe, 0xdc, 0x85, 0x59, 0x0f, 0x9c, 0xa6, 0x57, 0x34, 0xa5, 0x87, 0xeb, 0x7b, 0x1e, 0x0c, 0x3c}} , + {{0x2f, 0xbd, 0x84, 0x63, 0x0d, 0xb5, 0xa0, 0xf0, 0x4b, 0x9e, 0x93, 0xc6, 0x34, 0x9a, 0x34, 0xff, 0x73, 0x19, 0x2f, 0x6e, 0x54, 0x45, 0x2c, 0x92, 0x31, 0x76, 0x34, 0xf1, 0xb2, 0x26, 0xe8, 0x74}}}, +{{{0x0a, 0x67, 0x90, 0x6d, 0x0c, 0x4c, 0xcc, 0xc0, 0xe6, 0xbd, 0xa7, 0x5e, 0x55, 0x8c, 0xcd, 0x58, 0x9b, 0x11, 0xa2, 0xbb, 0x4b, 0xb1, 0x43, 0x04, 0x3c, 0x55, 0xed, 0x23, 0xfe, 0xcd, 0xb1, 0x53}} , + {{0x05, 0xfb, 0x75, 0xf5, 0x01, 0xaf, 0x38, 0x72, 0x58, 0xfc, 0x04, 0x29, 0x34, 0x7a, 0x67, 0xa2, 0x08, 0x50, 0x6e, 0xd0, 0x2b, 0x73, 0xd5, 0xb8, 0xe4, 0x30, 0x96, 0xad, 0x45, 0xdf, 0xa6, 0x5c}}}, +{{{0x0d, 0x88, 0x1a, 0x90, 0x7e, 0xdc, 0xd8, 0xfe, 0xc1, 0x2f, 0x5d, 0x67, 0xee, 0x67, 0x2f, 0xed, 0x6f, 0x55, 0x43, 0x5f, 0x87, 0x14, 0x35, 0x42, 0xd3, 0x75, 0xae, 0xd5, 0xd3, 0x85, 0x1a, 0x76}} , + {{0x87, 0xc8, 0xa0, 0x6e, 0xe1, 0xb0, 0xad, 0x6a, 0x4a, 0x34, 0x71, 0xed, 0x7c, 0xd6, 0x44, 0x03, 0x65, 0x4a, 0x5c, 0x5c, 0x04, 0xf5, 0x24, 0x3f, 0xb0, 0x16, 0x5e, 0x8c, 0xb2, 0xd2, 0xc5, 0x20}}}, +{{{0x98, 0x83, 0xc2, 0x37, 0xa0, 0x41, 0xa8, 0x48, 0x5c, 0x5f, 0xbf, 0xc8, 0xfa, 0x24, 0xe0, 0x59, 0x2c, 0xbd, 0xf6, 0x81, 0x7e, 0x88, 0xe6, 0xca, 0x04, 0xd8, 0x5d, 0x60, 0xbb, 0x74, 0xa7, 0x0b}} , + {{0x21, 0x13, 0x91, 0xbf, 0x77, 0x7a, 0x33, 0xbc, 0xe9, 0x07, 0x39, 0x0a, 0xdd, 0x7d, 0x06, 0x10, 0x9a, 0xee, 0x47, 0x73, 0x1b, 0x15, 0x5a, 0xfb, 0xcd, 0x4d, 0xd0, 0xd2, 0x3a, 0x01, 0xba, 0x54}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x48, 0xd5, 0x39, 0x4a, 0x0b, 0x20, 0x6a, 0x43, 0xa0, 0x07, 0x82, 0x5e, 0x49, 0x7c, 0xc9, 0x47, 0xf1, 0x7c, 0x37, 0xb9, 0x23, 0xef, 0x6b, 0x46, 0x45, 0x8c, 0x45, 0x76, 0xdf, 0x14, 0x6b, 0x6e}} , + {{0x42, 0xc9, 0xca, 0x29, 0x4c, 0x76, 0x37, 0xda, 0x8a, 0x2d, 0x7c, 0x3a, 0x58, 0xf2, 0x03, 0xb4, 0xb5, 0xb9, 0x1a, 0x13, 0x2d, 0xde, 0x5f, 0x6b, 0x9d, 0xba, 0x52, 0xc9, 0x5d, 0xb3, 0xf3, 0x30}}}, +{{{0x4c, 0x6f, 0xfe, 0x6b, 0x0c, 0x62, 0xd7, 0x48, 0x71, 0xef, 0xb1, 0x85, 0x79, 0xc0, 0xed, 0x24, 0xb1, 0x08, 0x93, 0x76, 0x8e, 0xf7, 0x38, 0x8e, 0xeb, 0xfe, 0x80, 0x40, 0xaf, 0x90, 0x64, 0x49}} , + {{0x4a, 0x88, 0xda, 0xc1, 0x98, 0x44, 0x3c, 0x53, 0x4e, 0xdb, 0x4b, 0xb9, 0x12, 0x5f, 0xcd, 0x08, 0x04, 0xef, 0x75, 0xe7, 0xb1, 0x3a, 0xe5, 0x07, 0xfa, 0xca, 0x65, 0x7b, 0x72, 0x10, 0x64, 0x7f}}}, +{{{0x3d, 0x81, 0xf0, 0xeb, 0x16, 0xfd, 0x58, 0x33, 0x8d, 0x7c, 0x1a, 0xfb, 0x20, 0x2c, 0x8a, 0xee, 0x90, 0xbb, 0x33, 0x6d, 0x45, 0xe9, 0x8e, 0x99, 0x85, 0xe1, 0x08, 0x1f, 0xc5, 0xf1, 0xb5, 0x46}} , + {{0xe4, 0xe7, 0x43, 0x4b, 0xa0, 0x3f, 0x2b, 0x06, 0xba, 0x17, 0xae, 0x3d, 0xe6, 0xce, 0xbd, 0xb8, 0xed, 0x74, 0x11, 0x35, 0xec, 0x96, 0xfe, 0x31, 0xe3, 0x0e, 0x7a, 0x4e, 0xc9, 0x1d, 0xcb, 0x20}}}, +{{{0xe0, 0x67, 0xe9, 0x7b, 0xdb, 0x96, 0x5c, 0xb0, 0x32, 0xd0, 0x59, 0x31, 0x90, 0xdc, 0x92, 0x97, 0xac, 0x09, 0x38, 0x31, 0x0f, 0x7e, 0xd6, 0x5d, 0xd0, 0x06, 0xb6, 0x1f, 0xea, 0xf0, 0x5b, 0x07}} , + {{0x81, 0x9f, 0xc7, 0xde, 0x6b, 0x41, 0x22, 0x35, 0x14, 0x67, 0x77, 0x3e, 0x90, 0x81, 0xb0, 0xd9, 0x85, 0x4c, 0xca, 0x9b, 0x3f, 0x04, 0x59, 0xd6, 0xaa, 0x17, 0xc3, 0x88, 0x34, 0x37, 0xba, 0x43}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x4c, 0xb6, 0x69, 0xc8, 0x81, 0x95, 0x94, 0x33, 0x92, 0x34, 0xe9, 0x3c, 0x84, 0x0d, 0x3d, 0x5a, 0x37, 0x9c, 0x22, 0xa0, 0xaa, 0x65, 0xce, 0xb4, 0xc2, 0x2d, 0x66, 0x67, 0x02, 0xff, 0x74, 0x10}} , + {{0x22, 0xb0, 0xd5, 0xe6, 0xc7, 0xef, 0xb1, 0xa7, 0x13, 0xda, 0x60, 0xb4, 0x80, 0xc1, 0x42, 0x7d, 0x10, 0x70, 0x97, 0x04, 0x4d, 0xda, 0x23, 0x89, 0xc2, 0x0e, 0x68, 0xcb, 0xde, 0xe0, 0x9b, 0x29}}}, +{{{0x33, 0xfe, 0x42, 0x2a, 0x36, 0x2b, 0x2e, 0x36, 0x64, 0x5c, 0x8b, 0xcc, 0x81, 0x6a, 0x15, 0x08, 0xa1, 0x27, 0xe8, 0x57, 0xe5, 0x78, 0x8e, 0xf2, 0x58, 0x19, 0x12, 0x42, 0xae, 0xc4, 0x63, 0x3e}} , + {{0x78, 0x96, 0x9c, 0xa7, 0xca, 0x80, 0xae, 0x02, 0x85, 0xb1, 0x7c, 0x04, 0x5c, 0xc1, 0x5b, 0x26, 0xc1, 0xba, 0xed, 0xa5, 0x59, 0x70, 0x85, 0x8c, 0x8c, 0xe8, 0x87, 0xac, 0x6a, 0x28, 0x99, 0x35}}}, +{{{0x9f, 0x04, 0x08, 0x28, 0xbe, 0x87, 0xda, 0x80, 0x28, 0x38, 0xde, 0x9f, 0xcd, 0xe4, 0xe3, 0x62, 0xfb, 0x2e, 0x46, 0x8d, 0x01, 0xb3, 0x06, 0x51, 0xd4, 0x19, 0x3b, 0x11, 0xfa, 0xe2, 0xad, 0x1e}} , + {{0xa0, 0x20, 0x99, 0x69, 0x0a, 0xae, 0xa3, 0x70, 0x4e, 0x64, 0x80, 0xb7, 0x85, 0x9c, 0x87, 0x54, 0x43, 0x43, 0x55, 0x80, 0x6d, 0x8d, 0x7c, 0xa9, 0x64, 0xca, 0x6c, 0x2e, 0x21, 0xd8, 0xc8, 0x6c}}}, +{{{0x91, 0x4a, 0x07, 0xad, 0x08, 0x75, 0xc1, 0x4f, 0xa4, 0xb2, 0xc3, 0x6f, 0x46, 0x3e, 0xb1, 0xce, 0x52, 0xab, 0x67, 0x09, 0x54, 0x48, 0x6b, 0x6c, 0xd7, 0x1d, 0x71, 0x76, 0xcb, 0xff, 0xdd, 0x31}} , + {{0x36, 0x88, 0xfa, 0xfd, 0xf0, 0x36, 0x6f, 0x07, 0x74, 0x88, 0x50, 0xd0, 0x95, 0x38, 0x4a, 0x48, 0x2e, 0x07, 0x64, 0x97, 0x11, 0x76, 0x01, 0x1a, 0x27, 0x4d, 0x8e, 0x25, 0x9a, 0x9b, 0x1c, 0x22}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xbe, 0x57, 0xbd, 0x0e, 0x0f, 0xac, 0x5e, 0x76, 0xa3, 0x71, 0xad, 0x2b, 0x10, 0x45, 0x02, 0xec, 0x59, 0xd5, 0x5d, 0xa9, 0x44, 0xcc, 0x25, 0x4c, 0xb3, 0x3c, 0x5b, 0x69, 0x07, 0x55, 0x26, 0x6b}} , + {{0x30, 0x6b, 0xd4, 0xa7, 0x51, 0x29, 0xe3, 0xf9, 0x7a, 0x75, 0x2a, 0x82, 0x2f, 0xd6, 0x1d, 0x99, 0x2b, 0x80, 0xd5, 0x67, 0x1e, 0x15, 0x9d, 0xca, 0xfd, 0xeb, 0xac, 0x97, 0x35, 0x09, 0x7f, 0x3f}}}, +{{{0x35, 0x0d, 0x34, 0x0a, 0xb8, 0x67, 0x56, 0x29, 0x20, 0xf3, 0x19, 0x5f, 0xe2, 0x83, 0x42, 0x73, 0x53, 0xa8, 0xc5, 0x02, 0x19, 0x33, 0xb4, 0x64, 0xbd, 0xc3, 0x87, 0x8c, 0xd7, 0x76, 0xed, 0x25}} , + {{0x47, 0x39, 0x37, 0x76, 0x0d, 0x1d, 0x0c, 0xf5, 0x5a, 0x6d, 0x43, 0x88, 0x99, 0x15, 0xb4, 0x52, 0x0f, 0x2a, 0xb3, 0xb0, 0x3f, 0xa6, 0xb3, 0x26, 0xb3, 0xc7, 0x45, 0xf5, 0x92, 0x5f, 0x9b, 0x17}}}, +{{{0x9d, 0x23, 0xbd, 0x15, 0xfe, 0x52, 0x52, 0x15, 0x26, 0x79, 0x86, 0xba, 0x06, 0x56, 0x66, 0xbb, 0x8c, 0x2e, 0x10, 0x11, 0xd5, 0x4a, 0x18, 0x52, 0xda, 0x84, 0x44, 0xf0, 0x3e, 0xe9, 0x8c, 0x35}} , + {{0xad, 0xa0, 0x41, 0xec, 0xc8, 0x4d, 0xb9, 0xd2, 0x6e, 0x96, 0x4e, 0x5b, 0xc5, 0xc2, 0xa0, 0x1b, 0xcf, 0x0c, 0xbf, 0x17, 0x66, 0x57, 0xc1, 0x17, 0x90, 0x45, 0x71, 0xc2, 0xe1, 0x24, 0xeb, 0x27}}}, +{{{0x2c, 0xb9, 0x42, 0xa4, 0xaf, 0x3b, 0x42, 0x0e, 0xc2, 0x0f, 0xf2, 0xea, 0x83, 0xaf, 0x9a, 0x13, 0x17, 0xb0, 0xbd, 0x89, 0x17, 0xe3, 0x72, 0xcb, 0x0e, 0x76, 0x7e, 0x41, 0x63, 0x04, 0x88, 0x71}} , + {{0x75, 0x78, 0x38, 0x86, 0x57, 0xdd, 0x9f, 0xee, 0x54, 0x70, 0x65, 0xbf, 0xf1, 0x2c, 0xe0, 0x39, 0x0d, 0xe3, 0x89, 0xfd, 0x8e, 0x93, 0x4f, 0x43, 0xdc, 0xd5, 0x5b, 0xde, 0xf9, 0x98, 0xe5, 0x7b}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xe7, 0x3b, 0x65, 0x11, 0xdf, 0xb2, 0xf2, 0x63, 0x94, 0x12, 0x6f, 0x5c, 0x9e, 0x77, 0xc1, 0xb6, 0xd8, 0xab, 0x58, 0x7a, 0x1d, 0x95, 0x73, 0xdd, 0xe7, 0xe3, 0x6f, 0xf2, 0x03, 0x1d, 0xdb, 0x76}} , + {{0xae, 0x06, 0x4e, 0x2c, 0x52, 0x1b, 0xbc, 0x5a, 0x5a, 0xa5, 0xbe, 0x27, 0xbd, 0xeb, 0xe1, 0x14, 0x17, 0x68, 0x26, 0x07, 0x03, 0xd1, 0x18, 0x0b, 0xdf, 0xf1, 0x06, 0x5c, 0xa6, 0x1b, 0xb9, 0x24}}}, +{{{0xc5, 0x66, 0x80, 0x13, 0x0e, 0x48, 0x8c, 0x87, 0x31, 0x84, 0xb4, 0x60, 0xed, 0xc5, 0xec, 0xb6, 0xc5, 0x05, 0x33, 0x5f, 0x2f, 0x7d, 0x40, 0xb6, 0x32, 0x1d, 0x38, 0x74, 0x1b, 0xf1, 0x09, 0x3d}} , + {{0xd4, 0x69, 0x82, 0xbc, 0x8d, 0xf8, 0x34, 0x36, 0x75, 0x55, 0x18, 0x55, 0x58, 0x3c, 0x79, 0xaf, 0x26, 0x80, 0xab, 0x9b, 0x95, 0x00, 0xf1, 0xcb, 0xda, 0xc1, 0x9f, 0xf6, 0x2f, 0xa2, 0xf4, 0x45}}}, +{{{0x17, 0xbe, 0xeb, 0x85, 0xed, 0x9e, 0xcd, 0x56, 0xf5, 0x17, 0x45, 0x42, 0xb4, 0x1f, 0x44, 0x4c, 0x05, 0x74, 0x15, 0x47, 0x00, 0xc6, 0x6a, 0x3d, 0x24, 0x09, 0x0d, 0x58, 0xb1, 0x42, 0xd7, 0x04}} , + {{0x8d, 0xbd, 0xa3, 0xc4, 0x06, 0x9b, 0x1f, 0x90, 0x58, 0x60, 0x74, 0xb2, 0x00, 0x3b, 0x3c, 0xd2, 0xda, 0x82, 0xbb, 0x10, 0x90, 0x69, 0x92, 0xa9, 0xb4, 0x30, 0x81, 0xe3, 0x7c, 0xa8, 0x89, 0x45}}}, +{{{0x3f, 0xdc, 0x05, 0xcb, 0x41, 0x3c, 0xc8, 0x23, 0x04, 0x2c, 0x38, 0x99, 0xe3, 0x68, 0x55, 0xf9, 0xd3, 0x32, 0xc7, 0xbf, 0xfa, 0xd4, 0x1b, 0x5d, 0xde, 0xdc, 0x10, 0x42, 0xc0, 0x42, 0xd9, 0x75}} , + {{0x2d, 0xab, 0x35, 0x4e, 0x87, 0xc4, 0x65, 0x97, 0x67, 0x24, 0xa4, 0x47, 0xad, 0x3f, 0x8e, 0xf3, 0xcb, 0x31, 0x17, 0x77, 0xc5, 0xe2, 0xd7, 0x8f, 0x3c, 0xc1, 0xcd, 0x56, 0x48, 0xc1, 0x6c, 0x69}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x14, 0xae, 0x5f, 0x88, 0x7b, 0xa5, 0x90, 0xdf, 0x10, 0xb2, 0x8b, 0x5e, 0x24, 0x17, 0xc3, 0xa3, 0xd4, 0x0f, 0x92, 0x61, 0x1a, 0x19, 0x5a, 0xad, 0x76, 0xbd, 0xd8, 0x1c, 0xdd, 0xe0, 0x12, 0x6d}} , + {{0x8e, 0xbd, 0x70, 0x8f, 0x02, 0xa3, 0x24, 0x4d, 0x5a, 0x67, 0xc4, 0xda, 0xf7, 0x20, 0x0f, 0x81, 0x5b, 0x7a, 0x05, 0x24, 0x67, 0x83, 0x0b, 0x2a, 0x80, 0xe7, 0xfd, 0x74, 0x4b, 0x9e, 0x5c, 0x0d}}}, +{{{0x94, 0xd5, 0x5f, 0x1f, 0xa2, 0xfb, 0xeb, 0xe1, 0x07, 0x34, 0xf8, 0x20, 0xad, 0x81, 0x30, 0x06, 0x2d, 0xa1, 0x81, 0x95, 0x36, 0xcf, 0x11, 0x0b, 0xaf, 0xc1, 0x2b, 0x9a, 0x6c, 0x55, 0xc1, 0x16}} , + {{0x36, 0x4f, 0xf1, 0x5e, 0x74, 0x35, 0x13, 0x28, 0xd7, 0x11, 0xcf, 0xb8, 0xde, 0x93, 0xb3, 0x05, 0xb8, 0xb5, 0x73, 0xe9, 0xeb, 0xad, 0x19, 0x1e, 0x89, 0x0f, 0x8b, 0x15, 0xd5, 0x8c, 0xe3, 0x23}}}, +{{{0x33, 0x79, 0xe7, 0x18, 0xe6, 0x0f, 0x57, 0x93, 0x15, 0xa0, 0xa7, 0xaa, 0xc4, 0xbf, 0x4f, 0x30, 0x74, 0x95, 0x5e, 0x69, 0x4a, 0x5b, 0x45, 0xe4, 0x00, 0xeb, 0x23, 0x74, 0x4c, 0xdf, 0x6b, 0x45}} , + {{0x97, 0x29, 0x6c, 0xc4, 0x42, 0x0b, 0xdd, 0xc0, 0x29, 0x5c, 0x9b, 0x34, 0x97, 0xd0, 0xc7, 0x79, 0x80, 0x63, 0x74, 0xe4, 0x8e, 0x37, 0xb0, 0x2b, 0x7c, 0xe8, 0x68, 0x6c, 0xc3, 0x82, 0x97, 0x57}}}, +{{{0x22, 0xbe, 0x83, 0xb6, 0x4b, 0x80, 0x6b, 0x43, 0x24, 0x5e, 0xef, 0x99, 0x9b, 0xa8, 0xfc, 0x25, 0x8d, 0x3b, 0x03, 0x94, 0x2b, 0x3e, 0xe7, 0x95, 0x76, 0x9b, 0xcc, 0x15, 0xdb, 0x32, 0xe6, 0x66}} , + {{0x84, 0xf0, 0x4a, 0x13, 0xa6, 0xd6, 0xfa, 0x93, 0x46, 0x07, 0xf6, 0x7e, 0x5c, 0x6d, 0x5e, 0xf6, 0xa6, 0xe7, 0x48, 0xf0, 0x06, 0xea, 0xff, 0x90, 0xc1, 0xcc, 0x4c, 0x19, 0x9c, 0x3c, 0x4e, 0x53}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2a, 0x50, 0xe3, 0x07, 0x15, 0x59, 0xf2, 0x8b, 0x81, 0xf2, 0xf3, 0xd3, 0x6c, 0x99, 0x8c, 0x70, 0x67, 0xec, 0xcc, 0xee, 0x9e, 0x59, 0x45, 0x59, 0x7d, 0x47, 0x75, 0x69, 0xf5, 0x24, 0x93, 0x5d}} , + {{0x6a, 0x4f, 0x1b, 0xbe, 0x6b, 0x30, 0xcf, 0x75, 0x46, 0xe3, 0x7b, 0x9d, 0xfc, 0xcd, 0xd8, 0x5c, 0x1f, 0xb4, 0xc8, 0xe2, 0x24, 0xec, 0x1a, 0x28, 0x05, 0x32, 0x57, 0xfd, 0x3c, 0x5a, 0x98, 0x10}}}, +{{{0xa3, 0xdb, 0xf7, 0x30, 0xd8, 0xc2, 0x9a, 0xe1, 0xd3, 0xce, 0x22, 0xe5, 0x80, 0x1e, 0xd9, 0xe4, 0x1f, 0xab, 0xc0, 0x71, 0x1a, 0x86, 0x0e, 0x27, 0x99, 0x5b, 0xfa, 0x76, 0x99, 0xb0, 0x08, 0x3c}} , + {{0x2a, 0x93, 0xd2, 0x85, 0x1b, 0x6a, 0x5d, 0xa6, 0xee, 0xd1, 0xd1, 0x33, 0xbd, 0x6a, 0x36, 0x73, 0x37, 0x3a, 0x44, 0xb4, 0xec, 0xa9, 0x7a, 0xde, 0x83, 0x40, 0xd7, 0xdf, 0x28, 0xba, 0xa2, 0x30}}}, +{{{0xd3, 0xb5, 0x6d, 0x05, 0x3f, 0x9f, 0xf3, 0x15, 0x8d, 0x7c, 0xca, 0xc9, 0xfc, 0x8a, 0x7c, 0x94, 0xb0, 0x63, 0x36, 0x9b, 0x78, 0xd1, 0x91, 0x1f, 0x93, 0xd8, 0x57, 0x43, 0xde, 0x76, 0xa3, 0x43}} , + {{0x9b, 0x35, 0xe2, 0xa9, 0x3d, 0x32, 0x1e, 0xbb, 0x16, 0x28, 0x70, 0xe9, 0x45, 0x2f, 0x8f, 0x70, 0x7f, 0x08, 0x7e, 0x53, 0xc4, 0x7a, 0xbf, 0xf7, 0xe1, 0xa4, 0x6a, 0xd8, 0xac, 0x64, 0x1b, 0x11}}}, +{{{0xb2, 0xeb, 0x47, 0x46, 0x18, 0x3e, 0x1f, 0x99, 0x0c, 0xcc, 0xf1, 0x2c, 0xe0, 0xe7, 0x8f, 0xe0, 0x01, 0x7e, 0x65, 0xb8, 0x0c, 0xd0, 0xfb, 0xc8, 0xb9, 0x90, 0x98, 0x33, 0x61, 0x3b, 0xd8, 0x27}} , + {{0xa0, 0xbe, 0x72, 0x3a, 0x50, 0x4b, 0x74, 0xab, 0x01, 0xc8, 0x93, 0xc5, 0xe4, 0xc7, 0x08, 0x6c, 0xb4, 0xca, 0xee, 0xeb, 0x8e, 0xd7, 0x4e, 0x26, 0xc6, 0x1d, 0xe2, 0x71, 0xaf, 0x89, 0xa0, 0x2a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x98, 0x0b, 0xe4, 0xde, 0xdb, 0xa8, 0xfa, 0x82, 0x74, 0x06, 0x52, 0x6d, 0x08, 0x52, 0x8a, 0xff, 0x62, 0xc5, 0x6a, 0x44, 0x0f, 0x51, 0x8c, 0x1f, 0x6e, 0xb6, 0xc6, 0x2c, 0x81, 0xd3, 0x76, 0x46}} , + {{0xf4, 0x29, 0x74, 0x2e, 0x80, 0xa7, 0x1a, 0x8f, 0xf6, 0xbd, 0xd6, 0x8e, 0xbf, 0xc1, 0x95, 0x2a, 0xeb, 0xa0, 0x7f, 0x45, 0xa0, 0x50, 0x14, 0x05, 0xb1, 0x57, 0x4c, 0x74, 0xb7, 0xe2, 0x89, 0x7d}}}, +{{{0x07, 0xee, 0xa7, 0xad, 0xb7, 0x09, 0x0b, 0x49, 0x4e, 0xbf, 0xca, 0xe5, 0x21, 0xe6, 0xe6, 0xaf, 0xd5, 0x67, 0xf3, 0xce, 0x7e, 0x7c, 0x93, 0x7b, 0x5a, 0x10, 0x12, 0x0e, 0x6c, 0x06, 0x11, 0x75}} , + {{0xd5, 0xfc, 0x86, 0xa3, 0x3b, 0xa3, 0x3e, 0x0a, 0xfb, 0x0b, 0xf7, 0x36, 0xb1, 0x5b, 0xda, 0x70, 0xb7, 0x00, 0xa7, 0xda, 0x88, 0x8f, 0x84, 0xa8, 0xbc, 0x1c, 0x39, 0xb8, 0x65, 0xf3, 0x4d, 0x60}}}, +{{{0x96, 0x9d, 0x31, 0xf4, 0xa2, 0xbe, 0x81, 0xb9, 0xa5, 0x59, 0x9e, 0xba, 0x07, 0xbe, 0x74, 0x58, 0xd8, 0xeb, 0xc5, 0x9f, 0x3d, 0xd1, 0xf4, 0xae, 0xce, 0x53, 0xdf, 0x4f, 0xc7, 0x2a, 0x89, 0x4d}} , + {{0x29, 0xd8, 0xf2, 0xaa, 0xe9, 0x0e, 0xf7, 0x2e, 0x5f, 0x9d, 0x8a, 0x5b, 0x09, 0xed, 0xc9, 0x24, 0x22, 0xf4, 0x0f, 0x25, 0x8f, 0x1c, 0x84, 0x6e, 0x34, 0x14, 0x6c, 0xea, 0xb3, 0x86, 0x5d, 0x04}}}, +{{{0x07, 0x98, 0x61, 0xe8, 0x6a, 0xd2, 0x81, 0x49, 0x25, 0xd5, 0x5b, 0x18, 0xc7, 0x35, 0x52, 0x51, 0xa4, 0x46, 0xad, 0x18, 0x0d, 0xc9, 0x5f, 0x18, 0x91, 0x3b, 0xb4, 0xc0, 0x60, 0x59, 0x8d, 0x66}} , + {{0x03, 0x1b, 0x79, 0x53, 0x6e, 0x24, 0xae, 0x57, 0xd9, 0x58, 0x09, 0x85, 0x48, 0xa2, 0xd3, 0xb5, 0xe2, 0x4d, 0x11, 0x82, 0xe6, 0x86, 0x3c, 0xe9, 0xb1, 0x00, 0x19, 0xc2, 0x57, 0xf7, 0x66, 0x7a}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x0f, 0xe3, 0x89, 0x03, 0xd7, 0x22, 0x95, 0x9f, 0xca, 0xb4, 0x8d, 0x9e, 0x6d, 0x97, 0xff, 0x8d, 0x21, 0x59, 0x07, 0xef, 0x03, 0x2d, 0x5e, 0xf8, 0x44, 0x46, 0xe7, 0x85, 0x80, 0xc5, 0x89, 0x50}} , + {{0x8b, 0xd8, 0x53, 0x86, 0x24, 0x86, 0x29, 0x52, 0x01, 0xfa, 0x20, 0xc3, 0x4e, 0x95, 0xcb, 0xad, 0x7b, 0x34, 0x94, 0x30, 0xb7, 0x7a, 0xfa, 0x96, 0x41, 0x60, 0x2b, 0xcb, 0x59, 0xb9, 0xca, 0x50}}}, +{{{0xc2, 0x5b, 0x9b, 0x78, 0x23, 0x1b, 0x3a, 0x88, 0x94, 0x5f, 0x0a, 0x9b, 0x98, 0x2b, 0x6e, 0x53, 0x11, 0xf6, 0xff, 0xc6, 0x7d, 0x42, 0xcc, 0x02, 0x80, 0x40, 0x0d, 0x1e, 0xfb, 0xaf, 0x61, 0x07}} , + {{0xb0, 0xe6, 0x2f, 0x81, 0x70, 0xa1, 0x2e, 0x39, 0x04, 0x7c, 0xc4, 0x2c, 0x87, 0x45, 0x4a, 0x5b, 0x69, 0x97, 0xac, 0x6d, 0x2c, 0x10, 0x42, 0x7c, 0x3b, 0x15, 0x70, 0x60, 0x0e, 0x11, 0x6d, 0x3a}}}, +{{{0x9b, 0x18, 0x80, 0x5e, 0xdb, 0x05, 0xbd, 0xc6, 0xb7, 0x3c, 0xc2, 0x40, 0x4d, 0x5d, 0xce, 0x97, 0x8a, 0x34, 0x15, 0xab, 0x28, 0x5d, 0x10, 0xf0, 0x37, 0x0c, 0xcc, 0x16, 0xfa, 0x1f, 0x33, 0x0d}} , + {{0x19, 0xf9, 0x35, 0xaa, 0x59, 0x1a, 0x0c, 0x5c, 0x06, 0xfc, 0x6a, 0x0b, 0x97, 0x53, 0x36, 0xfc, 0x2a, 0xa5, 0x5a, 0x9b, 0x30, 0xef, 0x23, 0xaf, 0x39, 0x5d, 0x9a, 0x6b, 0x75, 0x57, 0x48, 0x0b}}}, +{{{0x26, 0xdc, 0x76, 0x3b, 0xfc, 0xf9, 0x9c, 0x3f, 0x89, 0x0b, 0x62, 0x53, 0xaf, 0x83, 0x01, 0x2e, 0xbc, 0x6a, 0xc6, 0x03, 0x0d, 0x75, 0x2a, 0x0d, 0xe6, 0x94, 0x54, 0xcf, 0xb3, 0xe5, 0x96, 0x25}} , + {{0xfe, 0x82, 0xb1, 0x74, 0x31, 0x8a, 0xa7, 0x6f, 0x56, 0xbd, 0x8d, 0xf4, 0xe0, 0x94, 0x51, 0x59, 0xde, 0x2c, 0x5a, 0xf4, 0x84, 0x6b, 0x4a, 0x88, 0x93, 0xc0, 0x0c, 0x9a, 0xac, 0xa7, 0xa0, 0x68}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x25, 0x0d, 0xd6, 0xc7, 0x23, 0x47, 0x10, 0xad, 0xc7, 0x08, 0x5c, 0x87, 0x87, 0x93, 0x98, 0x18, 0xb8, 0xd3, 0x9c, 0xac, 0x5a, 0x3d, 0xc5, 0x75, 0xf8, 0x49, 0x32, 0x14, 0xcc, 0x51, 0x96, 0x24}} , + {{0x65, 0x9c, 0x5d, 0xf0, 0x37, 0x04, 0xf0, 0x34, 0x69, 0x2a, 0xf0, 0xa5, 0x64, 0xca, 0xde, 0x2b, 0x5b, 0x15, 0x10, 0xd2, 0xab, 0x06, 0xdd, 0xc4, 0xb0, 0xb6, 0x5b, 0xc1, 0x17, 0xdf, 0x8f, 0x02}}}, +{{{0xbd, 0x59, 0x3d, 0xbf, 0x5c, 0x31, 0x44, 0x2c, 0x32, 0x94, 0x04, 0x60, 0x84, 0x0f, 0xad, 0x00, 0xb6, 0x8f, 0xc9, 0x1d, 0xcc, 0x5c, 0xa2, 0x49, 0x0e, 0x50, 0x91, 0x08, 0x9a, 0x43, 0x55, 0x05}} , + {{0x5d, 0x93, 0x55, 0xdf, 0x9b, 0x12, 0x19, 0xec, 0x93, 0x85, 0x42, 0x9e, 0x66, 0x0f, 0x9d, 0xaf, 0x99, 0xaf, 0x26, 0x89, 0xbc, 0x61, 0xfd, 0xff, 0xce, 0x4b, 0xf4, 0x33, 0x95, 0xc9, 0x35, 0x58}}}, +{{{0x12, 0x55, 0xf9, 0xda, 0xcb, 0x44, 0xa7, 0xdc, 0x57, 0xe2, 0xf9, 0x9a, 0xe6, 0x07, 0x23, 0x60, 0x54, 0xa7, 0x39, 0xa5, 0x9b, 0x84, 0x56, 0x6e, 0xaa, 0x8b, 0x8f, 0xb0, 0x2c, 0x87, 0xaf, 0x67}} , + {{0x00, 0xa9, 0x4c, 0xb2, 0x12, 0xf8, 0x32, 0xa8, 0x7a, 0x00, 0x4b, 0x49, 0x32, 0xba, 0x1f, 0x5d, 0x44, 0x8e, 0x44, 0x7a, 0xdc, 0x11, 0xfb, 0x39, 0x08, 0x57, 0x87, 0xa5, 0x12, 0x42, 0x93, 0x0e}}}, +{{{0x17, 0xb4, 0xae, 0x72, 0x59, 0xd0, 0xaa, 0xa8, 0x16, 0x8b, 0x63, 0x11, 0xb3, 0x43, 0x04, 0xda, 0x0c, 0xa8, 0xb7, 0x68, 0xdd, 0x4e, 0x54, 0xe7, 0xaf, 0x5d, 0x5d, 0x05, 0x76, 0x36, 0xec, 0x0d}} , + {{0x6d, 0x7c, 0x82, 0x32, 0x38, 0x55, 0x57, 0x74, 0x5b, 0x7d, 0xc3, 0xc4, 0xfb, 0x06, 0x29, 0xf0, 0x13, 0x55, 0x54, 0xc6, 0xa7, 0xdc, 0x4c, 0x9f, 0x98, 0x49, 0x20, 0xa8, 0xc3, 0x8d, 0xfa, 0x48}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x87, 0x47, 0x9d, 0xe9, 0x25, 0xd5, 0xe3, 0x47, 0x78, 0xdf, 0x85, 0xa7, 0x85, 0x5e, 0x7a, 0x4c, 0x5f, 0x79, 0x1a, 0xf3, 0xa2, 0xb2, 0x28, 0xa0, 0x9c, 0xdd, 0x30, 0x40, 0xd4, 0x38, 0xbd, 0x28}} , + {{0xfc, 0xbb, 0xd5, 0x78, 0x6d, 0x1d, 0xd4, 0x99, 0xb4, 0xaa, 0x44, 0x44, 0x7a, 0x1b, 0xd8, 0xfe, 0xb4, 0x99, 0xb9, 0xcc, 0xe7, 0xc4, 0xd3, 0x3a, 0x73, 0x83, 0x41, 0x5c, 0x40, 0xd7, 0x2d, 0x55}}}, +{{{0x26, 0xe1, 0x7b, 0x5f, 0xe5, 0xdc, 0x3f, 0x7d, 0xa1, 0xa7, 0x26, 0x44, 0x22, 0x23, 0xc0, 0x8f, 0x7d, 0xf1, 0xb5, 0x11, 0x47, 0x7b, 0x19, 0xd4, 0x75, 0x6f, 0x1e, 0xa5, 0x27, 0xfe, 0xc8, 0x0e}} , + {{0xd3, 0x11, 0x3d, 0xab, 0xef, 0x2c, 0xed, 0xb1, 0x3d, 0x7c, 0x32, 0x81, 0x6b, 0xfe, 0xf8, 0x1c, 0x3c, 0x7b, 0xc0, 0x61, 0xdf, 0xb8, 0x75, 0x76, 0x7f, 0xaa, 0xd8, 0x93, 0xaf, 0x3d, 0xe8, 0x3d}}}, +{{{0xfd, 0x5b, 0x4e, 0x8d, 0xb6, 0x7e, 0x82, 0x9b, 0xef, 0xce, 0x04, 0x69, 0x51, 0x52, 0xff, 0xef, 0xa0, 0x52, 0xb5, 0x79, 0x17, 0x5e, 0x2f, 0xde, 0xd6, 0x3c, 0x2d, 0xa0, 0x43, 0xb4, 0x0b, 0x19}} , + {{0xc0, 0x61, 0x48, 0x48, 0x17, 0xf4, 0x9e, 0x18, 0x51, 0x2d, 0xea, 0x2f, 0xf2, 0xf2, 0xe0, 0xa3, 0x14, 0xb7, 0x8b, 0x3a, 0x30, 0xf5, 0x81, 0xc1, 0x5d, 0x71, 0x39, 0x62, 0x55, 0x1f, 0x60, 0x5a}}}, +{{{0xe5, 0x89, 0x8a, 0x76, 0x6c, 0xdb, 0x4d, 0x0a, 0x5b, 0x72, 0x9d, 0x59, 0x6e, 0x63, 0x63, 0x18, 0x7c, 0xe3, 0xfa, 0xe2, 0xdb, 0xa1, 0x8d, 0xf4, 0xa5, 0xd7, 0x16, 0xb2, 0xd0, 0xb3, 0x3f, 0x39}} , + {{0xce, 0x60, 0x09, 0x6c, 0xf5, 0x76, 0x17, 0x24, 0x80, 0x3a, 0x96, 0xc7, 0x94, 0x2e, 0xf7, 0x6b, 0xef, 0xb5, 0x05, 0x96, 0xef, 0xd3, 0x7b, 0x51, 0xda, 0x05, 0x44, 0x67, 0xbc, 0x07, 0x21, 0x4e}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xe9, 0x73, 0x6f, 0x21, 0xb9, 0xde, 0x22, 0x7d, 0xeb, 0x97, 0x31, 0x10, 0xa3, 0xea, 0xe1, 0xc6, 0x37, 0xeb, 0x8f, 0x43, 0x58, 0xde, 0x41, 0x64, 0x0e, 0x3e, 0x07, 0x99, 0x3d, 0xf1, 0xdf, 0x1e}} , + {{0xf8, 0xad, 0x43, 0xc2, 0x17, 0x06, 0xe2, 0xe4, 0xa9, 0x86, 0xcd, 0x18, 0xd7, 0x78, 0xc8, 0x74, 0x66, 0xd2, 0x09, 0x18, 0xa5, 0xf1, 0xca, 0xa6, 0x62, 0x92, 0xc1, 0xcb, 0x00, 0xeb, 0x42, 0x2e}}}, +{{{0x7b, 0x34, 0x24, 0x4c, 0xcf, 0x38, 0xe5, 0x6c, 0x0a, 0x01, 0x2c, 0x22, 0x0b, 0x24, 0x38, 0xad, 0x24, 0x7e, 0x19, 0xf0, 0x6c, 0xf9, 0x31, 0xf4, 0x35, 0x11, 0xf6, 0x46, 0x33, 0x3a, 0x23, 0x59}} , + {{0x20, 0x0b, 0xa1, 0x08, 0x19, 0xad, 0x39, 0x54, 0xea, 0x3e, 0x23, 0x09, 0xb6, 0xe2, 0xd2, 0xbc, 0x4d, 0xfc, 0x9c, 0xf0, 0x13, 0x16, 0x22, 0x3f, 0xb9, 0xd2, 0x11, 0x86, 0x90, 0x55, 0xce, 0x3c}}}, +{{{0xc4, 0x0b, 0x4b, 0x62, 0x99, 0x37, 0x84, 0x3f, 0x74, 0xa2, 0xf9, 0xce, 0xe2, 0x0b, 0x0f, 0x2a, 0x3d, 0xa3, 0xe3, 0xdb, 0x5a, 0x9d, 0x93, 0xcc, 0xa5, 0xef, 0x82, 0x91, 0x1d, 0xe6, 0x6c, 0x68}} , + {{0xa3, 0x64, 0x17, 0x9b, 0x8b, 0xc8, 0x3a, 0x61, 0xe6, 0x9d, 0xc6, 0xed, 0x7b, 0x03, 0x52, 0x26, 0x9d, 0x3a, 0xb3, 0x13, 0xcc, 0x8a, 0xfd, 0x2c, 0x1a, 0x1d, 0xed, 0x13, 0xd0, 0x55, 0x57, 0x0e}}}, +{{{0x1a, 0xea, 0xbf, 0xfd, 0x4a, 0x3c, 0x8e, 0xec, 0x29, 0x7e, 0x77, 0x77, 0x12, 0x99, 0xd7, 0x84, 0xf9, 0x55, 0x7f, 0xf1, 0x8b, 0xb4, 0xd2, 0x95, 0xa3, 0x8d, 0xf0, 0x8a, 0xa7, 0xeb, 0x82, 0x4b}} , + {{0x2c, 0x28, 0xf4, 0x3a, 0xf6, 0xde, 0x0a, 0xe0, 0x41, 0x44, 0x23, 0xf8, 0x3f, 0x03, 0x64, 0x9f, 0xc3, 0x55, 0x4c, 0xc6, 0xc1, 0x94, 0x1c, 0x24, 0x5d, 0x5f, 0x92, 0x45, 0x96, 0x57, 0x37, 0x14}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xc1, 0xcd, 0x90, 0x66, 0xb9, 0x76, 0xa0, 0x5b, 0xa5, 0x85, 0x75, 0x23, 0xf9, 0x89, 0xa5, 0x82, 0xb2, 0x6f, 0xb1, 0xeb, 0xc4, 0x69, 0x6f, 0x18, 0x5a, 0xed, 0x94, 0x3d, 0x9d, 0xd9, 0x2c, 0x1a}} , + {{0x35, 0xb0, 0xe6, 0x73, 0x06, 0xb7, 0x37, 0xe0, 0xf8, 0xb0, 0x22, 0xe8, 0xd2, 0xed, 0x0b, 0xef, 0xe6, 0xc6, 0x5a, 0x99, 0x9e, 0x1a, 0x9f, 0x04, 0x97, 0xe4, 0x4d, 0x0b, 0xbe, 0xba, 0x44, 0x40}}}, +{{{0xc1, 0x56, 0x96, 0x91, 0x5f, 0x1f, 0xbb, 0x54, 0x6f, 0x88, 0x89, 0x0a, 0xb2, 0xd6, 0x41, 0x42, 0x6a, 0x82, 0xee, 0x14, 0xaa, 0x76, 0x30, 0x65, 0x0f, 0x67, 0x39, 0xa6, 0x51, 0x7c, 0x49, 0x24}} , + {{0x35, 0xa3, 0x78, 0xd1, 0x11, 0x0f, 0x75, 0xd3, 0x70, 0x46, 0xdb, 0x20, 0x51, 0xcb, 0x92, 0x80, 0x54, 0x10, 0x74, 0x36, 0x86, 0xa9, 0xd7, 0xa3, 0x08, 0x78, 0xf1, 0x01, 0x29, 0xf8, 0x80, 0x3b}}}, +{{{0xdb, 0xa7, 0x9d, 0x9d, 0xbf, 0xa0, 0xcc, 0xed, 0x53, 0xa2, 0xa2, 0x19, 0x39, 0x48, 0x83, 0x19, 0x37, 0x58, 0xd1, 0x04, 0x28, 0x40, 0xf7, 0x8a, 0xc2, 0x08, 0xb7, 0xa5, 0x42, 0xcf, 0x53, 0x4c}} , + {{0xa7, 0xbb, 0xf6, 0x8e, 0xad, 0xdd, 0xf7, 0x90, 0xdd, 0x5f, 0x93, 0x89, 0xae, 0x04, 0x37, 0xe6, 0x9a, 0xb7, 0xe8, 0xc0, 0xdf, 0x16, 0x2a, 0xbf, 0xc4, 0x3a, 0x3c, 0x41, 0xd5, 0x89, 0x72, 0x5a}}}, +{{{0x1f, 0x96, 0xff, 0x34, 0x2c, 0x13, 0x21, 0xcb, 0x0a, 0x89, 0x85, 0xbe, 0xb3, 0x70, 0x9e, 0x1e, 0xde, 0x97, 0xaf, 0x96, 0x30, 0xf7, 0x48, 0x89, 0x40, 0x8d, 0x07, 0xf1, 0x25, 0xf0, 0x30, 0x58}} , + {{0x1e, 0xd4, 0x93, 0x57, 0xe2, 0x17, 0xe7, 0x9d, 0xab, 0x3c, 0x55, 0x03, 0x82, 0x2f, 0x2b, 0xdb, 0x56, 0x1e, 0x30, 0x2e, 0x24, 0x47, 0x6e, 0xe6, 0xff, 0x33, 0x24, 0x2c, 0x75, 0x51, 0xd4, 0x67}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0x2b, 0x06, 0xd9, 0xa1, 0x5d, 0xe1, 0xf4, 0xd1, 0x1e, 0x3c, 0x9a, 0xc6, 0x29, 0x2b, 0x13, 0x13, 0x78, 0xc0, 0xd8, 0x16, 0x17, 0x2d, 0x9e, 0xa9, 0xc9, 0x79, 0x57, 0xab, 0x24, 0x91, 0x92, 0x19}} , + {{0x69, 0xfb, 0xa1, 0x9c, 0xa6, 0x75, 0x49, 0x7d, 0x60, 0x73, 0x40, 0x42, 0xc4, 0x13, 0x0a, 0x95, 0x79, 0x1e, 0x04, 0x83, 0x94, 0x99, 0x9b, 0x1e, 0x0c, 0xe8, 0x1f, 0x54, 0xef, 0xcb, 0xc0, 0x52}}}, +{{{0x14, 0x89, 0x73, 0xa1, 0x37, 0x87, 0x6a, 0x7a, 0xcf, 0x1d, 0xd9, 0x2e, 0x1a, 0x67, 0xed, 0x74, 0xc0, 0xf0, 0x9c, 0x33, 0xdd, 0xdf, 0x08, 0xbf, 0x7b, 0xd1, 0x66, 0xda, 0xe6, 0xc9, 0x49, 0x08}} , + {{0xe9, 0xdd, 0x5e, 0x55, 0xb0, 0x0a, 0xde, 0x21, 0x4c, 0x5a, 0x2e, 0xd4, 0x80, 0x3a, 0x57, 0x92, 0x7a, 0xf1, 0xc4, 0x2c, 0x40, 0xaf, 0x2f, 0xc9, 0x92, 0x03, 0xe5, 0x5a, 0xbc, 0xdc, 0xf4, 0x09}}}, +{{{0xf3, 0xe1, 0x2b, 0x7c, 0x05, 0x86, 0x80, 0x93, 0x4a, 0xad, 0xb4, 0x8f, 0x7e, 0x99, 0x0c, 0xfd, 0xcd, 0xef, 0xd1, 0xff, 0x2c, 0x69, 0x34, 0x13, 0x41, 0x64, 0xcf, 0x3b, 0xd0, 0x90, 0x09, 0x1e}} , + {{0x9d, 0x45, 0xd6, 0x80, 0xe6, 0x45, 0xaa, 0xf4, 0x15, 0xaa, 0x5c, 0x34, 0x87, 0x99, 0xa2, 0x8c, 0x26, 0x84, 0x62, 0x7d, 0xb6, 0x29, 0xc0, 0x52, 0xea, 0xf5, 0x81, 0x18, 0x0f, 0x35, 0xa9, 0x0e}}}, +{{{0xe7, 0x20, 0x72, 0x7c, 0x6d, 0x94, 0x5f, 0x52, 0x44, 0x54, 0xe3, 0xf1, 0xb2, 0xb0, 0x36, 0x46, 0x0f, 0xae, 0x92, 0xe8, 0x70, 0x9d, 0x6e, 0x79, 0xb1, 0xad, 0x37, 0xa9, 0x5f, 0xc0, 0xde, 0x03}} , + {{0x15, 0x55, 0x37, 0xc6, 0x1c, 0x27, 0x1c, 0x6d, 0x14, 0x4f, 0xca, 0xa4, 0xc4, 0x88, 0x25, 0x46, 0x39, 0xfc, 0x5a, 0xe5, 0xfe, 0x29, 0x11, 0x69, 0xf5, 0x72, 0x84, 0x4d, 0x78, 0x9f, 0x94, 0x15}}}, +{{{0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}, + {{0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}}}, +{{{0xec, 0xd3, 0xff, 0x57, 0x0b, 0xb0, 0xb2, 0xdc, 0xf8, 0x4f, 0xe2, 0x12, 0xd5, 0x36, 0xbe, 0x6b, 0x09, 0x43, 0x6d, 0xa3, 0x4d, 0x90, 0x2d, 0xb8, 0x74, 0xe8, 0x71, 0x45, 0x19, 0x8b, 0x0c, 0x6a}} , + {{0xb8, 0x42, 0x1c, 0x03, 0xad, 0x2c, 0x03, 0x8e, 0xac, 0xd7, 0x98, 0x29, 0x13, 0xc6, 0x02, 0x29, 0xb5, 0xd4, 0xe7, 0xcf, 0xcc, 0x8b, 0x83, 0xec, 0x35, 0xc7, 0x9c, 0x74, 0xb7, 0xad, 0x85, 0x5f}}}, +{{{0x78, 0x84, 0xe1, 0x56, 0x45, 0x69, 0x68, 0x5a, 0x4f, 0xb8, 0xb1, 0x29, 0xff, 0x33, 0x03, 0x31, 0xb7, 0xcb, 0x96, 0x25, 0xe6, 0xe6, 0x41, 0x98, 0x1a, 0xbb, 0x03, 0x56, 0xf2, 0xb2, 0x91, 0x34}} , + {{0x2c, 0x6c, 0xf7, 0x66, 0xa4, 0x62, 0x6b, 0x39, 0xb3, 0xba, 0x65, 0xd3, 0x1c, 0xf8, 0x11, 0xaa, 0xbe, 0xdc, 0x80, 0x59, 0x87, 0xf5, 0x7b, 0xe5, 0xe3, 0xb3, 0x3e, 0x39, 0xda, 0xbe, 0x88, 0x09}}}, +{{{0x8b, 0xf1, 0xa0, 0xf5, 0xdc, 0x29, 0xb4, 0xe2, 0x07, 0xc6, 0x7a, 0x00, 0xd0, 0x89, 0x17, 0x51, 0xd4, 0xbb, 0xd4, 0x22, 0xea, 0x7e, 0x7d, 0x7c, 0x24, 0xea, 0xf2, 0xe8, 0x22, 0x12, 0x95, 0x06}} , + {{0xda, 0x7c, 0xa4, 0x0c, 0xf4, 0xba, 0x6e, 0xe1, 0x89, 0xb5, 0x59, 0xca, 0xf1, 0xc0, 0x29, 0x36, 0x09, 0x44, 0xe2, 0x7f, 0xd1, 0x63, 0x15, 0x99, 0xea, 0x25, 0xcf, 0x0c, 0x9d, 0xc0, 0x44, 0x6f}}}, +{{{0x1d, 0x86, 0x4e, 0xcf, 0xf7, 0x37, 0x10, 0x25, 0x8f, 0x12, 0xfb, 0x19, 0xfb, 0xe0, 0xed, 0x10, 0xc8, 0xe2, 0xf5, 0x75, 0xb1, 0x33, 0xc0, 0x96, 0x0d, 0xfb, 0x15, 0x6c, 0x0d, 0x07, 0x5f, 0x05}} , + {{0x69, 0x3e, 0x47, 0x97, 0x2c, 0xaf, 0x52, 0x7c, 0x78, 0x83, 0xad, 0x1b, 0x39, 0x82, 0x2f, 0x02, 0x6f, 0x47, 0xdb, 0x2a, 0xb0, 0xe1, 0x91, 0x99, 0x55, 0xb8, 0x99, 0x3a, 0xa0, 0x44, 0x11, 0x51}}} diff --git a/src/libs/libssh-0.12.2/src/external/libcrux_mlkem768_sha3.c b/src/libs/libssh-0.12.2/src/external/libcrux_mlkem768_sha3.c new file mode 100644 index 000000000000..59130fbd40a4 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/libcrux_mlkem768_sha3.c @@ -0,0 +1,8897 @@ +/* $OpenBSD: libcrux_mlkem768_sha3.h,v 1.4 2025/11/13 05:13:06 djm Exp $ */ + +/* Extracted from libcrux revision 026a87ab6d88ad3626b9fbbf3710d1e0483c1849 */ + +/* + * MIT License + * + * Copyright (c) 2024 Cryspen + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +#include "config.h" + +#include + +#include "libssh/mlkem_native.h" + +#if !defined(__GNUC__) || (__GNUC__ < 2) +# define __attribute__(x) +#endif +#define KRML_MUSTINLINE inline +#define KRML_NOINLINE __attribute__((noinline, unused)) +#define KRML_HOST_EPRINTF(...) +#define KRML_HOST_EXIT(x) do { \ + fprintf(stderr, "mlkem internal error"); \ + exit(x); \ +} while (0) + +static inline void +store64_le(uint8_t dst[8], uint64_t src) +{ + dst[0] = src & 0xff; + dst[1] = (src >> 8) & 0xff; + dst[2] = (src >> 16) & 0xff; + dst[3] = (src >> 24) & 0xff; + dst[4] = (src >> 32) & 0xff; + dst[5] = (src >> 40) & 0xff; + dst[6] = (src >> 48) & 0xff; + dst[7] = (src >> 56) & 0xff; +} + +static inline uint64_t +load64_le(uint8_t src[8]) +{ + return (uint64_t)(src[0]) | + ((uint64_t)(src[1]) << 8) | + ((uint64_t)(src[2]) << 16) | + ((uint64_t)(src[3]) << 24) | + ((uint64_t)(src[4]) << 32) | + ((uint64_t)(src[5]) << 40) | + ((uint64_t)(src[6]) << 48) | + ((uint64_t)(src[7]) << 56); +} + +#ifdef MISSING_BUILTIN_POPCOUNT +static inline unsigned int +__builtin_popcount(unsigned int num) +{ + const int v[16] = { 0, 1, 1, 2, 1, 2, 2, 3, 1, 2, 2, 3, 2, 3, 3, 4 }; + return v[num & 0xf] + v[(num >> 4) & 0xf]; +} +#endif + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/eurydice_glue.h */ + + +#ifdef _MSC_VER +// For __popcnt +#endif + + +// C++ HELPERS + +#if defined(__cplusplus) + +#ifndef KRML_HOST_EPRINTF +#define KRML_HOST_EPRINTF(...) fprintf(stderr, __VA_ARGS__) +#endif + + +#ifndef __cpp_lib_type_identity +template +struct type_identity { + using type = T; +}; + +template +using type_identity_t = typename type_identity::type; +#else +using std::type_identity_t; +#endif + +#define KRML_UNION_CONSTRUCTOR(T) \ + template \ + constexpr T(int t, V U::*m, type_identity_t v) : tag(t) { \ + val.*m = std::move(v); \ + } \ + T() = default; + +#endif + +// GENERAL-PURPOSE STUFF + +#define LowStar_Ignore_ignore(e, t, _ret_t) ((void)e) + +#define EURYDICE_ASSERT(test, msg) \ + do { \ + if (!(test)) { \ + fprintf(stderr, "assertion \"%s\" failed: file \"%s\", line %d\n", msg, \ + __FILE__, __LINE__); \ + exit(255); \ + } \ + } while (0) + +// SLICES, ARRAYS, ETC. + +// We represent a slice as a pair of an (untyped) pointer, along with the length +// of the slice, i.e. the number of elements in the slice (this is NOT the +// number of bytes). This design choice has two important consequences. +// - if you need to use `ptr`, you MUST cast it to a proper type *before* +// performing pointer arithmetic on it (remember that C desugars pointer +// arithmetic based on the type of the address) +// - if you need to use `len` for a C style function (e.g. memcpy, memcmp), you +// need to multiply it by sizeof t, where t is the type of the elements. +// +// Empty slices have `len == 0` and `ptr` always needs to be a valid pointer +// that is not NULL (otherwise the construction in EURYDICE_SLICE computes `NULL +// + start`). +typedef struct { + void *ptr; + size_t len; +} Eurydice_slice; + +#if defined(__cplusplus) +#define KRML_CLITERAL(type) type +#else +#define KRML_CLITERAL(type) (type) +#endif + +#if defined(__cplusplus) && defined(__cpp_designated_initializers) || \ + !(defined(__cplusplus)) +#define EURYDICE_CFIELD(X) X +#else +#define EURYDICE_CFIELD(X) +#endif + +// Helper macro to create a slice out of a pointer x, a start index in x +// (included), and an end index in x (excluded). The argument x must be suitably +// cast to something that can decay (see remark above about how pointer +// arithmetic works in C), meaning either pointer or array type. +#define EURYDICE_SLICE(x, start, end) \ + (KRML_CLITERAL(Eurydice_slice){(void *)(x + start), end - start}) + +// Slice length +#define EURYDICE_SLICE_LEN(s, _) (s).len +#define Eurydice_slice_len(s, _) (s).len + +// This macro is a pain because in case the dereferenced element type is an +// array, you cannot simply write `t x` as it would yield `int[4] x` instead, +// which is NOT correct C syntax, so we add a dedicated phase in Eurydice that +// adds an extra argument to this macro at the last minute so that we have the +// correct type of *pointers* to elements. +#define Eurydice_slice_index(s, i, t, t_ptr_t) (((t_ptr_t)s.ptr)[i]) + +// The following functions get sub slices from a slice. + +#define Eurydice_slice_subslice(s, r, t, _0, _1) \ + EURYDICE_SLICE((t *)s.ptr, r.start, r.end) + +// Variant for when the start and end indices are statically known (i.e., the +// range argument `r` is a literal). +#define Eurydice_slice_subslice2(s, start, end, t) \ + EURYDICE_SLICE((t *)s.ptr, (start), (end)) + +// Previous version above does not work when t is an array type (as usual). Will +// be deprecated soon. +#define Eurydice_slice_subslice3(s, start, end, t_ptr) \ + EURYDICE_SLICE((t_ptr)s.ptr, (start), (end)) + +#define Eurydice_slice_subslice_to(s, subslice_end_pos, t, _0, _1) \ + EURYDICE_SLICE((t *)s.ptr, 0, subslice_end_pos) + +#define Eurydice_slice_subslice_from(s, subslice_start_pos, t, _0, _1) \ + EURYDICE_SLICE((t *)s.ptr, subslice_start_pos, s.len) + +#define Eurydice_array_to_slice(end, x, t) \ + EURYDICE_SLICE(x, 0, \ + end) /* x is already at an array type, no need for cast */ +#define Eurydice_array_to_subslice(_arraylen, x, r, t, _0, _1) \ + EURYDICE_SLICE((t *)x, r.start, r.end) + +// Same as above, variant for when start and end are statically known +#define Eurydice_array_to_subslice2(x, start, end, t) \ + EURYDICE_SLICE((t *)x, (start), (end)) + +// Same as above, variant for when start and end are statically known +#define Eurydice_array_to_subslice3(x, start, end, t_ptr) \ + EURYDICE_SLICE((t_ptr)x, (start), (end)) + +#define Eurydice_array_repeat(dst, len, init, t) \ + ERROR "should've been desugared" + +// The following functions convert an array into a slice. + +#define Eurydice_array_to_subslice_to(_size, x, r, t, _range_t, _0) \ + EURYDICE_SLICE((t *)x, 0, r) +#define Eurydice_array_to_subslice_from(size, x, r, t, _range_t, _0) \ + EURYDICE_SLICE((t *)x, r, size) + +// Copy a slice with memcopy +#define Eurydice_slice_copy(dst, src, t) \ + memcpy(dst.ptr, src.ptr, dst.len * sizeof(t)) + +#define core_array___Array_T__N___as_slice(len_, ptr_, t, _ret_t) \ + KRML_CLITERAL(Eurydice_slice) { ptr_, len_ } + +#define core_array__core__clone__Clone_for__Array_T__N___clone( \ + len, src, dst, elem_type, _ret_t) \ + (memcpy(dst, src, len * sizeof(elem_type))) +#define TryFromSliceError uint8_t +#define core_array_TryFromSliceError uint8_t + +#define Eurydice_array_eq(sz, a1, a2, t) (memcmp(a1, a2, sz * sizeof(t)) == 0) + +// core::cmp::PartialEq<&0 (@Slice)> for @Array +#define Eurydice_array_eq_slice(sz, a1, s2, t, _) \ + (memcmp(a1, (s2)->ptr, sz * sizeof(t)) == 0) + +#define core_array_equality___core__cmp__PartialEq__Array_U__N___for__Array_T__N____eq( \ + sz, a1, a2, t, _, _ret_t) \ + Eurydice_array_eq(sz, a1, a2, t, _) +#define core_array_equality___core__cmp__PartialEq__0___Slice_U____for__Array_T__N___3__eq( \ + sz, a1, a2, t, _, _ret_t) \ + Eurydice_array_eq(sz, a1, ((a2)->ptr), t, _) + +#define Eurydice_slice_split_at(slice, mid, element_type, ret_t) \ + KRML_CLITERAL(ret_t) { \ + EURYDICE_CFIELD(.fst =) \ + EURYDICE_SLICE((element_type *)(slice).ptr, 0, mid), \ + EURYDICE_CFIELD(.snd =) \ + EURYDICE_SLICE((element_type *)(slice).ptr, mid, (slice).len) \ + } + +#define Eurydice_slice_split_at_mut(slice, mid, element_type, ret_t) \ + KRML_CLITERAL(ret_t) { \ + EURYDICE_CFIELD(.fst =) \ + KRML_CLITERAL(Eurydice_slice){EURYDICE_CFIELD(.ptr =)(slice.ptr), \ + EURYDICE_CFIELD(.len =) mid}, \ + EURYDICE_CFIELD(.snd =) KRML_CLITERAL(Eurydice_slice) { \ + EURYDICE_CFIELD(.ptr =) \ + ((char *)slice.ptr + mid * sizeof(element_type)), \ + EURYDICE_CFIELD(.len =)(slice.len - mid) \ + } \ + } + +// Conversion of slice to an array, rewritten (by Eurydice) to name the +// destination array, since arrays are not values in C. +// N.B.: see note in karamel/lib/Inlining.ml if you change this. +#define Eurydice_slice_to_array2(dst, src, _0, t_arr, _1) \ + Eurydice_slice_to_array3(&(dst)->tag, (char *)&(dst)->val.case_Ok, src, \ + sizeof(t_arr)) + +static inline void Eurydice_slice_to_array3(uint8_t *dst_tag, char *dst_ok, + Eurydice_slice src, size_t sz) { + *dst_tag = 0; + memcpy(dst_ok, src.ptr, sz); +} + +// SUPPORT FOR DSTs (Dynamically-Sized Types) + +// A DST is a fat pointer that keeps tracks of the size of it flexible array +// member. Slices are a specific case of DSTs, where [T; N] implements +// Unsize<[T]>, meaning an array of statically known size can be converted to a +// fat pointer, i.e. a slice. +// +// Unlike slices, DSTs have a built-in definition that gets monomorphized, of +// the form: +// +// typedef struct { +// T *ptr; +// size_t len; // number of elements +// } Eurydice_dst; +// +// Furthermore, T = T0<[U0]> where `struct T0`, where the `U` is the +// last field. This means that there are two monomorphizations of T0 in the +// program. One is `T0<[V; N]>` +// -- this is directly converted to a Eurydice_dst via suitable codegen (no +// macro). The other is `T = T0<[U]>`, where `[U]` gets emitted to +// `Eurydice_derefed_slice`, a type that only appears in that precise situation +// and is thus defined to give rise to a flexible array member. + +typedef char Eurydice_derefed_slice[]; + +#define Eurydice_slice_of_dst(fam_ptr, len_, t, _) \ + ((Eurydice_slice){.ptr = (void *)(fam_ptr), .len = len_}) + +#define Eurydice_slice_of_boxed_array(ptr_, len_, t, _) \ + ((Eurydice_slice){.ptr = (void *)(ptr_), .len = len_}) + +// CORE STUFF (conversions, endianness, ...) + +// We slap extern "C" on declarations that intend to implement a prototype +// generated by Eurydice, because Eurydice prototypes are always emitted within +// an extern "C" block, UNLESS you use -fcxx17-compat, in which case, you must +// pass -DKRML_CXX17_COMPAT="" to your C++ compiler. +#if defined(__cplusplus) && !defined(KRML_CXX17_COMPAT) +extern "C" { +#endif + +static inline void core_num__u64__to_le_bytes(uint64_t v, uint8_t buf[8]) { + store64_le(buf, v); +} + +static inline uint64_t core_num__u64__from_le_bytes(uint8_t buf[8]) { + return load64_le(buf); +} + +// unsigned overflow wraparound semantics in C +static inline uint16_t core_num__u16__wrapping_add(uint16_t x, uint16_t y) { + return x + y; +} +static inline uint8_t core_num__u8__wrapping_sub(uint8_t x, uint8_t y) { + return x - y; +} +static inline uint64_t core_num__u64__rotate_left(uint64_t x0, uint32_t x1) { + return (x0 << x1 | x0 >> (64 - x1)); +} + +#if defined(__cplusplus) && !defined(KRML_CXX17_COMPAT) +} +#endif + +// ITERATORS + +#define Eurydice_range_iter_next(iter_ptr, t, ret_t) \ + (((iter_ptr)->start >= (iter_ptr)->end) \ + ? (KRML_CLITERAL(ret_t){EURYDICE_CFIELD(.tag =) 0, \ + EURYDICE_CFIELD(.f0 =) 0}) \ + : (KRML_CLITERAL(ret_t){EURYDICE_CFIELD(.tag =) 1, \ + EURYDICE_CFIELD(.f0 =)(iter_ptr)->start++})) + +#define core_iter_range___core__iter__traits__iterator__Iterator_A__for_core__ops__range__Range_A__TraitClause_0___6__next \ + Eurydice_range_iter_next + +// See note in karamel/lib/Inlining.ml if you change this +#define Eurydice_into_iter(x, t, _ret_t, _) (x) +#define core_iter_traits_collect___core__iter__traits__collect__IntoIterator_Clause1_Item__I__for_I__1__into_iter \ + Eurydice_into_iter + +typedef struct { + Eurydice_slice s; + size_t index; +} Eurydice_slice_iterator; + +#define core_slice___Slice_T___iter(x, t, _ret_t) \ + ((Eurydice_slice_iterator){.s = x, .index = 0}) +#define core_slice_iter_Iter Eurydice_slice_iterator +#define core_slice_iter__core__slice__iter__Iter__a__T__181__next(iter, t, \ + ret_t) \ + (((iter)->index == (iter)->s.len) \ + ? (KRML_CLITERAL(ret_t){.tag = core_option_None}) \ + : (KRML_CLITERAL(ret_t){ \ + .tag = core_option_Some, \ + .f0 = ((iter)->index++, \ + &((t *)((iter)->s.ptr))[(iter)->index - 1])})) +#define core_option__core__option__Option_T__TraitClause_0___is_some(X, _0, \ + _1) \ + ((X)->tag == 1) +// STRINGS + +typedef const char *Prims_string; + +// MISC (UNTESTED) + +typedef void *core_fmt_Formatter; +typedef void *core_fmt_Arguments; +typedef void *core_fmt_rt_Argument; +#define core_fmt_rt__core__fmt__rt__Argument__a__1__new_display(x1, x2, x3, \ + x4) \ + NULL + +// BOXES + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_mlkem_core.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_mlkem_core_H +#define libcrux_mlkem_core_H + + +#if defined(__cplusplus) +extern "C" { +#endif + +/** +A monomorphic instance of core.ops.range.Range +with types size_t + +*/ +typedef struct core_ops_range_Range_08_s { + size_t start; + size_t end; +} core_ops_range_Range_08; + +static inline uint16_t core_num__u16__wrapping_add(uint16_t x0, uint16_t x1); + +static inline uint64_t core_num__u64__from_le_bytes(uint8_t x0[8U]); + +static inline uint64_t core_num__u64__rotate_left(uint64_t x0, uint32_t x1); + +static inline void core_num__u64__to_le_bytes(uint64_t x0, uint8_t x1[8U]); + +static inline uint8_t core_num__u8__wrapping_sub(uint8_t x0, uint8_t x1); + +#define LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE ((size_t)32U) + +#define LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_COEFFICIENT ((size_t)12U) + +#define LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT ((size_t)256U) + +#define LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT \ + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * (size_t)12U) + +#define LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT \ + (LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE ((size_t)32U) + +#define LIBCRUX_ML_KEM_CONSTANTS_G_DIGEST_SIZE ((size_t)64U) + +#define LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE ((size_t)32U) + +/** + K * BITS_PER_RING_ELEMENT / 8 + + [eurydice] Note that we can't use const generics here because that breaks + C extraction with eurydice. +*/ +static inline size_t libcrux_ml_kem_constants_ranked_bytes_per_ring_element( + size_t rank) { + return rank * LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT / (size_t)8U; +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint8_t + +*/ +static KRML_MUSTINLINE uint8_t +libcrux_secrets_int_public_integers_classify_27_90(uint8_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types int16_t + +*/ +static KRML_MUSTINLINE int16_t +libcrux_secrets_int_public_integers_declassify_d8_39(int16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE uint8_t libcrux_secrets_int_as_u8_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_90( + (uint8_t)libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types int16_t + +*/ +static KRML_MUSTINLINE int16_t +libcrux_secrets_int_public_integers_classify_27_39(int16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t + +*/ +static KRML_MUSTINLINE uint8_t +libcrux_secrets_int_public_integers_declassify_d8_90(uint8_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u8} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_59(uint8_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_90(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types int32_t + +*/ +static KRML_MUSTINLINE int32_t +libcrux_secrets_int_public_integers_classify_27_a8(int32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE int32_t libcrux_secrets_int_as_i32_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_a8( + (int32_t)libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types int32_t + +*/ +static KRML_MUSTINLINE int32_t +libcrux_secrets_int_public_integers_declassify_d8_a8(int32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i32} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_36(int32_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_a8(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint32_t + +*/ +static KRML_MUSTINLINE uint32_t +libcrux_secrets_int_public_integers_declassify_d8_df(uint32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u32} +*/ +static KRML_MUSTINLINE int32_t libcrux_secrets_int_as_i32_b8(uint32_t self) { + return libcrux_secrets_int_public_integers_classify_27_a8( + (int32_t)libcrux_secrets_int_public_integers_declassify_d8_df(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint16_t + +*/ +static KRML_MUSTINLINE uint16_t +libcrux_secrets_int_public_integers_classify_27_de(uint16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE uint16_t libcrux_secrets_int_as_u16_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_de( + (uint16_t)libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint16_t + +*/ +static KRML_MUSTINLINE uint16_t +libcrux_secrets_int_public_integers_declassify_d8_de(uint16_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u16} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_ca(uint16_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_de(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint64_t + +*/ +static KRML_MUSTINLINE uint64_t +libcrux_secrets_int_public_integers_classify_27_49(uint64_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u16} +*/ +static KRML_MUSTINLINE uint64_t libcrux_secrets_int_as_u64_ca(uint16_t self) { + return libcrux_secrets_int_public_integers_classify_27_49( + (uint64_t)libcrux_secrets_int_public_integers_declassify_d8_de(self)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types uint32_t + +*/ +static KRML_MUSTINLINE uint32_t +libcrux_secrets_int_public_integers_classify_27_df(uint32_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint64_t + +*/ +static KRML_MUSTINLINE uint64_t +libcrux_secrets_int_public_integers_declassify_d8_49(uint64_t self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u64} +*/ +static KRML_MUSTINLINE uint32_t libcrux_secrets_int_as_u32_a3(uint64_t self) { + return libcrux_secrets_int_public_integers_classify_27_df( + (uint32_t)libcrux_secrets_int_public_integers_declassify_d8_49(self)); +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for u32} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_b8(uint32_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + (int16_t)libcrux_secrets_int_public_integers_declassify_d8_df(self)); +} + +/** +This function found in impl {libcrux_secrets::int::CastOps for i16} +*/ +static KRML_MUSTINLINE int16_t libcrux_secrets_int_as_i16_f5(int16_t self) { + return libcrux_secrets_int_public_integers_classify_27_39( + libcrux_secrets_int_public_integers_declassify_d8_39(self)); +} + +typedef struct libcrux_ml_kem_utils_extraction_helper_Keypair768_s { + uint8_t fst[1152U]; + uint8_t snd[1184U]; +} libcrux_ml_kem_utils_extraction_helper_Keypair768; + +#define Ok 0 +#define Err 1 + +typedef uint8_t Result_b2_tags; + +/** +This function found in impl {core::convert::From<@Array> for +libcrux_ml_kem::types::MlKemPublicKey} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_fd +with const generics +- SIZE= 1184 +*/ +static inline libcrux_ml_kem_types_MlKemPublicKey_30 +libcrux_ml_kem_types_from_fd_d0(uint8_t value[1184U]) { + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_value[1184U]; + memcpy(copy_of_value, value, (size_t)1184U * sizeof(uint8_t)); + libcrux_ml_kem_types_MlKemPublicKey_30 lit; + memcpy(lit.value, copy_of_value, (size_t)1184U * sizeof(uint8_t)); + return lit; +} + +/** +This function found in impl +{libcrux_ml_kem::types::MlKemKeyPair} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_17 +with const generics +- PRIVATE_KEY_SIZE= 2400 +- PUBLIC_KEY_SIZE= 1184 +*/ +static inline libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_types_from_17_74(libcrux_ml_kem_types_MlKemPrivateKey_d9 sk, + libcrux_ml_kem_types_MlKemPublicKey_30 pk) { + return (KRML_CLITERAL(libcrux_ml_kem_mlkem768_MlKem768KeyPair){.sk = sk, + .pk = pk}); +} + +/** +This function found in impl {core::convert::From<@Array> for +libcrux_ml_kem::types::MlKemPrivateKey} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_77 +with const generics +- SIZE= 2400 +*/ +static inline libcrux_ml_kem_types_MlKemPrivateKey_d9 +libcrux_ml_kem_types_from_77_28(uint8_t value[2400U]) { + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_value[2400U]; + memcpy(copy_of_value, value, (size_t)2400U * sizeof(uint8_t)); + libcrux_ml_kem_types_MlKemPrivateKey_d9 lit; + memcpy(lit.value, copy_of_value, (size_t)2400U * sizeof(uint8_t)); + return lit; +} + +/** +A monomorphic instance of core.result.Result +with types uint8_t[32size_t], core_array_TryFromSliceError + +*/ +typedef struct Result_fb_s { + Result_b2_tags tag; + union { + uint8_t case_Ok[32U]; + TryFromSliceError case_Err; + } val; +} Result_fb; + +/** +This function found in impl {core::result::Result[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of core.result.unwrap_26 +with types uint8_t[32size_t], core_array_TryFromSliceError + +*/ +static inline void unwrap_26_b3(Result_fb self, uint8_t ret[32U]) { + if (self.tag == Ok) { + uint8_t f0[32U]; + memcpy(f0, self.val.case_Ok, (size_t)32U * sizeof(uint8_t)); + memcpy(ret, f0, (size_t)32U * sizeof(uint8_t)); + } else { + KRML_HOST_EPRINTF("KaRaMeL abort at %s:%d\n%s\n", __FILE__, __LINE__, + "unwrap not Ok"); + KRML_HOST_EXIT(255U); + } +} + +/** +This function found in impl {core::convert::From<@Array> for +libcrux_ml_kem::types::MlKemCiphertext} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.from_e0 +with const generics +- SIZE= 1088 +*/ +static inline libcrux_ml_kem_mlkem768_MlKem768Ciphertext +libcrux_ml_kem_types_from_e0_80(uint8_t value[1088U]) { + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_value[1088U]; + memcpy(copy_of_value, value, (size_t)1088U * sizeof(uint8_t)); + libcrux_ml_kem_mlkem768_MlKem768Ciphertext lit; + memcpy(lit.value, copy_of_value, (size_t)1088U * sizeof(uint8_t)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::types::MlKemPublicKey} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.as_slice_e6 +with const generics +- SIZE= 1184 +*/ +static inline uint8_t *libcrux_ml_kem_types_as_slice_e6_d0( + libcrux_ml_kem_types_MlKemPublicKey_30 *self) { + return self->value; +} + +/** +This function found in impl {libcrux_ml_kem::types::MlKemCiphertext} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.as_slice_a9 +with const generics +- SIZE= 1088 +*/ +static inline uint8_t *libcrux_ml_kem_types_as_slice_a9_80( + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *self) { + return self->value; +} + +/** +A monomorphic instance of libcrux_ml_kem.utils.prf_input_inc +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE uint8_t libcrux_ml_kem_utils_prf_input_inc_e0( + uint8_t (*prf_inputs)[33U], uint8_t domain_separator) { + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + prf_inputs[i0][32U] = domain_separator; + domain_separator = (uint32_t)domain_separator + 1U; + } + return domain_separator; +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 33 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_c8( + Eurydice_slice slice, uint8_t ret[33U]) { + uint8_t out[33U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)33U * sizeof(uint8_t)); +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 34 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_b6( + Eurydice_slice slice, uint8_t ret[34U]) { + uint8_t out[34U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)34U * sizeof(uint8_t)); +} + +/** +This function found in impl {core::convert::AsRef<@Slice> for +libcrux_ml_kem::types::MlKemCiphertext} +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.as_ref_d3 +with const generics +- SIZE= 1088 +*/ +static inline Eurydice_slice libcrux_ml_kem_types_as_ref_d3_80( + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *self) { + return Eurydice_array_to_slice((size_t)1088U, self->value, uint8_t); +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 1120 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_15( + Eurydice_slice slice, uint8_t ret[1120U]) { + uint8_t out[1120U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)1120U * sizeof(uint8_t)); +} + +/** + Pad the `slice` with `0`s at the end. +*/ +/** +A monomorphic instance of libcrux_ml_kem.utils.into_padded_array +with const generics +- LEN= 64 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_utils_into_padded_array_24( + Eurydice_slice slice, uint8_t ret[64U]) { + uint8_t out[64U] = {0U}; + uint8_t *uu____0 = out; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, (size_t)0U, Eurydice_slice_len(slice, uint8_t), uint8_t *), + slice, uint8_t); + memcpy(ret, out, (size_t)64U * sizeof(uint8_t)); +} + +typedef struct Eurydice_slice_uint8_t_x4_s { + Eurydice_slice fst; + Eurydice_slice snd; + Eurydice_slice thd; + Eurydice_slice f3; +} Eurydice_slice_uint8_t_x4; + +typedef struct Eurydice_slice_uint8_t_x2_s { + Eurydice_slice fst; + Eurydice_slice snd; +} Eurydice_slice_uint8_t_x2; + +/** + Unpack an incoming private key into it's different parts. + + We have this here in types to extract into a common core for C. +*/ +/** +A monomorphic instance of libcrux_ml_kem.types.unpack_private_key +with const generics +- CPA_SECRET_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +*/ +static inline Eurydice_slice_uint8_t_x4 +libcrux_ml_kem_types_unpack_private_key_b4(Eurydice_slice private_key) { + Eurydice_slice_uint8_t_x2 uu____0 = Eurydice_slice_split_at( + private_key, (size_t)1152U, uint8_t, Eurydice_slice_uint8_t_x2); + Eurydice_slice ind_cpa_secret_key = uu____0.fst; + Eurydice_slice secret_key0 = uu____0.snd; + Eurydice_slice_uint8_t_x2 uu____1 = Eurydice_slice_split_at( + secret_key0, (size_t)1184U, uint8_t, Eurydice_slice_uint8_t_x2); + Eurydice_slice ind_cpa_public_key = uu____1.fst; + Eurydice_slice secret_key = uu____1.snd; + Eurydice_slice_uint8_t_x2 uu____2 = Eurydice_slice_split_at( + secret_key, LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE, uint8_t, + Eurydice_slice_uint8_t_x2); + Eurydice_slice ind_cpa_public_key_hash = uu____2.fst; + Eurydice_slice implicit_rejection_value = uu____2.snd; + return ( + KRML_CLITERAL(Eurydice_slice_uint8_t_x4){.fst = ind_cpa_secret_key, + .snd = ind_cpa_public_key, + .thd = ind_cpa_public_key_hash, + .f3 = implicit_rejection_value}); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[24size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_d2(uint8_t self[24U], + uint8_t ret[24U]) { + memcpy(ret, self, (size_t)24U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[20size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_57(uint8_t self[20U], + uint8_t ret[20U]) { + memcpy(ret, self, (size_t)20U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[8size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_76(uint8_t self[8U], + uint8_t ret[8U]) { + memcpy(ret, self, (size_t)8U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Declassify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.declassify_d8 +with types uint8_t[2size_t] + +*/ +static KRML_MUSTINLINE void +libcrux_secrets_int_public_integers_declassify_d8_d4(uint8_t self[2U], + uint8_t ret[2U]) { + memcpy(ret, self, (size_t)2U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::Classify for T} +*/ +/** +A monomorphic instance of libcrux_secrets.int.public_integers.classify_27 +with types int16_t[16size_t] + +*/ +static KRML_MUSTINLINE void libcrux_secrets_int_public_integers_classify_27_46( + int16_t self[16U], int16_t ret[16U]) { + memcpy(ret, self, (size_t)16U * sizeof(int16_t)); +} + +/** +This function found in impl {libcrux_secrets::traits::ClassifyRef<&'a +(@Slice)> for &'a (@Slice)} +*/ +/** +A monomorphic instance of libcrux_secrets.int.classify_public.classify_ref_9b +with types uint8_t + +*/ +static KRML_MUSTINLINE Eurydice_slice +libcrux_secrets_int_classify_public_classify_ref_9b_90(Eurydice_slice self) { + return self; +} + +/** +This function found in impl {libcrux_secrets::traits::ClassifyRef<&'a +(@Slice)> for &'a (@Slice)} +*/ +/** +A monomorphic instance of libcrux_secrets.int.classify_public.classify_ref_9b +with types int16_t + +*/ +static KRML_MUSTINLINE Eurydice_slice +libcrux_secrets_int_classify_public_classify_ref_9b_39(Eurydice_slice self) { + return self; +} + +/** +A monomorphic instance of core.result.Result +with types int16_t[16size_t], core_array_TryFromSliceError + +*/ +typedef struct Result_0a_s { + Result_b2_tags tag; + union { + int16_t case_Ok[16U]; + TryFromSliceError case_Err; + } val; +} Result_0a; + +/** +This function found in impl {core::result::Result[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of core.result.unwrap_26 +with types int16_t[16size_t], core_array_TryFromSliceError + +*/ +static inline void unwrap_26_00(Result_0a self, int16_t ret[16U]) { + if (self.tag == Ok) { + int16_t f0[16U]; + memcpy(f0, self.val.case_Ok, (size_t)16U * sizeof(int16_t)); + memcpy(ret, f0, (size_t)16U * sizeof(int16_t)); + } else { + KRML_HOST_EPRINTF("KaRaMeL abort at %s:%d\n%s\n", __FILE__, __LINE__, + "unwrap not Ok"); + KRML_HOST_EXIT(255U); + } +} + +/** +A monomorphic instance of core.result.Result +with types uint8_t[8size_t], core_array_TryFromSliceError + +*/ +typedef struct Result_15_s { + Result_b2_tags tag; + union { + uint8_t case_Ok[8U]; + TryFromSliceError case_Err; + } val; +} Result_15; + +/** +This function found in impl {core::result::Result[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of core.result.unwrap_26 +with types uint8_t[8size_t], core_array_TryFromSliceError + +*/ +static inline void unwrap_26_68(Result_15 self, uint8_t ret[8U]) { + if (self.tag == Ok) { + uint8_t f0[8U]; + memcpy(f0, self.val.case_Ok, (size_t)8U * sizeof(uint8_t)); + memcpy(ret, f0, (size_t)8U * sizeof(uint8_t)); + } else { + KRML_HOST_EPRINTF("KaRaMeL abort at %s:%d\n%s\n", __FILE__, __LINE__, + "unwrap not Ok"); + KRML_HOST_EXIT(255U); + } +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_mlkem_core_H_DEFINED +#endif /* libcrux_mlkem_core_H */ + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_ct_ops.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_ct_ops_H +#define libcrux_ct_ops_H + + +#if defined(__cplusplus) +extern "C" { +#endif + + +/** + Return 1 if `value` is not zero and 0 otherwise. +*/ +static KRML_NOINLINE uint8_t +libcrux_ml_kem_constant_time_ops_inz(uint8_t value) { + uint16_t value0 = (uint16_t)value; + uint8_t result = + (uint8_t)((uint32_t)core_num__u16__wrapping_add(~value0, 1U) >> 8U); + return (uint32_t)result & 1U; +} + +static KRML_NOINLINE uint8_t +libcrux_ml_kem_constant_time_ops_is_non_zero(uint8_t value) { + return libcrux_ml_kem_constant_time_ops_inz(value); +} + +/** + Return 1 if the bytes of `lhs` and `rhs` do not exactly + match and 0 otherwise. +*/ +static KRML_NOINLINE uint8_t libcrux_ml_kem_constant_time_ops_compare( + Eurydice_slice lhs, Eurydice_slice rhs) { + uint8_t r = 0U; + for (size_t i = (size_t)0U; i < Eurydice_slice_len(lhs, uint8_t); i++) { + size_t i0 = i; + uint8_t nr = (uint32_t)r | + ((uint32_t)Eurydice_slice_index(lhs, i0, uint8_t, uint8_t *) ^ + (uint32_t)Eurydice_slice_index(rhs, i0, uint8_t, uint8_t *)); + r = nr; + } + return libcrux_ml_kem_constant_time_ops_is_non_zero(r); +} + +static KRML_NOINLINE uint8_t +libcrux_ml_kem_constant_time_ops_compare_ciphertexts_in_constant_time( + Eurydice_slice lhs, Eurydice_slice rhs) { + return libcrux_ml_kem_constant_time_ops_compare(lhs, rhs); +} + +/** + If `selector` is not zero, return the bytes in `rhs`; return the bytes in + `lhs` otherwise. +*/ +static KRML_NOINLINE void libcrux_ml_kem_constant_time_ops_select_ct( + Eurydice_slice lhs, Eurydice_slice rhs, uint8_t selector, + uint8_t ret[32U]) { + uint8_t mask = core_num__u8__wrapping_sub( + libcrux_ml_kem_constant_time_ops_is_non_zero(selector), 1U); + uint8_t out[32U] = {0U}; + for (size_t i = (size_t)0U; i < LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE; + i++) { + size_t i0 = i; + uint8_t outi = + ((uint32_t)Eurydice_slice_index(lhs, i0, uint8_t, uint8_t *) & + (uint32_t)mask) | + ((uint32_t)Eurydice_slice_index(rhs, i0, uint8_t, uint8_t *) & + (uint32_t)~mask); + out[i0] = outi; + } + memcpy(ret, out, (size_t)32U * sizeof(uint8_t)); +} + +static KRML_NOINLINE void +libcrux_ml_kem_constant_time_ops_select_shared_secret_in_constant_time( + Eurydice_slice lhs, Eurydice_slice rhs, uint8_t selector, + uint8_t ret[32U]) { + libcrux_ml_kem_constant_time_ops_select_ct(lhs, rhs, selector, ret); +} + +static KRML_NOINLINE void +libcrux_ml_kem_constant_time_ops_compare_ciphertexts_select_shared_secret_in_constant_time( + Eurydice_slice lhs_c, Eurydice_slice rhs_c, Eurydice_slice lhs_s, + Eurydice_slice rhs_s, uint8_t ret[32U]) { + uint8_t selector = + libcrux_ml_kem_constant_time_ops_compare_ciphertexts_in_constant_time( + lhs_c, rhs_c); + uint8_t ret0[32U]; + libcrux_ml_kem_constant_time_ops_select_shared_secret_in_constant_time( + lhs_s, rhs_s, selector, ret0); + memcpy(ret, ret0, (size_t)32U * sizeof(uint8_t)); +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_ct_ops_H_DEFINED +#endif /* libcrux_ct_ops_H */ + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_sha3_portable.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_sha3_portable_H +#define libcrux_sha3_portable_H + + +#if defined(__cplusplus) +extern "C" { +#endif + + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable_zero_d2(void) { + return 0ULL; +} + +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable__veor5q_u64( + uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) { + return (((a ^ b) ^ c) ^ d) ^ e; +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable_xor5_d2( + uint64_t a, uint64_t b, uint64_t c, uint64_t d, uint64_t e) { + return libcrux_sha3_simd_portable__veor5q_u64(a, b, c, d, e); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 1 +- RIGHT= 63 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_76(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)1); +} + +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vrax1q_u64(uint64_t a, uint64_t b) { + uint64_t uu____0 = a; + return uu____0 ^ libcrux_sha3_simd_portable_rotate_left_76(b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left1_and_xor_d2(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vrax1q_u64(a, b); +} + +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vbcaxq_u64(uint64_t a, uint64_t b, uint64_t c) { + return a ^ (b & ~c); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_and_not_xor_d2(uint64_t a, uint64_t b, uint64_t c) { + return libcrux_sha3_simd_portable__vbcaxq_u64(a, b, c); +} + +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__veorq_n_u64(uint64_t a, uint64_t c) { + return a ^ c; +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_constant_d2(uint64_t a, uint64_t c) { + return libcrux_sha3_simd_portable__veorq_n_u64(a, c); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +static KRML_MUSTINLINE uint64_t libcrux_sha3_simd_portable_xor_d2(uint64_t a, + uint64_t b) { + return a ^ b; +} + +static const uint64_t + libcrux_sha3_generic_keccak_constants_ROUNDCONSTANTS[24U] = { + 1ULL, + 32898ULL, + 9223372036854808714ULL, + 9223372039002292224ULL, + 32907ULL, + 2147483649ULL, + 9223372039002292353ULL, + 9223372036854808585ULL, + 138ULL, + 136ULL, + 2147516425ULL, + 2147483658ULL, + 2147516555ULL, + 9223372036854775947ULL, + 9223372036854808713ULL, + 9223372036854808579ULL, + 9223372036854808578ULL, + 9223372036854775936ULL, + 32778ULL, + 9223372039002259466ULL, + 9223372039002292353ULL, + 9223372036854808704ULL, + 2147483649ULL, + 9223372039002292232ULL}; + +typedef struct size_t_x2_s { + size_t fst; + size_t snd; +} size_t_x2; + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.KeccakState +with types uint64_t +with const generics +- $1size_t +*/ +typedef struct libcrux_sha3_generic_keccak_KeccakState_17_s { + uint64_t st[25U]; +} libcrux_sha3_generic_keccak_KeccakState_17; + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.new_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE libcrux_sha3_generic_keccak_KeccakState_17 +libcrux_sha3_generic_keccak_new_80_04(void) { + libcrux_sha3_generic_keccak_KeccakState_17 lit; + uint64_t repeat_expression[25U]; + for (size_t i = (size_t)0U; i < (size_t)25U; i++) { + repeat_expression[i] = libcrux_sha3_simd_portable_zero_d2(); + } + memcpy(lit.st, repeat_expression, (size_t)25U * sizeof(uint64_t)); + return lit; +} + +/** +A monomorphic instance of libcrux_sha3.traits.get_ij +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE uint64_t *libcrux_sha3_traits_get_ij_04(uint64_t *arr, + size_t i, + size_t j) { + return &arr[(size_t)5U * j + i]; +} + +/** +A monomorphic instance of libcrux_sha3.traits.set_ij +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_traits_set_ij_04(uint64_t *arr, + size_t i, size_t j, + uint64_t value) { + arr[(size_t)5U * j + i] = value; +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block +with const generics +- RATE= 72 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_block_f8( + uint64_t *state, Eurydice_slice blocks, size_t start) { + uint64_t state_flat[25U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)72U / (size_t)8U; i++) { + size_t i0 = i; + size_t offset = start + (size_t)8U * i0; + uint8_t uu____0[8U]; + Result_15 dst; + Eurydice_slice_to_array2( + &dst, + Eurydice_slice_subslice3(blocks, offset, offset + (size_t)8U, + uint8_t *), + Eurydice_slice, uint8_t[8U], TryFromSliceError); + unwrap_26_68(dst, uu____0); + state_flat[i0] = core_num__u64__from_le_bytes(uu____0); + } + for (size_t i = (size_t)0U; i < (size_t)72U / (size_t)8U; i++) { + size_t i0 = i; + libcrux_sha3_traits_set_ij_04( + state, i0 / (size_t)5U, i0 % (size_t)5U, + libcrux_sha3_traits_get_ij_04(state, i0 / (size_t)5U, + i0 % (size_t)5U)[0U] ^ + state_flat[i0]); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block_a1 +with const generics +- RATE= 72 +*/ +static inline void libcrux_sha3_simd_portable_load_block_a1_f8( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start) { + libcrux_sha3_simd_portable_load_block_f8(self->st, input[0U], start); +} + +/** +This function found in impl {core::ops::index::Index<(usize, usize), T> for +libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.index_c2 +with types uint64_t +with const generics +- N= 1 +*/ +static inline uint64_t *libcrux_sha3_generic_keccak_index_c2_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, size_t_x2 index) { + return libcrux_sha3_traits_get_ij_04(self->st, index.fst, index.snd); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.theta_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_theta_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, uint64_t ret[5U]) { + uint64_t c[5U] = { + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)0U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)1U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)1U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)2U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)2U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)3U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)3U}))[0U]), + libcrux_sha3_simd_portable_xor5_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)4U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)4U}))[0U])}; + uint64_t uu____0 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)0U + (size_t)4U) % (size_t)5U], + c[((size_t)0U + (size_t)1U) % (size_t)5U]); + uint64_t uu____1 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)1U + (size_t)4U) % (size_t)5U], + c[((size_t)1U + (size_t)1U) % (size_t)5U]); + uint64_t uu____2 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)2U + (size_t)4U) % (size_t)5U], + c[((size_t)2U + (size_t)1U) % (size_t)5U]); + uint64_t uu____3 = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)3U + (size_t)4U) % (size_t)5U], + c[((size_t)3U + (size_t)1U) % (size_t)5U]); + ret[0U] = uu____0; + ret[1U] = uu____1; + ret[2U] = uu____2; + ret[3U] = uu____3; + ret[4U] = libcrux_sha3_simd_portable_rotate_left1_and_xor_d2( + c[((size_t)4U + (size_t)4U) % (size_t)5U], + c[((size_t)4U + (size_t)1U) % (size_t)5U]); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.set_80 +with types uint64_t +with const generics +- N= 1 +*/ +static inline void libcrux_sha3_generic_keccak_set_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, size_t i, size_t j, + uint64_t v) { + libcrux_sha3_traits_set_ij_04(self->st, i, j, v); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 36 +- RIGHT= 28 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_02(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)36); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 36 +- RIGHT= 28 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_02(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_02(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 36 +- RIGHT= 28 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_02(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_02(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 3 +- RIGHT= 61 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_ac(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)3); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 3 +- RIGHT= 61 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_ac(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_ac(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 3 +- RIGHT= 61 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_ac(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_ac(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 41 +- RIGHT= 23 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_020(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)41); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 41 +- RIGHT= 23 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_020(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_020(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 41 +- RIGHT= 23 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_020(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_020(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 18 +- RIGHT= 46 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_a9(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)18); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 18 +- RIGHT= 46 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_a9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_a9(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 18 +- RIGHT= 46 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_a9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_a9(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 1 +- RIGHT= 63 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_76(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_76(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 1 +- RIGHT= 63 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_76(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_76(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 44 +- RIGHT= 20 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_58(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)44); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 44 +- RIGHT= 20 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_58(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_58(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 44 +- RIGHT= 20 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_58(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_58(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 10 +- RIGHT= 54 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_e0(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)10); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 10 +- RIGHT= 54 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_e0(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_e0(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 10 +- RIGHT= 54 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_e0(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_e0(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 45 +- RIGHT= 19 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_63(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)45); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 45 +- RIGHT= 19 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_63(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_63(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 45 +- RIGHT= 19 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_63(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_63(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 2 +- RIGHT= 62 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_6a(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)2); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 2 +- RIGHT= 62 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_6a(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_6a(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 2 +- RIGHT= 62 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_6a(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_6a(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 62 +- RIGHT= 2 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_ab(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)62); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 62 +- RIGHT= 2 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_ab(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_ab(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 62 +- RIGHT= 2 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_ab(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_ab(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 6 +- RIGHT= 58 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_5b(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)6); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 6 +- RIGHT= 58 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_5b(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_5b(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 6 +- RIGHT= 58 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_5b(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_5b(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 43 +- RIGHT= 21 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_6f(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)43); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 43 +- RIGHT= 21 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_6f(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_6f(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 43 +- RIGHT= 21 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_6f(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_6f(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 15 +- RIGHT= 49 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_62(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)15); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 15 +- RIGHT= 49 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_62(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_62(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 15 +- RIGHT= 49 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_62(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_62(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 61 +- RIGHT= 3 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_23(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)61); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 61 +- RIGHT= 3 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_23(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_23(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 61 +- RIGHT= 3 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_23(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_23(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 28 +- RIGHT= 36 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_37(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)28); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 28 +- RIGHT= 36 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_37(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_37(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 28 +- RIGHT= 36 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_37(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_37(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 55 +- RIGHT= 9 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_bb(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)55); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 55 +- RIGHT= 9 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_bb(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_bb(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 55 +- RIGHT= 9 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_bb(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_bb(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 25 +- RIGHT= 39 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_b9(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)25); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 25 +- RIGHT= 39 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_b9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_b9(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 25 +- RIGHT= 39 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_b9(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_b9(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 21 +- RIGHT= 43 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_54(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)21); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 21 +- RIGHT= 43 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_54(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_54(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 21 +- RIGHT= 43 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_54(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_54(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 56 +- RIGHT= 8 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_4c(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)56); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 56 +- RIGHT= 8 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_4c(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_4c(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 56 +- RIGHT= 8 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_4c(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_4c(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 27 +- RIGHT= 37 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_ce(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)27); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 27 +- RIGHT= 37 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_ce(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_ce(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 27 +- RIGHT= 37 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_ce(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_ce(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 20 +- RIGHT= 44 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_77(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)20); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 20 +- RIGHT= 44 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_77(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_77(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 20 +- RIGHT= 44 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_77(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_77(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 39 +- RIGHT= 25 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_25(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)39); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 39 +- RIGHT= 25 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_25(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_25(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 39 +- RIGHT= 25 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_25(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_25(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 8 +- RIGHT= 56 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_af(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)8); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 8 +- RIGHT= 56 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_af(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_af(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 8 +- RIGHT= 56 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_af(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_af(a, b); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.rotate_left +with const generics +- LEFT= 14 +- RIGHT= 50 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_rotate_left_fd(uint64_t x) { + return core_num__u64__rotate_left(x, (uint32_t)(int32_t)14); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable._vxarq_u64 +with const generics +- LEFT= 14 +- RIGHT= 50 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable__vxarq_u64_fd(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable_rotate_left_fd(a ^ b); +} + +/** +This function found in impl {libcrux_sha3::traits::KeccakItem<1usize> for u64} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.xor_and_rotate_d2 +with const generics +- LEFT= 14 +- RIGHT= 50 +*/ +static KRML_MUSTINLINE uint64_t +libcrux_sha3_simd_portable_xor_and_rotate_d2_fd(uint64_t a, uint64_t b) { + return libcrux_sha3_simd_portable__vxarq_u64_fd(a, b); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.rho_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_rho_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, uint64_t t[5U]) { + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)0U, + libcrux_sha3_simd_portable_xor_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____0 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____0, (size_t)1U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_02( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____1 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____1, (size_t)2U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_ac( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____2 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____2, (size_t)3U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_020( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____3 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____3, (size_t)4U, (size_t)0U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_a9( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)0U}))[0U], + t[0U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____4 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____4, (size_t)0U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_76( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____5 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____5, (size_t)1U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_58( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____6 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____6, (size_t)2U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_e0( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____7 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____7, (size_t)3U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_63( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____8 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____8, (size_t)4U, (size_t)1U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_6a( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)1U}))[0U], + t[1U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____9 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____9, (size_t)0U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_ab( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____10 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____10, (size_t)1U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_5b( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____11 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____11, (size_t)2U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_6f( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____12 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____12, (size_t)3U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_62( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____13 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____13, (size_t)4U, (size_t)2U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_23( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)2U}))[0U], + t[2U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____14 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____14, (size_t)0U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_37( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____15 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____15, (size_t)1U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_bb( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____16 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____16, (size_t)2U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_b9( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____17 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____17, (size_t)3U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_54( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____18 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____18, (size_t)4U, (size_t)3U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_4c( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)3U}))[0U], + t[3U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____19 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____19, (size_t)0U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_ce( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____20 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____20, (size_t)1U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_77( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____21 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____21, (size_t)2U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_25( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____22 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____22, (size_t)3U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_af( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)4U}))[0U], + t[4U])); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____23 = self; + libcrux_sha3_generic_keccak_set_80_04( + uu____23, (size_t)4U, (size_t)4U, + libcrux_sha3_simd_portable_xor_and_rotate_d2_fd( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)4U}))[0U], + t[4U])); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.pi_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_pi_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self) { + libcrux_sha3_generic_keccak_KeccakState_17 old = self[0U]; + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)0U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)1U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)1U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)2U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)2U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)1U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)3U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)3U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)4U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)1U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)2U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)2U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)0U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)3U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)3U}))[0U]); + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)4U, (size_t)4U, + libcrux_sha3_generic_keccak_index_c2_04( + &old, (KRML_CLITERAL(size_t_x2){.fst = (size_t)4U, + .snd = (size_t)1U}))[0U]); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.chi_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_chi_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self) { + libcrux_sha3_generic_keccak_KeccakState_17 old = self[0U]; + for (size_t i0 = (size_t)0U; i0 < (size_t)5U; i0++) { + size_t i1 = i0; + for (size_t i = (size_t)0U; i < (size_t)5U; i++) { + size_t j = i; + libcrux_sha3_generic_keccak_set_80_04( + self, i1, j, + libcrux_sha3_simd_portable_and_not_xor_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = i1, .snd = j}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + &old, + (KRML_CLITERAL(size_t_x2){ + .fst = i1, .snd = (j + (size_t)2U) % (size_t)5U}))[0U], + libcrux_sha3_generic_keccak_index_c2_04( + &old, + (KRML_CLITERAL(size_t_x2){ + .fst = i1, .snd = (j + (size_t)1U) % (size_t)5U}))[0U])); + } + } +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.iota_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_iota_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self, size_t i) { + libcrux_sha3_generic_keccak_set_80_04( + self, (size_t)0U, (size_t)0U, + libcrux_sha3_simd_portable_xor_constant_d2( + libcrux_sha3_generic_keccak_index_c2_04( + self, (KRML_CLITERAL(size_t_x2){.fst = (size_t)0U, + .snd = (size_t)0U}))[0U], + libcrux_sha3_generic_keccak_constants_ROUNDCONSTANTS[i])); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.keccakf1600_80 +with types uint64_t +with const generics +- N= 1 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_keccakf1600_80_04( + libcrux_sha3_generic_keccak_KeccakState_17 *self) { + for (size_t i = (size_t)0U; i < (size_t)24U; i++) { + size_t i0 = i; + uint64_t t[5U]; + libcrux_sha3_generic_keccak_theta_80_04(self, t); + libcrux_sha3_generic_keccak_KeccakState_17 *uu____0 = self; + uint64_t uu____1[5U]; + memcpy(uu____1, t, (size_t)5U * sizeof(uint64_t)); + libcrux_sha3_generic_keccak_rho_80_04(uu____0, uu____1); + libcrux_sha3_generic_keccak_pi_80_04(self); + libcrux_sha3_generic_keccak_chi_80_04(self); + libcrux_sha3_generic_keccak_iota_80_04(self, i0); + } +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_block_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 72 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_block_80_c6( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *blocks, + size_t start) { + libcrux_sha3_simd_portable_load_block_a1_f8(self, blocks, start); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 72 +- DELIMITER= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_96( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[72U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 6U; + size_t uu____0 = (size_t)72U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_f8( + state, Eurydice_array_to_slice((size_t)72U, buffer, uint8_t), (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 72 +- DELIMITER= 6 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_96( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_96(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 72 +- DELIM= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_96(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.store_block +with const generics +- RATE= 72 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_store_block_f8( + uint64_t *s, Eurydice_slice out, size_t start, size_t len) { + size_t octets = len / (size_t)8U; + for (size_t i = (size_t)0U; i < octets; i++) { + size_t i0 = i; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, start + (size_t)8U * i0, start + (size_t)8U * i0 + (size_t)8U, + uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, i0 / (size_t)5U, i0 % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)8U, ret, uint8_t), uint8_t); + } + size_t remaining = len % (size_t)8U; + if (remaining > (size_t)0U) { + Eurydice_slice uu____1 = Eurydice_slice_subslice3( + out, start + len - remaining, start + len, uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, octets / (size_t)5U, + octets % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____1, + Eurydice_array_to_subslice3(ret, (size_t)0U, remaining, uint8_t *), + uint8_t); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Squeeze1 for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.squeeze_13 +with const generics +- RATE= 72 +*/ +static inline void libcrux_sha3_simd_portable_squeeze_13_f8( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start, size_t len) { + libcrux_sha3_simd_portable_store_block_f8(self->st, out, start, len); +} + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.portable.keccak1 +with const generics +- RATE= 72 +- DELIM= 6 +*/ +static inline void libcrux_sha3_generic_keccak_portable_keccak1_96( + Eurydice_slice data, Eurydice_slice out) { + libcrux_sha3_generic_keccak_KeccakState_17 s = + libcrux_sha3_generic_keccak_new_80_04(); + size_t data_len = Eurydice_slice_len(data, uint8_t); + for (size_t i = (size_t)0U; i < data_len / (size_t)72U; i++) { + size_t i0 = i; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_block_80_c6(&s, buf, i0 * (size_t)72U); + } + size_t rem = data_len % (size_t)72U; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_final_80_9e(&s, buf, data_len - rem, rem); + size_t outlen = Eurydice_slice_len(out, uint8_t); + size_t blocks = outlen / (size_t)72U; + size_t last = outlen - outlen % (size_t)72U; + if (blocks == (size_t)0U) { + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, (size_t)0U, outlen); + } else { + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, (size_t)0U, (size_t)72U); + for (size_t i = (size_t)1U; i < blocks; i++) { + size_t i0 = i; + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, i0 * (size_t)72U, + (size_t)72U); + } + if (last < outlen) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_f8(&s, out, last, outlen - last); + } + } +} + +/** + A portable SHA3 512 implementation. +*/ +static KRML_MUSTINLINE void libcrux_sha3_portable_sha512(Eurydice_slice digest, + Eurydice_slice data) { + libcrux_sha3_generic_keccak_portable_keccak1_96(data, digest); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block +with const generics +- RATE= 136 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_block_5b( + uint64_t *state, Eurydice_slice blocks, size_t start) { + uint64_t state_flat[25U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)136U / (size_t)8U; i++) { + size_t i0 = i; + size_t offset = start + (size_t)8U * i0; + uint8_t uu____0[8U]; + Result_15 dst; + Eurydice_slice_to_array2( + &dst, + Eurydice_slice_subslice3(blocks, offset, offset + (size_t)8U, + uint8_t *), + Eurydice_slice, uint8_t[8U], TryFromSliceError); + unwrap_26_68(dst, uu____0); + state_flat[i0] = core_num__u64__from_le_bytes(uu____0); + } + for (size_t i = (size_t)0U; i < (size_t)136U / (size_t)8U; i++) { + size_t i0 = i; + libcrux_sha3_traits_set_ij_04( + state, i0 / (size_t)5U, i0 % (size_t)5U, + libcrux_sha3_traits_get_ij_04(state, i0 / (size_t)5U, + i0 % (size_t)5U)[0U] ^ + state_flat[i0]); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block_a1 +with const generics +- RATE= 136 +*/ +static inline void libcrux_sha3_simd_portable_load_block_a1_5b( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start) { + libcrux_sha3_simd_portable_load_block_5b(self->st, input[0U], start); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_block_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 136 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_block_80_c60( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *blocks, + size_t start) { + libcrux_sha3_simd_portable_load_block_a1_5b(self, blocks, start); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 136 +- DELIMITER= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_ad( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[136U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 6U; + size_t uu____0 = (size_t)136U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_5b( + state, Eurydice_array_to_slice((size_t)136U, buffer, uint8_t), + (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 136 +- DELIMITER= 6 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_ad( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_ad(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 136 +- DELIM= 6 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e0( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_ad(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.store_block +with const generics +- RATE= 136 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_store_block_5b( + uint64_t *s, Eurydice_slice out, size_t start, size_t len) { + size_t octets = len / (size_t)8U; + for (size_t i = (size_t)0U; i < octets; i++) { + size_t i0 = i; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, start + (size_t)8U * i0, start + (size_t)8U * i0 + (size_t)8U, + uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, i0 / (size_t)5U, i0 % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)8U, ret, uint8_t), uint8_t); + } + size_t remaining = len % (size_t)8U; + if (remaining > (size_t)0U) { + Eurydice_slice uu____1 = Eurydice_slice_subslice3( + out, start + len - remaining, start + len, uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, octets / (size_t)5U, + octets % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____1, + Eurydice_array_to_subslice3(ret, (size_t)0U, remaining, uint8_t *), + uint8_t); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Squeeze1 for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.squeeze_13 +with const generics +- RATE= 136 +*/ +static inline void libcrux_sha3_simd_portable_squeeze_13_5b( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start, size_t len) { + libcrux_sha3_simd_portable_store_block_5b(self->st, out, start, len); +} + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.portable.keccak1 +with const generics +- RATE= 136 +- DELIM= 6 +*/ +static inline void libcrux_sha3_generic_keccak_portable_keccak1_ad( + Eurydice_slice data, Eurydice_slice out) { + libcrux_sha3_generic_keccak_KeccakState_17 s = + libcrux_sha3_generic_keccak_new_80_04(); + size_t data_len = Eurydice_slice_len(data, uint8_t); + for (size_t i = (size_t)0U; i < data_len / (size_t)136U; i++) { + size_t i0 = i; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_block_80_c60(&s, buf, i0 * (size_t)136U); + } + size_t rem = data_len % (size_t)136U; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_final_80_9e0(&s, buf, data_len - rem, rem); + size_t outlen = Eurydice_slice_len(out, uint8_t); + size_t blocks = outlen / (size_t)136U; + size_t last = outlen - outlen % (size_t)136U; + if (blocks == (size_t)0U) { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, outlen); + } else { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, (size_t)136U); + for (size_t i = (size_t)1U; i < blocks; i++) { + size_t i0 = i; + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, i0 * (size_t)136U, + (size_t)136U); + } + if (last < outlen) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, last, outlen - last); + } + } +} + +/** + A portable SHA3 256 implementation. +*/ +static KRML_MUSTINLINE void libcrux_sha3_portable_sha256(Eurydice_slice digest, + Eurydice_slice data) { + libcrux_sha3_generic_keccak_portable_keccak1_ad(data, digest); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 136 +- DELIMITER= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_ad0( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[136U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 31U; + size_t uu____0 = (size_t)136U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_5b( + state, Eurydice_array_to_slice((size_t)136U, buffer, uint8_t), + (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 136 +- DELIMITER= 31 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_ad0( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_ad0(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 136 +- DELIM= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e1( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_ad0(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** +A monomorphic instance of libcrux_sha3.generic_keccak.portable.keccak1 +with const generics +- RATE= 136 +- DELIM= 31 +*/ +static inline void libcrux_sha3_generic_keccak_portable_keccak1_ad0( + Eurydice_slice data, Eurydice_slice out) { + libcrux_sha3_generic_keccak_KeccakState_17 s = + libcrux_sha3_generic_keccak_new_80_04(); + size_t data_len = Eurydice_slice_len(data, uint8_t); + for (size_t i = (size_t)0U; i < data_len / (size_t)136U; i++) { + size_t i0 = i; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_block_80_c60(&s, buf, i0 * (size_t)136U); + } + size_t rem = data_len % (size_t)136U; + Eurydice_slice buf[1U] = {data}; + libcrux_sha3_generic_keccak_absorb_final_80_9e1(&s, buf, data_len - rem, rem); + size_t outlen = Eurydice_slice_len(out, uint8_t); + size_t blocks = outlen / (size_t)136U; + size_t last = outlen - outlen % (size_t)136U; + if (blocks == (size_t)0U) { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, outlen); + } else { + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, (size_t)0U, (size_t)136U); + for (size_t i = (size_t)1U; i < blocks; i++) { + size_t i0 = i; + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, i0 * (size_t)136U, + (size_t)136U); + } + if (last < outlen) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(&s); + libcrux_sha3_simd_portable_squeeze_13_5b(&s, out, last, outlen - last); + } + } +} + +/** + A portable SHAKE256 implementation. +*/ +static KRML_MUSTINLINE void libcrux_sha3_portable_shake256( + Eurydice_slice digest, Eurydice_slice data) { + libcrux_sha3_generic_keccak_portable_keccak1_ad0(data, digest); +} + +typedef libcrux_sha3_generic_keccak_KeccakState_17 + libcrux_sha3_portable_KeccakState; + +/** + Create a new SHAKE-128 state object. +*/ +static KRML_MUSTINLINE libcrux_sha3_generic_keccak_KeccakState_17 +libcrux_sha3_portable_incremental_shake128_init(void) { + return libcrux_sha3_generic_keccak_new_80_04(); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_block +with const generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_block_3a( + uint64_t *state, Eurydice_slice blocks, size_t start) { + uint64_t state_flat[25U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)168U / (size_t)8U; i++) { + size_t i0 = i; + size_t offset = start + (size_t)8U * i0; + uint8_t uu____0[8U]; + Result_15 dst; + Eurydice_slice_to_array2( + &dst, + Eurydice_slice_subslice3(blocks, offset, offset + (size_t)8U, + uint8_t *), + Eurydice_slice, uint8_t[8U], TryFromSliceError); + unwrap_26_68(dst, uu____0); + state_flat[i0] = core_num__u64__from_le_bytes(uu____0); + } + for (size_t i = (size_t)0U; i < (size_t)168U / (size_t)8U; i++) { + size_t i0 = i; + libcrux_sha3_traits_set_ij_04( + state, i0 / (size_t)5U, i0 % (size_t)5U, + libcrux_sha3_traits_get_ij_04(state, i0 / (size_t)5U, + i0 % (size_t)5U)[0U] ^ + state_flat[i0]); + } +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last +with const generics +- RATE= 168 +- DELIMITER= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_load_last_c6( + uint64_t *state, Eurydice_slice blocks, size_t start, size_t len) { + uint8_t buffer[168U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3(buffer, (size_t)0U, len, uint8_t *), + Eurydice_slice_subslice3(blocks, start, start + len, uint8_t *), uint8_t); + buffer[len] = 31U; + size_t uu____0 = (size_t)168U - (size_t)1U; + buffer[uu____0] = (uint32_t)buffer[uu____0] | 128U; + libcrux_sha3_simd_portable_load_block_3a( + state, Eurydice_array_to_slice((size_t)168U, buffer, uint8_t), + (size_t)0U); +} + +/** +This function found in impl {libcrux_sha3::traits::Absorb<1usize> for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.load_last_a1 +with const generics +- RATE= 168 +- DELIMITER= 31 +*/ +static inline void libcrux_sha3_simd_portable_load_last_a1_c6( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *input, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_c6(self->st, input[0U], start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_sha3.generic_keccak.absorb_final_80 +with types uint64_t +with const generics +- N= 1 +- RATE= 168 +- DELIM= 31 +*/ +static KRML_MUSTINLINE void libcrux_sha3_generic_keccak_absorb_final_80_9e2( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice *last, + size_t start, size_t len) { + libcrux_sha3_simd_portable_load_last_a1_c6(self, last, start, len); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); +} + +/** + Absorb +*/ +static KRML_MUSTINLINE void +libcrux_sha3_portable_incremental_shake128_absorb_final( + libcrux_sha3_generic_keccak_KeccakState_17 *s, Eurydice_slice data0) { + libcrux_sha3_generic_keccak_KeccakState_17 *uu____0 = s; + Eurydice_slice uu____1[1U] = {data0}; + libcrux_sha3_generic_keccak_absorb_final_80_9e2( + uu____0, uu____1, (size_t)0U, Eurydice_slice_len(data0, uint8_t)); +} + +/** +A monomorphic instance of libcrux_sha3.simd.portable.store_block +with const generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void libcrux_sha3_simd_portable_store_block_3a( + uint64_t *s, Eurydice_slice out, size_t start, size_t len) { + size_t octets = len / (size_t)8U; + for (size_t i = (size_t)0U; i < octets; i++) { + size_t i0 = i; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, start + (size_t)8U * i0, start + (size_t)8U * i0 + (size_t)8U, + uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, i0 / (size_t)5U, i0 % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)8U, ret, uint8_t), uint8_t); + } + size_t remaining = len % (size_t)8U; + if (remaining > (size_t)0U) { + Eurydice_slice uu____1 = Eurydice_slice_subslice3( + out, start + len - remaining, start + len, uint8_t *); + uint8_t ret[8U]; + core_num__u64__to_le_bytes( + libcrux_sha3_traits_get_ij_04(s, octets / (size_t)5U, + octets % (size_t)5U)[0U], + ret); + Eurydice_slice_copy( + uu____1, + Eurydice_array_to_subslice3(ret, (size_t)0U, remaining, uint8_t *), + uint8_t); + } +} + +/** +This function found in impl {libcrux_sha3::traits::Squeeze1 for +libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of libcrux_sha3.simd.portable.squeeze_13 +with const generics +- RATE= 168 +*/ +static inline void libcrux_sha3_simd_portable_squeeze_13_3a( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start, size_t len) { + libcrux_sha3_simd_portable_store_block_3a(self->st, out, start, len); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of +libcrux_sha3.generic_keccak.portable.squeeze_first_three_blocks_b4 with const +generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void +libcrux_sha3_generic_keccak_portable_squeeze_first_three_blocks_b4_3a( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out) { + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, (size_t)0U, (size_t)168U); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, (size_t)168U, + (size_t)168U); + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, (size_t)2U * (size_t)168U, + (size_t)168U); +} + +/** + Squeeze three blocks +*/ +static KRML_MUSTINLINE void +libcrux_sha3_portable_incremental_shake128_squeeze_first_three_blocks( + libcrux_sha3_generic_keccak_KeccakState_17 *s, Eurydice_slice out0) { + libcrux_sha3_generic_keccak_portable_squeeze_first_three_blocks_b4_3a(s, + out0); +} + +/** +This function found in impl {libcrux_sha3::generic_keccak::KeccakState[core::marker::Sized, +libcrux_sha3::simd::portable::{libcrux_sha3::traits::KeccakItem<1usize> for +u64}]} +*/ +/** +A monomorphic instance of +libcrux_sha3.generic_keccak.portable.squeeze_next_block_b4 with const generics +- RATE= 168 +*/ +static KRML_MUSTINLINE void +libcrux_sha3_generic_keccak_portable_squeeze_next_block_b4_3a( + libcrux_sha3_generic_keccak_KeccakState_17 *self, Eurydice_slice out, + size_t start) { + libcrux_sha3_generic_keccak_keccakf1600_80_04(self); + libcrux_sha3_simd_portable_squeeze_13_3a(self, out, start, (size_t)168U); +} + +/** + Squeeze another block +*/ +static KRML_MUSTINLINE void +libcrux_sha3_portable_incremental_shake128_squeeze_next_block( + libcrux_sha3_generic_keccak_KeccakState_17 *s, Eurydice_slice out0) { + libcrux_sha3_generic_keccak_portable_squeeze_next_block_b4_3a(s, out0, + (size_t)0U); +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_sha3_portable_H_DEFINED +#endif /* libcrux_sha3_portable_H */ + +/* from libcrux/libcrux-ml-kem/extracts/c_header_only/generated/libcrux_mlkem768_portable.h */ +/* + * SPDX-FileCopyrightText: 2025 Cryspen Sarl + * + * SPDX-License-Identifier: MIT or Apache-2.0 + * + * This code was generated with the following revisions: + * Charon: 667d2fc98984ff7f3df989c2367e6c1fa4a000e7 + * Eurydice: 2381cbc416ef2ad0b561c362c500bc84f36b6785 + * Karamel: 80f5435f2fc505973c469a4afcc8d875cddd0d8b + * F*: 71d8221589d4d438af3706d89cb653cf53e18aab + * Libcrux: 68dfed5a4a9e40277f62828471c029afed1ecdcc + */ + +#ifndef libcrux_mlkem768_portable_H +#define libcrux_mlkem768_portable_H + + +#if defined(__cplusplus) +extern "C" { +#endif + + +static inline void libcrux_ml_kem_hash_functions_portable_G( + Eurydice_slice input, uint8_t ret[64U]) { + uint8_t digest[64U] = {0U}; + libcrux_sha3_portable_sha512( + Eurydice_array_to_slice((size_t)64U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)64U * sizeof(uint8_t)); +} + +static inline void libcrux_ml_kem_hash_functions_portable_H( + Eurydice_slice input, uint8_t ret[32U]) { + uint8_t digest[32U] = {0U}; + libcrux_sha3_portable_sha256( + Eurydice_array_to_slice((size_t)32U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)32U * sizeof(uint8_t)); +} + +static const int16_t libcrux_ml_kem_polynomial_ZETAS_TIMES_MONTGOMERY_R[128U] = + {(int16_t)-1044, (int16_t)-758, (int16_t)-359, (int16_t)-1517, + (int16_t)1493, (int16_t)1422, (int16_t)287, (int16_t)202, + (int16_t)-171, (int16_t)622, (int16_t)1577, (int16_t)182, + (int16_t)962, (int16_t)-1202, (int16_t)-1474, (int16_t)1468, + (int16_t)573, (int16_t)-1325, (int16_t)264, (int16_t)383, + (int16_t)-829, (int16_t)1458, (int16_t)-1602, (int16_t)-130, + (int16_t)-681, (int16_t)1017, (int16_t)732, (int16_t)608, + (int16_t)-1542, (int16_t)411, (int16_t)-205, (int16_t)-1571, + (int16_t)1223, (int16_t)652, (int16_t)-552, (int16_t)1015, + (int16_t)-1293, (int16_t)1491, (int16_t)-282, (int16_t)-1544, + (int16_t)516, (int16_t)-8, (int16_t)-320, (int16_t)-666, + (int16_t)-1618, (int16_t)-1162, (int16_t)126, (int16_t)1469, + (int16_t)-853, (int16_t)-90, (int16_t)-271, (int16_t)830, + (int16_t)107, (int16_t)-1421, (int16_t)-247, (int16_t)-951, + (int16_t)-398, (int16_t)961, (int16_t)-1508, (int16_t)-725, + (int16_t)448, (int16_t)-1065, (int16_t)677, (int16_t)-1275, + (int16_t)-1103, (int16_t)430, (int16_t)555, (int16_t)843, + (int16_t)-1251, (int16_t)871, (int16_t)1550, (int16_t)105, + (int16_t)422, (int16_t)587, (int16_t)177, (int16_t)-235, + (int16_t)-291, (int16_t)-460, (int16_t)1574, (int16_t)1653, + (int16_t)-246, (int16_t)778, (int16_t)1159, (int16_t)-147, + (int16_t)-777, (int16_t)1483, (int16_t)-602, (int16_t)1119, + (int16_t)-1590, (int16_t)644, (int16_t)-872, (int16_t)349, + (int16_t)418, (int16_t)329, (int16_t)-156, (int16_t)-75, + (int16_t)817, (int16_t)1097, (int16_t)603, (int16_t)610, + (int16_t)1322, (int16_t)-1285, (int16_t)-1465, (int16_t)384, + (int16_t)-1215, (int16_t)-136, (int16_t)1218, (int16_t)-1335, + (int16_t)-874, (int16_t)220, (int16_t)-1187, (int16_t)-1659, + (int16_t)-1185, (int16_t)-1530, (int16_t)-1278, (int16_t)794, + (int16_t)-1510, (int16_t)-854, (int16_t)-870, (int16_t)478, + (int16_t)-108, (int16_t)-308, (int16_t)996, (int16_t)991, + (int16_t)958, (int16_t)-1460, (int16_t)1522, (int16_t)1628}; + +static KRML_MUSTINLINE int16_t libcrux_ml_kem_polynomial_zeta(size_t i) { + return libcrux_ml_kem_polynomial_ZETAS_TIMES_MONTGOMERY_R[i]; +} + +#define LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT ((size_t)16U) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR ((size_t)16U) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_MONTGOMERY_R_SQUARED_MOD_FIELD_MODULUS \ + ((int16_t)1353) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS ((int16_t)3329) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_INVERSE_OF_MODULUS_MOD_MONTGOMERY_R \ + (62209U) + +typedef struct libcrux_ml_kem_vector_portable_vector_type_PortableVector_s { + int16_t elements[16U]; +} libcrux_ml_kem_vector_portable_vector_type_PortableVector; + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_vector_type_from_i16_array( + Eurydice_slice array) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector lit; + int16_t ret[16U]; + Result_0a dst; + Eurydice_slice_to_array2( + &dst, Eurydice_slice_subslice3(array, (size_t)0U, (size_t)16U, int16_t *), + Eurydice_slice, int16_t[16U], TryFromSliceError); + unwrap_26_00(dst, ret); + memcpy(lit.elements, ret, (size_t)16U * sizeof(int16_t)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_from_i16_array_b8(Eurydice_slice array) { + return libcrux_ml_kem_vector_portable_vector_type_from_i16_array( + libcrux_secrets_int_classify_public_classify_ref_9b_39(array)); +} + +typedef struct int16_t_x8_s { + int16_t fst; + int16_t snd; + int16_t thd; + int16_t f3; + int16_t f4; + int16_t f5; + int16_t f6; + int16_t f7; +} int16_t_x8; + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_vector_type_zero(void) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector lit; + int16_t ret[16U]; + int16_t buf[16U] = {0U}; + libcrux_secrets_int_public_integers_classify_27_46(buf, ret); + memcpy(lit.elements, ret, (size_t)16U * sizeof(int16_t)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ZERO_b8(void) { + return libcrux_ml_kem_vector_portable_vector_type_zero(); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_add( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + lhs.elements[uu____0] = lhs.elements[uu____0] + rhs->elements[i0]; + } + return lhs; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_add_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + return libcrux_ml_kem_vector_portable_arithmetic_add(lhs, rhs); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_sub( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + lhs.elements[uu____0] = lhs.elements[uu____0] - rhs->elements[i0]; + } + return lhs; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_sub_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs) { + return libcrux_ml_kem_vector_portable_arithmetic_sub(lhs, rhs); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_multiply_by_constant( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + vec.elements[uu____0] = vec.elements[uu____0] * c; + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_multiply_by_constant_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + return libcrux_ml_kem_vector_portable_arithmetic_multiply_by_constant(vec, c); +} + +/** + Note: This function is not secret independent + Only use with public values. +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_cond_subtract_3329( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + if (libcrux_secrets_int_public_integers_declassify_d8_39( + vec.elements[i0]) >= (int16_t)3329) { + size_t uu____0 = i0; + vec.elements[uu____0] = vec.elements[uu____0] - (int16_t)3329; + } + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_cond_subtract_3329_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v) { + return libcrux_ml_kem_vector_portable_arithmetic_cond_subtract_3329(v); +} + +#define LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_BARRETT_MULTIPLIER \ + ((int32_t)20159) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_SHIFT ((int32_t)26) + +#define LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_R \ + ((int32_t)1 << (uint32_t)LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_SHIFT) + +/** + Signed Barrett Reduction + + Given an input `value`, `barrett_reduce` outputs a representative `result` + such that: + + - result ≡ value (mod FIELD_MODULUS) + - the absolute value of `result` is bound as follows: + + `|result| ≤ FIELD_MODULUS / 2 · (|value|/BARRETT_R + 1) + + Note: The input bound is 28296 to prevent overflow in the multiplication of + quotient by FIELD_MODULUS + +*/ +static inline int16_t +libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce_element( + int16_t value) { + int32_t t = libcrux_secrets_int_as_i32_f5(value) * + LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_BARRETT_MULTIPLIER + + (LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_R >> 1U); + int16_t quotient = libcrux_secrets_int_as_i16_36( + t >> (uint32_t)LIBCRUX_ML_KEM_VECTOR_TRAITS_BARRETT_SHIFT); + return value - quotient * LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int16_t vi = + libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce_element( + vec.elements[i0]); + vec.elements[i0] = vi; + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_barrett_reduce_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vector) { + return libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce(vector); +} + +#define LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_MONTGOMERY_SHIFT (16U) + +/** + Signed Montgomery Reduction + + Given an input `value`, `montgomery_reduce` outputs a representative `o` + such that: + + - o ≡ value · MONTGOMERY_R^(-1) (mod FIELD_MODULUS) + - the absolute value of `o` is bound as follows: + + `|result| ≤ ceil(|value| / MONTGOMERY_R) + 1665 + + In particular, if `|value| ≤ FIELD_MODULUS-1 * FIELD_MODULUS-1`, then `|o| <= + FIELD_MODULUS-1`. And, if `|value| ≤ pow2 16 * FIELD_MODULUS-1`, then `|o| <= + FIELD_MODULUS + 1664 + +*/ +static inline int16_t +libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + int32_t value) { + int32_t k = + libcrux_secrets_int_as_i32_f5(libcrux_secrets_int_as_i16_36(value)) * + libcrux_secrets_int_as_i32_b8( + libcrux_secrets_int_public_integers_classify_27_df( + LIBCRUX_ML_KEM_VECTOR_TRAITS_INVERSE_OF_MODULUS_MOD_MONTGOMERY_R)); + int32_t k_times_modulus = + libcrux_secrets_int_as_i32_f5(libcrux_secrets_int_as_i16_36(k)) * + libcrux_secrets_int_as_i32_f5( + libcrux_secrets_int_public_integers_classify_27_39( + LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS)); + int16_t c = libcrux_secrets_int_as_i16_36( + k_times_modulus >> + (uint32_t)LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_MONTGOMERY_SHIFT); + int16_t value_high = libcrux_secrets_int_as_i16_36( + value >> + (uint32_t)LIBCRUX_ML_KEM_VECTOR_PORTABLE_ARITHMETIC_MONTGOMERY_SHIFT); + return value_high - c; +} + +/** + If `fe` is some field element 'x' of the Kyber field and `fer` is congruent to + `y · MONTGOMERY_R`, this procedure outputs a value that is congruent to + `x · y`, as follows: + + `fe · fer ≡ x · y · MONTGOMERY_R (mod FIELD_MODULUS)` + + `montgomery_reduce` takes the value `x · y · MONTGOMERY_R` and outputs a + representative `x · y · MONTGOMERY_R * MONTGOMERY_R^{-1} ≡ x · y (mod + FIELD_MODULUS)`. +*/ +static KRML_MUSTINLINE int16_t +libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + int16_t fe, int16_t fer) { + int32_t product = + libcrux_secrets_int_as_i32_f5(fe) * libcrux_secrets_int_as_i32_f5(fer); + return libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + product); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_by_constant( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + vec.elements[i0] = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + vec.elements[i0], c); + } + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vector, + int16_t constant) { + return libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_by_constant( + vector, libcrux_secrets_int_public_integers_classify_27_39(constant)); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_bitwise_and_with_constant( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, int16_t c) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + size_t uu____0 = i0; + vec.elements[uu____0] = vec.elements[uu____0] & c; + } + return vec; +} + +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.arithmetic.shift_right +with const generics +- SHIFT_BY= 15 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_shift_right_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + vec.elements[i0] = vec.elements[i0] >> (uint32_t)(int32_t)15; + } + return vec; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_arithmetic_to_unsigned_representative( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector t = + libcrux_ml_kem_vector_portable_arithmetic_shift_right_ef(a); + libcrux_ml_kem_vector_portable_vector_type_PortableVector fm = + libcrux_ml_kem_vector_portable_arithmetic_bitwise_and_with_constant( + t, LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS); + return libcrux_ml_kem_vector_portable_arithmetic_add(a, &fm); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_to_unsigned_representative_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_arithmetic_to_unsigned_representative( + a); +} + +/** + The `compress_*` functions implement the `Compress` function specified in the + NIST FIPS 203 standard (Page 18, Expression 4.5), which is defined as: + + ```plaintext + Compress_d: ℤq -> ℤ_{2ᵈ} + Compress_d(x) = ⌈(2ᵈ/q)·x⌋ + ``` + + Since `⌈x⌋ = ⌊x + 1/2⌋` we have: + + ```plaintext + Compress_d(x) = ⌊(2ᵈ/q)·x + 1/2⌋ + = ⌊(2^{d+1}·x + q) / 2q⌋ + ``` + + For further information about the function implementations, consult the + `implementation_notes.pdf` document in this directory. + + The NIST FIPS 203 standard can be found at + . +*/ +static inline uint8_t +libcrux_ml_kem_vector_portable_compress_compress_message_coefficient( + uint16_t fe) { + int16_t shifted = + libcrux_secrets_int_public_integers_classify_27_39((int16_t)1664) - + libcrux_secrets_int_as_i16_ca(fe); + int16_t mask = shifted >> 15U; + int16_t shifted_to_positive = mask ^ shifted; + int16_t shifted_positive_in_range = shifted_to_positive - (int16_t)832; + int16_t r0 = shifted_positive_in_range >> 15U; + int16_t r1 = r0 & (int16_t)1; + return libcrux_secrets_int_as_u8_f5(r1); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_compress_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + a.elements[i0] = libcrux_secrets_int_as_i16_59( + libcrux_ml_kem_vector_portable_compress_compress_message_coefficient( + libcrux_secrets_int_as_u16_f5(a.elements[i0]))); + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_1_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_compress_1(a); +} + +static KRML_MUSTINLINE uint32_t +libcrux_ml_kem_vector_portable_arithmetic_get_n_least_significant_bits( + uint8_t n, uint32_t value) { + return value & ((1U << (uint32_t)n) - 1U); +} + +static inline int16_t +libcrux_ml_kem_vector_portable_compress_compress_ciphertext_coefficient( + uint8_t coefficient_bits, uint16_t fe) { + uint64_t compressed = libcrux_secrets_int_as_u64_ca(fe) + << (uint32_t)coefficient_bits; + compressed = compressed + 1664ULL; + compressed = compressed * 10321340ULL; + compressed = compressed >> 35U; + return libcrux_secrets_int_as_i16_b8( + libcrux_ml_kem_vector_portable_arithmetic_get_n_least_significant_bits( + coefficient_bits, libcrux_secrets_int_as_u32_a3(compressed))); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_decompress_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector z = + libcrux_ml_kem_vector_portable_vector_type_zero(); + libcrux_ml_kem_vector_portable_vector_type_PortableVector s = + libcrux_ml_kem_vector_portable_arithmetic_sub(z, &a); + libcrux_ml_kem_vector_portable_vector_type_PortableVector res = + libcrux_ml_kem_vector_portable_arithmetic_bitwise_and_with_constant( + s, (int16_t)1665); + return res; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_decompress_1_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_decompress_1(a); +} + +static KRML_MUSTINLINE void libcrux_ml_kem_vector_portable_ntt_ntt_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *vec, + int16_t zeta, size_t i, size_t j) { + int16_t t = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + vec->elements[j], + libcrux_secrets_int_public_integers_classify_27_39(zeta)); + int16_t a_minus_t = vec->elements[i] - t; + int16_t a_plus_t = vec->elements[i] + t; + vec->elements[j] = a_minus_t; + vec->elements[i] = a_plus_t; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_layer_1_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)2U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)3U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)4U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)5U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta2, (size_t)8U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta2, (size_t)9U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta3, (size_t)12U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta3, (size_t)13U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_layer_1_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1, int16_t zeta2, int16_t zeta3) { + return libcrux_ml_kem_vector_portable_ntt_ntt_layer_1_step(a, zeta0, zeta1, + zeta2, zeta3); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_layer_2_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1) { + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)4U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)5U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)2U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta0, (size_t)3U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)8U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)9U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)10U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta1, (size_t)11U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_layer_2_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1) { + return libcrux_ml_kem_vector_portable_ntt_ntt_layer_2_step(a, zeta0, zeta1); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_layer_3_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta) { + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)0U, + (size_t)8U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)1U, + (size_t)9U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)2U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)3U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)4U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)5U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)6U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_ntt_step(&vec, zeta, (size_t)7U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_layer_3_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta) { + return libcrux_ml_kem_vector_portable_ntt_ntt_layer_3_step(a, zeta); +} + +static KRML_MUSTINLINE void libcrux_ml_kem_vector_portable_ntt_inv_ntt_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *vec, + int16_t zeta, size_t i, size_t j) { + int16_t a_minus_b = vec->elements[j] - vec->elements[i]; + int16_t a_plus_b = vec->elements[j] + vec->elements[i]; + int16_t o0 = libcrux_ml_kem_vector_portable_arithmetic_barrett_reduce_element( + a_plus_b); + int16_t o1 = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_multiply_fe_by_fer( + a_minus_b, libcrux_secrets_int_public_integers_classify_27_39(zeta)); + vec->elements[i] = o0; + vec->elements[j] = o1; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_1_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)2U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)3U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)4U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)5U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta2, (size_t)8U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta2, (size_t)9U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta3, (size_t)12U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta3, (size_t)13U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_inv_ntt_layer_1_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1, int16_t zeta2, int16_t zeta3) { + return libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_1_step( + a, zeta0, zeta1, zeta2, zeta3); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_2_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta0, int16_t zeta1) { + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)0U, + (size_t)4U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)1U, + (size_t)5U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)2U, + (size_t)6U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta0, (size_t)3U, + (size_t)7U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)8U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)9U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)10U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta1, (size_t)11U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_inv_ntt_layer_2_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta0, + int16_t zeta1) { + return libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_2_step(a, zeta0, + zeta1); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_3_step( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vec, + int16_t zeta) { + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)0U, + (size_t)8U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)1U, + (size_t)9U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)2U, + (size_t)10U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)3U, + (size_t)11U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)4U, + (size_t)12U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)5U, + (size_t)13U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)6U, + (size_t)14U); + libcrux_ml_kem_vector_portable_ntt_inv_ntt_step(&vec, zeta, (size_t)7U, + (size_t)15U); + return vec; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_inv_ntt_layer_3_step_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, int16_t zeta) { + return libcrux_ml_kem_vector_portable_ntt_inv_ntt_layer_3_step(a, zeta); +} + +/** + Compute the product of two Kyber binomials with respect to the + modulus `X² - zeta`. + + This function almost implements Algorithm 11 of the + NIST FIPS 203 standard, which is reproduced below: + + ```plaintext + Input: a₀, a₁, b₀, b₁ ∈ ℤq. + Input: γ ∈ ℤq. + Output: c₀, c₁ ∈ ℤq. + + c₀ ← a₀·b₀ + a₁·b₁·γ + c₁ ← a₀·b₁ + a₁·b₀ + return c₀, c₁ + ``` + We say "almost" because the coefficients output by this function are in + the Montgomery domain (unlike in the specification). + + The NIST FIPS 203 standard can be found at + . +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *a, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *b, int16_t zeta, + size_t i, libcrux_ml_kem_vector_portable_vector_type_PortableVector *out) { + int16_t ai = a->elements[(size_t)2U * i]; + int16_t bi = b->elements[(size_t)2U * i]; + int16_t aj = a->elements[(size_t)2U * i + (size_t)1U]; + int16_t bj = b->elements[(size_t)2U * i + (size_t)1U]; + int32_t ai_bi = + libcrux_secrets_int_as_i32_f5(ai) * libcrux_secrets_int_as_i32_f5(bi); + int32_t aj_bj_ = + libcrux_secrets_int_as_i32_f5(aj) * libcrux_secrets_int_as_i32_f5(bj); + int16_t aj_bj = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + aj_bj_); + int32_t aj_bj_zeta = libcrux_secrets_int_as_i32_f5(aj_bj) * + libcrux_secrets_int_as_i32_f5(zeta); + int32_t ai_bi_aj_bj = ai_bi + aj_bj_zeta; + int16_t o0 = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + ai_bi_aj_bj); + int32_t ai_bj = + libcrux_secrets_int_as_i32_f5(ai) * libcrux_secrets_int_as_i32_f5(bj); + int32_t aj_bi = + libcrux_secrets_int_as_i32_f5(aj) * libcrux_secrets_int_as_i32_f5(bi); + int32_t ai_bj_aj_bi = ai_bj + aj_bi; + int16_t o1 = + libcrux_ml_kem_vector_portable_arithmetic_montgomery_reduce_element( + ai_bj_aj_bi); + out->elements[(size_t)2U * i] = o0; + out->elements[(size_t)2U * i + (size_t)1U] = o1; +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_ntt_multiply( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + int16_t nzeta0 = -zeta0; + int16_t nzeta1 = -zeta1; + int16_t nzeta2 = -zeta2; + int16_t nzeta3 = -zeta3; + libcrux_ml_kem_vector_portable_vector_type_PortableVector out = + libcrux_ml_kem_vector_portable_vector_type_zero(); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta0), + (size_t)0U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta0), + (size_t)1U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta1), + (size_t)2U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta1), + (size_t)3U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta2), + (size_t)4U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta2), + (size_t)5U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(zeta3), + (size_t)6U, &out); + libcrux_ml_kem_vector_portable_ntt_ntt_multiply_binomials( + lhs, rhs, libcrux_secrets_int_public_integers_classify_27_39(nzeta3), + (size_t)7U, &out); + return out; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_ntt_multiply_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector *lhs, + libcrux_ml_kem_vector_portable_vector_type_PortableVector *rhs, + int16_t zeta0, int16_t zeta1, int16_t zeta2, int16_t zeta3) { + return libcrux_ml_kem_vector_portable_ntt_ntt_multiply(lhs, rhs, zeta0, zeta1, + zeta2, zeta3); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[2U]) { + uint8_t result0 = + (((((((uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[0U]) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[1U]) << 1U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[2U]) << 2U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[3U]) << 3U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[4U]) << 4U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[5U]) << 5U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[6U]) << 6U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[7U]) << 7U; + uint8_t result1 = + (((((((uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[8U]) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[9U]) << 1U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[10U]) << 2U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[11U]) << 3U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[12U]) << 4U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[13U]) << 5U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[14U]) << 6U) | + (uint32_t)libcrux_secrets_int_as_u8_f5(v.elements[15U]) << 7U; + ret[0U] = result0; + ret[1U] = result1; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[2U]) { + uint8_t ret0[2U]; + libcrux_ml_kem_vector_portable_serialize_serialize_1(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_d4(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_1_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[2U]) { + libcrux_ml_kem_vector_portable_serialize_1(a, ret); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_1(Eurydice_slice v) { + int16_t result0 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) & 1U); + int16_t result1 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 1U & + 1U); + int16_t result2 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 2U & + 1U); + int16_t result3 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 3U & + 1U); + int16_t result4 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 4U & + 1U); + int16_t result5 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 5U & + 1U); + int16_t result6 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 6U & + 1U); + int16_t result7 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)0U, uint8_t, uint8_t *) >> 7U & + 1U); + int16_t result8 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) & 1U); + int16_t result9 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 1U & + 1U); + int16_t result10 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 2U & + 1U); + int16_t result11 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 3U & + 1U); + int16_t result12 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 4U & + 1U); + int16_t result13 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 5U & + 1U); + int16_t result14 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 6U & + 1U); + int16_t result15 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(v, (size_t)1U, uint8_t, uint8_t *) >> 7U & + 1U); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {result0, result1, result2, result3, result4, result5, + result6, result7, result8, result9, result10, result11, + result12, result13, result14, result15}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_1(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_1( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_1_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_1(a); +} + +typedef struct uint8_t_x4_s { + uint8_t fst; + uint8_t snd; + uint8_t thd; + uint8_t f3; +} uint8_t_x4; + +static KRML_MUSTINLINE uint8_t_x4 +libcrux_ml_kem_vector_portable_serialize_serialize_4_int(Eurydice_slice v) { + uint8_t result0 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)0U, int16_t, int16_t *)); + uint8_t result1 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)3U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)2U, int16_t, int16_t *)); + uint8_t result2 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)5U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)4U, int16_t, int16_t *)); + uint8_t result3 = (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)7U, int16_t, int16_t *)) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5(Eurydice_slice_index( + v, (size_t)6U, int16_t, int16_t *)); + return (KRML_CLITERAL(uint8_t_x4){ + .fst = result0, .snd = result1, .thd = result2, .f3 = result3}); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_4( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[8U]) { + uint8_t_x4 result0_3 = + libcrux_ml_kem_vector_portable_serialize_serialize_4_int( + Eurydice_array_to_subslice3(v.elements, (size_t)0U, (size_t)8U, + int16_t *)); + uint8_t_x4 result4_7 = + libcrux_ml_kem_vector_portable_serialize_serialize_4_int( + Eurydice_array_to_subslice3(v.elements, (size_t)8U, (size_t)16U, + int16_t *)); + ret[0U] = result0_3.fst; + ret[1U] = result0_3.snd; + ret[2U] = result0_3.thd; + ret[3U] = result0_3.f3; + ret[4U] = result4_7.fst; + ret[5U] = result4_7.snd; + ret[6U] = result4_7.thd; + ret[7U] = result4_7.f3; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_4( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[8U]) { + uint8_t ret0[8U]; + libcrux_ml_kem_vector_portable_serialize_serialize_4(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_76(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_4_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[8U]) { + libcrux_ml_kem_vector_portable_serialize_4(a, ret); +} + +static KRML_MUSTINLINE int16_t_x8 +libcrux_ml_kem_vector_portable_serialize_deserialize_4_int( + Eurydice_slice bytes) { + int16_t v0 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *) & + 15U); + int16_t v1 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *) >> + 4U & + 15U); + int16_t v2 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *) & + 15U); + int16_t v3 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *) >> + 4U & + 15U); + int16_t v4 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *) & + 15U); + int16_t v5 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *) >> + 4U & + 15U); + int16_t v6 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *) & + 15U); + int16_t v7 = libcrux_secrets_int_as_i16_59( + (uint32_t)Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *) >> + 4U & + 15U); + return (KRML_CLITERAL(int16_t_x8){.fst = v0, + .snd = v1, + .thd = v2, + .f3 = v3, + .f4 = v4, + .f5 = v5, + .f6 = v6, + .f7 = v7}); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_4(Eurydice_slice bytes) { + int16_t_x8 v0_7 = libcrux_ml_kem_vector_portable_serialize_deserialize_4_int( + Eurydice_slice_subslice3(bytes, (size_t)0U, (size_t)4U, uint8_t *)); + int16_t_x8 v8_15 = libcrux_ml_kem_vector_portable_serialize_deserialize_4_int( + Eurydice_slice_subslice3(bytes, (size_t)4U, (size_t)8U, uint8_t *)); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {v0_7.fst, v0_7.snd, v0_7.thd, v0_7.f3, v0_7.f4, v0_7.f5, + v0_7.f6, v0_7.f7, v8_15.fst, v8_15.snd, v8_15.thd, + v8_15.f3, v8_15.f4, v8_15.f5, v8_15.f6, v8_15.f7}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_4(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_4( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_4_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_4(a); +} + +typedef struct uint8_t_x5_s { + uint8_t fst; + uint8_t snd; + uint8_t thd; + uint8_t f3; + uint8_t f4; +} uint8_t_x5; + +static KRML_MUSTINLINE uint8_t_x5 +libcrux_ml_kem_vector_portable_serialize_serialize_10_int(Eurydice_slice v) { + uint8_t r0 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) & (int16_t)255); + uint8_t r1 = + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) & (int16_t)63) + << 2U | + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) >> 8U & + (int16_t)3); + uint8_t r2 = + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)2U, int16_t, int16_t *) & (int16_t)15) + << 4U | + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) >> 6U & + (int16_t)15); + uint8_t r3 = + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)3U, int16_t, int16_t *) & (int16_t)3) + << 6U | + (uint32_t)libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)2U, int16_t, int16_t *) >> 4U & + (int16_t)63); + uint8_t r4 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)3U, int16_t, int16_t *) >> 2U & + (int16_t)255); + return (KRML_CLITERAL(uint8_t_x5){ + .fst = r0, .snd = r1, .thd = r2, .f3 = r3, .f4 = r4}); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_10( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[20U]) { + uint8_t_x5 r0_4 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)0U, (size_t)4U, + int16_t *)); + uint8_t_x5 r5_9 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)4U, (size_t)8U, + int16_t *)); + uint8_t_x5 r10_14 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)8U, (size_t)12U, + int16_t *)); + uint8_t_x5 r15_19 = libcrux_ml_kem_vector_portable_serialize_serialize_10_int( + Eurydice_array_to_subslice3(v.elements, (size_t)12U, (size_t)16U, + int16_t *)); + ret[0U] = r0_4.fst; + ret[1U] = r0_4.snd; + ret[2U] = r0_4.thd; + ret[3U] = r0_4.f3; + ret[4U] = r0_4.f4; + ret[5U] = r5_9.fst; + ret[6U] = r5_9.snd; + ret[7U] = r5_9.thd; + ret[8U] = r5_9.f3; + ret[9U] = r5_9.f4; + ret[10U] = r10_14.fst; + ret[11U] = r10_14.snd; + ret[12U] = r10_14.thd; + ret[13U] = r10_14.f3; + ret[14U] = r10_14.f4; + ret[15U] = r15_19.fst; + ret[16U] = r15_19.snd; + ret[17U] = r15_19.thd; + ret[18U] = r15_19.f3; + ret[19U] = r15_19.f4; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_10( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[20U]) { + uint8_t ret0[20U]; + libcrux_ml_kem_vector_portable_serialize_serialize_10(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_57(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_10_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[20U]) { + libcrux_ml_kem_vector_portable_serialize_10(a, ret); +} + +static KRML_MUSTINLINE int16_t_x8 +libcrux_ml_kem_vector_portable_serialize_deserialize_10_int( + Eurydice_slice bytes) { + int16_t r0 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *)) & + (int16_t)3) + << 8U | + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *)) & + (int16_t)255)); + int16_t r1 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *)) & + (int16_t)15) + << 6U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *)) >> + 2U); + int16_t r2 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *)) & + (int16_t)63) + << 4U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *)) >> + 4U); + int16_t r3 = libcrux_secrets_int_as_i16_f5( + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)4U, uint8_t, uint8_t *)) + << 2U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)3U, uint8_t, uint8_t *)) >> + 6U); + int16_t r4 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)6U, uint8_t, uint8_t *)) & + (int16_t)3) + << 8U | + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)5U, uint8_t, uint8_t *)) & + (int16_t)255)); + int16_t r5 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)7U, uint8_t, uint8_t *)) & + (int16_t)15) + << 6U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)6U, uint8_t, uint8_t *)) >> + 2U); + int16_t r6 = libcrux_secrets_int_as_i16_f5( + (libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)8U, uint8_t, uint8_t *)) & + (int16_t)63) + << 4U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)7U, uint8_t, uint8_t *)) >> + 4U); + int16_t r7 = libcrux_secrets_int_as_i16_f5( + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)9U, uint8_t, uint8_t *)) + << 2U | + libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)8U, uint8_t, uint8_t *)) >> + 6U); + return (KRML_CLITERAL(int16_t_x8){.fst = r0, + .snd = r1, + .thd = r2, + .f3 = r3, + .f4 = r4, + .f5 = r5, + .f6 = r6, + .f7 = r7}); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_10(Eurydice_slice bytes) { + int16_t_x8 v0_7 = libcrux_ml_kem_vector_portable_serialize_deserialize_10_int( + Eurydice_slice_subslice3(bytes, (size_t)0U, (size_t)10U, uint8_t *)); + int16_t_x8 v8_15 = + libcrux_ml_kem_vector_portable_serialize_deserialize_10_int( + Eurydice_slice_subslice3(bytes, (size_t)10U, (size_t)20U, uint8_t *)); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {v0_7.fst, v0_7.snd, v0_7.thd, v0_7.f3, v0_7.f4, v0_7.f5, + v0_7.f6, v0_7.f7, v8_15.fst, v8_15.snd, v8_15.thd, + v8_15.f3, v8_15.f4, v8_15.f5, v8_15.f6, v8_15.f7}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_10(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_10( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_10_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_10(a); +} + +typedef struct uint8_t_x3_s { + uint8_t fst; + uint8_t snd; + uint8_t thd; +} uint8_t_x3; + +static KRML_MUSTINLINE uint8_t_x3 +libcrux_ml_kem_vector_portable_serialize_serialize_12_int(Eurydice_slice v) { + uint8_t r0 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) & (int16_t)255); + uint8_t r1 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)0U, int16_t, int16_t *) >> 8U | + (Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) & (int16_t)15) + << 4U); + uint8_t r2 = libcrux_secrets_int_as_u8_f5( + Eurydice_slice_index(v, (size_t)1U, int16_t, int16_t *) >> 4U & + (int16_t)255); + return (KRML_CLITERAL(uint8_t_x3){.fst = r0, .snd = r1, .thd = r2}); +} + +static KRML_MUSTINLINE void +libcrux_ml_kem_vector_portable_serialize_serialize_12( + libcrux_ml_kem_vector_portable_vector_type_PortableVector v, + uint8_t ret[24U]) { + uint8_t_x3 r0_2 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)0U, (size_t)2U, + int16_t *)); + uint8_t_x3 r3_5 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)2U, (size_t)4U, + int16_t *)); + uint8_t_x3 r6_8 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)4U, (size_t)6U, + int16_t *)); + uint8_t_x3 r9_11 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)6U, (size_t)8U, + int16_t *)); + uint8_t_x3 r12_14 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)8U, (size_t)10U, + int16_t *)); + uint8_t_x3 r15_17 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)10U, (size_t)12U, + int16_t *)); + uint8_t_x3 r18_20 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)12U, (size_t)14U, + int16_t *)); + uint8_t_x3 r21_23 = libcrux_ml_kem_vector_portable_serialize_serialize_12_int( + Eurydice_array_to_subslice3(v.elements, (size_t)14U, (size_t)16U, + int16_t *)); + ret[0U] = r0_2.fst; + ret[1U] = r0_2.snd; + ret[2U] = r0_2.thd; + ret[3U] = r3_5.fst; + ret[4U] = r3_5.snd; + ret[5U] = r3_5.thd; + ret[6U] = r6_8.fst; + ret[7U] = r6_8.snd; + ret[8U] = r6_8.thd; + ret[9U] = r9_11.fst; + ret[10U] = r9_11.snd; + ret[11U] = r9_11.thd; + ret[12U] = r12_14.fst; + ret[13U] = r12_14.snd; + ret[14U] = r12_14.thd; + ret[15U] = r15_17.fst; + ret[16U] = r15_17.snd; + ret[17U] = r15_17.thd; + ret[18U] = r18_20.fst; + ret[19U] = r18_20.snd; + ret[20U] = r18_20.thd; + ret[21U] = r21_23.fst; + ret[22U] = r21_23.snd; + ret[23U] = r21_23.thd; +} + +static inline void libcrux_ml_kem_vector_portable_serialize_12( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[24U]) { + uint8_t ret0[24U]; + libcrux_ml_kem_vector_portable_serialize_serialize_12(a, ret0); + libcrux_secrets_int_public_integers_declassify_d8_d2(ret0, ret); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline void libcrux_ml_kem_vector_portable_serialize_12_b8( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + uint8_t ret[24U]) { + libcrux_ml_kem_vector_portable_serialize_12(a, ret); +} + +typedef struct int16_t_x2_s { + int16_t fst; + int16_t snd; +} int16_t_x2; + +static KRML_MUSTINLINE int16_t_x2 +libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice bytes) { + int16_t byte0 = libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)0U, uint8_t, uint8_t *)); + int16_t byte1 = libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)1U, uint8_t, uint8_t *)); + int16_t byte2 = libcrux_secrets_int_as_i16_59( + Eurydice_slice_index(bytes, (size_t)2U, uint8_t, uint8_t *)); + int16_t r0 = (byte1 & (int16_t)15) << 8U | (byte0 & (int16_t)255); + int16_t r1 = byte2 << 4U | (byte1 >> 4U & (int16_t)15); + return (KRML_CLITERAL(int16_t_x2){.fst = r0, .snd = r1}); +} + +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_serialize_deserialize_12(Eurydice_slice bytes) { + int16_t_x2 v0_1 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)0U, (size_t)3U, uint8_t *)); + int16_t_x2 v2_3 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)3U, (size_t)6U, uint8_t *)); + int16_t_x2 v4_5 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)6U, (size_t)9U, uint8_t *)); + int16_t_x2 v6_7 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)9U, (size_t)12U, uint8_t *)); + int16_t_x2 v8_9 = libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)12U, (size_t)15U, uint8_t *)); + int16_t_x2 v10_11 = + libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)15U, (size_t)18U, uint8_t *)); + int16_t_x2 v12_13 = + libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)18U, (size_t)21U, uint8_t *)); + int16_t_x2 v14_15 = + libcrux_ml_kem_vector_portable_serialize_deserialize_12_int( + Eurydice_slice_subslice3(bytes, (size_t)21U, (size_t)24U, uint8_t *)); + return ( + KRML_CLITERAL(libcrux_ml_kem_vector_portable_vector_type_PortableVector){ + .elements = {v0_1.fst, v0_1.snd, v2_3.fst, v2_3.snd, v4_5.fst, + v4_5.snd, v6_7.fst, v6_7.snd, v8_9.fst, v8_9.snd, + v10_11.fst, v10_11.snd, v12_13.fst, v12_13.snd, + v14_15.fst, v14_15.snd}}); +} + +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_12(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_serialize_deserialize_12( + libcrux_secrets_int_classify_public_classify_ref_9b_90(a)); +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_deserialize_12_b8(Eurydice_slice a) { + return libcrux_ml_kem_vector_portable_deserialize_12(a); +} + +static KRML_MUSTINLINE size_t +libcrux_ml_kem_vector_portable_sampling_rej_sample(Eurydice_slice a, + Eurydice_slice result) { + size_t sampled = (size_t)0U; + for (size_t i = (size_t)0U; i < Eurydice_slice_len(a, uint8_t) / (size_t)3U; + i++) { + size_t i0 = i; + int16_t b1 = (int16_t)Eurydice_slice_index(a, i0 * (size_t)3U + (size_t)0U, + uint8_t, uint8_t *); + int16_t b2 = (int16_t)Eurydice_slice_index(a, i0 * (size_t)3U + (size_t)1U, + uint8_t, uint8_t *); + int16_t b3 = (int16_t)Eurydice_slice_index(a, i0 * (size_t)3U + (size_t)2U, + uint8_t, uint8_t *); + int16_t d1 = (b2 & (int16_t)15) << 8U | b1; + int16_t d2 = b3 << 4U | b2 >> 4U; + if (d1 < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS) { + if (sampled < (size_t)16U) { + Eurydice_slice_index(result, sampled, int16_t, int16_t *) = d1; + sampled++; + } + } + if (d2 < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS) { + if (sampled < (size_t)16U) { + Eurydice_slice_index(result, sampled, int16_t, int16_t *) = d2; + sampled++; + } + } + } + return sampled; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +static inline size_t libcrux_ml_kem_vector_portable_rej_sample_b8( + Eurydice_slice a, Eurydice_slice out) { + return libcrux_ml_kem_vector_portable_sampling_rej_sample(a, out); +} + +#define LIBCRUX_ML_KEM_MLKEM768_VECTOR_U_COMPRESSION_FACTOR ((size_t)10U) + +#define LIBCRUX_ML_KEM_MLKEM768_C1_BLOCK_SIZE \ + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_MLKEM768_VECTOR_U_COMPRESSION_FACTOR / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_RANK ((size_t)3U) + +#define LIBCRUX_ML_KEM_MLKEM768_C1_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_C1_BLOCK_SIZE * LIBCRUX_ML_KEM_MLKEM768_RANK) + +#define LIBCRUX_ML_KEM_MLKEM768_VECTOR_V_COMPRESSION_FACTOR ((size_t)4U) + +#define LIBCRUX_ML_KEM_MLKEM768_C2_SIZE \ + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_MLKEM768_VECTOR_V_COMPRESSION_FACTOR / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_CIPHERTEXT_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_C1_SIZE + LIBCRUX_ML_KEM_MLKEM768_C2_SIZE) + +#define LIBCRUX_ML_KEM_MLKEM768_T_AS_NTT_ENCODED_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_RANK * \ + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_COEFFICIENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_PUBLIC_KEY_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_T_AS_NTT_ENCODED_SIZE + (size_t)32U) + +#define LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_SECRET_KEY_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_RANK * \ + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * \ + LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_COEFFICIENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA1 ((size_t)2U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA1_RANDOMNESS_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_ETA1 * (size_t)64U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA2 ((size_t)2U) + +#define LIBCRUX_ML_KEM_MLKEM768_ETA2_RANDOMNESS_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_ETA2 * (size_t)64U) + +#define LIBCRUX_ML_KEM_MLKEM768_IMPLICIT_REJECTION_HASH_INPUT_SIZE \ + (LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE + \ + LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_CIPHERTEXT_SIZE) + +typedef libcrux_ml_kem_types_MlKemPrivateKey_d9 + libcrux_ml_kem_mlkem768_MlKem768PrivateKey; + +typedef libcrux_ml_kem_types_MlKemPublicKey_30 + libcrux_ml_kem_mlkem768_MlKem768PublicKey; + +#define LIBCRUX_ML_KEM_MLKEM768_RANKED_BYTES_PER_RING_ELEMENT \ + (LIBCRUX_ML_KEM_MLKEM768_RANK * \ + LIBCRUX_ML_KEM_CONSTANTS_BITS_PER_RING_ELEMENT / (size_t)8U) + +#define LIBCRUX_ML_KEM_MLKEM768_SECRET_KEY_SIZE \ + (LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_SECRET_KEY_SIZE + \ + LIBCRUX_ML_KEM_MLKEM768_CPA_PKE_PUBLIC_KEY_SIZE + \ + LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE + \ + LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE) + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.PolynomialRingElement +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector + +*/ +typedef struct libcrux_ml_kem_polynomial_PolynomialRingElement_1d_s { + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficients[16U]; +} libcrux_ml_kem_polynomial_PolynomialRingElement_1d; + +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.unpacked.IndCpaPrivateKeyUnpacked with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- $3size_t +*/ +typedef struct libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0_s { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d secret_as_ntt[3U]; +} libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0; + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ZERO_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ZERO_d6_ea(void) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d lit; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + repeat_expression[16U]; + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + repeat_expression[i] = libcrux_ml_kem_vector_portable_ZERO_b8(); + } + memcpy(lit.coefficients, repeat_expression, + (size_t)16U * + sizeof(libcrux_ml_kem_vector_portable_vector_type_PortableVector)); + return lit; +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for libcrux_ml_kem::ind_cpa::decrypt::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.decrypt.call_mut_0b +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- VECTOR_U_ENCODED_SIZE= 960 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_decrypt_call_mut_0b_42(void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_to_uncompressed_ring_element with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_to_uncompressed_ring_element_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)24U; i++) { + size_t i0 = i; + Eurydice_slice bytes = + Eurydice_slice_subslice3(serialized, i0 * (size_t)24U, + i0 * (size_t)24U + (size_t)24U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_deserialize_12_b8(bytes); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** + Call [`deserialize_to_uncompressed_ring_element`] for each ring element. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.deserialize_vector +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_deserialize_vector_1b( + Eurydice_slice secret_key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *secret_as_ntt) { + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_serialize_deserialize_to_uncompressed_ring_element_ea( + Eurydice_slice_subslice3( + secret_key, + i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + (i0 + (size_t)1U) * + LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + uint8_t *)); + secret_as_ntt[i0] = uu____0; + } +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for +libcrux_ml_kem::ind_cpa::deserialize_then_decompress_u::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.deserialize_then_decompress_u.call_mut_35 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- U_COMPRESSION_FACTOR= 10 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_call_mut_35_6c( + void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.compress.decompress_ciphertext_coefficient with +const generics +- COEFFICIENT_BITS= 10 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int32_t decompressed = + libcrux_secrets_int_as_i32_f5(a.elements[i0]) * + libcrux_secrets_int_as_i32_f5( + libcrux_secrets_int_public_integers_classify_27_39( + LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS)); + decompressed = (decompressed << 1U) + ((int32_t)1 << (uint32_t)(int32_t)10); + decompressed = decompressed >> (uint32_t)((int32_t)10 + (int32_t)1); + a.elements[i0] = libcrux_secrets_int_as_i16_36(decompressed); + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.decompress_ciphertext_coefficient_b8 with const +generics +- COEFFICIENT_BITS= 10 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_ef( + a); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_10 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_10_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)20U; i++) { + size_t i0 = i; + Eurydice_slice bytes = + Eurydice_slice_subslice3(serialized, i0 * (size_t)20U, + i0 * (size_t)20U + (size_t)20U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_deserialize_10_b8(bytes); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_ef( + coefficient); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_ring_element_u with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- COMPRESSION_FACTOR= 10 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_u_0a( + Eurydice_slice serialized) { + return libcrux_ml_kem_serialize_deserialize_then_decompress_10_ea(serialized); +} + +typedef struct libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2_s { + libcrux_ml_kem_vector_portable_vector_type_PortableVector fst; + libcrux_ml_kem_vector_portable_vector_type_PortableVector snd; +} libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2; + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_layer_int_vec_step +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 + libcrux_ml_kem_ntt_ntt_layer_int_vec_step_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + libcrux_ml_kem_vector_portable_vector_type_PortableVector b, + int16_t zeta_r) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector t = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8(b, + zeta_r); + b = libcrux_ml_kem_vector_portable_sub_b8(a, &t); + a = libcrux_ml_kem_vector_portable_add_b8(a, &t); + return (KRML_CLITERAL( + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2){.fst = a, + .snd = b}); +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_4_plus +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t layer, size_t _initial_coefficient_bound) { + size_t step = (size_t)1U << (uint32_t)layer; + for (size_t i0 = (size_t)0U; i0 < (size_t)128U >> (uint32_t)layer; i0++) { + size_t round = i0; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + size_t offset = round * step * (size_t)2U; + size_t offset_vec = offset / (size_t)16U; + size_t step_vec = step / (size_t)16U; + for (size_t i = offset_vec; i < offset_vec + step_vec; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 uu____0 = + libcrux_ml_kem_ntt_ntt_layer_int_vec_step_ea( + re->coefficients[j], re->coefficients[j + step_vec], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + libcrux_ml_kem_vector_portable_vector_type_PortableVector x = uu____0.fst; + libcrux_ml_kem_vector_portable_vector_type_PortableVector y = uu____0.snd; + re->coefficients[j] = x; + re->coefficients[j + step_vec] = y; + } + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_3 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_3_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t _initial_coefficient_bound) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_ntt_layer_3_step_b8( + re->coefficients[round], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + re->coefficients[round] = uu____0; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_2 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_2_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t _initial_coefficient_bound) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_ntt_layer_2_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)1U)); + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_1_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t _initial_coefficient_bound) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] + (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_ntt_layer_1_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)1U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)2U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] + (size_t)3U)); + zeta_i[0U] = zeta_i[0U] + (size_t)3U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.poly_barrett_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_poly_barrett_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_barrett_reduce_b8( + myself->coefficients[i0]); + myself->coefficients[i0] = uu____0; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.poly_barrett_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self) { + libcrux_ml_kem_polynomial_poly_barrett_reduce_ea(self); +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_vector_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- VECTOR_U_COMPRESSION_FACTOR= 10 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_vector_u_0a( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + size_t zeta_i = (size_t)0U; + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)7U, + (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)6U, + (size_t)2U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)5U, + (size_t)3U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)4U, + (size_t)4U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_3_ea(&zeta_i, re, (size_t)5U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_2_ea(&zeta_i, re, (size_t)6U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_1_ea(&zeta_i, re, (size_t)7U * (size_t)3328U); + libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea(re); +} + +/** + Call [`deserialize_then_decompress_ring_element_u`] on each ring element + in the `ciphertext`. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.deserialize_then_decompress_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- U_COMPRESSION_FACTOR= 10 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_6c( + uint8_t *ciphertext, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d u_as_ntt[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + u_as_ntt[i] = + libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_call_mut_35_6c( + &lvalue, i); + } + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice((size_t)1088U, ciphertext, uint8_t), + uint8_t) / + (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U); + i++) { + size_t i0 = i; + Eurydice_slice u_bytes = Eurydice_array_to_subslice3( + ciphertext, + i0 * (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U), + i0 * (LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U) + + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT * + (size_t)10U / (size_t)8U, + uint8_t *); + u_as_ntt[i0] = + libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_u_0a( + u_bytes); + libcrux_ml_kem_ntt_ntt_vector_u_0a(&u_as_ntt[i0]); + } + memcpy( + ret, u_as_ntt, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.compress.decompress_ciphertext_coefficient with +const generics +- COEFFICIENT_BITS= 4 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int32_t decompressed = + libcrux_secrets_int_as_i32_f5(a.elements[i0]) * + libcrux_secrets_int_as_i32_f5( + libcrux_secrets_int_public_integers_classify_27_39( + LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_MODULUS)); + decompressed = (decompressed << 1U) + ((int32_t)1 << (uint32_t)(int32_t)4); + decompressed = decompressed >> (uint32_t)((int32_t)4 + (int32_t)1); + a.elements[i0] = libcrux_secrets_int_as_i16_36(decompressed); + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.vector.portable.decompress_ciphertext_coefficient_b8 with const +generics +- COEFFICIENT_BITS= 4 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_decompress_ciphertext_coefficient_d1( + a); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.deserialize_then_decompress_4 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_4_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)8U; i++) { + size_t i0 = i; + Eurydice_slice bytes = Eurydice_slice_subslice3( + serialized, i0 * (size_t)8U, i0 * (size_t)8U + (size_t)8U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_deserialize_4_b8(bytes); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_decompress_ciphertext_coefficient_b8_d1( + coefficient); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_ring_element_v with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- COMPRESSION_FACTOR= 4 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_v_89( + Eurydice_slice serialized) { + return libcrux_ml_kem_serialize_deserialize_then_decompress_4_ea(serialized); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ZERO +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ZERO_ea(void) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d lit; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + repeat_expression[16U]; + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + repeat_expression[i] = libcrux_ml_kem_vector_portable_ZERO_b8(); + } + memcpy(lit.coefficients, repeat_expression, + (size_t)16U * + sizeof(libcrux_ml_kem_vector_portable_vector_type_PortableVector)); + return lit; +} + +/** + Given two `KyberPolynomialRingElement`s in their NTT representations, + compute their product. Given two polynomials in the NTT domain `f^` and `ĵ`, + the `iᵗʰ` coefficient of the product `k̂` is determined by the calculation: + + ```plaintext + ĥ[2·i] + ĥ[2·i + 1]X = (f^[2·i] + f^[2·i + 1]X)·(ĝ[2·i] + ĝ[2·i + 1]X) mod (X² + - ζ^(2·BitRev₇(i) + 1)) + ``` + + This function almost implements Algorithm 10 of the + NIST FIPS 203 standard, which is reproduced below: + + ```plaintext + Input: Two arrays fˆ ∈ ℤ₂₅₆ and ĝ ∈ ℤ₂₅₆. + Output: An array ĥ ∈ ℤq. + + for(i ← 0; i < 128; i++) + (ĥ[2i], ĥ[2i+1]) ← BaseCaseMultiply(fˆ[2i], fˆ[2i+1], ĝ[2i], ĝ[2i+1], + ζ^(2·BitRev₇(i) + 1)) end for return ĥ + ``` + We say "almost" because the coefficients of the ring element output by + this function are in the Montgomery domain. + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ntt_multiply +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ntt_multiply_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d out = + libcrux_ml_kem_polynomial_ZERO_ea(); + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_ntt_multiply_b8( + &myself->coefficients[i0], &rhs->coefficients[i0], + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0), + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0 + + (size_t)1U), + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0 + + (size_t)2U), + libcrux_ml_kem_polynomial_zeta((size_t)64U + (size_t)4U * i0 + + (size_t)3U)); + out.coefficients[i0] = uu____0; + } + return out; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.ntt_multiply_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_ntt_multiply_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + return libcrux_ml_kem_polynomial_ntt_multiply_ea(self, rhs); +} + +/** + Given two polynomial ring elements `lhs` and `rhs`, compute the pointwise + sum of their constituent coefficients. +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_to_ring_element +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_to_ring_element_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)16U, myself->coefficients, + libcrux_ml_kem_vector_portable_vector_type_PortableVector), + libcrux_ml_kem_vector_portable_vector_type_PortableVector); + i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_add_b8(myself->coefficients[i0], + &rhs->coefficients[i0]); + myself->coefficients[i0] = uu____0; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_to_ring_element_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *rhs) { + libcrux_ml_kem_polynomial_add_to_ring_element_1b(self, rhs); +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_1_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_inv_ntt_layer_1_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)1U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)2U), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)3U)); + zeta_i[0U] = zeta_i[0U] - (size_t)3U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_2 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_2_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + re->coefficients[round] = + libcrux_ml_kem_vector_portable_inv_ntt_layer_2_step_b8( + re->coefficients[round], libcrux_ml_kem_polynomial_zeta(zeta_i[0U]), + libcrux_ml_kem_polynomial_zeta(zeta_i[0U] - (size_t)1U)); + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_3 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_3_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t round = i; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_inv_ntt_layer_3_step_b8( + re->coefficients[round], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + re->coefficients[round] = uu____0; + } +} + +/** +A monomorphic instance of +libcrux_ml_kem.invert_ntt.inv_ntt_layer_int_vec_step_reduce with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 + libcrux_ml_kem_invert_ntt_inv_ntt_layer_int_vec_step_reduce_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a, + libcrux_ml_kem_vector_portable_vector_type_PortableVector b, + int16_t zeta_r) { + libcrux_ml_kem_vector_portable_vector_type_PortableVector a_minus_b = + libcrux_ml_kem_vector_portable_sub_b8(b, &a); + a = libcrux_ml_kem_vector_portable_barrett_reduce_b8( + libcrux_ml_kem_vector_portable_add_b8(a, &b)); + b = libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + a_minus_b, zeta_r); + return (KRML_CLITERAL( + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2){.fst = a, + .snd = b}); +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_at_layer_4_plus +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea( + size_t *zeta_i, libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, + size_t layer) { + size_t step = (size_t)1U << (uint32_t)layer; + for (size_t i0 = (size_t)0U; i0 < (size_t)128U >> (uint32_t)layer; i0++) { + size_t round = i0; + zeta_i[0U] = zeta_i[0U] - (size_t)1U; + size_t offset = round * step * (size_t)2U; + size_t offset_vec = + offset / LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; + size_t step_vec = + step / LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; + for (size_t i = offset_vec; i < offset_vec + step_vec; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector_x2 uu____0 = + libcrux_ml_kem_invert_ntt_inv_ntt_layer_int_vec_step_reduce_ea( + re->coefficients[j], re->coefficients[j + step_vec], + libcrux_ml_kem_polynomial_zeta(zeta_i[0U])); + libcrux_ml_kem_vector_portable_vector_type_PortableVector x = uu____0.fst; + libcrux_ml_kem_vector_portable_vector_type_PortableVector y = uu____0.snd; + re->coefficients[j] = x; + re->coefficients[j + step_vec] = y; + } + } +} + +/** +A monomorphic instance of libcrux_ml_kem.invert_ntt.invert_ntt_montgomery +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + size_t zeta_i = + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT / (size_t)2U; + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_1_ea(&zeta_i, re); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_2_ea(&zeta_i, re); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_3_ea(&zeta_i, re); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)4U); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)5U); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)6U); + libcrux_ml_kem_invert_ntt_invert_ntt_at_layer_4_plus_ea(&zeta_i, re, + (size_t)7U); + libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea(re); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.subtract_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_subtract_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d b) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + b.coefficients[i0], (int16_t)1441); + libcrux_ml_kem_vector_portable_vector_type_PortableVector diff = + libcrux_ml_kem_vector_portable_sub_b8(myself->coefficients[i0], + &coefficient_normal_form); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(diff); + b.coefficients[i0] = red; + } + return b; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.subtract_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_subtract_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d b) { + return libcrux_ml_kem_polynomial_subtract_reduce_ea(self, b); +} + +/** + The following functions compute various expressions involving + vectors and matrices. The computation of these expressions has been + abstracted away into these functions in order to save on loop iterations. + Compute v − InverseNTT(sᵀ ◦ NTT(u)) +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_message +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_matrix_compute_message_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *v, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *secret_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *u_as_ntt) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(&secret_as_ntt[i0], + &u_as_ntt[i0]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&result, &product); + } + libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b(&result); + return libcrux_ml_kem_polynomial_subtract_reduce_d6_ea(v, result); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.to_unsigned_field_modulus +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_to_unsigned_representative_b8(a); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.compress_then_serialize_message with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_message_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re, uint8_t ret[32U]) { + uint8_t serialized[32U] = {0U}; + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re.coefficients[i0]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_compressed = + libcrux_ml_kem_vector_portable_compress_1_b8(coefficient); + uint8_t bytes[2U]; + libcrux_ml_kem_vector_portable_serialize_1_b8(coefficient_compressed, + bytes); + Eurydice_slice_copy( + Eurydice_array_to_subslice3(serialized, (size_t)2U * i0, + (size_t)2U * i0 + (size_t)2U, uint8_t *), + Eurydice_array_to_slice((size_t)2U, bytes, uint8_t), uint8_t); + } + memcpy(ret, serialized, (size_t)32U * sizeof(uint8_t)); +} + +/** + This function implements Algorithm 14 of the + NIST FIPS 203 specification; this is the Kyber CPA-PKE decryption algorithm. + + Algorithm 14 is reproduced below: + + ```plaintext + Input: decryption key dkₚₖₑ ∈ 𝔹^{384k}. + Input: ciphertext c ∈ 𝔹^{32(dᵤk + dᵥ)}. + Output: message m ∈ 𝔹^{32}. + + c₁ ← c[0 : 32dᵤk] + c₂ ← c[32dᵤk : 32(dᵤk + dᵥ)] + u ← Decompress_{dᵤ}(ByteDecode_{dᵤ}(c₁)) + v ← Decompress_{dᵥ}(ByteDecode_{dᵥ}(c₂)) + ŝ ← ByteDecode₁₂(dkₚₖₑ) + w ← v - NTT-¹(ŝᵀ ◦ NTT(u)) + m ← ByteEncode₁(Compress₁(w)) + return m + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.decrypt_unpacked +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- VECTOR_U_ENCODED_SIZE= 960 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_decrypt_unpacked_42( + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 *secret_key, + uint8_t *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d u_as_ntt[3U]; + libcrux_ml_kem_ind_cpa_deserialize_then_decompress_u_6c(ciphertext, u_as_ntt); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d v = + libcrux_ml_kem_serialize_deserialize_then_decompress_ring_element_v_89( + Eurydice_array_to_subslice_from((size_t)1088U, ciphertext, + (size_t)960U, uint8_t, size_t, + uint8_t[])); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d message = + libcrux_ml_kem_matrix_compute_message_1b(&v, secret_key->secret_as_ntt, + u_as_ntt); + uint8_t ret0[32U]; + libcrux_ml_kem_serialize_compress_then_serialize_message_ea(message, ret0); + memcpy(ret, ret0, (size_t)32U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.decrypt +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- VECTOR_U_ENCODED_SIZE= 960 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_decrypt_42( + Eurydice_slice secret_key, uint8_t *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 + secret_key_unpacked; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret0[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + ret0[i] = libcrux_ml_kem_ind_cpa_decrypt_call_mut_0b_42(&lvalue, i); + } + memcpy( + secret_key_unpacked.secret_as_ntt, ret0, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + libcrux_ml_kem_ind_cpa_deserialize_vector_1b( + secret_key, secret_key_unpacked.secret_as_ntt); + uint8_t ret1[32U]; + libcrux_ml_kem_ind_cpa_decrypt_unpacked_42(&secret_key_unpacked, ciphertext, + ret1); + memcpy(ret, ret1, (size_t)32U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.G_4a +with const generics +- K= 3 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_slice input, uint8_t ret[64U]) { + libcrux_ml_kem_hash_functions_portable_G(input, ret); +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF +with const generics +- LEN= 32 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_9e( + Eurydice_slice input, uint8_t ret[32U]) { + uint8_t digest[32U] = {0U}; + libcrux_sha3_portable_shake256( + Eurydice_array_to_slice((size_t)32U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)32U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF_4a +with const generics +- K= 3 +- LEN= 32 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_4a_41( + Eurydice_slice input, uint8_t ret[32U]) { + libcrux_ml_kem_hash_functions_portable_PRF_9e(input, ret); +} + +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.unpacked.IndCpaPublicKeyUnpacked with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- $3size_t +*/ +typedef struct libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0_s { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d t_as_ntt[3U]; + uint8_t seed_for_A[32U]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d A[3U][3U]; +} libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0; + +/** +This function found in impl {core::default::Default for +libcrux_ml_kem::ind_cpa::unpacked::IndCpaPublicKeyUnpacked[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.unpacked.default_8b +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static inline libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 +libcrux_ml_kem_ind_cpa_unpacked_default_8b_1b(void) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + uu____0[i] = libcrux_ml_kem_polynomial_ZERO_d6_ea(); + } + uint8_t uu____1[32U] = {0U}; + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 lit; + memcpy( + lit.t_as_ntt, uu____0, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + memcpy(lit.seed_for_A, uu____1, (size_t)32U * sizeof(uint8_t)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d repeat_expression0[3U][3U]; + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d repeat_expression[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + repeat_expression[i] = libcrux_ml_kem_polynomial_ZERO_d6_ea(); + } + memcpy(repeat_expression0[i0], repeat_expression, + (size_t)3U * + sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + } + memcpy(lit.A, repeat_expression0, + (size_t)3U * + sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U])); + return lit; +} + +/** + Only use with public values. + + This MUST NOT be used with secret inputs, like its caller + `deserialize_ring_elements_reduced`. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_to_reduced_ring_element with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_to_reduced_ring_element_ea( + Eurydice_slice serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(serialized, uint8_t) / (size_t)24U; i++) { + size_t i0 = i; + Eurydice_slice bytes = + Eurydice_slice_subslice3(serialized, i0 * (size_t)24U, + i0 * (size_t)24U + (size_t)24U, uint8_t *); + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_deserialize_12_b8(bytes); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_cond_subtract_3329_b8(coefficient); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** + See [deserialize_ring_elements_reduced_out]. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_ring_elements_reduced with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_1b( + Eurydice_slice public_key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *deserialized_pk) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len(public_key, uint8_t) / + LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT; + i++) { + size_t i0 = i; + Eurydice_slice ring_element = Eurydice_slice_subslice3( + public_key, i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT + + LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + uint8_t *); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_serialize_deserialize_to_reduced_ring_element_ea( + ring_element); + deserialized_pk[i0] = uu____0; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PortableHash +with const generics +- $3size_t +*/ +typedef struct libcrux_ml_kem_hash_functions_portable_PortableHash_88_s { + libcrux_sha3_generic_keccak_KeccakState_17 shake128_state[3U]; +} libcrux_ml_kem_hash_functions_portable_PortableHash_88; + +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_init_absorb_final with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_hash_functions_portable_PortableHash_88 +libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_e0( + uint8_t (*input)[34U]) { + libcrux_ml_kem_hash_functions_portable_PortableHash_88 shake128_state; + libcrux_sha3_generic_keccak_KeccakState_17 repeat_expression[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + repeat_expression[i] = libcrux_sha3_portable_incremental_shake128_init(); + } + memcpy(shake128_state.shake128_state, repeat_expression, + (size_t)3U * sizeof(libcrux_sha3_generic_keccak_KeccakState_17)); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_incremental_shake128_absorb_final( + &shake128_state.shake128_state[i0], + Eurydice_array_to_slice((size_t)34U, input[i0], uint8_t)); + } + return shake128_state; +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_init_absorb_final_4a with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_hash_functions_portable_PortableHash_88 +libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_4a_e0( + uint8_t (*input)[34U]) { + return libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_e0( + input); +} + +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_first_three_blocks with +const generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *st, + uint8_t ret[3U][504U]) { + uint8_t out[3U][504U] = {{0U}}; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_incremental_shake128_squeeze_first_three_blocks( + &st->shake128_state[i0], + Eurydice_array_to_slice((size_t)504U, out[i0], uint8_t)); + } + memcpy(ret, out, (size_t)3U * sizeof(uint8_t[504U])); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_first_three_blocks_4a +with const generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_4a_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *self, + uint8_t ret[3U][504U]) { + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_e0( + self, ret); +} + +/** + If `bytes` contains a set of uniformly random bytes, this function + uniformly samples a ring element `â` that is treated as being the NTT + representation of the corresponding polynomial `a`. + + Since rejection sampling is used, it is possible the supplied bytes are + not enough to sample the element, in which case an `Err` is returned and the + caller must try again with a fresh set of bytes. + + This function partially implements Algorithm + 6 of the NIST FIPS 203 standard, We say "partially" because this + implementation only accepts a finite set of bytes as input and returns an error + if the set is not enough; Algorithm 6 of the FIPS 203 standard on the other + hand samples from an infinite stream of bytes until the ring element is filled. + Algorithm 6 is reproduced below: + + ```plaintext + Input: byte stream B ∈ 𝔹*. + Output: array â ∈ ℤ₂₅₆. + + i ← 0 + j ← 0 + while j < 256 do + d₁ ← B[i] + 256·(B[i+1] mod 16) + d₂ ← ⌊B[i+1]/16⌋ + 16·B[i+2] + if d₁ < q then + â[j] ← d₁ + j ← j + 1 + end if + if d₂ < q and j < 256 then + â[j] ← d₂ + j ← j + 1 + end if + i ← i + 3 + end while + return â + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_uniform_distribution_next with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- N= 504 +*/ +static KRML_MUSTINLINE bool +libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_89( + uint8_t (*randomness)[504U], size_t *sampled_coefficients, + int16_t (*out)[272U]) { + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + size_t i1 = i0; + for (size_t i = (size_t)0U; i < (size_t)504U / (size_t)24U; i++) { + size_t r = i; + if (sampled_coefficients[i1] < + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + size_t sampled = libcrux_ml_kem_vector_portable_rej_sample_b8( + Eurydice_array_to_subslice3(randomness[i1], r * (size_t)24U, + r * (size_t)24U + (size_t)24U, + uint8_t *), + Eurydice_array_to_subslice3(out[i1], sampled_coefficients[i1], + sampled_coefficients[i1] + (size_t)16U, + int16_t *)); + size_t uu____0 = i1; + sampled_coefficients[uu____0] = sampled_coefficients[uu____0] + sampled; + } + } + } + bool done = true; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + if (sampled_coefficients[i0] >= + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + sampled_coefficients[i0] = + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT; + } else { + done = false; + } + } + return done; +} + +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_next_block with const +generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *st, + uint8_t ret[3U][168U]) { + uint8_t out[3U][168U] = {{0U}}; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_incremental_shake128_squeeze_next_block( + &st->shake128_state[i0], + Eurydice_array_to_slice((size_t)168U, out[i0], uint8_t)); + } + memcpy(ret, out, (size_t)3U * sizeof(uint8_t[168U])); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.hash_functions.portable.shake128_squeeze_next_block_4a with const +generics +- K= 3 +*/ +static inline void +libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_4a_e0( + libcrux_ml_kem_hash_functions_portable_PortableHash_88 *self, + uint8_t ret[3U][168U]) { + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_e0(self, + ret); +} + +/** + If `bytes` contains a set of uniformly random bytes, this function + uniformly samples a ring element `â` that is treated as being the NTT + representation of the corresponding polynomial `a`. + + Since rejection sampling is used, it is possible the supplied bytes are + not enough to sample the element, in which case an `Err` is returned and the + caller must try again with a fresh set of bytes. + + This function partially implements Algorithm + 6 of the NIST FIPS 203 standard, We say "partially" because this + implementation only accepts a finite set of bytes as input and returns an error + if the set is not enough; Algorithm 6 of the FIPS 203 standard on the other + hand samples from an infinite stream of bytes until the ring element is filled. + Algorithm 6 is reproduced below: + + ```plaintext + Input: byte stream B ∈ 𝔹*. + Output: array â ∈ ℤ₂₅₆. + + i ← 0 + j ← 0 + while j < 256 do + d₁ ← B[i] + 256·(B[i+1] mod 16) + d₂ ← ⌊B[i+1]/16⌋ + 16·B[i+2] + if d₁ < q then + â[j] ← d₁ + j ← j + 1 + end if + if d₂ < q and j < 256 then + â[j] ← d₂ + j ← j + 1 + end if + i ← i + 3 + end while + return â + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_uniform_distribution_next with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- N= 168 +*/ +static KRML_MUSTINLINE bool +libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_890( + uint8_t (*randomness)[168U], size_t *sampled_coefficients, + int16_t (*out)[272U]) { + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + size_t i1 = i0; + for (size_t i = (size_t)0U; i < (size_t)168U / (size_t)24U; i++) { + size_t r = i; + if (sampled_coefficients[i1] < + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + size_t sampled = libcrux_ml_kem_vector_portable_rej_sample_b8( + Eurydice_array_to_subslice3(randomness[i1], r * (size_t)24U, + r * (size_t)24U + (size_t)24U, + uint8_t *), + Eurydice_array_to_subslice3(out[i1], sampled_coefficients[i1], + sampled_coefficients[i1] + (size_t)16U, + int16_t *)); + size_t uu____0 = i1; + sampled_coefficients[uu____0] = sampled_coefficients[uu____0] + sampled; + } + } + } + bool done = true; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + if (sampled_coefficients[i0] >= + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT) { + sampled_coefficients[i0] = + LIBCRUX_ML_KEM_CONSTANTS_COEFFICIENTS_IN_RING_ELEMENT; + } else { + done = false; + } + } + return done; +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.from_i16_array +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_from_i16_array_ea(Eurydice_slice a) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result = + libcrux_ml_kem_polynomial_ZERO_ea(); + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_from_i16_array_b8( + Eurydice_slice_subslice3(a, i0 * (size_t)16U, + (i0 + (size_t)1U) * (size_t)16U, + int16_t *)); + result.coefficients[i0] = uu____0; + } + return result; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.from_i16_array_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_from_i16_array_d6_ea(Eurydice_slice a) { + return libcrux_ml_kem_polynomial_from_i16_array_ea(a); +} + +/** +This function found in impl {core::ops::function::FnMut<(@Array), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@2]> for libcrux_ml_kem::sampling::sample_from_xof::closure[TraitClause@0, TraitClause@1, TraitClause@2, TraitClause@3]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.sampling.sample_from_xof.call_mut_e7 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_sampling_sample_from_xof_call_mut_e7_2b( + void **_, int16_t tupled_args[272U]) { + int16_t s[272U]; + memcpy(s, tupled_args, (size_t)272U * sizeof(int16_t)); + return libcrux_ml_kem_polynomial_from_i16_array_d6_ea( + Eurydice_array_to_subslice3(s, (size_t)0U, (size_t)256U, int16_t *)); +} + +/** +A monomorphic instance of libcrux_ml_kem.sampling.sample_from_xof +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_sampling_sample_from_xof_2b( + uint8_t (*seeds)[34U], + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + size_t sampled_coefficients[3U] = {0U}; + int16_t out[3U][272U] = {{0U}}; + libcrux_ml_kem_hash_functions_portable_PortableHash_88 xof_state = + libcrux_ml_kem_hash_functions_portable_shake128_init_absorb_final_4a_e0( + seeds); + uint8_t randomness0[3U][504U]; + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_first_three_blocks_4a_e0( + &xof_state, randomness0); + bool done = libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_89( + randomness0, sampled_coefficients, out); + while (true) { + if (done) { + break; + } else { + uint8_t randomness[3U][168U]; + libcrux_ml_kem_hash_functions_portable_shake128_squeeze_next_block_4a_e0( + &xof_state, randomness); + done = libcrux_ml_kem_sampling_sample_from_uniform_distribution_next_890( + randomness, sampled_coefficients, out); + } + } + /* Passing arrays by value in Rust generates a copy in C */ + int16_t copy_of_out[3U][272U]; + memcpy(copy_of_out, out, (size_t)3U * sizeof(int16_t[272U])); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret0[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + ret0[i] = libcrux_ml_kem_sampling_sample_from_xof_call_mut_e7_2b( + &lvalue, copy_of_out[i]); + } + memcpy( + ret, ret0, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** +A monomorphic instance of libcrux_ml_kem.matrix.sample_matrix_A +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_matrix_sample_matrix_A_2b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*A_transpose)[3U], + uint8_t *seed, bool transpose) { + for (size_t i0 = (size_t)0U; i0 < (size_t)3U; i0++) { + size_t i1 = i0; + uint8_t seeds[3U][34U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + core_array__core__clone__Clone_for__Array_T__N___clone( + (size_t)34U, seed, seeds[i], uint8_t, void *); + } + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t j = i; + seeds[j][32U] = (uint8_t)i1; + seeds[j][33U] = (uint8_t)j; + } + libcrux_ml_kem_polynomial_PolynomialRingElement_1d sampled[3U]; + libcrux_ml_kem_sampling_sample_from_xof_2b(seeds, sampled); + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, sampled, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t j = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d sample = sampled[j]; + if (transpose) { + A_transpose[j][i1] = sample; + } else { + A_transpose[i1][j] = sample; + } + } + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.build_unpacked_public_key_mut +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- T_AS_NTT_ENCODED_SIZE= 1152 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ind_cpa_build_unpacked_public_key_mut_3f( + Eurydice_slice public_key, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + *unpacked_public_key) { + Eurydice_slice uu____0 = Eurydice_slice_subslice_to( + public_key, (size_t)1152U, uint8_t, size_t, uint8_t[]); + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_1b( + uu____0, unpacked_public_key->t_as_ntt); + Eurydice_slice seed = Eurydice_slice_subslice_from( + public_key, (size_t)1152U, uint8_t, size_t, uint8_t[]); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d(*uu____1)[3U] = + unpacked_public_key->A; + uint8_t ret[34U]; + libcrux_ml_kem_utils_into_padded_array_b6(seed, ret); + libcrux_ml_kem_matrix_sample_matrix_A_2b(uu____1, ret, false); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.build_unpacked_public_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- T_AS_NTT_ENCODED_SIZE= 1152 +*/ +static KRML_MUSTINLINE + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + libcrux_ml_kem_ind_cpa_build_unpacked_public_key_3f( + Eurydice_slice public_key) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + unpacked_public_key = libcrux_ml_kem_ind_cpa_unpacked_default_8b_1b(); + libcrux_ml_kem_ind_cpa_build_unpacked_public_key_mut_3f(public_key, + &unpacked_public_key); + return unpacked_public_key; +} + +/** +A monomorphic instance of K. +with types libcrux_ml_kem_polynomial_PolynomialRingElement +libcrux_ml_kem_vector_portable_vector_type_PortableVector[3size_t], +libcrux_ml_kem_polynomial_PolynomialRingElement +libcrux_ml_kem_vector_portable_vector_type_PortableVector + +*/ +typedef struct tuple_ed_s { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d fst[3U]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d snd; +} tuple_ed; + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@2]> for libcrux_ml_kem::ind_cpa::encrypt_c1::closure[TraitClause@0, TraitClause@1, TraitClause@2, +TraitClause@3]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c1.call_mut_f1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- C1_LEN= 960 +- U_COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_f1_85(void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRFxN +with const generics +- K= 3 +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRFxN_41( + uint8_t (*input)[33U], uint8_t ret[3U][128U]) { + uint8_t out[3U][128U] = {{0U}}; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_sha3_portable_shake256( + Eurydice_array_to_slice((size_t)128U, out[i0], uint8_t), + Eurydice_array_to_slice((size_t)33U, input[i0], uint8_t)); + } + memcpy(ret, out, (size_t)3U * sizeof(uint8_t[128U])); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRFxN_4a +with const generics +- K= 3 +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRFxN_4a_41( + uint8_t (*input)[33U], uint8_t ret[3U][128U]) { + libcrux_ml_kem_hash_functions_portable_PRFxN_41(input, ret); +} + +/** + Given a series of uniformly random bytes in `randomness`, for some number + `eta`, the `sample_from_binomial_distribution_{eta}` functions sample a ring + element from a binomial distribution centered at 0 that uses two sets of `eta` + coin flips. If, for example, `eta = ETA`, each ring coefficient is a value `v` + such such that `v ∈ {-ETA, -ETA + 1, ..., 0, ..., ETA + 1, ETA}` and: + + ```plaintext + - If v < 0, Pr[v] = Pr[-v] + - If v >= 0, Pr[v] = BINOMIAL_COEFFICIENT(2 * ETA; ETA - v) / 2 ^ (2 * ETA) + ``` + + The values `v < 0` are mapped to the appropriate `KyberFieldElement`. + + The expected value is: + + ```plaintext + E[X] = (-ETA)Pr[-ETA] + (-(ETA - 1))Pr[-(ETA - 1)] + ... + (ETA - 1)Pr[ETA - 1] + + (ETA)Pr[ETA] = 0 since Pr[-v] = Pr[v] when v < 0. + ``` + + And the variance is: + + ```plaintext + Var(X) = E[(X - E[X])^2] + = E[X^2] + = sum_(v=-ETA to ETA)v^2 * (BINOMIAL_COEFFICIENT(2 * ETA; ETA - v) / + 2^(2 * ETA)) = ETA / 2 + ``` + + This function implements Algorithm 7 of the NIST FIPS 203 + standard, which is reproduced below: + + ```plaintext + Input: byte array B ∈ 𝔹^{64η}. + Output: array f ∈ ℤ₂₅₆. + + b ← BytesToBits(B) + for (i ← 0; i < 256; i++) + x ← ∑(j=0 to η - 1) b[2iη + j] + y ← ∑(j=0 to η - 1) b[2iη + η + j] + f[i] ← x−y mod q + end for + return f + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_binomial_distribution_2 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_sampling_sample_from_binomial_distribution_2_ea( + Eurydice_slice randomness) { + int16_t sampled_i16s[256U] = {0U}; + for (size_t i0 = (size_t)0U; + i0 < Eurydice_slice_len(randomness, uint8_t) / (size_t)4U; i0++) { + size_t chunk_number = i0; + Eurydice_slice byte_chunk = Eurydice_slice_subslice3( + randomness, chunk_number * (size_t)4U, + chunk_number * (size_t)4U + (size_t)4U, uint8_t *); + uint32_t random_bits_as_u32 = + (((uint32_t)Eurydice_slice_index(byte_chunk, (size_t)0U, uint8_t, + uint8_t *) | + (uint32_t)Eurydice_slice_index(byte_chunk, (size_t)1U, uint8_t, + uint8_t *) + << 8U) | + (uint32_t)Eurydice_slice_index(byte_chunk, (size_t)2U, uint8_t, + uint8_t *) + << 16U) | + (uint32_t)Eurydice_slice_index(byte_chunk, (size_t)3U, uint8_t, + uint8_t *) + << 24U; + uint32_t even_bits = random_bits_as_u32 & 1431655765U; + uint32_t odd_bits = random_bits_as_u32 >> 1U & 1431655765U; + uint32_t coin_toss_outcomes = even_bits + odd_bits; + for (uint32_t i = 0U; i < 32U / 4U; i++) { + uint32_t outcome_set = i; + uint32_t outcome_set0 = outcome_set * 4U; + int16_t outcome_1 = + (int16_t)(coin_toss_outcomes >> (uint32_t)outcome_set0 & 3U); + int16_t outcome_2 = + (int16_t)(coin_toss_outcomes >> (uint32_t)(outcome_set0 + 2U) & 3U); + size_t offset = (size_t)(outcome_set0 >> 2U); + sampled_i16s[(size_t)8U * chunk_number + offset] = outcome_1 - outcome_2; + } + } + return libcrux_ml_kem_polynomial_from_i16_array_d6_ea( + Eurydice_array_to_slice((size_t)256U, sampled_i16s, int16_t)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.sampling.sample_from_binomial_distribution with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- ETA= 2 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_slice randomness) { + return libcrux_ml_kem_sampling_sample_from_binomial_distribution_2_ea( + randomness); +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_at_layer_7 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ntt_ntt_at_layer_7_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + size_t step = LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT / (size_t)2U; + for (size_t i = (size_t)0U; i < step; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector t = + libcrux_ml_kem_vector_portable_multiply_by_constant_b8( + re->coefficients[j + step], (int16_t)-1600); + re->coefficients[j + step] = + libcrux_ml_kem_vector_portable_sub_b8(re->coefficients[j], &t); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____1 = + libcrux_ml_kem_vector_portable_add_b8(re->coefficients[j], &t); + re->coefficients[j] = uu____1; + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ntt.ntt_binomially_sampled_ring_element +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ntt_ntt_binomially_sampled_ring_element_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re) { + libcrux_ml_kem_ntt_ntt_at_layer_7_ea(re); + size_t zeta_i = (size_t)1U; + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)6U, + (size_t)11207U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea(&zeta_i, re, (size_t)5U, + (size_t)11207U + (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_4_plus_ea( + &zeta_i, re, (size_t)4U, (size_t)11207U + (size_t)2U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_3_ea( + &zeta_i, re, (size_t)11207U + (size_t)3U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_2_ea( + &zeta_i, re, (size_t)11207U + (size_t)4U * (size_t)3328U); + libcrux_ml_kem_ntt_ntt_at_layer_1_ea( + &zeta_i, re, (size_t)11207U + (size_t)5U * (size_t)3328U); + libcrux_ml_kem_polynomial_poly_barrett_reduce_d6_ea(re); +} + +/** + Sample a vector of ring elements from a centered binomial distribution and + convert them into their NTT representations. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.sample_vector_cbd_then_ntt +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- ETA= 2 +- ETA_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE uint8_t +libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re_as_ntt, + uint8_t *prf_input, uint8_t domain_separator) { + uint8_t prf_inputs[3U][33U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + core_array__core__clone__Clone_for__Array_T__N___clone( + (size_t)33U, prf_input, prf_inputs[i], uint8_t, void *); + } + domain_separator = + libcrux_ml_kem_utils_prf_input_inc_e0(prf_inputs, domain_separator); + uint8_t prf_outputs[3U][128U]; + libcrux_ml_kem_hash_functions_portable_PRFxN_4a_41(prf_inputs, prf_outputs); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + re_as_ntt[i0] = + libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_array_to_slice((size_t)128U, prf_outputs[i0], uint8_t)); + libcrux_ml_kem_ntt_ntt_binomially_sampled_ring_element_ea(&re_as_ntt[i0]); + } + return domain_separator; +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@2]> for libcrux_ml_kem::ind_cpa::encrypt_c1::closure#1[TraitClause@0, TraitClause@1, TraitClause@2, +TraitClause@3]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c1.call_mut_dd +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- C1_LEN= 960 +- U_COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_dd_85(void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** + Sample a vector of ring elements from a centered binomial distribution. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.sample_ring_element_cbd +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- ETA2_RANDOMNESS_SIZE= 128 +- ETA2= 2 +*/ +static KRML_MUSTINLINE uint8_t +libcrux_ml_kem_ind_cpa_sample_ring_element_cbd_3b( + uint8_t *prf_input, uint8_t domain_separator, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_1) { + uint8_t prf_inputs[3U][33U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + core_array__core__clone__Clone_for__Array_T__N___clone( + (size_t)33U, prf_input, prf_inputs[i], uint8_t, void *); + } + domain_separator = + libcrux_ml_kem_utils_prf_input_inc_e0(prf_inputs, domain_separator); + uint8_t prf_outputs[3U][128U]; + libcrux_ml_kem_hash_functions_portable_PRFxN_4a_41(prf_inputs, prf_outputs); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_array_to_slice((size_t)128U, prf_outputs[i0], uint8_t)); + error_1[i0] = uu____0; + } + return domain_separator; +} + +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF +with const generics +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_a6( + Eurydice_slice input, uint8_t ret[128U]) { + uint8_t digest[128U] = {0U}; + libcrux_sha3_portable_shake256( + Eurydice_array_to_slice((size_t)128U, digest, uint8_t), input); + memcpy(ret, digest, (size_t)128U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.PRF_4a +with const generics +- K= 3 +- LEN= 128 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_PRF_4a_410( + Eurydice_slice input, uint8_t ret[128U]) { + libcrux_ml_kem_hash_functions_portable_PRF_a6(input, ret); +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for libcrux_ml_kem::matrix::compute_vector_u::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_vector_u.call_mut_a8 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_matrix_compute_vector_u_call_mut_a8_1b(void **_, + size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_error_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_error_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + myself->coefficients[j], (int16_t)1441); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum = + libcrux_ml_kem_vector_portable_add_b8(coefficient_normal_form, + &error->coefficients[j]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(sum); + myself->coefficients[j] = red; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_error_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_polynomial_add_error_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + libcrux_ml_kem_polynomial_add_error_reduce_ea(self, error); +} + +/** + Compute u := InvertNTT(Aᵀ ◦ r̂) + e₁ +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_vector_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_matrix_compute_vector_u_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*a_as_ntt)[3U], + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *r_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_1, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + result[i] = + libcrux_ml_kem_matrix_compute_vector_u_call_mut_a8_1b(&lvalue, i); + } + for (size_t i0 = (size_t)0U; + i0 < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, a_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]); + i0++) { + size_t i1 = i0; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *row = a_as_ntt[i1]; + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, row, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t j = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *a_element = &row[j]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(a_element, &r_as_ntt[j]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&result[i1], + &product); + } + libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b(&result[i1]); + libcrux_ml_kem_polynomial_add_error_reduce_d6_ea(&result[i1], &error_1[i1]); + } + memcpy( + ret, result, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress.compress +with const generics +- COEFFICIENT_BITS= 10 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_compress_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int16_t uu____0 = libcrux_secrets_int_as_i16_f5( + libcrux_ml_kem_vector_portable_compress_compress_ciphertext_coefficient( + (uint8_t)(int32_t)10, + libcrux_secrets_int_as_u16_f5(a.elements[i0]))); + a.elements[i0] = uu____0; + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress_b8 +with const generics +- COEFFICIENT_BITS= 10 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_b8_ef( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_compress_ef(a); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.compress_then_serialize_10 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- OUT_LEN= 320 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_10_ff( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, uint8_t ret[320U]) { + uint8_t serialized[320U] = {0U}; + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_compress_b8_ef( + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re->coefficients[i0])); + uint8_t bytes[20U]; + libcrux_ml_kem_vector_portable_serialize_10_b8(coefficient, bytes); + Eurydice_slice_copy( + Eurydice_array_to_subslice3(serialized, (size_t)20U * i0, + (size_t)20U * i0 + (size_t)20U, uint8_t *), + Eurydice_array_to_slice((size_t)20U, bytes, uint8_t), uint8_t); + } + memcpy(ret, serialized, (size_t)320U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.compress_then_serialize_ring_element_u with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- COMPRESSION_FACTOR= 10 +- OUT_LEN= 320 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_ring_element_u_fe( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, uint8_t ret[320U]) { + uint8_t uu____0[320U]; + libcrux_ml_kem_serialize_compress_then_serialize_10_ff(re, uu____0); + memcpy(ret, uu____0, (size_t)320U * sizeof(uint8_t)); +} + +/** + Call [`compress_then_serialize_ring_element_u`] on each ring element. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.compress_then_serialize_u +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- OUT_LEN= 960 +- COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_compress_then_serialize_u_43( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d input[3U], + Eurydice_slice out) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, input, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = input[i0]; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, i0 * ((size_t)960U / (size_t)3U), + (i0 + (size_t)1U) * ((size_t)960U / (size_t)3U), uint8_t *); + uint8_t ret[320U]; + libcrux_ml_kem_serialize_compress_then_serialize_ring_element_u_fe(&re, + ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)320U, ret, uint8_t), uint8_t); + } +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c1 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- C1_LEN= 960 +- U_COMPRESSION_FACTOR= 10 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE tuple_ed libcrux_ml_kem_ind_cpa_encrypt_c1_85( + Eurydice_slice randomness, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*matrix)[3U], + Eurydice_slice ciphertext) { + uint8_t prf_input[33U]; + libcrux_ml_kem_utils_into_padded_array_c8(randomness, prf_input); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d r_as_ntt[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + r_as_ntt[i] = libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_f1_85(&lvalue, i); + } + uint8_t domain_separator0 = + libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b(r_as_ntt, prf_input, + 0U); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_1[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + error_1[i] = libcrux_ml_kem_ind_cpa_encrypt_c1_call_mut_dd_85(&lvalue, i); + } + uint8_t domain_separator = libcrux_ml_kem_ind_cpa_sample_ring_element_cbd_3b( + prf_input, domain_separator0, error_1); + prf_input[32U] = domain_separator; + uint8_t prf_output[128U]; + libcrux_ml_kem_hash_functions_portable_PRF_4a_410( + Eurydice_array_to_slice((size_t)33U, prf_input, uint8_t), prf_output); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_2 = + libcrux_ml_kem_sampling_sample_from_binomial_distribution_a0( + Eurydice_array_to_slice((size_t)128U, prf_output, uint8_t)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d u[3U]; + libcrux_ml_kem_matrix_compute_vector_u_1b(matrix, r_as_ntt, error_1, u); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0[3U]; + memcpy( + uu____0, u, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + libcrux_ml_kem_ind_cpa_compress_then_serialize_u_43(uu____0, ciphertext); + /* Passing arrays by value in Rust generates a copy in C */ + libcrux_ml_kem_polynomial_PolynomialRingElement_1d copy_of_r_as_ntt[3U]; + memcpy( + copy_of_r_as_ntt, r_as_ntt, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + tuple_ed lit; + memcpy( + lit.fst, copy_of_r_as_ntt, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + lit.snd = error_2; + return lit; +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_then_decompress_message with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_then_decompress_message_ea( + uint8_t *serialized) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; i < (size_t)16U; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_compressed = + libcrux_ml_kem_vector_portable_deserialize_1_b8( + Eurydice_array_to_subslice3(serialized, (size_t)2U * i0, + (size_t)2U * i0 + (size_t)2U, + uint8_t *)); + libcrux_ml_kem_vector_portable_vector_type_PortableVector uu____0 = + libcrux_ml_kem_vector_portable_decompress_1_b8(coefficient_compressed); + re.coefficients[i0] = uu____0; + } + return re; +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_message_error_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_add_message_error_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *message, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + result.coefficients[i0], (int16_t)1441); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum1 = + libcrux_ml_kem_vector_portable_add_b8(myself->coefficients[i0], + &message->coefficients[i0]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum2 = + libcrux_ml_kem_vector_portable_add_b8(coefficient_normal_form, &sum1); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(sum2); + result.coefficients[i0] = red; + } + return result; +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_message_error_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_polynomial_add_message_error_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *message, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result) { + return libcrux_ml_kem_polynomial_add_message_error_reduce_ea(self, message, + result); +} + +/** + Compute InverseNTT(tᵀ ◦ r̂) + e₂ + message +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_ring_element_v +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_matrix_compute_ring_element_v_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *r_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_2, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *message) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d result = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(&t_as_ntt[i0], + &r_as_ntt[i0]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&result, &product); + } + libcrux_ml_kem_invert_ntt_invert_ntt_montgomery_1b(&result); + return libcrux_ml_kem_polynomial_add_message_error_reduce_d6_ea( + error_2, message, result); +} + +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress.compress +with const generics +- COEFFICIENT_BITS= 4 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_compress_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_VECTOR_TRAITS_FIELD_ELEMENTS_IN_VECTOR; i++) { + size_t i0 = i; + int16_t uu____0 = libcrux_secrets_int_as_i16_f5( + libcrux_ml_kem_vector_portable_compress_compress_ciphertext_coefficient( + (uint8_t)(int32_t)4, + libcrux_secrets_int_as_u16_f5(a.elements[i0]))); + a.elements[i0] = uu____0; + } + return a; +} + +/** +This function found in impl {libcrux_ml_kem::vector::traits::Operations for +libcrux_ml_kem::vector::portable::vector_type::PortableVector} +*/ +/** +A monomorphic instance of libcrux_ml_kem.vector.portable.compress_b8 +with const generics +- COEFFICIENT_BITS= 4 +*/ +static inline libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_vector_portable_compress_b8_d1( + libcrux_ml_kem_vector_portable_vector_type_PortableVector a) { + return libcrux_ml_kem_vector_portable_compress_compress_d1(a); +} + +/** +A monomorphic instance of libcrux_ml_kem.serialize.compress_then_serialize_4 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_4_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re, + Eurydice_slice serialized) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_vector_portable_compress_b8_d1( + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re.coefficients[i0])); + uint8_t bytes[8U]; + libcrux_ml_kem_vector_portable_serialize_4_b8(coefficient, bytes); + Eurydice_slice_copy( + Eurydice_slice_subslice3(serialized, (size_t)8U * i0, + (size_t)8U * i0 + (size_t)8U, uint8_t *), + Eurydice_array_to_slice((size_t)8U, bytes, uint8_t), uint8_t); + } +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.compress_then_serialize_ring_element_v with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +- COMPRESSION_FACTOR= 4 +- OUT_LEN= 128 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_compress_then_serialize_ring_element_v_6c( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re, Eurydice_slice out) { + libcrux_ml_kem_serialize_compress_then_serialize_4_ea(re, out); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_c2 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- V_COMPRESSION_FACTOR= 4 +- C2_LEN= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_encrypt_c2_6c( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *r_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_2, + uint8_t *message, Eurydice_slice ciphertext) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d message_as_ring_element = + libcrux_ml_kem_serialize_deserialize_then_decompress_message_ea(message); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d v = + libcrux_ml_kem_matrix_compute_ring_element_v_1b( + t_as_ntt, r_as_ntt, error_2, &message_as_ring_element); + libcrux_ml_kem_serialize_compress_then_serialize_ring_element_v_6c( + v, ciphertext); +} + +/** + This function implements Algorithm 13 of the + NIST FIPS 203 specification; this is the Kyber CPA-PKE encryption algorithm. + + Algorithm 13 is reproduced below: + + ```plaintext + Input: encryption key ekₚₖₑ ∈ 𝔹^{384k+32}. + Input: message m ∈ 𝔹^{32}. + Input: encryption randomness r ∈ 𝔹^{32}. + Output: ciphertext c ∈ 𝔹^{32(dᵤk + dᵥ)}. + + N ← 0 + t̂ ← ByteDecode₁₂(ekₚₖₑ[0:384k]) + ρ ← ekₚₖₑ[384k: 384k + 32] + for (i ← 0; i < k; i++) + for(j ← 0; j < k; j++) + Â[i,j] ← SampleNTT(XOF(ρ, i, j)) + end for + end for + for(i ← 0; i < k; i++) + r[i] ← SamplePolyCBD_{η₁}(PRF_{η₁}(r,N)) + N ← N + 1 + end for + for(i ← 0; i < k; i++) + e₁[i] ← SamplePolyCBD_{η₂}(PRF_{η₂}(r,N)) + N ← N + 1 + end for + e₂ ← SamplePolyCBD_{η₂}(PRF_{η₂}(r,N)) + r̂ ← NTT(r) + u ← NTT-¹(Âᵀ ◦ r̂) + e₁ + μ ← Decompress₁(ByteDecode₁(m))) + v ← NTT-¹(t̂ᵀ ◦ rˆ) + e₂ + μ + c₁ ← ByteEncode_{dᵤ}(Compress_{dᵤ}(u)) + c₂ ← ByteEncode_{dᵥ}(Compress_{dᵥ}(v)) + return c ← (c₁ ‖ c₂) + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt_unpacked +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_LEN= 960 +- C2_LEN= 128 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_encrypt_unpacked_2a( + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 *public_key, + uint8_t *message, Eurydice_slice randomness, uint8_t ret[1088U]) { + uint8_t ciphertext[1088U] = {0U}; + tuple_ed uu____0 = libcrux_ml_kem_ind_cpa_encrypt_c1_85( + randomness, public_key->A, + Eurydice_array_to_subslice3(ciphertext, (size_t)0U, (size_t)960U, + uint8_t *)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d r_as_ntt[3U]; + memcpy( + r_as_ntt, uu____0.fst, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_2 = uu____0.snd; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____1 = + public_key->t_as_ntt; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____2 = r_as_ntt; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____3 = &error_2; + uint8_t *uu____4 = message; + libcrux_ml_kem_ind_cpa_encrypt_c2_6c( + uu____1, uu____2, uu____3, uu____4, + Eurydice_array_to_subslice_from((size_t)1088U, ciphertext, (size_t)960U, + uint8_t, size_t, uint8_t[])); + memcpy(ret, ciphertext, (size_t)1088U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.encrypt +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] with const +generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_LEN= 960 +- C2_LEN= 128 +- U_COMPRESSION_FACTOR= 10 +- V_COMPRESSION_FACTOR= 4 +- BLOCK_LEN= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_encrypt_2a( + Eurydice_slice public_key, uint8_t *message, Eurydice_slice randomness, + uint8_t ret[1088U]) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 + unpacked_public_key = + libcrux_ml_kem_ind_cpa_build_unpacked_public_key_3f(public_key); + uint8_t ret0[1088U]; + libcrux_ml_kem_ind_cpa_encrypt_unpacked_2a(&unpacked_public_key, message, + randomness, ret0); + memcpy(ret, ret0, (size_t)1088U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::variant::Variant for +libcrux_ml_kem::variant::MlKem} +*/ +/** +A monomorphic instance of libcrux_ml_kem.variant.kdf_39 +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_variant_kdf_39_d6( + Eurydice_slice shared_secret, uint8_t *_, uint8_t ret[32U]) { + uint8_t out[32U] = {0U}; + Eurydice_slice_copy(Eurydice_array_to_slice((size_t)32U, out, uint8_t), + shared_secret, uint8_t); + memcpy(ret, out, (size_t)32U * sizeof(uint8_t)); +} + +/** + This code verifies on some machines, runs out of memory on others +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.decapsulate +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- SECRET_KEY_SIZE= 2400 +- CPA_SECRET_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +- IMPLICIT_REJECTION_HASH_INPUT_SIZE= 1120 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cca_decapsulate_62( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, uint8_t ret[32U]) { + Eurydice_slice_uint8_t_x4 uu____0 = + libcrux_ml_kem_types_unpack_private_key_b4( + Eurydice_array_to_slice((size_t)2400U, private_key->value, uint8_t)); + Eurydice_slice ind_cpa_secret_key = uu____0.fst; + Eurydice_slice ind_cpa_public_key = uu____0.snd; + Eurydice_slice ind_cpa_public_key_hash = uu____0.thd; + Eurydice_slice implicit_rejection_value = uu____0.f3; + uint8_t decrypted[32U]; + libcrux_ml_kem_ind_cpa_decrypt_42(ind_cpa_secret_key, ciphertext->value, + decrypted); + uint8_t to_hash0[64U]; + libcrux_ml_kem_utils_into_padded_array_24( + Eurydice_array_to_slice((size_t)32U, decrypted, uint8_t), to_hash0); + Eurydice_slice_copy( + Eurydice_array_to_subslice_from( + (size_t)64U, to_hash0, LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, + uint8_t, size_t, uint8_t[]), + ind_cpa_public_key_hash, uint8_t); + uint8_t hashed[64U]; + libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_array_to_slice((size_t)64U, to_hash0, uint8_t), hashed); + Eurydice_slice_uint8_t_x2 uu____1 = Eurydice_slice_split_at( + Eurydice_array_to_slice((size_t)64U, hashed, uint8_t), + LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, uint8_t, + Eurydice_slice_uint8_t_x2); + Eurydice_slice shared_secret0 = uu____1.fst; + Eurydice_slice pseudorandomness = uu____1.snd; + uint8_t to_hash[1120U]; + libcrux_ml_kem_utils_into_padded_array_15(implicit_rejection_value, to_hash); + Eurydice_slice uu____2 = Eurydice_array_to_subslice_from( + (size_t)1120U, to_hash, LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, + uint8_t, size_t, uint8_t[]); + Eurydice_slice_copy(uu____2, libcrux_ml_kem_types_as_ref_d3_80(ciphertext), + uint8_t); + uint8_t implicit_rejection_shared_secret0[32U]; + libcrux_ml_kem_hash_functions_portable_PRF_4a_41( + Eurydice_array_to_slice((size_t)1120U, to_hash, uint8_t), + implicit_rejection_shared_secret0); + uint8_t expected_ciphertext[1088U]; + libcrux_ml_kem_ind_cpa_encrypt_2a(ind_cpa_public_key, decrypted, + pseudorandomness, expected_ciphertext); + uint8_t implicit_rejection_shared_secret[32U]; + libcrux_ml_kem_variant_kdf_39_d6( + Eurydice_array_to_slice((size_t)32U, implicit_rejection_shared_secret0, + uint8_t), + libcrux_ml_kem_types_as_slice_a9_80(ciphertext), + implicit_rejection_shared_secret); + uint8_t shared_secret[32U]; + libcrux_ml_kem_variant_kdf_39_d6( + shared_secret0, libcrux_ml_kem_types_as_slice_a9_80(ciphertext), + shared_secret); + uint8_t ret0[32U]; + libcrux_ml_kem_constant_time_ops_compare_ciphertexts_select_shared_secret_in_constant_time( + libcrux_ml_kem_types_as_ref_d3_80(ciphertext), + Eurydice_array_to_slice((size_t)1088U, expected_ciphertext, uint8_t), + Eurydice_array_to_slice((size_t)32U, shared_secret, uint8_t), + Eurydice_array_to_slice((size_t)32U, implicit_rejection_shared_secret, + uint8_t), + ret0); + memcpy(ret, ret0, (size_t)32U * sizeof(uint8_t)); +} + +/** + Portable decapsulate +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.decapsulate with const generics +- K= 3 +- SECRET_KEY_SIZE= 2400 +- CPA_SECRET_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +- CIPHERTEXT_SIZE= 1088 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +- IMPLICIT_REJECTION_HASH_INPUT_SIZE= 1120 +*/ +static inline void +libcrux_ml_kem_ind_cca_instantiations_portable_decapsulate_35( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_ind_cca_decapsulate_62(private_key, ciphertext, ret); +} + +/** + Decapsulate ML-KEM 768 + + Generates an [`MlKemSharedSecret`]. + The input is a reference to an [`MlKem768PrivateKey`] and an + [`MlKem768Ciphertext`]. +*/ +void libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, uint8_t ret[32U]) { + libcrux_ml_kem_ind_cca_instantiations_portable_decapsulate_35( + private_key, ciphertext, ret); +} + +/** +This function found in impl {libcrux_ml_kem::variant::Variant for +libcrux_ml_kem::variant::MlKem} +*/ +/** +A monomorphic instance of libcrux_ml_kem.variant.entropy_preprocess_39 +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_variant_entropy_preprocess_39_9c( + Eurydice_slice randomness, uint8_t ret[32U]) { + uint8_t out[32U] = {0U}; + Eurydice_slice_copy(Eurydice_array_to_slice((size_t)32U, out, uint8_t), + randomness, uint8_t); + memcpy(ret, out, (size_t)32U * sizeof(uint8_t)); +} + +/** +This function found in impl {libcrux_ml_kem::hash_functions::Hash for +libcrux_ml_kem::hash_functions::portable::PortableHash} +*/ +/** +A monomorphic instance of libcrux_ml_kem.hash_functions.portable.H_4a +with const generics +- K= 3 +*/ +static inline void libcrux_ml_kem_hash_functions_portable_H_4a_e0( + Eurydice_slice input, uint8_t ret[32U]) { + libcrux_ml_kem_hash_functions_portable_H(input, ret); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.encapsulate +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- PUBLIC_KEY_SIZE= 1184 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE tuple_c2 libcrux_ml_kem_ind_cca_encapsulate_ca( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, uint8_t *randomness) { + uint8_t randomness0[32U]; + libcrux_ml_kem_variant_entropy_preprocess_39_9c( + Eurydice_array_to_slice((size_t)32U, randomness, uint8_t), randomness0); + uint8_t to_hash[64U]; + libcrux_ml_kem_utils_into_padded_array_24( + Eurydice_array_to_slice((size_t)32U, randomness0, uint8_t), to_hash); + Eurydice_slice uu____0 = Eurydice_array_to_subslice_from( + (size_t)64U, to_hash, LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE, uint8_t, + size_t, uint8_t[]); + uint8_t ret0[32U]; + libcrux_ml_kem_hash_functions_portable_H_4a_e0( + Eurydice_array_to_slice((size_t)1184U, + libcrux_ml_kem_types_as_slice_e6_d0(public_key), + uint8_t), + ret0); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)32U, ret0, uint8_t), uint8_t); + uint8_t hashed[64U]; + libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_array_to_slice((size_t)64U, to_hash, uint8_t), hashed); + Eurydice_slice_uint8_t_x2 uu____1 = Eurydice_slice_split_at( + Eurydice_array_to_slice((size_t)64U, hashed, uint8_t), + LIBCRUX_ML_KEM_CONSTANTS_SHARED_SECRET_SIZE, uint8_t, + Eurydice_slice_uint8_t_x2); + Eurydice_slice shared_secret = uu____1.fst; + Eurydice_slice pseudorandomness = uu____1.snd; + uint8_t ciphertext[1088U]; + libcrux_ml_kem_ind_cpa_encrypt_2a( + Eurydice_array_to_slice((size_t)1184U, + libcrux_ml_kem_types_as_slice_e6_d0(public_key), + uint8_t), + randomness0, pseudorandomness, ciphertext); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_ciphertext[1088U]; + memcpy(copy_of_ciphertext, ciphertext, (size_t)1088U * sizeof(uint8_t)); + tuple_c2 lit; + lit.fst = libcrux_ml_kem_types_from_e0_80(copy_of_ciphertext); + uint8_t ret[32U]; + libcrux_ml_kem_variant_kdf_39_d6(shared_secret, ciphertext, ret); + memcpy(lit.snd, ret, (size_t)32U * sizeof(uint8_t)); + return lit; +} + +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.encapsulate with const generics +- K= 3 +- CIPHERTEXT_SIZE= 1088 +- PUBLIC_KEY_SIZE= 1184 +- T_AS_NTT_ENCODED_SIZE= 1152 +- C1_SIZE= 960 +- C2_SIZE= 128 +- VECTOR_U_COMPRESSION_FACTOR= 10 +- VECTOR_V_COMPRESSION_FACTOR= 4 +- C1_BLOCK_SIZE= 320 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +- ETA2= 2 +- ETA2_RANDOMNESS_SIZE= 128 +*/ +static inline tuple_c2 +libcrux_ml_kem_ind_cca_instantiations_portable_encapsulate_cd( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, uint8_t *randomness) { + return libcrux_ml_kem_ind_cca_encapsulate_ca(public_key, randomness); +} + +/** + Encapsulate ML-KEM 768 + + Generates an ([`MlKem768Ciphertext`], [`MlKemSharedSecret`]) tuple. + The input is a reference to an [`MlKem768PublicKey`] and [`SHARED_SECRET_SIZE`] + bytes of `randomness`. +*/ +tuple_c2 libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]) { + return libcrux_ml_kem_ind_cca_instantiations_portable_encapsulate_cd( + public_key, randomness); +} + +/** +This function found in impl {core::default::Default for +libcrux_ml_kem::ind_cpa::unpacked::IndCpaPrivateKeyUnpacked[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.unpacked.default_70 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static inline libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 +libcrux_ml_kem_ind_cpa_unpacked_default_70_1b(void) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 lit; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d repeat_expression[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + repeat_expression[i] = libcrux_ml_kem_polynomial_ZERO_d6_ea(); + } + memcpy( + lit.secret_as_ntt, repeat_expression, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); + return lit; +} + +/** +This function found in impl {libcrux_ml_kem::variant::Variant for +libcrux_ml_kem::variant::MlKem} +*/ +/** +A monomorphic instance of libcrux_ml_kem.variant.cpa_keygen_seed_39 +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_variant_cpa_keygen_seed_39_9c( + Eurydice_slice key_generation_seed, uint8_t ret[64U]) { + uint8_t seed[33U] = {0U}; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + seed, (size_t)0U, + LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE, uint8_t *), + key_generation_seed, uint8_t); + seed[LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE] = + (uint8_t)(size_t)3U; + uint8_t ret0[64U]; + libcrux_ml_kem_hash_functions_portable_G_4a_e0( + Eurydice_array_to_slice((size_t)33U, seed, uint8_t), ret0); + memcpy(ret, ret0, (size_t)64U * sizeof(uint8_t)); +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@3]> for +libcrux_ml_kem::ind_cpa::generate_keypair_unpacked::closure[TraitClause@0, TraitClause@1, +TraitClause@2, TraitClause@3, TraitClause@4, TraitClause@5]} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cpa.generate_keypair_unpacked.call_mut_73 with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_call_mut_73_1c( + void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.to_standard_domain +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE libcrux_ml_kem_vector_portable_vector_type_PortableVector +libcrux_ml_kem_polynomial_to_standard_domain_ea( + libcrux_ml_kem_vector_portable_vector_type_PortableVector vector) { + return libcrux_ml_kem_vector_portable_montgomery_multiply_by_constant_b8( + vector, + LIBCRUX_ML_KEM_VECTOR_TRAITS_MONTGOMERY_R_SQUARED_MOD_FIELD_MODULUS); +} + +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_standard_error_reduce +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_polynomial_add_standard_error_reduce_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *myself, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t j = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector + coefficient_normal_form = + libcrux_ml_kem_polynomial_to_standard_domain_ea( + myself->coefficients[j]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector sum = + libcrux_ml_kem_vector_portable_add_b8(coefficient_normal_form, + &error->coefficients[j]); + libcrux_ml_kem_vector_portable_vector_type_PortableVector red = + libcrux_ml_kem_vector_portable_barrett_reduce_b8(sum); + myself->coefficients[j] = red; + } +} + +/** +This function found in impl +{libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]} +*/ +/** +A monomorphic instance of libcrux_ml_kem.polynomial.add_standard_error_reduce_d6 +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_polynomial_add_standard_error_reduce_d6_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *self, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error) { + libcrux_ml_kem_polynomial_add_standard_error_reduce_ea(self, error); +} + +/** + Compute  ◦ ŝ + ê +*/ +/** +A monomorphic instance of libcrux_ml_kem.matrix.compute_As_plus_e +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_matrix_compute_As_plus_e_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d (*matrix_A)[3U], + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *s_as_ntt, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *error_as_ntt) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, matrix_A, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d[3U]); + i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *row = matrix_A[i0]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d uu____0 = + libcrux_ml_kem_polynomial_ZERO_d6_ea(); + t_as_ntt[i0] = uu____0; + for (size_t i1 = (size_t)0U; + i1 < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, row, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i1++) { + size_t j = i1; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *matrix_element = + &row[j]; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d product = + libcrux_ml_kem_polynomial_ntt_multiply_d6_ea(matrix_element, + &s_as_ntt[j]); + libcrux_ml_kem_polynomial_add_to_ring_element_d6_1b(&t_as_ntt[i0], + &product); + } + libcrux_ml_kem_polynomial_add_standard_error_reduce_d6_ea( + &t_as_ntt[i0], &error_as_ntt[i0]); + } +} + +/** + This function implements most of Algorithm 12 of the + NIST FIPS 203 specification; this is the Kyber CPA-PKE key generation + algorithm. + + We say "most of" since Algorithm 12 samples the required randomness within + the function itself, whereas this implementation expects it to be provided + through the `key_generation_seed` parameter. + + Algorithm 12 is reproduced below: + + ```plaintext + Output: encryption key ekₚₖₑ ∈ 𝔹^{384k+32}. + Output: decryption key dkₚₖₑ ∈ 𝔹^{384k}. + + d ←$ B + (ρ,σ) ← G(d) + N ← 0 + for (i ← 0; i < k; i++) + for(j ← 0; j < k; j++) + Â[i,j] ← SampleNTT(XOF(ρ, i, j)) + end for + end for + for(i ← 0; i < k; i++) + s[i] ← SamplePolyCBD_{η₁}(PRF_{η₁}(σ,N)) + N ← N + 1 + end for + for(i ← 0; i < k; i++) + e[i] ← SamplePolyCBD_{η₂}(PRF_{η₂}(σ,N)) + N ← N + 1 + end for + ŝ ← NTT(s) + ê ← NTT(e) + t̂ ← Â◦ŝ + ê + ekₚₖₑ ← ByteEncode₁₂(t̂) ‖ ρ + dkₚₖₑ ← ByteEncode₁₂(ŝ) + ``` + + The NIST FIPS 203 standard can be found at + . +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.generate_keypair_unpacked +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_1c( + Eurydice_slice key_generation_seed, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 *private_key, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 *public_key) { + uint8_t hashed[64U]; + libcrux_ml_kem_variant_cpa_keygen_seed_39_9c(key_generation_seed, hashed); + Eurydice_slice_uint8_t_x2 uu____0 = Eurydice_slice_split_at( + Eurydice_array_to_slice((size_t)64U, hashed, uint8_t), (size_t)32U, + uint8_t, Eurydice_slice_uint8_t_x2); + Eurydice_slice seed_for_A = uu____0.fst; + Eurydice_slice seed_for_secret_and_error = uu____0.snd; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d(*uu____1)[3U] = + public_key->A; + uint8_t ret[34U]; + libcrux_ml_kem_utils_into_padded_array_b6(seed_for_A, ret); + libcrux_ml_kem_matrix_sample_matrix_A_2b(uu____1, ret, true); + uint8_t prf_input[33U]; + libcrux_ml_kem_utils_into_padded_array_c8(seed_for_secret_and_error, + prf_input); + uint8_t domain_separator = + libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b( + private_key->secret_as_ntt, prf_input, 0U); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d error_as_ntt[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + error_as_ntt[i] = + libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_call_mut_73_1c(&lvalue, + i); + } + libcrux_ml_kem_ind_cpa_sample_vector_cbd_then_ntt_3b(error_as_ntt, prf_input, + domain_separator); + libcrux_ml_kem_matrix_compute_As_plus_e_1b( + public_key->t_as_ntt, public_key->A, private_key->secret_as_ntt, + error_as_ntt); + uint8_t uu____2[32U]; + Result_fb dst; + Eurydice_slice_to_array2(&dst, seed_for_A, Eurydice_slice, uint8_t[32U], + TryFromSliceError); + unwrap_26_b3(dst, uu____2); + memcpy(public_key->seed_for_A, uu____2, (size_t)32U * sizeof(uint8_t)); +} + +/** +A monomorphic instance of +libcrux_ml_kem.serialize.serialize_uncompressed_ring_element with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics + +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_serialize_uncompressed_ring_element_ea( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *re, uint8_t ret[384U]) { + uint8_t serialized[384U] = {0U}; + for (size_t i = (size_t)0U; + i < LIBCRUX_ML_KEM_POLYNOMIAL_VECTORS_IN_RING_ELEMENT; i++) { + size_t i0 = i; + libcrux_ml_kem_vector_portable_vector_type_PortableVector coefficient = + libcrux_ml_kem_serialize_to_unsigned_field_modulus_ea( + re->coefficients[i0]); + uint8_t bytes[24U]; + libcrux_ml_kem_vector_portable_serialize_12_b8(coefficient, bytes); + Eurydice_slice_copy( + Eurydice_array_to_subslice3(serialized, (size_t)24U * i0, + (size_t)24U * i0 + (size_t)24U, uint8_t *), + Eurydice_array_to_slice((size_t)24U, bytes, uint8_t), uint8_t); + } + memcpy(ret, serialized, (size_t)384U * sizeof(uint8_t)); +} + +/** + Call [`serialize_uncompressed_ring_element`] for each ring element. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_vector +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_serialize_vector_1b( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *key, + Eurydice_slice out) { + for (size_t i = (size_t)0U; + i < Eurydice_slice_len( + Eurydice_array_to_slice( + (size_t)3U, key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d), + libcrux_ml_kem_polynomial_PolynomialRingElement_1d); + i++) { + size_t i0 = i; + libcrux_ml_kem_polynomial_PolynomialRingElement_1d re = key[i0]; + Eurydice_slice uu____0 = Eurydice_slice_subslice3( + out, i0 * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + (i0 + (size_t)1U) * LIBCRUX_ML_KEM_CONSTANTS_BYTES_PER_RING_ELEMENT, + uint8_t *); + uint8_t ret[384U]; + libcrux_ml_kem_serialize_serialize_uncompressed_ring_element_ea(&re, ret); + Eurydice_slice_copy( + uu____0, Eurydice_array_to_slice((size_t)384U, ret, uint8_t), uint8_t); + } +} + +/** + Concatenate `t` and `ρ` into the public key. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_public_key_mut +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_serialize_public_key_mut_89( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + Eurydice_slice seed_for_a, uint8_t *serialized) { + libcrux_ml_kem_ind_cpa_serialize_vector_1b( + t_as_ntt, + Eurydice_array_to_subslice3( + serialized, (size_t)0U, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t *)); + Eurydice_slice_copy( + Eurydice_array_to_subslice_from( + (size_t)1184U, serialized, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t, size_t, uint8_t[]), + seed_for_a, uint8_t); +} + +/** + Concatenate `t` and `ρ` into the public key. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_public_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cpa_serialize_public_key_89( + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *t_as_ntt, + Eurydice_slice seed_for_a, uint8_t ret[1184U]) { + uint8_t public_key_serialized[1184U] = {0U}; + libcrux_ml_kem_ind_cpa_serialize_public_key_mut_89(t_as_ntt, seed_for_a, + public_key_serialized); + memcpy(ret, public_key_serialized, (size_t)1184U * sizeof(uint8_t)); +} + +/** + Serialize the secret key from the unpacked key pair generation. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.serialize_unpacked_secret_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PRIVATE_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +*/ +static inline libcrux_ml_kem_utils_extraction_helper_Keypair768 +libcrux_ml_kem_ind_cpa_serialize_unpacked_secret_key_6c( + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 *public_key, + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 *private_key) { + uint8_t public_key_serialized[1184U]; + libcrux_ml_kem_ind_cpa_serialize_public_key_89( + public_key->t_as_ntt, + Eurydice_array_to_slice((size_t)32U, public_key->seed_for_A, uint8_t), + public_key_serialized); + uint8_t secret_key_serialized[1152U] = {0U}; + libcrux_ml_kem_ind_cpa_serialize_vector_1b( + private_key->secret_as_ntt, + Eurydice_array_to_slice((size_t)1152U, secret_key_serialized, uint8_t)); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_secret_key_serialized[1152U]; + memcpy(copy_of_secret_key_serialized, secret_key_serialized, + (size_t)1152U * sizeof(uint8_t)); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_public_key_serialized[1184U]; + memcpy(copy_of_public_key_serialized, public_key_serialized, + (size_t)1184U * sizeof(uint8_t)); + libcrux_ml_kem_utils_extraction_helper_Keypair768 lit; + memcpy(lit.fst, copy_of_secret_key_serialized, + (size_t)1152U * sizeof(uint8_t)); + memcpy(lit.snd, copy_of_public_key_serialized, + (size_t)1184U * sizeof(uint8_t)); + return lit; +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cpa.generate_keypair +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- PRIVATE_KEY_SIZE= 1152 +- PUBLIC_KEY_SIZE= 1184 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_utils_extraction_helper_Keypair768 +libcrux_ml_kem_ind_cpa_generate_keypair_ea(Eurydice_slice key_generation_seed) { + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPrivateKeyUnpacked_a0 private_key = + libcrux_ml_kem_ind_cpa_unpacked_default_70_1b(); + libcrux_ml_kem_ind_cpa_unpacked_IndCpaPublicKeyUnpacked_a0 public_key = + libcrux_ml_kem_ind_cpa_unpacked_default_8b_1b(); + libcrux_ml_kem_ind_cpa_generate_keypair_unpacked_1c( + key_generation_seed, &private_key, &public_key); + return libcrux_ml_kem_ind_cpa_serialize_unpacked_secret_key_6c(&public_key, + &private_key); +} + +/** + Serialize the secret key. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.serialize_kem_secret_key_mut +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +- SERIALIZED_KEY_LEN= 2400 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_ind_cca_serialize_kem_secret_key_mut_d6( + Eurydice_slice private_key, Eurydice_slice public_key, + Eurydice_slice implicit_rejection_value, uint8_t *serialized) { + size_t pointer = (size_t)0U; + uint8_t *uu____0 = serialized; + size_t uu____1 = pointer; + size_t uu____2 = pointer; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____0, uu____1, uu____2 + Eurydice_slice_len(private_key, uint8_t), + uint8_t *), + private_key, uint8_t); + pointer = pointer + Eurydice_slice_len(private_key, uint8_t); + uint8_t *uu____3 = serialized; + size_t uu____4 = pointer; + size_t uu____5 = pointer; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____3, uu____4, uu____5 + Eurydice_slice_len(public_key, uint8_t), + uint8_t *), + public_key, uint8_t); + pointer = pointer + Eurydice_slice_len(public_key, uint8_t); + Eurydice_slice uu____6 = Eurydice_array_to_subslice3( + serialized, pointer, pointer + LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE, + uint8_t *); + uint8_t ret[32U]; + libcrux_ml_kem_hash_functions_portable_H_4a_e0(public_key, ret); + Eurydice_slice_copy( + uu____6, Eurydice_array_to_slice((size_t)32U, ret, uint8_t), uint8_t); + pointer = pointer + LIBCRUX_ML_KEM_CONSTANTS_H_DIGEST_SIZE; + uint8_t *uu____7 = serialized; + size_t uu____8 = pointer; + size_t uu____9 = pointer; + Eurydice_slice_copy( + Eurydice_array_to_subslice3( + uu____7, uu____8, + uu____9 + Eurydice_slice_len(implicit_rejection_value, uint8_t), + uint8_t *), + implicit_rejection_value, uint8_t); +} + +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.serialize_kem_secret_key +with types libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]] +with const generics +- K= 3 +- SERIALIZED_KEY_LEN= 2400 +*/ +static KRML_MUSTINLINE void libcrux_ml_kem_ind_cca_serialize_kem_secret_key_d6( + Eurydice_slice private_key, Eurydice_slice public_key, + Eurydice_slice implicit_rejection_value, uint8_t ret[2400U]) { + uint8_t out[2400U] = {0U}; + libcrux_ml_kem_ind_cca_serialize_kem_secret_key_mut_d6( + private_key, public_key, implicit_rejection_value, out); + memcpy(ret, out, (size_t)2400U * sizeof(uint8_t)); +} + +/** + Packed API + + Generate a key pair. + + Depending on the `Vector` and `Hasher` used, this requires different hardware + features +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.generate_keypair +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector, +libcrux_ml_kem_hash_functions_portable_PortableHash[[$3size_t]], +libcrux_ml_kem_variant_MlKem with const generics +- K= 3 +- CPA_PRIVATE_KEY_SIZE= 1152 +- PRIVATE_KEY_SIZE= 2400 +- PUBLIC_KEY_SIZE= 1184 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static KRML_MUSTINLINE libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_ind_cca_generate_keypair_15(uint8_t *randomness) { + Eurydice_slice ind_cpa_keypair_randomness = Eurydice_array_to_subslice3( + randomness, (size_t)0U, + LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE, uint8_t *); + Eurydice_slice implicit_rejection_value = Eurydice_array_to_subslice_from( + (size_t)64U, randomness, + LIBCRUX_ML_KEM_CONSTANTS_CPA_PKE_KEY_GENERATION_SEED_SIZE, uint8_t, + size_t, uint8_t[]); + libcrux_ml_kem_utils_extraction_helper_Keypair768 uu____0 = + libcrux_ml_kem_ind_cpa_generate_keypair_ea(ind_cpa_keypair_randomness); + uint8_t ind_cpa_private_key[1152U]; + memcpy(ind_cpa_private_key, uu____0.fst, (size_t)1152U * sizeof(uint8_t)); + uint8_t public_key[1184U]; + memcpy(public_key, uu____0.snd, (size_t)1184U * sizeof(uint8_t)); + uint8_t secret_key_serialized[2400U]; + libcrux_ml_kem_ind_cca_serialize_kem_secret_key_d6( + Eurydice_array_to_slice((size_t)1152U, ind_cpa_private_key, uint8_t), + Eurydice_array_to_slice((size_t)1184U, public_key, uint8_t), + implicit_rejection_value, secret_key_serialized); + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_secret_key_serialized[2400U]; + memcpy(copy_of_secret_key_serialized, secret_key_serialized, + (size_t)2400U * sizeof(uint8_t)); + libcrux_ml_kem_types_MlKemPrivateKey_d9 private_key = + libcrux_ml_kem_types_from_77_28(copy_of_secret_key_serialized); + libcrux_ml_kem_types_MlKemPrivateKey_d9 uu____2 = private_key; + /* Passing arrays by value in Rust generates a copy in C */ + uint8_t copy_of_public_key[1184U]; + memcpy(copy_of_public_key, public_key, (size_t)1184U * sizeof(uint8_t)); + return libcrux_ml_kem_types_from_17_74( + uu____2, libcrux_ml_kem_types_from_fd_d0(copy_of_public_key)); +} + +/** + Portable generate key pair. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.generate_keypair with const +generics +- K= 3 +- CPA_PRIVATE_KEY_SIZE= 1152 +- PRIVATE_KEY_SIZE= 2400 +- PUBLIC_KEY_SIZE= 1184 +- ETA1= 2 +- ETA1_RANDOMNESS_SIZE= 128 +*/ +static inline libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_ind_cca_instantiations_portable_generate_keypair_ce( + uint8_t *randomness) { + return libcrux_ml_kem_ind_cca_generate_keypair_15(randomness); +} + +/** + Generate ML-KEM 768 Key Pair +*/ +libcrux_ml_kem_mlkem768_MlKem768KeyPair +libcrux_ml_kem_mlkem768_portable_generate_key_pair(uint8_t randomness[64U]) { + return libcrux_ml_kem_ind_cca_instantiations_portable_generate_keypair_ce( + randomness); +} + +/** +This function found in impl {core::ops::function::FnMut<(usize), +libcrux_ml_kem::polynomial::PolynomialRingElement[TraitClause@0, +TraitClause@1]> for +libcrux_ml_kem::serialize::deserialize_ring_elements_reduced_out::closure[TraitClause@0, TraitClause@1]} +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_ring_elements_reduced_out.call_mut_0b with +types libcrux_ml_kem_vector_portable_vector_type_PortableVector with const +generics +- K= 3 +*/ +static inline libcrux_ml_kem_polynomial_PolynomialRingElement_1d +libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_call_mut_0b_1b( + void **_, size_t tupled_args) { + return libcrux_ml_kem_polynomial_ZERO_d6_ea(); +} + +/** + This function deserializes ring elements and reduces the result by the field + modulus. + + This function MUST NOT be used on secret inputs. +*/ +/** +A monomorphic instance of +libcrux_ml_kem.serialize.deserialize_ring_elements_reduced_out with types +libcrux_ml_kem_vector_portable_vector_type_PortableVector with const generics +- K= 3 +*/ +static KRML_MUSTINLINE void +libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_1b( + Eurydice_slice public_key, + libcrux_ml_kem_polynomial_PolynomialRingElement_1d ret[3U]) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d deserialized_pk[3U]; + for (size_t i = (size_t)0U; i < (size_t)3U; i++) { + /* original Rust expression is not an lvalue in C */ + void *lvalue = (void *)0U; + deserialized_pk[i] = + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_call_mut_0b_1b( + &lvalue, i); + } + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_1b( + public_key, deserialized_pk); + memcpy( + ret, deserialized_pk, + (size_t)3U * sizeof(libcrux_ml_kem_polynomial_PolynomialRingElement_1d)); +} + +/** + Validate an ML-KEM public key. + + This implements the Modulus check in 7.2 2. + Note that the size check in 7.2 1 is covered by the `PUBLIC_KEY_SIZE` in the + `public_key` type. +*/ +/** +A monomorphic instance of libcrux_ml_kem.ind_cca.validate_public_key +with types libcrux_ml_kem_vector_portable_vector_type_PortableVector +with const generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE bool libcrux_ml_kem_ind_cca_validate_public_key_89( + uint8_t *public_key) { + libcrux_ml_kem_polynomial_PolynomialRingElement_1d deserialized_pk[3U]; + libcrux_ml_kem_serialize_deserialize_ring_elements_reduced_out_1b( + Eurydice_array_to_subslice_to( + (size_t)1184U, public_key, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t, size_t, uint8_t[]), + deserialized_pk); + libcrux_ml_kem_polynomial_PolynomialRingElement_1d *uu____0 = deserialized_pk; + uint8_t public_key_serialized[1184U]; + libcrux_ml_kem_ind_cpa_serialize_public_key_89( + uu____0, + Eurydice_array_to_subslice_from( + (size_t)1184U, public_key, + libcrux_ml_kem_constants_ranked_bytes_per_ring_element((size_t)3U), + uint8_t, size_t, uint8_t[]), + public_key_serialized); + return Eurydice_array_eq((size_t)1184U, public_key, public_key_serialized, + uint8_t); +} + +/** + Public key validation +*/ +/** +A monomorphic instance of +libcrux_ml_kem.ind_cca.instantiations.portable.validate_public_key with const +generics +- K= 3 +- PUBLIC_KEY_SIZE= 1184 +*/ +static KRML_MUSTINLINE bool +libcrux_ml_kem_ind_cca_instantiations_portable_validate_public_key_41( + uint8_t *public_key) { + return libcrux_ml_kem_ind_cca_validate_public_key_89(public_key); +} + +/** + Validate a public key. + + Returns `true` if valid, and `false` otherwise. +*/ +bool libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key) { + return libcrux_ml_kem_ind_cca_instantiations_portable_validate_public_key_41( + public_key->value); +} + +#if defined(__cplusplus) +} +#endif + +#define libcrux_mlkem768_portable_H_DEFINED +#endif /* libcrux_mlkem768_portable_H */ + + +/* rename some types to be a bit more ergonomic */ +#define libcrux_mlkem768_keypair libcrux_ml_kem_mlkem768_MlKem768KeyPair_s +#define libcrux_mlkem768_pk libcrux_ml_kem_types_MlKemPublicKey_30_s +#define libcrux_mlkem768_sk libcrux_ml_kem_types_MlKemPrivateKey_d9_s +#define libcrux_mlkem768_ciphertext libcrux_ml_kem_mlkem768_MlKem768Ciphertext_s +#define libcrux_mlkem768_enc_result tuple_c2_s +/* defines for PRNG inputs */ +#define LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN 64U +#define LIBCRUX_ML_KEM_ENC_PRNG_LEN 32 diff --git a/src/libs/libssh-0.12.2/src/external/poly1305.c b/src/libs/libssh-0.12.2/src/external/poly1305.c new file mode 100644 index 000000000000..916dd625fffa --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/poly1305.c @@ -0,0 +1,156 @@ +/* + * Public Domain poly1305 from Andrew Moon + * poly1305-donna-unrolled.c from https://github.com/floodyberry/poly1305-donna + */ + +#include "config.h" + +#include +#include + +#include "libssh/poly1305.h" + +#define mul32x32_64(a,b) ((uint64_t)(a) * (b)) + +#define U8TO32_LE(p) \ + (((uint32_t)((p)[0])) | \ + ((uint32_t)((p)[1]) << 8) | \ + ((uint32_t)((p)[2]) << 16) | \ + ((uint32_t)((p)[3]) << 24)) + +#define U32TO8_LE(p, v) \ + do { \ + (p)[0] = (uint8_t)((v)); \ + (p)[1] = (uint8_t)((v) >> 8); \ + (p)[2] = (uint8_t)((v) >> 16); \ + (p)[3] = (uint8_t)((v) >> 24); \ + } while (0) + +void +poly1305_auth(unsigned char out[POLY1305_TAGLEN], const unsigned char *m, size_t inlen, const unsigned char key[POLY1305_KEYLEN]) { + uint32_t t0,t1,t2,t3; + uint32_t h0,h1,h2,h3,h4; + uint32_t r0,r1,r2,r3,r4; + uint32_t s1,s2,s3,s4; + uint32_t b, nb; + size_t j; + uint64_t t[5]; + uint64_t f0,f1,f2,f3; + uint32_t g0,g1,g2,g3,g4; + uint64_t c; + unsigned char mp[16]; + + /* clamp key */ + t0 = U8TO32_LE(key+0); + t1 = U8TO32_LE(key+4); + t2 = U8TO32_LE(key+8); + t3 = U8TO32_LE(key+12); + + /* precompute multipliers */ + r0 = t0 & 0x3ffffff; t0 >>= 26; t0 |= t1 << 6; + r1 = t0 & 0x3ffff03; t1 >>= 20; t1 |= t2 << 12; + r2 = t1 & 0x3ffc0ff; t2 >>= 14; t2 |= t3 << 18; + r3 = t2 & 0x3f03fff; t3 >>= 8; + r4 = t3 & 0x00fffff; + + s1 = r1 * 5; + s2 = r2 * 5; + s3 = r3 * 5; + s4 = r4 * 5; + + /* init state */ + h0 = 0; + h1 = 0; + h2 = 0; + h3 = 0; + h4 = 0; + + /* full blocks */ + if (inlen < 16) goto poly1305_donna_atmost15bytes; +poly1305_donna_16bytes: + m += 16; + inlen -= 16; + + t0 = U8TO32_LE(m-16); + t1 = U8TO32_LE(m-12); + t2 = U8TO32_LE(m-8); + t3 = U8TO32_LE(m-4); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8) | (1 << 24); + + +poly1305_donna_mul: + t[0] = mul32x32_64(h0,r0) + mul32x32_64(h1,s4) + mul32x32_64(h2,s3) + mul32x32_64(h3,s2) + mul32x32_64(h4,s1); + t[1] = mul32x32_64(h0,r1) + mul32x32_64(h1,r0) + mul32x32_64(h2,s4) + mul32x32_64(h3,s3) + mul32x32_64(h4,s2); + t[2] = mul32x32_64(h0,r2) + mul32x32_64(h1,r1) + mul32x32_64(h2,r0) + mul32x32_64(h3,s4) + mul32x32_64(h4,s3); + t[3] = mul32x32_64(h0,r3) + mul32x32_64(h1,r2) + mul32x32_64(h2,r1) + mul32x32_64(h3,r0) + mul32x32_64(h4,s4); + t[4] = mul32x32_64(h0,r4) + mul32x32_64(h1,r3) + mul32x32_64(h2,r2) + mul32x32_64(h3,r1) + mul32x32_64(h4,r0); + + h0 = (uint32_t)t[0] & 0x3ffffff; c = (t[0] >> 26); + t[1] += c; h1 = (uint32_t)t[1] & 0x3ffffff; b = (uint32_t)(t[1] >> 26); + t[2] += b; h2 = (uint32_t)t[2] & 0x3ffffff; b = (uint32_t)(t[2] >> 26); + t[3] += b; h3 = (uint32_t)t[3] & 0x3ffffff; b = (uint32_t)(t[3] >> 26); + t[4] += b; h4 = (uint32_t)t[4] & 0x3ffffff; b = (uint32_t)(t[4] >> 26); + h0 += b * 5; + + if (inlen >= 16) goto poly1305_donna_16bytes; + + /* final bytes */ +poly1305_donna_atmost15bytes: + if (!inlen) goto poly1305_donna_finish; + + for (j = 0; j < inlen; j++) mp[j] = m[j]; + mp[j++] = 1; + for (; j < 16; j++) mp[j] = 0; + inlen = 0; + + t0 = U8TO32_LE(mp+0); + t1 = U8TO32_LE(mp+4); + t2 = U8TO32_LE(mp+8); + t3 = U8TO32_LE(mp+12); + + h0 += t0 & 0x3ffffff; + h1 += ((((uint64_t)t1 << 32) | t0) >> 26) & 0x3ffffff; + h2 += ((((uint64_t)t2 << 32) | t1) >> 20) & 0x3ffffff; + h3 += ((((uint64_t)t3 << 32) | t2) >> 14) & 0x3ffffff; + h4 += (t3 >> 8); + + goto poly1305_donna_mul; + +poly1305_donna_finish: + b = h0 >> 26; h0 = h0 & 0x3ffffff; + h1 += b; b = h1 >> 26; h1 = h1 & 0x3ffffff; + h2 += b; b = h2 >> 26; h2 = h2 & 0x3ffffff; + h3 += b; b = h3 >> 26; h3 = h3 & 0x3ffffff; + h4 += b; b = h4 >> 26; h4 = h4 & 0x3ffffff; + h0 += b * 5; b = h0 >> 26; h0 = h0 & 0x3ffffff; + h1 += b; + + g0 = h0 + 5; b = g0 >> 26; g0 &= 0x3ffffff; + g1 = h1 + b; b = g1 >> 26; g1 &= 0x3ffffff; + g2 = h2 + b; b = g2 >> 26; g2 &= 0x3ffffff; + g3 = h3 + b; b = g3 >> 26; g3 &= 0x3ffffff; + g4 = h4 + b - (1 << 26); + + b = (g4 >> 31) - 1; + nb = ~b; + h0 = (h0 & nb) | (g0 & b); + h1 = (h1 & nb) | (g1 & b); + h2 = (h2 & nb) | (g2 & b); + h3 = (h3 & nb) | (g3 & b); + h4 = (h4 & nb) | (g4 & b); + + f0 = ((h0 ) | (h1 << 26)) + (uint64_t)U8TO32_LE(&key[16]); + f1 = ((h1 >> 6) | (h2 << 20)) + (uint64_t)U8TO32_LE(&key[20]); + f2 = ((h2 >> 12) | (h3 << 14)) + (uint64_t)U8TO32_LE(&key[24]); + f3 = ((h3 >> 18) | (h4 << 8)) + (uint64_t)U8TO32_LE(&key[28]); + + U32TO8_LE(&out[ 0], f0); f1 += (f0 >> 32); + U32TO8_LE(&out[ 4], f1); f2 += (f1 >> 32); + U32TO8_LE(&out[ 8], f2); f3 += (f2 >> 32); + U32TO8_LE(&out[12], f3); +} diff --git a/src/libs/libssh-0.12.2/src/external/sc25519.c b/src/libs/libssh-0.12.2/src/external/sc25519.c new file mode 100644 index 000000000000..5da91f70d9c7 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/sc25519.c @@ -0,0 +1,375 @@ +/* + * Public Domain, Authors: Daniel J. Bernstein, Niels Duif, Tanja Lange, + * Peter Schwabe, Bo-Yin Yang. + * Copied from supercop-20130419/crypto_sign/ed25519/ref/sc25519.c + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/sc25519.h" + +/*Arithmetic modulo the group order m = 2^252 + 27742317777372353535851937790883648493 = 7237005577332262213973186563042994240857116359379907606001950938285454250989 */ + +static const uint32_t m[32] = { + 0xED, 0xD3, 0xF5, 0x5C, 0x1A, 0x63, 0x12, 0x58, + 0xD6, 0x9C, 0xF7, 0xA2, 0xDE, 0xF9, 0xDE, 0x14, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10 +}; + +static const uint32_t mu[33] = { + 0x1B, 0x13, 0x2C, 0x0A, 0xA3, 0xE5, 0x9C, 0xED, + 0xA7, 0x29, 0x63, 0x08, 0x5D, 0x21, 0x06, 0x21, + 0xEB, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x0F +}; + +static uint32_t lt(uint32_t a,uint32_t b) /* 16-bit inputs */ +{ + unsigned int x = a; + + x -= (unsigned int) b; /* 0..65535: no; 4294901761..4294967295: yes */ + x >>= 31; /* 0: no; 1: yes */ + + return x; +} + +/* Reduce coefficients of r before calling reduce_add_sub */ +static void reduce_add_sub(sc25519 *r) +{ + uint32_t pb = 0; + uint32_t b; + uint32_t mask; + int i; + unsigned char t[32]; + + for (i = 0; i < 32; i++) { + pb += m[i]; + b = lt(r->v[i],pb); + t[i] = r->v[i]-pb+(b<<8); + pb = b; + } + mask = b - 1; + for (i = 0; i < 32; i++) { + r->v[i] ^= mask & (r->v[i] ^ t[i]); + } +} + +/* Reduce coefficients of x before calling barrett_reduce */ +static void barrett_reduce(sc25519 *r, const uint32_t x[64]) +{ + /* See HAC, Alg. 14.42 */ + int i,j; + uint32_t q2[66]; + uint32_t *q3 = q2 + 33; + uint32_t r1[33]; + uint32_t r2[33]; + uint32_t carry; + uint32_t pb = 0; + uint32_t b; + + for (i = 0; i < 66; i++) { + q2[i] = 0; + } + for (i = 0; i < 33; i++) { + r2[i] = 0; + } + + for (i = 0; i < 33; i++) { + for (j = 0; j < 33; j++) { + if (i + j >= 31) { + q2[i+j] += mu[i]*x[j+31]; + } + } + } + + carry = q2[31] >> 8; + q2[32] += carry; + carry = q2[32] >> 8; + q2[33] += carry; + + for (i = 0; i < 33; i++) { + r1[i] = x[i]; + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 33; j++) { + if (i + j < 33) { + r2[i+j] += m[i]*q3[j]; + } + } + } + + for (i = 0; i < 32; i++) { + carry = r2[i] >> 8; + r2[i+1] += carry; + r2[i] &= 0xff; + } + + for (i = 0; i < 32; i++) { + pb += r2[i]; + b = lt(r1[i],pb); + r->v[i] = r1[i]-pb+(b<<8); + pb = b; + } + + /* XXX: Can it really happen that r<0?, See HAC, Alg 14.42, Step 3 + * If so: Handle it here! + */ + + reduce_add_sub(r); + reduce_add_sub(r); +} + +void sc25519_from32bytes(sc25519 *r, const unsigned char x[32]) +{ + int i; + uint32_t t[64]; + + for (i = 0; i < 32; i++) { + t[i] = x[i]; + } + for (i = 32; i < 64; i++) { + t[i] = 0; + } + + barrett_reduce(r, t); +} + +void shortsc25519_from16bytes(shortsc25519 *r, const unsigned char x[16]) +{ + int i; + + for (i = 0; i < 16; i++) { + r->v[i] = x[i]; + } +} + +void sc25519_from64bytes(sc25519 *r, const unsigned char x[64]) +{ + int i; + uint32_t t[64]; + + for (i = 0; i < 64; i++) { + t[i] = x[i]; + } + + barrett_reduce(r, t); +} + +void sc25519_from_shortsc(sc25519 *r, const shortsc25519 *x) +{ + int i; + + for (i = 0; i < 16; i++) { + r->v[i] = x->v[i]; + } + for (i = 0; i < 16; i++) { + r->v[16+i] = 0; + } +} + +void sc25519_to32bytes(unsigned char r[32], const sc25519 *x) +{ + int i; + + for (i = 0; i < 32; i++) { + r[i] = x->v[i]; + } +} + +int sc25519_iszero_vartime(const sc25519 *x) +{ + int i; + + for (i = 0; i < 32; i++) { + if(x->v[i] != 0) { + return 0; + } + } + + return 1; +} + +int sc25519_isshort_vartime(const sc25519 *x) +{ + int i; + + for (i = 31; i > 15; i--) { + if (x->v[i] != 0) { + return 0; + } + } + + return 1; +} + +int sc25519_lt_vartime(const sc25519 *x, const sc25519 *y) +{ + int i; + + for (i = 31; i >= 0; i--) { + if (x->v[i] < y->v[i]) { + return 1; + } + if (x->v[i] > y->v[i]) { + return 0; + } + } + + return 0; +} + +void sc25519_add(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + uint32_t i, carry; + + for (i = 0; i < 32; i++) { + r->v[i] = x->v[i] + y->v[i]; + } + + for (i = 0;i < 31; i++) { + carry = r->v[i] >> 8; + r->v[i+1] += carry; + r->v[i] &= 0xff; + } + + reduce_add_sub(r); +} + +void sc25519_sub_nored(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + uint32_t b = 0; + uint32_t t; + int i; + + for (i = 0; i < 32; i++) { + t = x->v[i] - y->v[i] - b; + r->v[i] = t & 255; + b = (t >> 8) & 1; + } +} + +void sc25519_mul(sc25519 *r, const sc25519 *x, const sc25519 *y) +{ + uint32_t i,j,carry; + uint32_t t[64]; + + for (i = 0; i < 64; i++) { + t[i] = 0; + } + + for (i = 0; i < 32; i++) { + for (j = 0; j < 32; j++) { + t[i+j] += x->v[i] * y->v[j]; + } + } + + /* Reduce coefficients */ + for (i = 0; i < 63; i++) { + carry = t[i] >> 8; + t[i+1] += carry; + t[i] &= 0xff; + } + + barrett_reduce(r, t); +} + +void sc25519_mul_shortsc(sc25519 *r, const sc25519 *x, const shortsc25519 *y) +{ + sc25519 t; + sc25519_from_shortsc(&t, y); + sc25519_mul(r, x, &t); +} + +void sc25519_window3(signed char r[85], const sc25519 *s) +{ + char carry; + int i; + + for (i = 0; i < 10; i++) { + r[8*i+0] = s->v[3*i+0] & 7; + r[8*i+1] = (s->v[3*i+0] >> 3) & 7; + r[8*i+2] = (s->v[3*i+0] >> 6) & 7; + r[8*i+2] ^= (s->v[3*i+1] << 2) & 7; + r[8*i+3] = (s->v[3*i+1] >> 1) & 7; + r[8*i+4] = (s->v[3*i+1] >> 4) & 7; + r[8*i+5] = (s->v[3*i+1] >> 7) & 7; + r[8*i+5] ^= (s->v[3*i+2] << 1) & 7; + r[8*i+6] = (s->v[3*i+2] >> 2) & 7; + r[8*i+7] = (s->v[3*i+2] >> 5) & 7; + } + r[8*i+0] = s->v[3*i+0] & 7; + r[8*i+1] = (s->v[3*i+0] >> 3) & 7; + r[8*i+2] = (s->v[3*i+0] >> 6) & 7; + r[8*i+2] ^= (s->v[3*i+1] << 2) & 7; + r[8*i+3] = (s->v[3*i+1] >> 1) & 7; + r[8*i+4] = (s->v[3*i+1] >> 4) & 7; + + /* Making it signed */ + carry = 0; + for (i = 0; i < 84; i++) { + r[i] += carry; + r[i+1] += r[i] >> 3; + r[i] &= 7; + carry = r[i] >> 2; + r[i] -= carry<<3; + } + + r[84] += carry; +} + +void sc25519_window5(signed char r[51], const sc25519 *s) +{ + char carry; + int i; + + for (i = 0; i < 6; i++) { + r[8*i+0] = s->v[5*i+0] & 31; + r[8*i+1] = (s->v[5*i+0] >> 5) & 31; + r[8*i+1] ^= (s->v[5*i+1] << 3) & 31; + r[8*i+2] = (s->v[5*i+1] >> 2) & 31; + r[8*i+3] = (s->v[5*i+1] >> 7) & 31; + r[8*i+3] ^= (s->v[5*i+2] << 1) & 31; + r[8*i+4] = (s->v[5*i+2] >> 4) & 31; + r[8*i+4] ^= (s->v[5*i+3] << 4) & 31; + r[8*i+5] = (s->v[5*i+3] >> 1) & 31; + r[8*i+6] = (s->v[5*i+3] >> 6) & 31; + r[8*i+6] ^= (s->v[5*i+4] << 2) & 31; + r[8*i+7] = (s->v[5*i+4] >> 3) & 31; + } + r[8*i+0] = s->v[5*i+0] & 31; + r[8*i+1] = (s->v[5*i+0] >> 5) & 31; + r[8*i+1] ^= (s->v[5*i+1] << 3) & 31; + r[8*i+2] = (s->v[5*i+1] >> 2) & 31; + + /* Making it signed */ + carry = 0; + for (i = 0; i < 50; i++) { + r[i] += carry; + r[i+1] += r[i] >> 5; + r[i] &= 31; + carry = r[i] >> 4; + r[i] -= carry<<5; + } + + r[50] += carry; +} + +void sc25519_2interleave2(unsigned char r[127], + const sc25519 *s1, + const sc25519 *s2) +{ + int i; + + for (i = 0; i < 31; i++) { + r[4*i] = ( s1->v[i] & 3) ^ (( s2->v[i] & 3) << 2); + r[4*i+1] = ((s1->v[i] >> 2) & 3) ^ (((s2->v[i] >> 2) & 3) << 2); + r[4*i+2] = ((s1->v[i] >> 4) & 3) ^ (((s2->v[i] >> 4) & 3) << 2); + r[4*i+3] = ((s1->v[i] >> 6) & 3) ^ (((s2->v[i] >> 6) & 3) << 2); + } + r[124] = ( s1->v[31] & 3) ^ (( s2->v[31] & 3) << 2); + r[125] = ((s1->v[31] >> 2) & 3) ^ (((s2->v[31] >> 2) & 3) << 2); + r[126] = ((s1->v[31] >> 4) & 3) ^ (((s2->v[31] >> 4) & 3) << 2); +} diff --git a/src/libs/libssh-0.12.2/src/external/sntrup761.c b/src/libs/libssh-0.12.2/src/external/sntrup761.c new file mode 100644 index 000000000000..2f45622dddd9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/external/sntrup761.c @@ -0,0 +1,1058 @@ +/* + * Derived from public domain source, written by (in alphabetical order): + * - Daniel J. Bernstein + * - Chitchanok Chuengsatiansup + * - Tanja Lange + * - Christine van Vredendaal + */ + +#include +#include + +#define SNTRUP761_SECRETKEY_SIZE 1763 +#define SNTRUP761_PUBLICKEY_SIZE 1158 +#define SNTRUP761_CIPHERTEXT_SIZE 1039 +#define SNTRUP761_SIZE 32 + +typedef void sntrup761_random_func (void *ctx, size_t length, uint8_t *dst); + +void +sntrup761_keypair (uint8_t *pk, uint8_t *sk, + void *random_ctx, sntrup761_random_func *random); + +void +sntrup761_enc (uint8_t *c, uint8_t *k, const uint8_t *pk, + void *random_ctx, sntrup761_random_func *random); + +void +sntrup761_dec (uint8_t *k, const uint8_t *c, const uint8_t *sk); + +extern int sha512(const unsigned char *digest, size_t len, unsigned char *hash); + +#define MAX_LEN 761 + +/* from supercop-20201130/crypto_sort/int32/portable4/int32_minmax.inc */ +#define int32_MINMAX(a,b) \ +do { \ + int64_t ab = (int64_t)b ^ (int64_t)a; \ + int64_t c = (int64_t)b - (int64_t)a; \ + c ^= ab & (c ^ b); \ + c >>= 31; \ + c &= ab; \ + a ^= c; \ + b ^= c; \ +} while(0) + +/* from supercop-20201130/crypto_sort/int32/portable4/sort.c */ +static void +crypto_sort_int32 (void *array, long long n) +{ + long long top, p, q, r, i, j; + int32_t *x = array; + + if (n < 2) + return; + top = 1; + while (top < n - top) + top += top; + + for (p = top; p >= 1; p >>= 1) + { + i = 0; + while (i + 2 * p <= n) + { + for (j = i; j < i + p; ++j) + int32_MINMAX (x[j], x[j + p]); + i += 2 * p; + } + for (j = i; j < n - p; ++j) + int32_MINMAX (x[j], x[j + p]); + + i = 0; + j = 0; + for (q = top; q > p; q >>= 1) + { + if (j != i) + for (;;) + { + int32_t a; + if (j == n - q) + goto done; + a = x[j + p]; + for (r = q; r > p; r >>= 1) + int32_MINMAX (a, x[j + r]); + x[j + p] = a; + ++j; + if (j == i + p) + { + i += 2 * p; + break; + } + } + while (i + p <= n - q) + { + for (j = i; j < i + p; ++j) + { + int32_t a = x[j + p]; + for (r = q; r > p; r >>= 1) + int32_MINMAX (a, x[j + r]); + x[j + p] = a; + } + i += 2 * p; + } + /* now i + p > n - q */ + j = i; + while (j < n - q) + { + int32_t a = x[j + p]; + for (r = q; r > p; r >>= 1) + int32_MINMAX (a, x[j + r]); + x[j + p] = a; + ++j; + } + + done:; + } + } +} + +/* from supercop-20201130/crypto_sort/uint32/useint32/sort.c */ + +/* can save time by vectorizing xor loops */ +/* can save time by integrating xor loops with int32_sort */ + +static void +crypto_sort_uint32 (void *array, long long n) +{ + uint32_t *x = array; + long long j; + for (j = 0; j < n; ++j) + x[j] ^= 0x80000000; + crypto_sort_int32 (array, n); + for (j = 0; j < n; ++j) + x[j] ^= 0x80000000; +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/uint32.c */ + +/* +CPU division instruction typically takes time depending on x. +This software is designed to take time independent of x. +Time still varies depending on m; user must ensure that m is constant. +Time also varies on CPUs where multiplication is variable-time. +There could be more CPU issues. +There could also be compiler issues. +*/ + +static void +uint32_divmod_uint14 (uint32_t * q, uint16_t * r, uint32_t x, uint16_t m) +{ + uint32_t v = 0x80000000; + uint32_t qpart; + uint32_t mask; + + v /= m; + + /* caller guarantees m > 0 */ + /* caller guarantees m < 16384 */ + /* vm <= 2^31 <= vm+m-1 */ + /* xvm <= 2^31 x <= xvm+x(m-1) */ + + *q = 0; + + qpart = (x * (uint64_t) v) >> 31; + /* 2^31 qpart <= xv <= 2^31 qpart + 2^31-1 */ + /* 2^31 qpart m <= xvm <= 2^31 qpart m + (2^31-1)m */ + /* 2^31 qpart m <= 2^31 x <= 2^31 qpart m + (2^31-1)m + x(m-1) */ + /* 0 <= 2^31 newx <= (2^31-1)m + x(m-1) */ + /* 0 <= newx <= (1-1/2^31)m + x(m-1)/2^31 */ + /* 0 <= newx <= (1-1/2^31)(2^14-1) + (2^32-1)((2^14-1)-1)/2^31 */ + + x -= qpart * m; + *q += qpart; + /* x <= 49146 */ + + qpart = (x * (uint64_t) v) >> 31; + /* 0 <= newx <= (1-1/2^31)m + x(m-1)/2^31 */ + /* 0 <= newx <= m + 49146(2^14-1)/2^31 */ + /* 0 <= newx <= m + 0.4 */ + /* 0 <= newx <= m */ + + x -= qpart * m; + *q += qpart; + /* x <= m */ + + x -= m; + *q += 1; + mask = -(x >> 31); + x += mask & (uint32_t) m; + *q += mask; + /* x < m */ + + *r = x; +} + + +static uint16_t +uint32_mod_uint14 (uint32_t x, uint16_t m) +{ + uint32_t q; + uint16_t r; + uint32_divmod_uint14 (&q, &r, x, m); + return r; +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/int32.c */ + +static void +int32_divmod_uint14 (int32_t * q, uint16_t * r, int32_t x, uint16_t m) +{ + uint32_t uq, uq2; + uint16_t ur, ur2; + uint32_t mask; + + uint32_divmod_uint14 (&uq, &ur, 0x80000000 + (uint32_t) x, m); + uint32_divmod_uint14 (&uq2, &ur2, 0x80000000, m); + ur -= ur2; + uq -= uq2; + mask = -(uint32_t) (ur >> 15); + ur += mask & m; + uq += mask; + *r = ur; + *q = uq; +} + + +static uint16_t +int32_mod_uint14 (int32_t x, uint16_t m) +{ + int32_t q; + uint16_t r; + int32_divmod_uint14 (&q, &r, x, m); + return r; +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/paramsmenu.h */ +#define p 761 +#define q 4591 +#define Rounded_bytes 1007 +#define Rq_bytes 1158 +#define w 286 + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Decode.h */ + +/* Decode(R,s,M,len) */ +/* assumes 0 < M[i] < 16384 */ +/* produces 0 <= R[i] < M[i] */ + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Decode.c */ + +static void +Decode (uint16_t * out, const unsigned char *S, const uint16_t * M, + long long len) +{ + if (len == 1) + { + if (M[0] == 1) + *out = 0; + else if (M[0] <= 256) + *out = uint32_mod_uint14 (S[0], M[0]); + else + *out = uint32_mod_uint14 (S[0] + (((uint16_t) S[1]) << 8), M[0]); + } + if (len > 1) + { + uint16_t R2[(MAX_LEN + 1) / 2]; + uint16_t M2[(MAX_LEN + 1) / 2]; + uint16_t bottomr[MAX_LEN / 2]; + uint32_t bottomt[MAX_LEN / 2]; + long long i; + for (i = 0; i < len - 1; i += 2) + { + uint32_t m = M[i] * (uint32_t) M[i + 1]; + if (m > 256 * 16383) + { + bottomt[i / 2] = 256 * 256; + bottomr[i / 2] = S[0] + 256 * S[1]; + S += 2; + M2[i / 2] = (((m + 255) >> 8) + 255) >> 8; + } + else if (m >= 16384) + { + bottomt[i / 2] = 256; + bottomr[i / 2] = S[0]; + S += 1; + M2[i / 2] = (m + 255) >> 8; + } + else + { + bottomt[i / 2] = 1; + bottomr[i / 2] = 0; + M2[i / 2] = m; + } + } + if (i < len) + M2[i / 2] = M[i]; + Decode (R2, S, M2, (len + 1) / 2); + for (i = 0; i < len - 1; i += 2) + { + uint32_t r = bottomr[i / 2]; + uint32_t r1; + uint16_t r0; + r += bottomt[i / 2] * R2[i / 2]; + uint32_divmod_uint14 (&r1, &r0, r, M[i]); + r1 = uint32_mod_uint14 (r1, M[i + 1]); /* only needed for invalid inputs */ + *out++ = r0; + *out++ = r1; + } + if (i < len) + *out++ = R2[i / 2]; + } +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Encode.h */ + +/* Encode(s,R,M,len) */ +/* assumes 0 <= R[i] < M[i] < 16384 */ + +/* from supercop-20201130/crypto_kem/sntrup761/ref/Encode.c */ + +/* 0 <= R[i] < M[i] < 16384 */ +static void +Encode (unsigned char *out, const uint16_t * R, const uint16_t * M, + long long len) +{ + if (len == 1) + { + uint16_t r = R[0]; + uint16_t m = M[0]; + while (m > 1) + { + *out++ = r; + r >>= 8; + m = (m + 255) >> 8; + } + } + if (len > 1) + { + uint16_t R2[(MAX_LEN + 1) / 2]; + uint16_t M2[(MAX_LEN + 1) / 2]; + long long i; + for (i = 0; i < len - 1; i += 2) + { + uint32_t m0 = M[i]; + uint32_t r = R[i] + R[i + 1] * m0; + uint32_t m = M[i + 1] * m0; + while (m >= 16384) + { + *out++ = r; + r >>= 8; + m = (m + 255) >> 8; + } + R2[i / 2] = r; + M2[i / 2] = m; + } + if (i < len) + { + R2[i / 2] = R[i]; + M2[i / 2] = M[i]; + } + Encode (out, R2, M2, (len + 1) / 2); + } +} + +/* from supercop-20201130/crypto_kem/sntrup761/ref/kem.c */ + +/* ----- masks */ + +/* return -1 if x!=0; else return 0 */ +static int +int16_t_nonzero_mask (int16_t x) +{ + uint16_t u = x; /* 0, else 1...65535 */ + uint32_t v = u; /* 0, else 1...65535 */ + v = -v; /* 0, else 2^32-65535...2^32-1 */ + v >>= 31; /* 0, else 1 */ + return -v; /* 0, else -1 */ +} + +/* return -1 if x<0; otherwise return 0 */ +static int +int16_t_negative_mask (int16_t x) +{ + uint16_t u = x; + u >>= 15; + return -(int) u; + /* alternative with gcc -fwrapv: */ + /* x>>15 compiles to CPU's arithmetic right shift */ +} + +/* ----- arithmetic mod 3 */ + +typedef int8_t small; + +/* F3 is always represented as -1,0,1 */ +/* so ZZ_fromF3 is a no-op */ + +/* x must not be close to top int16_t */ +static small +F3_freeze (int16_t x) +{ + return int32_mod_uint14 (x + 1, 3) - 1; +} + +/* ----- arithmetic mod q */ + +#define q12 ((q-1)/2) +typedef int16_t Fq; +/* always represented as -q12...q12 */ +/* so ZZ_fromFq is a no-op */ + +/* x must not be close to top int32 */ +static Fq +Fq_freeze (int32_t x) +{ + return int32_mod_uint14 (x + q12, q) - q12; +} + +static Fq +Fq_recip (Fq a1) +{ + int i = 1; + Fq ai = a1; + + while (i < q - 2) + { + ai = Fq_freeze (a1 * (int32_t) ai); + i += 1; + } + return ai; +} + +/* ----- small polynomials */ + +/* 0 if Weightw_is(r), else -1 */ +static int +Weightw_mask (small * r) +{ + int weight = 0; + int i; + + for (i = 0; i < p; ++i) + weight += r[i] & 1; + return int16_t_nonzero_mask (weight - w); +} + +/* R3_fromR(R_fromRq(r)) */ +static void +R3_fromRq (small * out, const Fq * r) +{ + int i; + for (i = 0; i < p; ++i) + out[i] = F3_freeze (r[i]); +} + +/* h = f*g in the ring R3 */ +static void +R3_mult (small * h, const small * f, const small * g) +{ + small fg[p + p - 1]; + small result; + int i, j; + + for (i = 0; i < p; ++i) + { + result = 0; + for (j = 0; j <= i; ++j) + result = F3_freeze (result + f[j] * g[i - j]); + fg[i] = result; + } + for (i = p; i < p + p - 1; ++i) + { + result = 0; + for (j = i - p + 1; j < p; ++j) + result = F3_freeze (result + f[j] * g[i - j]); + fg[i] = result; + } + + for (i = p + p - 2; i >= p; --i) + { + fg[i - p] = F3_freeze (fg[i - p] + fg[i]); + fg[i - p + 1] = F3_freeze (fg[i - p + 1] + fg[i]); + } + + for (i = 0; i < p; ++i) + h[i] = fg[i]; +} + +/* returns 0 if recip succeeded; else -1 */ +static int +R3_recip (small * out, const small * in) +{ + small f[p + 1], g[p + 1], v[p + 1], r[p + 1]; + int i, loop, delta; + int sign, swap, t; + + for (i = 0; i < p + 1; ++i) + v[i] = 0; + for (i = 0; i < p + 1; ++i) + r[i] = 0; + r[0] = 1; + for (i = 0; i < p; ++i) + f[i] = 0; + f[0] = 1; + f[p - 1] = f[p] = -1; + for (i = 0; i < p; ++i) + g[p - 1 - i] = in[i]; + g[p] = 0; + + delta = 1; + + for (loop = 0; loop < 2 * p - 1; ++loop) + { + for (i = p; i > 0; --i) + v[i] = v[i - 1]; + v[0] = 0; + + sign = -g[0] * f[0]; + swap = int16_t_negative_mask (-delta) & int16_t_nonzero_mask (g[0]); + delta ^= swap & (delta ^ -delta); + delta += 1; + + for (i = 0; i < p + 1; ++i) + { + t = swap & (f[i] ^ g[i]); + f[i] ^= t; + g[i] ^= t; + t = swap & (v[i] ^ r[i]); + v[i] ^= t; + r[i] ^= t; + } + + for (i = 0; i < p + 1; ++i) + g[i] = F3_freeze (g[i] + sign * f[i]); + for (i = 0; i < p + 1; ++i) + r[i] = F3_freeze (r[i] + sign * v[i]); + + for (i = 0; i < p; ++i) + g[i] = g[i + 1]; + g[p] = 0; + } + + sign = f[0]; + for (i = 0; i < p; ++i) + out[i] = sign * v[p - 1 - i]; + + return int16_t_nonzero_mask (delta); +} + +/* ----- polynomials mod q */ + +/* h = f*g in the ring Rq */ +static void +Rq_mult_small (Fq * h, const Fq * f, const small * g) +{ + Fq fg[p + p - 1]; + Fq result; + int i, j; + + for (i = 0; i < p; ++i) + { + result = 0; + for (j = 0; j <= i; ++j) + result = Fq_freeze (result + f[j] * (int32_t) g[i - j]); + fg[i] = result; + } + for (i = p; i < p + p - 1; ++i) + { + result = 0; + for (j = i - p + 1; j < p; ++j) + result = Fq_freeze (result + f[j] * (int32_t) g[i - j]); + fg[i] = result; + } + + for (i = p + p - 2; i >= p; --i) + { + fg[i - p] = Fq_freeze (fg[i - p] + fg[i]); + fg[i - p + 1] = Fq_freeze (fg[i - p + 1] + fg[i]); + } + + for (i = 0; i < p; ++i) + h[i] = fg[i]; +} + +/* h = 3f in Rq */ +static void +Rq_mult3 (Fq * h, const Fq * f) +{ + int i; + + for (i = 0; i < p; ++i) + h[i] = Fq_freeze (3 * f[i]); +} + +/* out = 1/(3*in) in Rq */ +/* returns 0 if recip succeeded; else -1 */ +static int +Rq_recip3 (Fq * out, const small * in) +{ + Fq f[p + 1], g[p + 1], v[p + 1], r[p + 1]; + int i, loop, delta; + int swap, t; + int32_t f0, g0; + Fq scale; + + for (i = 0; i < p + 1; ++i) + v[i] = 0; + for (i = 0; i < p + 1; ++i) + r[i] = 0; + r[0] = Fq_recip (3); + for (i = 0; i < p; ++i) + f[i] = 0; + f[0] = 1; + f[p - 1] = f[p] = -1; + for (i = 0; i < p; ++i) + g[p - 1 - i] = in[i]; + g[p] = 0; + + delta = 1; + + for (loop = 0; loop < 2 * p - 1; ++loop) + { + for (i = p; i > 0; --i) + v[i] = v[i - 1]; + v[0] = 0; + + swap = int16_t_negative_mask (-delta) & int16_t_nonzero_mask (g[0]); + delta ^= swap & (delta ^ -delta); + delta += 1; + + for (i = 0; i < p + 1; ++i) + { + t = swap & (f[i] ^ g[i]); + f[i] ^= t; + g[i] ^= t; + t = swap & (v[i] ^ r[i]); + v[i] ^= t; + r[i] ^= t; + } + + f0 = f[0]; + g0 = g[0]; + for (i = 0; i < p + 1; ++i) + g[i] = Fq_freeze (f0 * g[i] - g0 * f[i]); + for (i = 0; i < p + 1; ++i) + r[i] = Fq_freeze (f0 * r[i] - g0 * v[i]); + + for (i = 0; i < p; ++i) + g[i] = g[i + 1]; + g[p] = 0; + } + + scale = Fq_recip (f[0]); + for (i = 0; i < p; ++i) + out[i] = Fq_freeze (scale * (int32_t) v[p - 1 - i]); + + return int16_t_nonzero_mask (delta); +} + +/* ----- rounded polynomials mod q */ + +static void +Round (Fq * out, const Fq * a) +{ + int i; + for (i = 0; i < p; ++i) + out[i] = a[i] - F3_freeze (a[i]); +} + +/* ----- sorting to generate short polynomial */ + +static void +Short_fromlist (small * out, const uint32_t * in) +{ + uint32_t L[p]; + int i; + + for (i = 0; i < w; ++i) + L[i] = in[i] & (uint32_t) - 2; + for (i = w; i < p; ++i) + L[i] = (in[i] & (uint32_t) - 3) | 1; + crypto_sort_uint32 (L, p); + for (i = 0; i < p; ++i) + out[i] = (L[i] & 3) - 1; +} + +/* ----- underlying hash function */ + +#define Hash_bytes 32 + +/* e.g., b = 0 means out = Hash0(in) */ +static void +Hash_prefix (unsigned char *out, int b, const unsigned char *in, int inlen) +{ +#define MAX_X_LEN 1158 + unsigned char x[MAX_X_LEN + 1]; + unsigned char h[64]; + int i; + + x[0] = b; + for (i = 0; i < inlen; ++i) + x[i + 1] = in[i]; + sha512 (x, inlen + 1, h); + for (i = 0; i < 32; ++i) + out[i] = h[i]; +} + +/* ----- higher-level randomness */ + +static uint32_t +urandom32 (void *random_ctx, sntrup761_random_func * random) +{ + unsigned char c[4]; + uint32_t out[4]; + + random (random_ctx, 4, c); + out[0] = (uint32_t) c[0]; + out[1] = ((uint32_t) c[1]) << 8; + out[2] = ((uint32_t) c[2]) << 16; + out[3] = ((uint32_t) c[3]) << 24; + return out[0] + out[1] + out[2] + out[3]; +} + +static void +Short_random (small * out, void *random_ctx, sntrup761_random_func * random) +{ + uint32_t L[p]; + int i; + + for (i = 0; i < p; ++i) + L[i] = urandom32 (random_ctx, random); + Short_fromlist (out, L); +} + +static void +Small_random (small * out, void *random_ctx, sntrup761_random_func * random) +{ + int i; + + for (i = 0; i < p; ++i) + out[i] = (((urandom32 (random_ctx, random) & 0x3fffffff) * 3) >> 30) - 1; +} + +/* ----- Streamlined NTRU Prime Core */ + +/* h,(f,ginv) = KeyGen() */ +static void +KeyGen (Fq * h, small * f, small * ginv, void *random_ctx, + sntrup761_random_func * random) +{ + small g[p]; + Fq finv[p]; + + for (;;) + { + Small_random (g, random_ctx, random); + if (R3_recip (ginv, g) == 0) + break; + } + Short_random (f, random_ctx, random); + Rq_recip3 (finv, f); /* always works */ + Rq_mult_small (h, finv, g); +} + +/* c = Encrypt(r,h) */ +static void +Encrypt (Fq * c, const small * r, const Fq * h) +{ + Fq hr[p]; + + Rq_mult_small (hr, h, r); + Round (c, hr); +} + +/* r = Decrypt(c,(f,ginv)) */ +static void +Decrypt (small * r, const Fq * c, const small * f, const small * ginv) +{ + Fq cf[p]; + Fq cf3[p]; + small e[p]; + small ev[p]; + int mask; + int i; + + Rq_mult_small (cf, c, f); + Rq_mult3 (cf3, cf); + R3_fromRq (e, cf3); + R3_mult (ev, e, ginv); + + mask = Weightw_mask (ev); /* 0 if weight w, else -1 */ + for (i = 0; i < w; ++i) + r[i] = ((ev[i] ^ 1) & ~mask) ^ 1; + for (i = w; i < p; ++i) + r[i] = ev[i] & ~mask; +} + +/* ----- encoding small polynomials (including short polynomials) */ + +#define Small_bytes ((p+3)/4) + +/* these are the only functions that rely on p mod 4 = 1 */ + +static void +Small_encode (unsigned char *s, const small * f) +{ + small x; + int i; + + for (i = 0; i < p / 4; ++i) + { + x = *f++ + 1; + x += (*f++ + 1) << 2; + x += (*f++ + 1) << 4; + x += (*f++ + 1) << 6; + *s++ = x; + } + x = *f++ + 1; + *s++ = x; +} + +static void +Small_decode (small * f, const unsigned char *s) +{ + unsigned char x; + int i; + + for (i = 0; i < p / 4; ++i) + { + x = *s++; + *f++ = ((small) (x & 3)) - 1; + x >>= 2; + *f++ = ((small) (x & 3)) - 1; + x >>= 2; + *f++ = ((small) (x & 3)) - 1; + x >>= 2; + *f++ = ((small) (x & 3)) - 1; + } + x = *s++; + *f++ = ((small) (x & 3)) - 1; +} + +/* ----- encoding general polynomials */ + +static void +Rq_encode (unsigned char *s, const Fq * r) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + R[i] = r[i] + q12; + for (i = 0; i < p; ++i) + M[i] = q; + Encode (s, R, M, p); +} + +static void +Rq_decode (Fq * r, const unsigned char *s) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + M[i] = q; + Decode (R, s, M, p); + for (i = 0; i < p; ++i) + r[i] = ((Fq) R[i]) - q12; +} + +/* ----- encoding rounded polynomials */ + +static void +Rounded_encode (unsigned char *s, const Fq * r) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + R[i] = ((r[i] + q12) * 10923) >> 15; + for (i = 0; i < p; ++i) + M[i] = (q + 2) / 3; + Encode (s, R, M, p); +} + +static void +Rounded_decode (Fq * r, const unsigned char *s) +{ + uint16_t R[p], M[p]; + int i; + + for (i = 0; i < p; ++i) + M[i] = (q + 2) / 3; + Decode (R, s, M, p); + for (i = 0; i < p; ++i) + r[i] = R[i] * 3 - q12; +} + +/* ----- Streamlined NTRU Prime Core plus encoding */ + +typedef small Inputs[p]; /* passed by reference */ +#define Inputs_random Short_random +#define Inputs_encode Small_encode +#define Inputs_bytes Small_bytes + +#define Ciphertexts_bytes Rounded_bytes +#define SecretKeys_bytes (2*Small_bytes) +#define PublicKeys_bytes Rq_bytes + +/* pk,sk = ZKeyGen() */ +static void +ZKeyGen (unsigned char *pk, unsigned char *sk, void *random_ctx, + sntrup761_random_func * random) +{ + Fq h[p]; + small f[p], v[p]; + + KeyGen (h, f, v, random_ctx, random); + Rq_encode (pk, h); + Small_encode (sk, f); + sk += Small_bytes; + Small_encode (sk, v); +} + +/* C = ZEncrypt(r,pk) */ +static void +ZEncrypt (unsigned char *C, const Inputs r, const unsigned char *pk) +{ + Fq h[p]; + Fq c[p]; + Rq_decode (h, pk); + Encrypt (c, r, h); + Rounded_encode (C, c); +} + +/* r = ZDecrypt(C,sk) */ +static void +ZDecrypt (Inputs r, const unsigned char *C, const unsigned char *sk) +{ + small f[p], v[p]; + Fq c[p]; + + Small_decode (f, sk); + sk += Small_bytes; + Small_decode (v, sk); + Rounded_decode (c, C); + Decrypt (r, c, f, v); +} + +/* ----- confirmation hash */ + +#define Confirm_bytes 32 + +/* h = HashConfirm(r,pk,cache); cache is Hash4(pk) */ +static void +HashConfirm (unsigned char *h, const unsigned char *r, + /* const unsigned char *pk, */ const unsigned char *cache) +{ + unsigned char x[Hash_bytes * 2]; + int i; + + Hash_prefix (x, 3, r, Inputs_bytes); + for (i = 0; i < Hash_bytes; ++i) + x[Hash_bytes + i] = cache[i]; + Hash_prefix (h, 2, x, sizeof x); +} + +/* ----- session-key hash */ + +/* k = HashSession(b,y,z) */ +static void +HashSession (unsigned char *k, int b, const unsigned char *y, + const unsigned char *z) +{ + unsigned char x[Hash_bytes + Ciphertexts_bytes + Confirm_bytes]; + int i; + + Hash_prefix (x, 3, y, Inputs_bytes); + for (i = 0; i < Ciphertexts_bytes + Confirm_bytes; ++i) + x[Hash_bytes + i] = z[i]; + Hash_prefix (k, b, x, sizeof x); +} + +/* ----- Streamlined NTRU Prime */ + +/* pk,sk = KEM_KeyGen() */ +void +sntrup761_keypair (unsigned char *pk, unsigned char *sk, void *random_ctx, + sntrup761_random_func * random) +{ + int i; + + ZKeyGen (pk, sk, random_ctx, random); + sk += SecretKeys_bytes; + for (i = 0; i < PublicKeys_bytes; ++i) + *sk++ = pk[i]; + random (random_ctx, Inputs_bytes, sk); + sk += Inputs_bytes; + Hash_prefix (sk, 4, pk, PublicKeys_bytes); +} + +/* c,r_enc = Hide(r,pk,cache); cache is Hash4(pk) */ +static void +Hide (unsigned char *c, unsigned char *r_enc, const Inputs r, + const unsigned char *pk, const unsigned char *cache) +{ + Inputs_encode (r_enc, r); + ZEncrypt (c, r, pk); + c += Ciphertexts_bytes; + HashConfirm (c, r_enc, cache); +} + +/* c,k = Encap(pk) */ +void +sntrup761_enc (unsigned char *c, unsigned char *k, const unsigned char *pk, + void *random_ctx, sntrup761_random_func * random) +{ + Inputs r; + unsigned char r_enc[Inputs_bytes]; + unsigned char cache[Hash_bytes]; + + Hash_prefix (cache, 4, pk, PublicKeys_bytes); + Inputs_random (r, random_ctx, random); + Hide (c, r_enc, r, pk, cache); + HashSession (k, 1, r_enc, c); +} + +/* 0 if matching ciphertext+confirm, else -1 */ +static int +Ciphertexts_diff_mask (const unsigned char *c, const unsigned char *c2) +{ + uint16_t differentbits = 0; + int len = Ciphertexts_bytes + Confirm_bytes; + + while (len-- > 0) + differentbits |= (*c++) ^ (*c2++); + return (1 & ((differentbits - 1) >> 8)) - 1; +} + +/* k = Decap(c,sk) */ +void +sntrup761_dec (unsigned char *k, const unsigned char *c, const unsigned char *sk) +{ + const unsigned char *pk = sk + SecretKeys_bytes; + const unsigned char *rho = pk + PublicKeys_bytes; + const unsigned char *cache = rho + Inputs_bytes; + Inputs r; + unsigned char r_enc[Inputs_bytes]; + unsigned char cnew[Ciphertexts_bytes + Confirm_bytes]; + int mask; + int i; + + ZDecrypt (r, c, sk); + Hide (cnew, r_enc, r, pk, cache); + mask = Ciphertexts_diff_mask (c, cnew); + for (i = 0; i < Inputs_bytes; ++i) + r_enc[i] ^= mask & (r_enc[i] ^ rho[i]); + HashSession (k, 1 + mask, r_enc, c); +} diff --git a/src/libs/libssh-0.12.2/src/gcrypt_missing.c b/src/libs/libssh-0.12.2/src/gcrypt_missing.c new file mode 100644 index 000000000000..5f84e6b6326b --- /dev/null +++ b/src/libs/libssh-0.12.2/src/gcrypt_missing.c @@ -0,0 +1,124 @@ +/* + * gcrypt_missing.c - routines that are in OpenSSL but not in libgcrypt. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2006 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "libssh/priv.h" +#include "libssh/libgcrypt.h" + +#ifdef HAVE_LIBGCRYPT +int ssh_gcry_dec2bn(bignum *bn, const char *data) { + int count; + + *bn = bignum_new(); + if (*bn == NULL) { + return 0; + } + gcry_mpi_set_ui(*bn, 0); + for (count = 0; data[count]; count++) { + gcry_mpi_mul_ui(*bn, *bn, 10); + gcry_mpi_add_ui(*bn, *bn, data[count] - '0'); + } + + return count; +} + +char *ssh_gcry_bn2dec(bignum bn) { + bignum bndup, num, ten; + char *ret = NULL; + int count, count2; + int size, rsize; + char decnum; + + size = gcry_mpi_get_nbits(bn) * 3; + rsize = size / 10 + size / 1000 + 2; + + ret = gcry_malloc(rsize + 1); + if (ret == NULL) { + return NULL; + } + + if (!gcry_mpi_cmp_ui(bn, 0)) { + strcpy(ret, "0"); + } else { + ten = bignum_new(); + if (ten == NULL) { + SAFE_FREE(ret); + return NULL; + } + + num = bignum_new(); + if (num == NULL) { + SAFE_FREE(ret); + bignum_safe_free(ten); + return NULL; + } + + for (bndup = gcry_mpi_copy(bn), bignum_set_word(ten, 10), count = rsize; + count; count--) { + gcry_mpi_div(bndup, num, bndup, ten, 0); + for (decnum = 0, count2 = gcry_mpi_get_nbits(num); count2; + decnum *= 2, decnum += (gcry_mpi_test_bit(num, count2 - 1) ? 1 : 0), + count2--) + ; + ret[count - 1] = decnum + '0'; + } + for (count = 0; count < rsize && ret[count] == '0'; count++) + ; + for (count2 = 0; count2 < rsize - count; ++count2) { + ret[count2] = ret[count2 + count]; + } + ret[count2] = 0; + bignum_safe_free(num); + bignum_safe_free(bndup); + bignum_safe_free(ten); + } + + return ret; +} + +/** @brief generates a random integer between 0 and max + * @returns 1 in case of success, 0 otherwise + */ +int ssh_gcry_rand_range(bignum dest, bignum max) +{ + size_t bits; + bignum rnd; + int rc; + + bits = bignum_num_bits(max) + 64; + rnd = bignum_new(); + if (rnd == NULL) { + return 0; + } + rc = bignum_rand(rnd, bits); + if (rc != 1) { + return rc; + } + gcry_mpi_mod(dest, rnd, max); + bignum_safe_free(rnd); + return 1; +} +#endif diff --git a/src/libs/libssh-0.12.2/src/getpass.c b/src/libs/libssh-0.12.2/src/getpass.c new file mode 100644 index 000000000000..2edab7d571c6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/getpass.c @@ -0,0 +1,295 @@ +/* + * getpass.c - platform independent getpass function. + * + * This file is part of the SSH Library + * + * Copyright (c) 2011-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include + +/** + * @internal + * + * @brief Get the password from the console. + * + * @param[in] prompt The prompt to display. + * + * @param[in] buf The buffer to fill. + * + * @param[in] len The length of the buffer. + * + * @param[in] verify Should the password be verified? + * + * @return 1 on success, 0 on error. + */ +static int ssh_gets(const char *prompt, char *buf, size_t len, int verify) +{ + char *tmp = NULL; + char *ptr = NULL; + int ok = 0; + + tmp = calloc(1, len); + if (tmp == NULL) { + return 0; + } + + /* read the password */ + while (!ok) { + if (buf[0] != '\0') { + fprintf(stdout, "%s[%s] ", prompt, buf); + } else { + fprintf(stdout, "%s", prompt); + } + fflush(stdout); + if (fgets(tmp, (int)len, stdin) == NULL) { + free(tmp); + return 0; + } + + if ((ptr = strchr(tmp, '\n'))) { + *ptr = '\0'; + } + fprintf(stdout, "\n"); + + if (*tmp) { + strncpy(buf, tmp, len); + } + + if (verify) { + char *key_string = NULL; + + key_string = calloc(1, len); + if (key_string == NULL) { + break; + } + + fprintf(stdout, "\nVerifying, please re-enter. %s", prompt); + fflush(stdout); + if (!fgets(key_string, (int)len, stdin)) { + ssh_burn(key_string, len); + SAFE_FREE(key_string); + clearerr(stdin); + continue; + } + if ((ptr = strchr(key_string, '\n'))) { + *ptr = '\0'; + } + fprintf(stdout, "\n"); + if (strcmp(buf, key_string)) { + printf("\n\07\07Mismatch - try again\n"); + ssh_burn(key_string, len); + SAFE_FREE(key_string); + fflush(stdout); + continue; + } + ssh_burn(key_string, len); + SAFE_FREE(key_string); + } + ok = 1; + } + ssh_burn(tmp, len); + free(tmp); + + return ok; +} + +#ifdef _WIN32 +#include + +int ssh_getpass(const char *prompt, + char *buf, + size_t len, + int echo, + int verify) +{ + HANDLE h; + DWORD mode = 0; + int ok; + + /* fgets needs at least len - 1 */ + if (prompt == NULL || buf == NULL || len < 2) { + return -1; + } + + /* get stdin and mode */ + h = GetStdHandle(STD_INPUT_HANDLE); + if (!GetConsoleMode(h, &mode)) { + return -1; + } + + /* disable echo */ + if (!echo) { + if (!SetConsoleMode(h, mode & ~ENABLE_ECHO_INPUT)) { + return -1; + } + } + + ok = ssh_gets(prompt, buf, len, verify); + + /* reset echo */ + SetConsoleMode(h, mode); + + if (!ok) { + ssh_burn(buf, len); + return -1; + } + + /* force termination */ + buf[len - 1] = '\0'; + + return 0; +} + +#else + +#include +#ifdef HAVE_TERMIOS_H +#include +#endif +#ifdef HAVE_UNISTD_H +#include +#endif + +/** + * @ingroup libssh_misc + * + * @brief Get a password from the console. + * + * You should make sure that the buffer is an empty string! + * + * You can also use this function to ask for a username. Then you can fill the + * buffer with the username and it is shows to the users. If the users just + * presses enter the buffer will be untouched. + * + * @code + * char username[128]; + * + * snprintf(username, sizeof(username), "john"); + * + * ssh_getpass("Username:", username, sizeof(username), 1, 0); + * @endcode + * + * The prompt will look like this: + * + * Username: [john] + * + * If you press enter then john is used as the username, or you can type it in + * to change it. + * + * @param[in] prompt The prompt to show to ask for the password. + * + * @param[out] buf The buffer the password should be stored. It NEEDS to be + * empty or filled out. + * + * @param[in] len The length of the buffer. + * + * @param[in] echo Should we echo what you type. + * + * @param[in] verify Should we ask for the password twice. + * + * @return 0 on success, -1 on error. + */ +int ssh_getpass(const char *prompt, + char *buf, + size_t len, + int echo, + int verify) +{ + struct termios attr; + struct termios old_attr; + int ok = 0; + int fd = -1; + + /* fgets needs at least len - 1 */ + if (prompt == NULL || buf == NULL || len < 2) { + return -1; + } + + if (isatty(STDIN_FILENO)) { + ZERO_STRUCT(attr); + ZERO_STRUCT(old_attr); + + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + + /* save terminal attributes */ + memcpy(&old_attr, &attr, sizeof(attr)); + if((fd = fcntl(0, F_GETFL, 0)) < 0) { + perror("fcntl"); + return -1; + } + + /* disable echo */ + if (!echo) { + attr.c_lflag &= ~(ECHO); + } + + /* write attributes to terminal */ + if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &attr) < 0) { + perror("tcsetattr"); + return -1; + } + } + + /* disable nonblocking I/O */ + if (fd & O_NONBLOCK) { + ok = fcntl(0, F_SETFL, fd & ~O_NONBLOCK); + if (ok < 0) { + perror("fcntl"); + return -1; + } + } + + ok = ssh_gets(prompt, buf, len, verify); + + if (isatty(STDIN_FILENO)) { + /* reset terminal */ + tcsetattr(STDIN_FILENO, TCSANOW, &old_attr); + } + + /* close fd */ + if (fd & O_NONBLOCK) { + ok = fcntl(0, F_SETFL, fd); + if (ok < 0) { + perror("fcntl"); + return -1; + } + } + + if (!ok) { + ssh_burn(buf, len); + return -1; + } + + /* force termination */ + buf[len - 1] = '\0'; + + return 0; +} + +#endif diff --git a/src/libs/libssh-0.12.2/src/getrandom_crypto.c b/src/libs/libssh-0.12.2/src/getrandom_crypto.c new file mode 100644 index 000000000000..df8bd19f9418 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/getrandom_crypto.c @@ -0,0 +1,64 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include + +/** + * @addtogroup libssh_misc + * + * @{ + */ + +/** + * @brief Get random bytes + * + * Make sure to always check the return code of this function! + * + * @param[in] where The buffer to fill with random bytes + * + * @param[in] len The size of the buffer to fill. + * + * @param[in] strong Use a strong or private RNG source. + * + * @return 1 on success, 0 on error. + */ +int +ssh_get_random(void *where, int len, int strong) +{ +#ifdef HAVE_OPENSSL_RAND_PRIV_BYTES + if (strong) { + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_priv_bytes(where, len); + } +#else + (void)strong; +#endif /* HAVE_RAND_PRIV_BYTES */ + + /* Returns -1 when not supported, 0 on error, 1 on success */ + return !!RAND_bytes(where, len); +} + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/getrandom_gcrypt.c b/src/libs/libssh-0.12.2/src/getrandom_gcrypt.c new file mode 100644 index 000000000000..da7264051f25 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/getrandom_gcrypt.c @@ -0,0 +1,38 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include + +int +ssh_get_random(void *where, int len, int strong) +{ + /* variable not used in gcrypt */ + (void)strong; + + /* not using GCRY_VERY_STRONG_RANDOM which is a bit overkill */ + gcry_randomize(where, len, GCRY_STRONG_RANDOM); + + return 1; +} diff --git a/src/libs/libssh-0.12.2/src/getrandom_mbedcrypto.c b/src/libs/libssh-0.12.2/src/getrandom_mbedcrypto.c new file mode 100644 index 000000000000..7e87b6a6c334 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/getrandom_mbedcrypto.c @@ -0,0 +1,52 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "mbedcrypto-compat.h" + +mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; + +int +ssh_mbedtls_random(void *where, int len, int strong) +{ + int rc = 0; + if (strong) { + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_ON); + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + mbedtls_ctr_drbg_set_prediction_resistance(&ssh_mbedtls_ctr_drbg, + MBEDTLS_CTR_DRBG_PR_OFF); + } else { + rc = mbedtls_ctr_drbg_random(&ssh_mbedtls_ctr_drbg, where, len); + } + + return !rc; +} + +int +ssh_get_random(void *where, int len, int strong) +{ + return ssh_mbedtls_random(where, len, strong); +} diff --git a/src/libs/libssh-0.12.2/src/gssapi.c b/src/libs/libssh-0.12.2/src/gssapi.c new file mode 100644 index 000000000000..da4c704c248f --- /dev/null +++ b/src/libs/libssh-0.12.2/src/gssapi.c @@ -0,0 +1,1458 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#ifdef HAVE_UNISTD_H +#include +#endif + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +static gss_OID_desc spnego_oid = {6, (void *)"\x2B\x06\x01\x05\x05\x02"}; + +/** @internal + * @initializes a gssapi context for authentication + */ +int +ssh_gssapi_init(ssh_session session) +{ + if (session->gssapi != NULL) + return SSH_OK; + session->gssapi = calloc(1, sizeof(struct ssh_gssapi_struct)); + if (session->gssapi == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + session->gssapi->server_creds = GSS_C_NO_CREDENTIAL; + session->gssapi->client_creds = GSS_C_NO_CREDENTIAL; + session->gssapi->ctx = GSS_C_NO_CONTEXT; + session->gssapi->state = SSH_GSSAPI_STATE_NONE; + return SSH_OK; +} + +void +ssh_gssapi_log_error(int verb, const char *msg_a, int maj_stat, int min_stat) +{ + gss_buffer_desc msg = GSS_C_EMPTY_BUFFER; + OM_uint32 dummy_min; + OM_uint32 message_context = 0; + + do { + gss_display_status(&dummy_min, + maj_stat, + GSS_C_GSS_CODE, + GSS_C_NO_OID, + &message_context, + &msg); + SSH_LOG(verb, "GSSAPI(%s): %s", msg_a, (const char *)msg.value); + gss_release_buffer(&dummy_min, &msg); + + } while (message_context != 0); + + do { + gss_display_status(&dummy_min, + min_stat, + GSS_C_MECH_CODE, + GSS_C_NO_OID, + &message_context, + &msg); + SSH_LOG(verb, "GSSAPI(%s): %s", msg_a, (const char *)msg.value); + gss_release_buffer(&dummy_min, &msg); + + } while (message_context != 0); +} + +/** @internal + * @frees a gssapi context + */ +void +ssh_gssapi_free(ssh_session session) +{ + OM_uint32 min; + if (session->gssapi == NULL) + return; + SAFE_FREE(session->gssapi->user); + + gss_release_name(&min, &session->gssapi->client.server_name); + gss_release_cred(&min,&session->gssapi->server_creds); + if (session->gssapi->client.creds != + session->gssapi->client.client_deleg_creds) { + gss_release_cred(&min, &session->gssapi->client.creds); + } + gss_release_oid(&min, &session->gssapi->client.oid); + gss_delete_sec_context(&min, &session->gssapi->ctx, GSS_C_NO_BUFFER); + + SAFE_FREE(session->gssapi->canonic_user); + SAFE_FREE(session->gssapi); +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token){ +#ifdef WITH_SERVER + if(session->server) + return ssh_packet_userauth_gssapi_token_server(session, type, packet, user); +#endif + return ssh_packet_userauth_gssapi_token_client(session, type, packet, user); +} +#ifdef WITH_SERVER + +/** @internal + * @brief sends a SSH_MSG_USERAUTH_GSSAPI_RESPONSE packet + * @param[in] oid the OID that was selected for authentication + */ +static int ssh_gssapi_send_response(ssh_session session, ssh_string oid) +{ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_USERAUTH_GSSAPI_RESPONSE) < 0 || + ssh_buffer_add_ssh_string(session->out_buffer,oid) < 0) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + + ssh_packet_send(session); + SSH_LOG(SSH_LOG_PACKET, + "Sent SSH_MSG_USERAUTH_GSSAPI_RESPONSE"); + return SSH_OK; +} + +#endif /* WITH_SERVER */ + +#ifdef WITH_SERVER + +/** @internal + * @brief get all the oids server supports + * @param[out] selected OID set of supported oids + * @returns SSH_OK if successful, SSH_ERROR otherwise + */ +int ssh_gssapi_server_oids(gss_OID_set *selected) +{ + OM_uint32 maj_stat, min_stat; + size_t i; + char *ptr = NULL; + gss_OID_set supported; /* oids supported by server */ + + maj_stat = gss_indicate_mechs(&min_stat, &supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "indicate mechs", + maj_stat, + min_stat); + return SSH_ERROR; + } + + for (i = 0; i < supported->count; ++i) { + ptr = ssh_get_hexa(supported->elements[i].elements, + supported->elements[i].length); + /* According to RFC 4462 we MUST NOT use SPNEGO */ + if (supported->elements[i].length == spnego_oid.length && + memcmp(supported->elements[i].elements, + spnego_oid.elements, + supported->elements[i].length) == 0) { + SAFE_FREE(ptr); + continue; + } + SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); + SAFE_FREE(ptr); + } + + *selected = supported; + + return SSH_OK; +} + +/** @internal + * @brief handles an user authentication using GSSAPI + */ +int +ssh_gssapi_handle_userauth(ssh_session session, const char *user, + uint32_t n_oid, ssh_string *oids) +{ + char *hostname = NULL; + OM_uint32 maj_stat, min_stat; + size_t i; + gss_OID_set supported; /* oids supported by server */ + gss_OID_set both_supported; /* oids supported by both client and server */ + gss_OID_set selected; /* oid selected for authentication */ + int present=0; + size_t oid_count=0; + struct gss_OID_desc_struct oid; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + /* Destroy earlier GSSAPI context if any */ + ssh_gssapi_free(session); + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + return rc; + } + + /* Callback should select oid and acquire credential */ + if (ssh_callbacks_exists(session->server_callbacks, + gssapi_select_oid_function)) { + ssh_string oid_s = NULL; + session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; + SAFE_FREE(session->gssapi->user); + session->gssapi->user = strdup(user); + oid_s = session->server_callbacks->gssapi_select_oid_function( + session, + user, + n_oid, + oids, + session->server_callbacks->userdata); + if (oid_s != NULL) { + rc = ssh_gssapi_send_response(session, oid_s); + return rc; + } else { + return ssh_auth_reply_default(session, 0); + } + } + /* Default implementation for selecting oid and acquiring credential */ + gss_create_empty_oid_set(&min_stat, &both_supported); + + /* Get the server supported oids */ + rc = ssh_gssapi_server_oids(&supported); + if (rc != SSH_OK) { + gss_release_oid_set(&min_stat, &both_supported); + return SSH_ERROR; + } + + /* Loop through client supported oids */ + for (i=0 ; i< n_oid ; ++i){ + unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); + size_t len = ssh_string_len(oids[i]); + + if (oid_s == NULL) { + continue; + } + if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ + SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); + continue; + } + /* Convert oid from string to gssapi format */ + oid.elements = &oid_s[2]; + oid.length = len - 2; + /* Check if this client oid is supported by server */ + gss_test_oid_set_member(&min_stat,&oid,supported,&present); + if(present){ + gss_add_oid_set_member(&min_stat,&oid,&both_supported); + oid_count++; + } + } + gss_release_oid_set(&min_stat, &supported); + if (oid_count == 0){ + SSH_LOG(SSH_LOG_DEBUG,"GSSAPI: no OID match"); + ssh_auth_reply_default(session, 0); + gss_release_oid_set(&min_stat, &both_supported); + return SSH_OK; + } + + hostname = ssh_get_local_hostname(); + if (hostname == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error getting hostname: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + rc = ssh_gssapi_import_name(session->gssapi, hostname); + SAFE_FREE(hostname); + if (rc != SSH_OK) { + ssh_auth_reply_default(session, 0); + gss_release_oid_set(&min_stat, &both_supported); + return SSH_ERROR; + } + + maj_stat = gss_acquire_cred(&min_stat, + session->gssapi->client.server_name, + 0, + both_supported, + GSS_C_ACCEPT, + &session->gssapi->server_creds, + &selected, + NULL); + gss_release_oid_set(&min_stat, &both_supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_TRACE, + "acquiring creds", + maj_stat, + min_stat); + ssh_auth_reply_default(session,0); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_DEBUG, "acquired credentials"); + + /* finding which OID from client we selected */ + for (i=0 ; i< n_oid ; ++i){ + unsigned char *oid_s = (unsigned char *) ssh_string_data(oids[i]); + size_t len = ssh_string_len(oids[i]); + + if (oid_s == NULL) { + continue; + } + if(len < 2 || oid_s[0] != SSH_OID_TAG || ((size_t)oid_s[1]) != len - 2){ + SSH_LOG(SSH_LOG_TRACE,"GSSAPI: received invalid OID"); + continue; + } + oid.elements = &oid_s[2]; + oid.length = len - 2; + gss_test_oid_set_member(&min_stat,&oid,selected,&present); + if(present){ + SSH_LOG(SSH_LOG_PACKET, "Selected oid %zu", i); + break; + } + } + gss_release_oid_set(&min_stat, &selected); + if (i == n_oid) { + SSH_LOG(SSH_LOG_TRACE, "GSSAPI: no selected OID matched client OIDs"); + ssh_auth_reply_default(session, 0); + return SSH_ERROR; + } + session->gssapi->user = strdup(user); + session->gssapi->state = SSH_GSSAPI_STATE_RCV_TOKEN; + return ssh_gssapi_send_response(session, oids[i]); +} + +char * +ssh_gssapi_name_to_char(gss_name_t name) +{ + gss_buffer_desc buffer; + OM_uint32 maj_stat, min_stat; + char *ptr = NULL; + maj_stat = gss_display_name(&min_stat, name, &buffer, NULL); + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "converting name", + maj_stat, + min_stat); + ptr = malloc(buffer.length + 1); + if (ptr == NULL) { + gss_release_buffer(&min_stat, &buffer); + return NULL; + } + memcpy(ptr, buffer.value, buffer.length); + ptr[buffer.length] = '\0'; + gss_release_buffer(&min_stat, &buffer); + return ptr; + +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_server) +{ + ssh_string token = NULL; + char *hexa = NULL; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags=0; + gss_channel_bindings_t input_bindings=GSS_C_NO_CHANNEL_BINDINGS; + int rc; + + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_USERAUTH_GSSAPI_TOKEN"); + if (!session->gssapi || session->gssapi->state != SSH_GSSAPI_STATE_RCV_TOKEN){ + ssh_set_error(session, SSH_FATAL, "Received SSH_MSG_USERAUTH_GSSAPI_TOKEN in invalid state"); + return SSH_PACKET_USED; + } + token = ssh_buffer_get_ssh_string(packet); + + if (token == NULL){ + ssh_set_error(session, SSH_REQUEST_DENIED, "ssh_packet_userauth_gssapi_token: invalid packet"); + return SSH_PACKET_USED; + } + + if (ssh_callbacks_exists(session->server_callbacks, gssapi_accept_sec_ctx_function)){ + ssh_string out_token = NULL; + rc = session->server_callbacks->gssapi_accept_sec_ctx_function(session, + token, &out_token, session->server_callbacks->userdata); + if (rc == SSH_ERROR){ + ssh_auth_reply_default(session, 0); + return SSH_PACKET_USED; + } + if (ssh_string_len(out_token) != 0){ + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + out_token); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_PACKET_USED; + } + ssh_packet_send(session); + SSH_STRING_FREE(out_token); + } + session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; + return SSH_PACKET_USED; + } + hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s", hexa); + SAFE_FREE(hexa); + input_token.length = ssh_string_len(token); + input_token.value = ssh_string_data(token); + + maj_stat = gss_accept_sec_context(&min_stat, &session->gssapi->ctx, session->gssapi->server_creds, + &input_token, input_bindings, &client_name, NULL /*mech_oid*/, &output_token, &ret_flags, + NULL /*time*/, &session->gssapi->client_creds); + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "accepting token", + maj_stat, + min_stat); + SSH_STRING_FREE(token); + if (client_name != GSS_C_NO_NAME){ + session->gssapi->client_name = client_name; + session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); + } + if (GSS_ERROR(maj_stat)){ + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "accepting token failed", + maj_stat, + min_stat); + gss_release_buffer(&min_stat, &output_token); + ssh_auth_reply_default(session,0); + return SSH_PACKET_USED; + } + + if (output_token.length != 0){ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); + SAFE_FREE(hexa); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_auth_reply_default(session, 0); + return SSH_PACKET_USED; + } + ssh_packet_send(session); + } + + gss_release_buffer(&min_stat, &output_token); + gss_release_name(&min_stat, &client_name); + + if (maj_stat == GSS_S_COMPLETE) { + session->gssapi->state = SSH_GSSAPI_STATE_RCV_MIC; + } + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +ssh_buffer ssh_gssapi_build_mic(ssh_session session, const char *context) +{ + struct ssh_crypto_struct *crypto = NULL; + ssh_buffer mic_buffer = NULL; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return NULL; + } + + mic_buffer = ssh_buffer_new(); + if (mic_buffer == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + rc = ssh_buffer_pack(mic_buffer, + "dPbsss", + crypto->session_id_len, + crypto->session_id_len, + crypto->session_id, + SSH2_MSG_USERAUTH_REQUEST, + session->gssapi->user, + "ssh-connection", + context); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(mic_buffer); + return NULL; + } + + return mic_buffer; +} + +#ifdef WITH_SERVER + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_mic) +{ + ssh_string mic_token = NULL; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; + ssh_buffer mic_buffer = NULL; + + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_USERAUTH_GSSAPI_MIC"); + mic_token = ssh_buffer_get_ssh_string(packet); + if (mic_token == NULL) { + ssh_set_error(session, SSH_FATAL, "Missing MIC in packet"); + goto error; + } + if (session->gssapi == NULL || + session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) { + ssh_set_error(session, + SSH_FATAL, + "Received SSH_MSG_USERAUTH_GSSAPI_MIC in invalid state"); + goto error; + } + + mic_buffer = ssh_gssapi_build_mic(session, "gssapi-with-mic"); + if (mic_buffer == NULL) { + ssh_set_error_oom(session); + goto error; + } + if (ssh_callbacks_exists(session->server_callbacks, + gssapi_verify_mic_function)) { + int rc = session->server_callbacks->gssapi_verify_mic_function(session, mic_token, + ssh_buffer_get(mic_buffer), ssh_buffer_get_len(mic_buffer), + session->server_callbacks->userdata); + if (rc != SSH_OK) { + goto error; + } + } else { + mic_buf.length = ssh_buffer_get_len(mic_buffer); + mic_buf.value = ssh_buffer_get(mic_buffer); + mic_token_buf.length = ssh_string_len(mic_token); + mic_token_buf.value = ssh_string_data(mic_token); + + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &mic_buf, + &mic_token_buf, + NULL); + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "verifying MIC", + maj_stat, + min_stat); + if (maj_stat == GSS_S_DEFECTIVE_TOKEN || GSS_ERROR(maj_stat)) { + goto error; + } + } + + if (ssh_callbacks_exists(session->server_callbacks, auth_gssapi_mic_function)){ + switch(session->server_callbacks->auth_gssapi_mic_function(session, + session->gssapi->user, session->gssapi->canonic_user, + session->server_callbacks->userdata)){ + case SSH_AUTH_SUCCESS: + ssh_auth_reply_success(session, 0); + break; + case SSH_AUTH_PARTIAL: + ssh_auth_reply_success(session, 1); + break; + default: + ssh_auth_reply_default(session, 0); + break; + } + } + + goto end; + +error: + ssh_auth_reply_default(session,0); + +end: + if (mic_buffer != NULL) { + SSH_BUFFER_FREE(mic_buffer); + } + if (mic_token != NULL) { + SSH_STRING_FREE(mic_token); + } + + return SSH_PACKET_USED; +} + +/** @brief returns the client credentials of the connected client. + * If the client has given a forwardable token, the SSH server will + * retrieve it. + * @returns gssapi credentials handle. + * @returns NULL if no forwardable token is available. + */ +ssh_gssapi_creds ssh_gssapi_get_creds(ssh_session session) +{ + if (!session || !session->gssapi || session->gssapi->client_creds == GSS_C_NO_CREDENTIAL) + return NULL; + return (ssh_gssapi_creds)session->gssapi->client_creds; +} + +#endif /* SERVER */ + +/** + * @brief Set the forwardable ticket to be given to the server for authentication. + * Unlike ssh_gssapi_get_creds() this is called on the client side of an ssh + * connection. + * + * @param[in] session The session + * @param[in] creds gssapi credentials handle. + */ +void ssh_gssapi_set_creds(ssh_session session, const ssh_gssapi_creds creds) +{ + int rc; + + if (session == NULL) { + return; + } + if (session->gssapi == NULL) { + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + return; + } + } + + session->gssapi->client.client_deleg_creds = (gss_cred_id_t)creds; +} + +static int +ssh_gssapi_send_auth_mic(ssh_session session, ssh_string *oid_set, int n_oid) +{ + int rc; + int i; + + rc = ssh_buffer_pack(session->out_buffer, + "bsssd", + SSH2_MSG_USERAUTH_REQUEST, + session->opts.username, + "ssh-connection", + "gssapi-with-mic", + n_oid); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto fail; + } + + for (i=0; iout_buffer, oid_set[i]); + if (rc < 0) { + goto fail; + } + } + + session->auth.state = SSH_AUTH_STATE_GSSAPI_REQUEST_SENT; + return ssh_packet_send(session); +fail: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + +/** @internal + * @brief Get the base64 encoding of md5 of the oid to add as suffix to GSSAPI + * key exchange algorithms. + * + * @param[in] oid The OID as a ssh_string + * + * @returns the hash or NULL on error + */ +char *ssh_gssapi_oid_hash(ssh_string oid) +{ + unsigned char *h = NULL; + int rc; + char *base64 = NULL; + + h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); + if (h == NULL) { + return NULL; + } + + rc = md5(ssh_string_data(oid), ssh_string_len(oid), h); + if (rc != SSH_OK) { + SAFE_FREE(h); + return NULL; + } + + base64 = (char *)bin_to_base64(h, 16); + SAFE_FREE(h); + return base64; +} + +/** @internal + * @brief Check if client has GSSAPI mechanisms configured + * + * @param[in] session The SSH session + * + * @returns SSH_OK if any one of the mechanisms is configured or NULL + */ +int ssh_gssapi_check_client_config(ssh_session session) +{ + OM_uint32 maj_stat, min_stat; + size_t i; + char *ptr = NULL; + gss_OID_set supported = GSS_C_NO_OID_SET; + gss_name_t client_id = GSS_C_NO_NAME; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc namebuf = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + struct ssh_gssapi_struct *gssapi = NULL; + int ret = SSH_ERROR; + gss_OID_set one_oidset = GSS_C_NO_OID_SET; + + maj_stat = gss_indicate_mechs(&min_stat, &supported); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "indicate mechs", + maj_stat, + min_stat); + return SSH_ERROR; + } + + for (i = 0; i < supported->count; ++i) { + gssapi = calloc(1, sizeof(struct ssh_gssapi_struct)); + if (gssapi == NULL) { + ssh_set_error_oom(session); + ret = SSH_ERROR; + break; + } + gssapi->server_creds = GSS_C_NO_CREDENTIAL; + gssapi->client_creds = GSS_C_NO_CREDENTIAL; + gssapi->ctx = GSS_C_NO_CONTEXT; + gssapi->state = SSH_GSSAPI_STATE_NONE; + + /* According to RFC 4462 we MUST NOT use SPNEGO */ + if (supported->elements[i].length == spnego_oid.length && + memcmp(supported->elements[i].elements, + spnego_oid.elements, + supported->elements[i].length) == 0) { + ret = SSH_ERROR; + goto end; + } + + gss_create_empty_oid_set(&min_stat, &one_oidset); + gss_add_oid_set_member(&min_stat, &supported->elements[i], &one_oidset); + + if (session->opts.gss_client_identity != NULL) { + namebuf.value = (void *)session->opts.gss_client_identity; + namebuf.length = strlen(session->opts.gss_client_identity); + + maj_stat = gss_import_name(&min_stat, + &namebuf, + GSS_C_NT_USER_NAME, + &client_id); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + + maj_stat = gss_acquire_cred(&min_stat, + client_id, + GSS_C_INDEFINITE, + one_oidset, + GSS_C_INITIATE, + &gssapi->client.creds, + NULL, + NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "acquiring credential", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + + ret = ssh_gssapi_import_name(gssapi, session->opts.host); + if (ret != SSH_OK) { + goto end; + } + + maj_stat = + ssh_gssapi_init_ctx(gssapi, &input_token, &output_token, &oflags); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "initializing context", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + + ptr = ssh_get_hexa(supported->elements[i].elements, + supported->elements[i].length); + SSH_LOG(SSH_LOG_DEBUG, "Supported mech %zu: %s", i, ptr); + free(ptr); + + /* If at least one mechanism is configured then return successfully */ + ret = SSH_OK; + + end: + if (ret == SSH_ERROR) { + SSH_LOG(SSH_LOG_WARN, "GSSAPI not configured correctly"); + } + SAFE_FREE(gssapi->user); + + gss_release_oid_set(&min_stat, &one_oidset); + + gss_release_name(&min_stat, &gssapi->client.server_name); + gss_release_cred(&min_stat, &gssapi->server_creds); + gss_release_cred(&min_stat, &gssapi->client.creds); + gss_release_oid(&min_stat, &gssapi->client.oid); + gss_release_buffer(&min_stat, &output_token); + gss_delete_sec_context(&min_stat, &gssapi->ctx, GSS_C_NO_BUFFER); + + if (client_id != GSS_C_NO_NAME) { + gss_release_name(&min_stat, &client_id); + client_id = GSS_C_NO_NAME; + } + + SAFE_FREE(gssapi->canonic_user); + SAFE_FREE(gssapi); + + if (ret == SSH_OK) { + break; + } + } + gss_release_oid_set(&min_stat, &supported); + + return ret; +} + +/** @internal + * @brief acquires a credential and returns a set of mechanisms for which it is + * valid + * + * @param[in] session The SSH session + * @param[out] valid_oids The set of OIDs for which the credential is valid + * + * @returns SSH_OK if successful, SSH_ERROR otherwise + */ +int ssh_gssapi_client_identity(ssh_session session, gss_OID_set *valid_oids) +{ + OM_uint32 maj_stat, min_stat, lifetime; + gss_OID_set actual_mechs = GSS_C_NO_OID_SET; + gss_buffer_desc namebuf; + gss_name_t client_id = GSS_C_NO_NAME; + gss_OID oid; + unsigned int i; + char *ptr = NULL; + int ret; + + if (session == NULL || session->gssapi == NULL) { + return SSH_ERROR; + } + + if (session->gssapi->client.client_deleg_creds == NULL) { + if (session->opts.gss_client_identity != NULL) { + namebuf.value = (void *)session->opts.gss_client_identity; + namebuf.length = strlen(session->opts.gss_client_identity); + + maj_stat = gss_import_name(&min_stat, &namebuf, + GSS_C_NT_USER_NAME, &client_id); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + + maj_stat = gss_acquire_cred(&min_stat, client_id, GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, GSS_C_INITIATE, + &session->gssapi->client.creds, + &actual_mechs, NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "acquiring credential", + maj_stat, + min_stat); + ret = SSH_ERROR; + goto end; + } + } else { + session->gssapi->client.creds = + session->gssapi->client.client_deleg_creds; + + maj_stat = gss_inquire_cred(&min_stat, session->gssapi->client.creds, + &client_id, NULL, NULL, &actual_mechs); + if (GSS_ERROR(maj_stat)) { + ret = SSH_ERROR; + goto end; + } + } + SSH_LOG(SSH_LOG_DEBUG, "acquired credentials"); + + gss_create_empty_oid_set(&min_stat, valid_oids); + + /* double check each single cred */ + for (i = 0; i < actual_mechs->count; i++) { + /* check lifetime is not 0 or skip */ + lifetime = 0; + oid = &actual_mechs->elements[i]; + maj_stat = gss_inquire_cred_by_mech(&min_stat, + session->gssapi->client.creds, + oid, NULL, &lifetime, NULL, NULL); + if (maj_stat == GSS_S_COMPLETE && lifetime > 0) { + gss_add_oid_set_member(&min_stat, oid, valid_oids); + ptr = ssh_get_hexa(oid->elements, oid->length); + SSH_LOG(SSH_LOG_DEBUG, "GSSAPI valid oid %d : %s", i, ptr); + SAFE_FREE(ptr); + } + } + + ret = SSH_OK; + +end: + gss_release_oid_set(&min_stat, &actual_mechs); + gss_release_name(&min_stat, &client_id); + return ret; +} + +/** @internal + * @brief Add suffixes of oid hash to each GSSAPI key exchange algorithm + * @param[in] session current session handler + * @returns string suffixed kex algorithms or NULL on error + */ +char *ssh_gssapi_kex_mechs(ssh_session session) +{ + size_t i, j; + /* oid selected for authentication */ + gss_OID_set selected = GSS_C_NO_OID_SET; + ssh_string *oids = NULL; + int rc; + size_t n_oids = 0; + struct ssh_tokens_st *algs = NULL; + char *oid_hash = NULL; + const char *gss_algs = session->opts.gssapi_key_exchange_algs; + char *new_gss_algs = NULL; + char gss_kex_algs[8000] = {0}; + OM_uint32 min_stat; + size_t offset = 0; + + /* Get supported oids */ + if (session->server) { +#ifdef WITH_SERVER + rc = ssh_gssapi_server_oids(&selected); + if (rc == SSH_ERROR) { + return NULL; + } +#endif + } else { + rc = ssh_gssapi_client_identity(session, &selected); + if (rc == SSH_ERROR) { + return NULL; + } + } + ssh_gssapi_free(session); + + n_oids = selected->count; + SSH_LOG(SSH_LOG_DEBUG, "Sending %zu oids", n_oids); + + oids = calloc(n_oids, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + /* Check if algorithms are valid */ + new_gss_algs = + ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, gss_algs); + if (gss_algs == NULL) { + ssh_set_error( + session, + SSH_FATAL, + "GSSAPI key exchange algorithms not supported or invalid"); + rc = SSH_ERROR; + goto out; + } + + algs = ssh_tokenize(new_gss_algs, ','); + if (algs == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Couldn't tokenize GSSAPI key exchange algs"); + rc = SSH_ERROR; + goto out; + } + for (i = 0; i < n_oids; ++i) { + oids[i] = ssh_string_new(selected->elements[i].length + 2); + if (oids[i] == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + ((unsigned char *)oids[i]->data)[0] = SSH_OID_TAG; + ((unsigned char *)oids[i]->data)[1] = selected->elements[i].length; + memcpy((unsigned char *)oids[i]->data + 2, + selected->elements[i].elements, + selected->elements[i].length); + + /* Get the algorithm suffix */ + oid_hash = ssh_gssapi_oid_hash(oids[i]); + if (oid_hash == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + + /* For each oid loop through the algorithms, append the oid and append + * the algorithms to a string */ + for (j = 0; algs->tokens[j]; j++) { + if (sizeof(gss_kex_algs) < offset) { + ssh_set_error(session, SSH_FATAL, "snprintf failed"); + rc = SSH_ERROR; + goto out; + } + rc = snprintf(&gss_kex_algs[offset], + sizeof(gss_kex_algs) - offset, + "%s%s,", + algs->tokens[j], + oid_hash); + if (rc < 0 || rc >= (ssize_t)sizeof(gss_kex_algs)) { + ssh_set_error(session, SSH_FATAL, "snprintf failed"); + rc = SSH_ERROR; + goto out; + } + /* + 1 for ',' */ + offset += strlen(algs->tokens[j]) + strlen(oid_hash) + 1; + } + SAFE_FREE(oid_hash); + SSH_STRING_FREE(oids[i]); + } + + rc = SSH_OK; + +out: + SAFE_FREE(oid_hash); + SAFE_FREE(oids); + SAFE_FREE(new_gss_algs); + gss_release_oid_set(&min_stat, &selected); + ssh_tokens_free(algs); + + if (rc != SSH_OK) { + return NULL; + } + + return strdup(gss_kex_algs); +} + +int ssh_gssapi_import_name(struct ssh_gssapi_struct *gssapi, const char *host) +{ + gss_buffer_desc hostname; + char name_buf[256] = {0}; + OM_uint32 maj_stat, min_stat; + + /* import target host name */ + snprintf(name_buf, sizeof(name_buf), "host@%s", host); + + hostname.value = name_buf; + hostname.length = strlen(name_buf) + 1; + maj_stat = gss_import_name(&min_stat, + &hostname, + (gss_OID)GSS_C_NT_HOSTBASED_SERVICE, + &gssapi->client.server_name); + SSH_LOG(SSH_LOG_DEBUG, "importing name: %s", name_buf); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "error importing name", + maj_stat, + min_stat); + } + + return maj_stat; +} + +OM_uint32 ssh_gssapi_init_ctx(struct ssh_gssapi_struct *gssapi, + gss_buffer_desc *input_token, + gss_buffer_desc *output_token, + OM_uint32 *ret_flags) +{ + OM_uint32 maj_stat, min_stat; + + maj_stat = gss_init_sec_context(&min_stat, + gssapi->client.creds, + &gssapi->ctx, + gssapi->client.server_name, + gssapi->client.oid, + gssapi->client.flags, + 0, + NULL, + input_token, + NULL, + output_token, + ret_flags, + NULL); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "initializing gssapi context", + maj_stat, + min_stat); + } + return maj_stat; +} + +/** + * @brief launches a gssapi-with-mic auth request + * @returns SSH_AUTH_ERROR: A serious error happened\n + * SSH_AUTH_DENIED: Authentication failed : use another method\n + * SSH_AUTH_AGAIN: In nonblocking mode, you've got to call this again + * later. + */ +int ssh_gssapi_auth_mic(ssh_session session) +{ + size_t i; + gss_OID_set selected = GSS_C_NO_OID_SET; /* oid selected for authentication */ + ssh_string *oids = NULL; + int rc; + size_t n_oids = 0; + OM_uint32 min_stat; + const char *gss_host = session->opts.host; + + /* Destroy earlier GSSAPI context if any */ + ssh_gssapi_free(session); + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + return SSH_AUTH_ERROR; + } + + if (session->opts.gss_server_identity != NULL) { + gss_host = session->opts.gss_server_identity; + } + + rc = ssh_gssapi_import_name(session->gssapi, gss_host); + if (rc != SSH_OK) { + return SSH_AUTH_DENIED; + } + + /* copy username */ + session->gssapi->user = strdup(session->opts.username); + if (session->gssapi->user == NULL) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, "Authenticating with gssapi to host %s with user %s", + session->opts.host, session->gssapi->user); + rc = ssh_gssapi_client_identity(session, &selected); + if (rc == SSH_ERROR) { + return SSH_AUTH_DENIED; + } + + n_oids = selected->count; + SSH_LOG(SSH_LOG_DEBUG, "Sending %zu oids", n_oids); + + oids = calloc(n_oids, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + return SSH_AUTH_ERROR; + } + + for (i=0; ielements[i].length + 2); + if (oids[i] == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto out; + } + ((unsigned char *)oids[i]->data)[0] = SSH_OID_TAG; + ((unsigned char *)oids[i]->data)[1] = selected->elements[i].length; + memcpy((unsigned char *)oids[i]->data + 2, selected->elements[i].elements, + selected->elements[i].length); + } + + rc = ssh_gssapi_send_auth_mic(session, oids, n_oids); + +out: + for (i = 0; i < n_oids; i++) { + SSH_STRING_FREE(oids[i]); + } + free(oids); + gss_release_oid_set(&min_stat, &selected); + + if (rc != SSH_ERROR) { + return SSH_AUTH_AGAIN; + } + + return SSH_AUTH_ERROR; +} + +/** + * @brief Get the MIC for "gssapi-keyex" authentication. + * @returns SSH_ERROR: A serious error happened\n + * SSH_OK: MIC token is stored in mic_token_buf + */ +int ssh_gssapi_auth_keyex_mic(ssh_session session, + gss_buffer_desc *mic_token_buf) +{ + ssh_buffer buf = NULL; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + OM_uint32 maj_stat, min_stat; + + if (session->gssapi == NULL || session->gssapi->ctx == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + return SSH_ERROR; + } + + buf = ssh_gssapi_build_mic(session, "gssapi-keyex"); + if (buf == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + mic_buf.length = ssh_buffer_get_len(buf); + mic_buf.value = ssh_buffer_get(buf); + + maj_stat = gss_get_mic(&min_stat, + session->gssapi->ctx, + GSS_C_QOP_DEFAULT, + &mic_buf, + mic_token_buf); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "generating MIC", + maj_stat, + min_stat); + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + SSH_BUFFER_FREE(buf); + + return SSH_OK; +} + +static gss_OID ssh_gssapi_oid_from_string(ssh_string oid_s) +{ + gss_OID ret = NULL; + unsigned char *data = ssh_string_data(oid_s); + size_t len = ssh_string_len(oid_s); + + if (data == NULL) { + return NULL; + } + + if (len > 256 || len <= 2) { + SAFE_FREE(ret); + return NULL; + } + + if (data[0] != SSH_OID_TAG || data[1] != len - 2) { + SAFE_FREE(ret); + return NULL; + } + + ret = malloc(sizeof(gss_OID_desc)); + if (ret == NULL) { + return NULL; + } + + ret->elements = malloc(len - 2); + if (ret->elements == NULL) { + SAFE_FREE(ret); + return NULL; + } + memcpy(ret->elements, &data[2], len-2); + ret->length = len-2; + + return ret; +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_response){ + int rc; + ssh_string oid_s = NULL; + gss_uint32 maj_stat, min_stat; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + char *hexa = NULL; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_USERAUTH_GSSAPI_RESPONSE"); + if (session->auth.state != SSH_AUTH_STATE_GSSAPI_REQUEST_SENT){ + ssh_set_error(session, SSH_FATAL, "Invalid state in ssh_packet_userauth_gssapi_response"); + goto error; + } + + oid_s = ssh_buffer_get_ssh_string(packet); + if (!oid_s){ + ssh_set_error(session, SSH_FATAL, "Missing OID"); + goto error; + } + session->gssapi->client.oid = ssh_gssapi_oid_from_string(oid_s); + SSH_STRING_FREE(oid_s); + if (!session->gssapi->client.oid) { + ssh_set_error(session, SSH_FATAL, "Invalid OID"); + goto error; + } + + session->gssapi->client.flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; + if (session->opts.gss_delegate_creds) { + session->gssapi->client.flags |= GSS_C_DELEG_FLAG; + } + + maj_stat = + ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL); + if (GSS_ERROR(maj_stat)) { + goto error; + } + + if (output_token.length != 0){ + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s", hexa); + SAFE_FREE(hexa); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN; + } + + gss_release_buffer(&min_stat, &output_token); + return SSH_PACKET_USED; + +error: + session->auth.state = SSH_AUTH_STATE_ERROR; + return SSH_PACKET_USED; +} + +static int ssh_gssapi_send_mic(ssh_session session) +{ + OM_uint32 maj_stat, min_stat; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + gss_buffer_desc mic_token_buf = GSS_C_EMPTY_BUFFER; + ssh_buffer mic_buffer; + int rc; + + SSH_LOG(SSH_LOG_PACKET,"Sending SSH_MSG_USERAUTH_GSSAPI_MIC"); + + mic_buffer = ssh_gssapi_build_mic(session, "gssapi-with-mic"); + if (mic_buffer == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + mic_buf.length = ssh_buffer_get_len(mic_buffer); + mic_buf.value = ssh_buffer_get(mic_buffer); + + maj_stat = gss_get_mic(&min_stat,session->gssapi->ctx, GSS_C_QOP_DEFAULT, + &mic_buf, &mic_token_buf); + + SSH_BUFFER_FREE(mic_buffer); + + if (GSS_ERROR(maj_stat)){ + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "generating MIC", + maj_stat, + min_stat); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_MIC, + mic_token_buf.length, + (size_t)mic_token_buf.length, mic_token_buf.value); + + gss_release_buffer(&min_stat, &mic_token_buf); + + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + return ssh_packet_send(session); +} + +SSH_PACKET_CALLBACK(ssh_packet_userauth_gssapi_token_client) +{ + int rc; + ssh_string token = NULL; + char *hexa = NULL; + OM_uint32 maj_stat, min_stat; + gss_buffer_desc input_token, output_token = GSS_C_EMPTY_BUFFER; + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_USERAUTH_GSSAPI_TOKEN"); + if (!session->gssapi || session->auth.state != SSH_AUTH_STATE_GSSAPI_TOKEN) { + ssh_set_error(session, SSH_FATAL, + "Received SSH_MSG_USERAUTH_GSSAPI_TOKEN in invalid state"); + goto error; + } + token = ssh_buffer_get_ssh_string(packet); + + if (token == NULL){ + ssh_set_error(session, SSH_REQUEST_DENIED, + "ssh_packet_userauth_gssapi_token: invalid packet"); + goto error; + } + + hexa = ssh_get_hexa(ssh_string_data(token),ssh_string_len(token)); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI Token : %s",hexa); + SAFE_FREE(hexa); + + input_token.length = ssh_string_len(token); + input_token.value = ssh_string_data(token); + maj_stat = + ssh_gssapi_init_ctx(session->gssapi, &input_token, &output_token, NULL); + SSH_STRING_FREE(token); + if (GSS_ERROR(maj_stat)) { + goto error; + } + + if (output_token.length != 0) { + hexa = ssh_get_hexa(output_token.value, output_token.length); + SSH_LOG(SSH_LOG_PACKET, "GSSAPI: sending token %s",hexa); + SAFE_FREE(hexa); + rc = ssh_buffer_pack(session->out_buffer, + "bdP", + SSH2_MSG_USERAUTH_GSSAPI_TOKEN, + output_token.length, + (size_t)output_token.length, output_token.value); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + } + + gss_release_buffer(&min_stat, &output_token); + + if (maj_stat == GSS_S_COMPLETE) { + ssh_gssapi_send_mic(session); + session->auth.state = SSH_AUTH_STATE_GSSAPI_MIC_SENT; + } + + return SSH_PACKET_USED; + +error: + session->auth.state = SSH_AUTH_STATE_ERROR; + return SSH_PACKET_USED; +} diff --git a/src/libs/libssh-0.12.2/src/gzip.c b/src/libs/libssh-0.12.2/src/gzip.c new file mode 100644 index 000000000000..e814080ae23e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/gzip.c @@ -0,0 +1,302 @@ +/* + * gzip.c - hooks for compression of packets + * + * This file is part of the SSH Library + * + * Copyright (c) 2003 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#ifdef WITH_ZLIB +#include + +#ifndef BLOCKSIZE +#define BLOCKSIZE 4092 +#endif + +static z_stream * +initcompress(ssh_session session, int level) +{ + z_stream *stream = NULL; + int status; + + stream = calloc(1, sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } + + status = deflateInit(stream, level); + if (status != Z_OK) { + deflateEnd(stream); + SAFE_FREE(stream); + ssh_set_error(session, + SSH_FATAL, + "status %d initialising zlib deflate", + status); + return NULL; + } + + return stream; +} + +static ssh_buffer +gzip_compress(ssh_session session, ssh_buffer source, int level) +{ + struct ssh_crypto_struct *crypto = NULL; + z_stream *zout = NULL; + void *in_ptr = ssh_buffer_get(source); + uint32_t in_size = ssh_buffer_get_len(source); + ssh_buffer dest = NULL; + unsigned char out_buf[BLOCKSIZE] = {0}; + uint32_t len; + int status; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto == NULL) { + return NULL; + } + zout = crypto->compress_out_ctx; + if (zout == NULL) { + zout = crypto->compress_out_ctx = initcompress(session, level); + if (zout == NULL) { + return NULL; + } + } + + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; + } + + zout->next_out = out_buf; + zout->next_in = in_ptr; + zout->avail_in = in_size; + do { + zout->avail_out = BLOCKSIZE; + status = deflate(zout, Z_PARTIAL_FLUSH); + if (status != Z_OK) { + SSH_BUFFER_FREE(dest); + ssh_set_error(session, + SSH_FATAL, + "status %d deflating zlib packet", + status); + return NULL; + } + len = BLOCKSIZE - zout->avail_out; + if (ssh_buffer_add_data(dest, out_buf, len) < 0) { + SSH_BUFFER_FREE(dest); + return NULL; + } + zout->next_out = out_buf; + } while (zout->avail_out == 0); + + return dest; +} + +int +compress_buffer(ssh_session session, ssh_buffer buf) +{ + ssh_buffer dest = NULL; + int rv; + + dest = gzip_compress(session, buf, session->opts.compressionlevel); + if (dest == NULL) { + return -1; + } + + if (ssh_buffer_reinit(buf) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + rv = ssh_buffer_add_data(buf, + ssh_buffer_get(dest), + ssh_buffer_get_len(dest)); + if (rv < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + SSH_BUFFER_FREE(dest); + return 0; +} + +/* decompression */ + +static z_stream * +initdecompress(ssh_session session) +{ + z_stream *stream = NULL; + int status; + + stream = calloc(1, sizeof(z_stream)); + if (stream == NULL) { + return NULL; + } + + status = inflateInit(stream); + if (status != Z_OK) { + inflateEnd(stream); + SAFE_FREE(stream); + ssh_set_error(session, + SSH_FATAL, + "Status = %d initiating inflate context!", + status); + return NULL; + } + + return stream; +} + +static ssh_buffer +gzip_decompress(ssh_session session, ssh_buffer source, size_t maxlen) +{ + struct ssh_crypto_struct *crypto = NULL; + z_stream *zin = NULL; + void *in_ptr = ssh_buffer_get(source); + uint32_t in_size = ssh_buffer_get_len(source); + unsigned char out_buf[BLOCKSIZE] = {0}; + ssh_buffer dest = NULL; + uint32_t len; + int status; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return NULL; + } + + zin = crypto->compress_in_ctx; + if (zin == NULL) { + zin = crypto->compress_in_ctx = initdecompress(session); + if (zin == NULL) { + return NULL; + } + } + + dest = ssh_buffer_new(); + if (dest == NULL) { + return NULL; + } + + zin->next_out = out_buf; + zin->next_in = in_ptr; + zin->avail_in = in_size; + + do { + zin->avail_out = BLOCKSIZE; + status = inflate(zin, Z_PARTIAL_FLUSH); + if (status != Z_OK && status != Z_BUF_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "status %d inflating zlib packet", + status); + SSH_BUFFER_FREE(dest); + return NULL; + } + + len = BLOCKSIZE - zin->avail_out; + if (ssh_buffer_add_data(dest, out_buf, len) < 0) { + SSH_BUFFER_FREE(dest); + return NULL; + } + if (ssh_buffer_get_len(dest) > maxlen) { + /* Size of packet exceeded, avoid a denial of service attack */ + SSH_BUFFER_FREE(dest); + return NULL; + } + zin->next_out = out_buf; + } while (zin->avail_out == 0); + + return dest; +} + +int +decompress_buffer(ssh_session session, ssh_buffer buf, size_t maxlen) +{ + ssh_buffer dest = NULL; + int rv; + + dest = gzip_decompress(session, buf, maxlen); + if (dest == NULL) { + return -1; + } + + if (ssh_buffer_reinit(buf) < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + rv = ssh_buffer_add_data(buf, + ssh_buffer_get(dest), + ssh_buffer_get_len(dest)); + if (rv < 0) { + SSH_BUFFER_FREE(dest); + return -1; + } + + SSH_BUFFER_FREE(dest); + return 0; +} + +void +compress_cleanup(struct ssh_crypto_struct *crypto) +{ + if (crypto->compress_out_ctx) { + deflateEnd(crypto->compress_out_ctx); + } + SAFE_FREE(crypto->compress_out_ctx); + + if (crypto->compress_in_ctx) { + inflateEnd(crypto->compress_in_ctx); + } + SAFE_FREE(crypto->compress_in_ctx); +} +#else /* WITH_ZLIB */ + +int +compress_buffer(UNUSED_PARAM(ssh_session session), UNUSED_PARAM(ssh_buffer buf)) +{ + /* without zlib compiled in, this should never happen */ + return -1; +} +int +decompress_buffer(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_buffer buf), + UNUSED_PARAM(size_t maxlen)) +{ + /* without zlib compiled in, this should never happen */ + return -1; +} + +void +compress_cleanup(UNUSED_PARAM(struct ssh_crypto_struct *crypto)) +{ + /* no-op */ +} + +#endif /* WITH_ZLIB */ diff --git a/src/libs/libssh-0.12.2/src/hybrid_mlkem.c b/src/libs/libssh-0.12.2/src/hybrid_mlkem.c new file mode 100644 index 000000000000..d13729abe2d3 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/hybrid_mlkem.c @@ -0,0 +1,910 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Sahana Prasad + * Author: Pavol Žáčik + * Author: Claude (Anthropic) + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/buffer.h" +#include "libssh/hybrid_mlkem.h" +#include "libssh/pki.h" +#include "libssh/ssh2.h" + +/* sorry, this needs to come last to avoid header dependency issues */ +#include "libssh/bignum.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_hybrid_mlkem_reply); + +static ssh_packet_callback dh_client_callbacks[] = { + ssh_packet_client_hybrid_mlkem_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_hybrid_mlkem_client_callbacks = { + .start = SSH2_MSG_KEX_HYBRID_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL, +}; + +static ssh_string derive_curve25519_secret(ssh_session session) +{ + ssh_string secret = NULL; + int rc; + + secret = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (secret == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + rc = ssh_curve25519_create_k(session, ssh_string_data(secret)); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Curve25519 secret derivation failed"); + ssh_string_free(secret); + return NULL; + } + + return secret; +} + +static ssh_string derive_nist_curve_secret(ssh_session session, + size_t secret_size) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string secret = NULL; + int rc; + + rc = ecdh_build_k(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "ECDH secret derivation failed"); + return NULL; + } + + secret = ssh_make_padded_bignum_string(crypto->shared_secret, secret_size); + if (secret == NULL) { + ssh_set_error(session, SSH_FATAL, "Failed to encode the shared secret"); + } + + bignum_safe_free(crypto->shared_secret); + + return secret; +} + +static ssh_string derive_ecdh_secret(ssh_session session) +{ + ssh_string secret = NULL; + + switch (session->next_crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + secret = derive_curve25519_secret(session); + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: + secret = derive_nist_curve_secret(session, NISTP256_SHARED_SECRET_SIZE); + break; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + secret = derive_nist_curve_secret(session, NISTP384_SHARED_SECRET_SIZE); + break; +#endif + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + return NULL; + } + + return secret; +} + +static int derive_hybrid_secret(ssh_session session, + ssh_mlkem_shared_secret mlkem_shared_secret, + ssh_string ecdh_shared_secret) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_buffer combined_secret = NULL; + int (*digest)(const unsigned char *, size_t, unsigned char *) = NULL; + size_t digest_len; + int rc, ret = SSH_ERROR; + + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + digest = sha256; + digest_len = SHA256_DIGEST_LEN; + break; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + digest = sha384; + digest_len = SHA384_DIGEST_LEN; + break; +#endif + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Concatenate the two shared secrets */ + combined_secret = ssh_buffer_new(); + if (combined_secret == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + ssh_buffer_set_secure(combined_secret); + + rc = ssh_buffer_pack(combined_secret, + "PP", + (size_t)MLKEM_SHARED_SECRET_SIZE, + mlkem_shared_secret, + ssh_string_len(ecdh_shared_secret), + ssh_string_data(ecdh_shared_secret)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to concatenate shared secrets"); + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Concatenated shared secrets", + ssh_buffer_get(combined_secret), + ssh_buffer_get_len(combined_secret)); +#endif + + /* Store the hashed combined shared secrets */ + ssh_string_burn(crypto->hybrid_shared_secret); + ssh_string_free(crypto->hybrid_shared_secret); + crypto->hybrid_shared_secret = ssh_string_new(digest_len); + if (crypto->hybrid_shared_secret == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = digest(ssh_buffer_get(combined_secret), + ssh_buffer_get_len(combined_secret), + ssh_string_data(crypto->hybrid_shared_secret)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Shared secret hashing failed"); + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Hybrid shared secret", + ssh_string_data(crypto->hybrid_shared_secret), + digest_len); +#endif + + ret = SSH_OK; + +cleanup: + ssh_buffer_free(combined_secret); + return ret; +} + +int ssh_client_hybrid_mlkem_init(ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_buffer client_init_buffer = NULL; + int rc, ret = SSH_ERROR; + + SSH_LOG(SSH_LOG_TRACE, "Initializing hybrid ML-KEM key exchange"); + + /* Prepare a buffer to concatenate ML-KEM + ECDH public keys */ + client_init_buffer = ssh_buffer_new(); + if (client_init_buffer == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + /* Generate an ML-KEM keypair */ + rc = ssh_mlkem_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate an ML-KEM keypair"); + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM client pubkey", + ssh_string_data(crypto->mlkem_client_pubkey), + ssh_string_len(crypto->mlkem_client_pubkey)); +#endif + + /* Generate an ECDH keypair and concatenate the public keys */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a Curve25519 ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 client pubkey", + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + rc = ssh_buffer_pack(client_init_buffer, + "PP", + ssh_string_len(crypto->mlkem_client_pubkey), + ssh_string_data(crypto->mlkem_client_pubkey), + (size_t)CURVE25519_PUBKEY_SIZE, + crypto->curve25519_client_pubkey); + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a NIST-curve ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH client pubkey", + ssh_string_data(crypto->ecdh_client_pubkey), + ssh_string_len(crypto->ecdh_client_pubkey)); +#endif + rc = ssh_buffer_pack(client_init_buffer, + "PP", + ssh_string_len(crypto->mlkem_client_pubkey), + ssh_string_data(crypto->mlkem_client_pubkey), + ssh_string_len(crypto->ecdh_client_pubkey), + ssh_string_data(crypto->ecdh_client_pubkey)); + + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to construct client init buffer"); + goto cleanup; + } + + /* Convert the client init buffer to an SSH string */ + ssh_string_free(crypto->hybrid_client_init); + crypto->hybrid_client_init = ssh_string_new(ssh_buffer_get_len(client_init_buffer)); + if (crypto->hybrid_client_init == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = ssh_string_fill(crypto->hybrid_client_init, + ssh_buffer_get(client_init_buffer), + ssh_buffer_get_len(client_init_buffer)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to convert client init to string"); + goto cleanup; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_KEX_HYBRID_INIT, + crypto->hybrid_client_init); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to construct SSH_MSG_KEX_HYBRID_INIT"); + goto cleanup; + } + + ssh_packet_set_callbacks(session, &ssh_hybrid_mlkem_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_HYBRID_INIT"); + goto cleanup; + } + + ret = SSH_OK; + +cleanup: + ssh_buffer_free(client_init_buffer); + return ret; +} + +static SSH_PACKET_CALLBACK(ssh_packet_client_hybrid_mlkem_reply) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + ssh_mlkem_shared_secret mlkem_shared_secret; + ssh_string ecdh_shared_secret = NULL; + ssh_buffer server_reply_buffer = NULL; + size_t read_len; + size_t ecdh_server_pubkey_size; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM hybrid server reply"); + + ssh_client_hybrid_mlkem_remove_callbacks(session); + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + ssh_set_error(session, SSH_FATAL, "Unknown ML-KEM type"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "No public key in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to import public key"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Get server reply containing ML-KEM ciphertext + ECDH public key */ + ssh_string_free(crypto->hybrid_server_reply); + crypto->hybrid_server_reply = ssh_buffer_get_ssh_string(packet); + if (crypto->hybrid_server_reply == NULL) { + ssh_set_error(session, SSH_FATAL, "No server reply in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + server_reply_buffer = ssh_buffer_new(); + if (server_reply_buffer == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_buffer_add_data(server_reply_buffer, + ssh_string_data(crypto->hybrid_server_reply), + ssh_string_len(crypto->hybrid_server_reply)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack server reply to a buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Store ML-KEM ciphertext for decapsulation and sessionid calculation */ + ssh_string_free(crypto->mlkem_ciphertext); + crypto->mlkem_ciphertext = ssh_string_new(mlkem_info->ciphertext_size); + if (crypto->mlkem_ciphertext == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + read_len = ssh_buffer_get_data(server_reply_buffer, + ssh_string_data(crypto->mlkem_ciphertext), + mlkem_info->ciphertext_size); + if (read_len != mlkem_info->ciphertext_size) { + ssh_set_error(session, + SSH_FATAL, + "Could not read ML-KEM ciphertext from " + "the server reply buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM ciphertext", + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->mlkem_ciphertext)); +#endif + + /* Extract server ECDH public key */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + read_len = ssh_buffer_get_data(server_reply_buffer, + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (read_len != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Could not read Curve25519 pubkey from " + "the server reply buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + if (ssh_buffer_get_len(server_reply_buffer) > 0) { + ssh_set_error(session, + SSH_FATAL, + "Unrecognized data in the server reply buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 server pubkey", + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ecdh_server_pubkey_size = ssh_buffer_get_len(server_reply_buffer); + ssh_string_free(crypto->ecdh_server_pubkey); + crypto->ecdh_server_pubkey = ssh_string_new(ecdh_server_pubkey_size); + if (crypto->ecdh_server_pubkey == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + ssh_buffer_get_data(server_reply_buffer, + ssh_string_data(crypto->ecdh_server_pubkey), + ecdh_server_pubkey_size); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH server pubkey", + ssh_string_data(crypto->ecdh_server_pubkey), + ssh_string_len(crypto->ecdh_server_pubkey)); +#endif + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Decapsulate ML-KEM shared secret */ + rc = ssh_mlkem_decapsulate(session, mlkem_shared_secret); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "ML-KEM decapsulation failed"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM shared secret", + mlkem_shared_secret, + MLKEM_SHARED_SECRET_SIZE); +#endif + + /* Derive the classical ECDH shared secret */ + ecdh_shared_secret = derive_ecdh_secret(session); + if (ecdh_shared_secret == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH shared secret", + ssh_string_data(ecdh_shared_secret), + ssh_string_len(ecdh_shared_secret)); +#endif + + /* Derive the final shared secret */ + rc = derive_hybrid_secret(session, mlkem_shared_secret, ecdh_shared_secret); + if (rc != SSH_OK) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Get signature for verification */ + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + crypto->dh_server_signature = signature; + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + +cleanup: + ssh_burn(mlkem_shared_secret, sizeof(mlkem_shared_secret)); + ssh_string_burn(ecdh_shared_secret); + ssh_string_free(ecdh_shared_secret); + ssh_string_free(pubkey_blob); + ssh_buffer_free(server_reply_buffer); + return SSH_PACKET_USED; +} + +void ssh_client_hybrid_mlkem_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_hybrid_mlkem_client_callbacks); +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_hybrid_mlkem_init, +}; + +static struct ssh_packet_callbacks_struct ssh_hybrid_mlkem_server_callbacks = { + .start = SSH2_MSG_KEX_HYBRID_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL, +}; + +static SSH_PACKET_CALLBACK(ssh_packet_server_hybrid_mlkem_init) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string ecdh_shared_secret = NULL; + ssh_mlkem_shared_secret mlkem_shared_secret; + ssh_buffer server_reply_buffer = NULL; + ssh_buffer client_init_buffer = NULL; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string signature = NULL; + ssh_string pubkey_blob = NULL; + size_t ecdh_client_pubkey_size; + size_t read_len; + int rc; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "Received ML-KEM hybrid client init"); + + ssh_packet_remove_callbacks(session, &ssh_hybrid_mlkem_server_callbacks); + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + ssh_set_error(session, SSH_FATAL, "Unknown ML-KEM type"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Generate an ECDH keypair */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a Curve25519 ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 server pubkey", + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to generate a NIST-curve ECDH keypair"); + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH server pubkey", + ssh_string_data(crypto->ecdh_server_pubkey), + ssh_string_len(crypto->ecdh_server_pubkey)); +#endif + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Get client init: ML-KEM public key + ECDH public key */ + ssh_string_free(crypto->hybrid_client_init); + crypto->hybrid_client_init = ssh_buffer_get_ssh_string(packet); + if (crypto->hybrid_client_init == NULL) { + ssh_set_error(session, SSH_FATAL, "No client public keys in packet"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + client_init_buffer = ssh_buffer_new(); + if (client_init_buffer == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_buffer_add_data(client_init_buffer, + ssh_string_data(crypto->hybrid_client_init), + ssh_string_len(crypto->hybrid_client_init)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack client init to a buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Extract client ML-KEM public key */ + ssh_string_free(crypto->mlkem_client_pubkey); + crypto->mlkem_client_pubkey = ssh_string_new(mlkem_info->pubkey_size); + if (crypto->mlkem_client_pubkey == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + read_len = ssh_buffer_get_data(client_init_buffer, + ssh_string_data(crypto->mlkem_client_pubkey), + mlkem_info->pubkey_size); + if (read_len != mlkem_info->pubkey_size) { + ssh_set_error(session, + SSH_FATAL, + "Could not read ML-KEM pubkey from " + "the client init buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM client pubkey", + ssh_string_data(crypto->mlkem_client_pubkey), + ssh_string_len(crypto->mlkem_client_pubkey)); +#endif + + /* Extract client ECDH public key */ + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + read_len = ssh_buffer_get_data(client_init_buffer, + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); + if (read_len != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Could not read Curve25519 pubkey from " + "the client init buffer, buffer too short"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + if (ssh_buffer_get_len(client_init_buffer) > 0) { + ssh_set_error(session, + SSH_FATAL, + "Unrecognized data in the client init buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 client pubkey", + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ecdh_client_pubkey_size = ssh_buffer_get_len(client_init_buffer); + ssh_string_free(crypto->ecdh_client_pubkey); + crypto->ecdh_client_pubkey = ssh_string_new(ecdh_client_pubkey_size); + if (crypto->ecdh_client_pubkey == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + ssh_buffer_get_data(client_init_buffer, + ssh_string_data(crypto->ecdh_client_pubkey), + ecdh_client_pubkey_size); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH client pubkey", + ssh_string_data(crypto->ecdh_client_pubkey), + ssh_string_len(crypto->ecdh_client_pubkey)); +#endif + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + + /* Encapsulate an ML-KEM shared secret using client's ML-KEM public key */ + rc = ssh_mlkem_encapsulate(session, mlkem_shared_secret); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "ML-KEM encapsulation failed"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ML-KEM shared secret", + mlkem_shared_secret, + MLKEM_SHARED_SECRET_SIZE); + ssh_log_hexdump("ML-KEM ciphertext", + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->mlkem_ciphertext)); +#endif + + /* Derive the classical ECDH shared secret */ + ecdh_shared_secret = derive_ecdh_secret(session); + if (ecdh_shared_secret == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH shared secret", + ssh_string_data(ecdh_shared_secret), + ssh_string_len(ecdh_shared_secret)); +#endif + + /* Derive the final shared secret */ + rc = derive_hybrid_secret(session, mlkem_shared_secret, ecdh_shared_secret); + if (rc != SSH_OK) { + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Create server reply: ML-KEM ciphertext + ECDH public key */ + server_reply_buffer = ssh_buffer_new(); + if (server_reply_buffer == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + switch (crypto->kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + rc = ssh_buffer_pack(server_reply_buffer, + "PP", + ssh_string_len(crypto->mlkem_ciphertext), + ssh_string_data(crypto->mlkem_ciphertext), + (size_t)CURVE25519_PUBKEY_SIZE, + crypto->curve25519_server_pubkey); + break; + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_buffer_pack(server_reply_buffer, + "PP", + ssh_string_len(crypto->mlkem_ciphertext), + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->ecdh_server_pubkey), + ssh_string_data(crypto->ecdh_server_pubkey)); + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported KEX type"); + goto cleanup; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to construct server reply buffer"); + goto cleanup; + } + + /* Convert the reply buffer to an SSH string for sending */ + ssh_string_free(crypto->hybrid_server_reply); + crypto->hybrid_server_reply = ssh_string_new(ssh_buffer_get_len(server_reply_buffer)); + if (crypto->hybrid_server_reply == NULL) { + ssh_set_error_oom(session); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_string_fill(crypto->hybrid_server_reply, + ssh_buffer_get(server_reply_buffer), + ssh_buffer_get_len(server_reply_buffer)); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to convert reply buffer to string"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add MSG_KEX_ECDH_REPLY header */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_HYBRID_REPLY); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add MSG_KEX_HYBRID_REPLY to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Get server host key */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not get server key params"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Build session ID */ + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add server public key to output */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add server hostkey to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add server reply */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, crypto->hybrid_server_reply); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add server reply to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Sign the exchange hash */ + signature = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Add signature */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, signature); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to add signature to buffer"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_KEX_ECDH_REPLY"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to send SSH_MSG_NEWKEYS"); + session->session_state = SSH_SESSION_STATE_ERROR; + goto cleanup; + } + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + +cleanup: + ssh_burn(mlkem_shared_secret, sizeof(mlkem_shared_secret)); + ssh_string_burn(ecdh_shared_secret); + ssh_string_free(ecdh_shared_secret); + ssh_string_free(pubkey_blob); + ssh_string_free(signature); + ssh_buffer_free(client_init_buffer); + ssh_buffer_free(server_reply_buffer); + return SSH_PACKET_USED; +} + +void ssh_server_hybrid_mlkem_init(ssh_session session) +{ + SSH_LOG(SSH_LOG_TRACE, "Setting up ML-KEM hybrid server callbacks"); + ssh_packet_set_callbacks(session, &ssh_hybrid_mlkem_server_callbacks); +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/src/init.c b/src/libs/libssh-0.12.2/src/init.c new file mode 100644 index 000000000000..e516c3318036 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/init.c @@ -0,0 +1,295 @@ +/* + * init.c - initialization and finalization of the library + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "libssh/priv.h" +#include "libssh/socket.h" +#include "libssh/dh.h" +#include "libssh/poll.h" +#include "libssh/threads.h" + +#ifdef _WIN32 +#include +#endif + +#ifdef HAVE_CONSTRUCTOR_ATTRIBUTE +#define CONSTRUCTOR_ATTRIBUTE __attribute__((constructor)) +#else +#define CONSTRUCTOR_ATTRIBUTE +#endif /* HAVE_CONSTRUCTOR_ATTRIBUTE */ + +#ifdef HAVE_DESTRUCTOR_ATTRIBUTE +#define DESTRUCTOR_ATTRIBUTE __attribute__((destructor)) +#else +#define DESTRUCTOR_ATTRIBUTE +#endif /* HAVE_DESTRUCTOR_ATTRIBUTE */ + +/* Declare static mutex */ +static SSH_MUTEX ssh_init_mutex = SSH_MUTEX_STATIC_INIT; + +/* Counter for initializations */ +static int _ssh_initialized = 0; + +/* Cache the returned value */ +static int _ssh_init_ret = 0; + +void libssh_constructor(void) CONSTRUCTOR_ATTRIBUTE; +void libssh_destructor(void) DESTRUCTOR_ATTRIBUTE; + +static int _ssh_init(unsigned constructor) { + + int rc = 0; + + if (!constructor) { + ssh_mutex_lock(&ssh_init_mutex); + } + + _ssh_initialized++; + + if (_ssh_initialized > 1) { + rc = _ssh_init_ret; + goto _ret; + } + + rc = ssh_threads_init(); + if (rc) { + goto _ret; + } + + rc = ssh_crypto_init(); + if (rc) { + goto _ret; + } + + rc = ssh_dh_init(); + if (rc) { + goto _ret; + } + + rc = ssh_socket_init(); + if (rc) { + goto _ret; + } + +_ret: + _ssh_init_ret = rc; + + if (!constructor) { + ssh_mutex_unlock(&ssh_init_mutex); + } + + return rc; +} + +/** + * @brief Initialize global cryptographic data structures. + * + * This function is automatically called when the library is loaded. + * + */ +void libssh_constructor(void) +{ + + int rc; + + rc = _ssh_init(1); + + if (rc < 0) { + fprintf(stderr, "Error in auto_init()\n"); + } + + return; +} + +/** + * @defgroup libssh The libssh API + * + * The libssh library is implementing the SSH protocols and some of its + * extensions. This group of functions is mostly used to implement an SSH + * client. + * Some functions are needed to implement an SSH server too. + * + * @{ + */ + +/** + * @brief Initialize global cryptographic data structures. + * + * Since version 0.8.0, when libssh is dynamically linked, it is not necessary + * to call this function on systems that fully support threading (that is, + * systems with pthreads available). + * + * If libssh is statically linked, it is necessary to explicitly call ssh_init() + * before calling any other provided API, and it is necessary to explicitly call + * ssh_finalize() to free the allocated resources before exiting. + * + * If the library is already initialized, increments the _ssh_initialized + * counter and return the error code cached in _ssh_init_ret. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + * + * @see ssh_finalize() + */ +int ssh_init(void) { + return _ssh_init(0); +} + +static int _ssh_finalize(unsigned destructor) { + + if (!destructor) { + ssh_mutex_lock(&ssh_init_mutex); + + if (_ssh_initialized > 1) { + _ssh_initialized--; + ssh_mutex_unlock(&ssh_init_mutex); + return 0; + } + + if (_ssh_initialized == 1) { + if (_ssh_init_ret < 0) { + ssh_mutex_unlock(&ssh_init_mutex); + return 0; + } + } + } + + /* If the counter reaches zero or it is the destructor calling, finalize */ + ssh_dh_finalize(); + ssh_crypto_finalize(); + ssh_socket_cleanup(); + /* It is important to finalize threading after CRYPTO because + * it still depends on it */ + ssh_threads_finalize(); + + _ssh_initialized = 0; + + if (!destructor) { + ssh_mutex_unlock(&ssh_init_mutex); + } + +#if (defined(_WIN32) && !defined(HAVE_PTHREAD)) + if (ssh_init_mutex != NULL) { + DeleteCriticalSection(ssh_init_mutex); + SAFE_FREE(ssh_init_mutex); + } +#endif + + return 0; +} + +/** + * @brief Finalize and clean up all libssh and cryptographic data structures. + * + * This function is automatically called when the library is unloaded. + * + */ +void libssh_destructor(void) +{ + int rc; + + rc = _ssh_finalize(1); + + if (rc < 0) { + fprintf(stderr, "Error in libssh_destructor()\n"); + } +} + +/** + * @brief Finalize and clean up all libssh and cryptographic data structures. + * + * Since version 0.8.0, when libssh is dynamically linked, it is not necessary + * to call this function, since it is automatically called when the library is + * unloaded. + * + * If libssh is statically linked, it is necessary to explicitly call ssh_init() + * before calling any other provided API, and it is necessary to explicitly call + * ssh_finalize() to free the allocated resources before exiting. + * + * If ssh_init() is called explicitly, then ssh_finalize() must be called + * explicitly. + * + * When called, decrements the counter _ssh_initialized. If the counter reaches + * zero, then the libssh and cryptographic data structures are cleaned up. + * + * @returns 0 on success, -1 if an error occurred. + * + * @see ssh_init() + */ +int ssh_finalize(void) { + return _ssh_finalize(0); +} + +#ifdef _WIN32 + +#if defined(_MSC_VER) && !defined(LIBSSH_STATIC) +/* Library constructor and destructor */ +BOOL WINAPI DllMain(HINSTANCE hinstDLL, + DWORD fdwReason, + LPVOID lpvReserved) +{ + int rc = 0; + + switch(fdwReason) { + case DLL_PROCESS_ATTACH: + rc = _ssh_init(1); + if (rc != 0) { + fprintf(stderr, "DllMain: ssh_init failed!"); + return FALSE; + } + break; + case DLL_PROCESS_DETACH: + _ssh_finalize(1); + break; + default: + break; + } + + return TRUE; +} +#endif /* _MSC_VER && !LIBSSH_STATIC */ + +#endif /* _WIN32 */ + +/** + * @internal + * @brief Return whether the library is initialized + * + * @returns true if the library is initialized; false otherwise. + * + * @see ssh_init() + */ +bool is_ssh_initialized(void) { + + bool is_initialized = false; + + ssh_mutex_lock(&ssh_init_mutex); + is_initialized = _ssh_initialized > 0; + ssh_mutex_unlock(&ssh_init_mutex); + + return is_initialized; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/kdf.c b/src/libs/libssh-0.12.2/src/kdf.c new file mode 100644 index 000000000000..6bc477ce76ff --- /dev/null +++ b/src/libs/libssh-0.12.2/src/kdf.c @@ -0,0 +1,238 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyrihgt (c) 2018 Red Hat, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; either + * version 2.1 of the License, or (at your option) any later version. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + */ + +#include "config.h" +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/ssh2.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" + +#include "libssh/string.h" + + +/* The following implements the SSHKDF for crypto backend that + * do not have a native implementation */ +struct ssh_mac_ctx_struct { + enum ssh_kdf_digest digest_type; + union { + SHACTX sha1_ctx; + SHA256CTX sha256_ctx; + SHA384CTX sha384_ctx; + SHA512CTX sha512_ctx; + } ctx; +}; + +static ssh_mac_ctx ssh_mac_ctx_init(enum ssh_kdf_digest type) +{ + ssh_mac_ctx ctx = malloc(sizeof(struct ssh_mac_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + + ctx->digest_type = type; + switch (type) { + case SSH_KDF_SHA1: + ctx->ctx.sha1_ctx = sha1_init(); + if (ctx->ctx.sha1_ctx == NULL) { + goto err; + } + return ctx; + case SSH_KDF_SHA256: + ctx->ctx.sha256_ctx = sha256_init(); + if (ctx->ctx.sha256_ctx == NULL) { + goto err; + } + return ctx; + case SSH_KDF_SHA384: + ctx->ctx.sha384_ctx = sha384_init(); + if (ctx->ctx.sha384_ctx == NULL) { + goto err; + } + return ctx; + case SSH_KDF_SHA512: + ctx->ctx.sha512_ctx = sha512_init(); + if (ctx->ctx.sha512_ctx == NULL) { + goto err; + } + return ctx; + } +err: + SAFE_FREE(ctx); + return NULL; +} + +static void ssh_mac_ctx_free(ssh_mac_ctx ctx) +{ + if (ctx == NULL) { + return; + } + + switch (ctx->digest_type) { + case SSH_KDF_SHA1: + sha1_ctx_free(ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: + sha256_ctx_free(ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: + sha384_ctx_free(ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: + sha512_ctx_free(ctx->ctx.sha512_ctx); + break; + } + SAFE_FREE(ctx); +} + +static int ssh_mac_update(ssh_mac_ctx ctx, const void *data, size_t len) +{ + switch (ctx->digest_type) { + case SSH_KDF_SHA1: + return sha1_update(ctx->ctx.sha1_ctx, data, len); + case SSH_KDF_SHA256: + return sha256_update(ctx->ctx.sha256_ctx, data, len); + case SSH_KDF_SHA384: + return sha384_update(ctx->ctx.sha384_ctx, data, len); + case SSH_KDF_SHA512: + return sha512_update(ctx->ctx.sha512_ctx, data, len); + } + return SSH_ERROR; +} + +static int ssh_mac_final(unsigned char *md, ssh_mac_ctx ctx) +{ + int rc = SSH_ERROR; + + switch (ctx->digest_type) { + case SSH_KDF_SHA1: + rc = sha1_final(md, ctx->ctx.sha1_ctx); + break; + case SSH_KDF_SHA256: + rc = sha256_final(md, ctx->ctx.sha256_ctx); + break; + case SSH_KDF_SHA384: + rc = sha384_final(md, ctx->ctx.sha384_ctx); + break; + case SSH_KDF_SHA512: + rc = sha512_final(md, ctx->ctx.sha512_ctx); + break; + } + SAFE_FREE(ctx); + return rc; +} + +int sshkdf_derive_key(struct ssh_crypto_struct *crypto, + unsigned char *key, + size_t key_len, + uint8_t key_type, + unsigned char *output, + size_t requested_len) +{ + /* Can't use VLAs with Visual Studio, so allocate the biggest + * digest buffer we can possibly need */ + unsigned char digest[DIGEST_MAX_LEN]; + size_t output_len = crypto->digest_len; + ssh_mac_ctx ctx; + int rc; + + if (DIGEST_MAX_LEN < crypto->digest_len) { + return -1; + } + + ctx = ssh_mac_ctx_init(crypto->digest_type); + if (ctx == NULL) { + return -1; + } + + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, &key_type, 1); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->session_id, crypto->session_id_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } + + if (requested_len < output_len) { + output_len = requested_len; + } + memcpy(output, digest, output_len); + + while (requested_len > output_len) { + ctx = ssh_mac_ctx_init(crypto->digest_type); + if (ctx == NULL) { + return -1; + } + rc = ssh_mac_update(ctx, key, key_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, crypto->secret_hash, crypto->digest_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_update(ctx, output, output_len); + if (rc != SSH_OK) { + ssh_mac_ctx_free(ctx); + return -1; + } + rc = ssh_mac_final(digest, ctx); + if (rc != SSH_OK) { + return -1; + } + if (requested_len < output_len + crypto->digest_len) { + memcpy(output + output_len, digest, requested_len - output_len); + } else { + memcpy(output + output_len, digest, crypto->digest_len); + } + output_len += crypto->digest_len; + } + + return 0; +} diff --git a/src/libs/libssh-0.12.2/src/kex-gss.c b/src/libs/libssh-0.12.2/src/kex-gss.c new file mode 100644 index 000000000000..0cf8b5915186 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/kex-gss.c @@ -0,0 +1,687 @@ +/* + * kex-gss.c - GSSAPI key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Gauravsingh Sisodia + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/gssapi.h" +#include +#include +#include + +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/kex-gss.h" +#include "libssh/bignum.h" +#include "libssh/curve25519.h" +#include "libssh/ecdh.h" +#include "libssh/dh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply); + +static ssh_packet_callback gss_kex_client_callbacks[] = { + ssh_packet_client_gss_kex_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callbacks = { + .start = SSH2_MSG_KEXGSS_COMPLETE, + .n_callbacks = 1, + .callbacks = gss_kex_client_callbacks, + .user = NULL, +}; + +static SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey); + +static ssh_packet_callback gss_kex_client_callback_hostkey[] = { + ssh_packet_client_gss_kex_hostkey, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_kex_client_callback_hostkey = { + .start = SSH2_MSG_KEXGSS_HOSTKEY, + .n_callbacks = 1, + .callbacks = gss_kex_client_callback_hostkey, + .user = NULL, +}; + +static ssh_string dh_init(ssh_session session) +{ + int rc, keypair; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum const_pubkey; +#endif + bignum pubkey = NULL; + ssh_string pubkey_string = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + + if (session->server) { + keypair = DH_SERVER_KEYPAIR; + } else { + keypair = DH_CLIENT_KEYPAIR; + } + + rc = ssh_dh_init_common(crypto); + if (rc != SSH_OK) { + goto end; + } + + rc = ssh_dh_keypair_gen_keys(crypto->dh_ctx, keypair); + if (rc != SSH_OK) { + goto end; + } + +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &const_pubkey); + bignum_dup(const_pubkey, &pubkey); +#else + rc = ssh_dh_keypair_get_keys(crypto->dh_ctx, keypair, NULL, &pubkey); +#endif + if (rc != SSH_OK) { + goto end; + } + + pubkey_string = ssh_make_bignum_string(pubkey); + +end: + bignum_safe_free(pubkey); + return pubkey_string; +} + +static int dh_import_peer_key(ssh_session session, ssh_string peer_key) +{ + int rc, keypair; + bignum peer_key_bn; + struct ssh_crypto_struct *crypto = session->next_crypto; + + if (session->server) { + keypair = DH_CLIENT_KEYPAIR; + } else { + keypair = DH_SERVER_KEYPAIR; + } + + peer_key_bn = ssh_make_string_bn(peer_key); + rc = ssh_dh_keypair_set_keys(crypto->dh_ctx, keypair, NULL, peer_key_bn); + if (rc != SSH_OK) { + bignum_safe_free(peer_key_bn); + } + + return rc; +} + +/** @internal + * @brief Starts gssapi key exchange + */ +int ssh_client_gss_kex_init(ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + int rc, ret = SSH_ERROR; + /* oid selected for authentication */ + gss_OID_set selected = GSS_C_NO_OID_SET; + OM_uint32 maj_stat, min_stat; + const char *gss_host = session->opts.host; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + ssh_string pubkey = NULL; + + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + pubkey = dh_init(session); + if (pubkey == NULL) { + ssh_set_error(session, SSH_FATAL, "Failed to generate DH keypair"); + goto out; + } + break; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate ECDH keypair"); + goto out; + } + pubkey = ssh_string_copy(crypto->ecdh_client_pubkey); + break; + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate Curve25519 keypair"); + goto out; + } + pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (pubkey == NULL) { + ssh_set_error_oom(session); + goto out; + } + rc = ssh_string_fill(pubkey, + crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey"); + goto out; + } + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method"); + goto out; + } + + rc = ssh_gssapi_init(session); + if (rc != SSH_OK) { + goto out; + } + + if (session->opts.gss_server_identity != NULL) { + gss_host = session->opts.gss_server_identity; + } + + rc = ssh_gssapi_import_name(session->gssapi, gss_host); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_gssapi_client_identity(session, &selected); + if (rc != SSH_OK) { + goto out; + } + + session->gssapi->client.flags = GSS_C_MUTUAL_FLAG | GSS_C_INTEG_FLAG; + maj_stat = ssh_gssapi_init_ctx(session->gssapi, + &input_token, + &output_token, + &oflags); + gss_release_oid_set(&min_stat, &selected); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_WARN, + "Initializing gssapi context", + maj_stat, + min_stat); + goto out; + } + if (!(oflags & GSS_C_INTEG_FLAG) || !(oflags & GSS_C_MUTUAL_FLAG)) { + SSH_LOG(SSH_LOG_WARN, + "GSSAPI(init) integrity and mutual flags were not set"); + goto out; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdPS", + SSH2_MSG_KEXGSS_INIT, + output_token.length, + (size_t)output_token.length, + output_token.value, + pubkey); + if (rc != SSH_OK) { + goto out; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callbacks); + ssh_packet_set_callbacks(session, &ssh_gss_kex_client_callback_hostkey); + session->dh_handshake_state = DH_STATE_INIT_SENT; + + rc = ssh_packet_send(session); + if (rc != SSH_OK) { + goto out; + } + + ret = SSH_OK; + +out: + gss_release_buffer(&min_stat, &output_token); + ssh_string_free(pubkey); + return ret; +} + +void ssh_client_gss_kex_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callbacks); +} + +void ssh_client_gss_kex_remove_callback_hostkey(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_gss_kex_client_callback_hostkey); +} + +SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_reply) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string mic = NULL, otoken = NULL, server_pubkey = NULL; + uint8_t b; + int rc; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + OM_uint32 oflags; + OM_uint32 maj_stat; + + (void)type; + (void)user; + + ssh_client_gss_kex_remove_callbacks(session); + + rc = ssh_buffer_unpack(packet, "SSbS", &server_pubkey, &mic, &b, &otoken); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "No public key in server reply"); + goto error; + } + + SSH_STRING_FREE(session->gssapi_key_exchange_mic); + session->gssapi_key_exchange_mic = mic; + input_token.length = ssh_string_len(otoken); + input_token.value = ssh_string_data(otoken); + maj_stat = ssh_gssapi_init_ctx(session->gssapi, + &input_token, + &output_token, + &oflags); + if (maj_stat != GSS_S_COMPLETE) { + goto error; + } + SSH_STRING_FREE(otoken); + + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + rc = dh_import_peer_key(session, server_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not import server pubkey"); + goto error; + } + rc = ssh_dh_compute_shared_secret(crypto->dh_ctx, + DH_CLIENT_KEYPAIR, + DH_SERVER_KEYPAIR, + &crypto->shared_secret); + ssh_dh_debug_crypto(crypto); + break; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + crypto->ecdh_server_pubkey = ssh_string_copy(server_pubkey); + rc = ecdh_build_k(session); + break; + case SSH_GSS_KEX_CURVE25519_SHA256: + if (ssh_string_len(server_pubkey) != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect length of received server Curve25519 pubkey"); + goto error; + } + memcpy(crypto->curve25519_server_pubkey, + ssh_string_data(server_pubkey), + CURVE25519_PUBKEY_SIZE); + rc = ssh_curve25519_build_k(session); + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method"); + goto error; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not derive shared secret"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + ssh_string_free(server_pubkey); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + return SSH_PACKET_USED; + +error: + ssh_string_free(server_pubkey); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_client_gss_kex_hostkey) +{ + ssh_string pubkey_blob = NULL; + int rc; + + (void)type; + (void)user; + + ssh_client_gss_kex_remove_callback_hostkey(session); + + rc = ssh_buffer_unpack(packet, "S", &pubkey_blob); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Invalid SSH2_MSG_KEXGSS_HOSTKEY packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + goto error; + } + + return SSH_PACKET_USED; +error: + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init); + +static ssh_packet_callback gss_kex_server_callbacks[] = { + ssh_packet_server_gss_kex_init, +}; + +static struct ssh_packet_callbacks_struct ssh_gss_kex_server_callbacks = { + .start = SSH2_MSG_KEXGSS_INIT, + .n_callbacks = 1, + .callbacks = gss_kex_server_callbacks, + .user = NULL, +}; + +/** @internal + * @brief sets up the gssapi kex callbacks + */ +void ssh_server_gss_kex_init(ssh_session session) +{ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_gss_kex_server_callbacks); +} + +/** @internal + * @brief processes a SSH_MSG_KEXGSS_INIT and sends + * the appropriate SSH_MSG_KEXGSS_COMPLETE + */ +int ssh_server_gss_kex_process_init(ssh_session session, ssh_buffer packet) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string client_pubkey = NULL; + ssh_string server_pubkey = NULL; + int rc; + gss_buffer_desc input_token = GSS_C_EMPTY_BUFFER; + gss_buffer_desc output_token = GSS_C_EMPTY_BUFFER; + ssh_string otoken = NULL; + ssh_string server_pubkey_blob = NULL; + OM_uint32 maj_stat, min_stat; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags = 0; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER; + char *hostname = NULL; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + rc = ssh_buffer_unpack(packet, "S", &otoken); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No token in client request"); + goto error; + } + input_token.length = ssh_string_len(otoken); + input_token.value = ssh_string_data(otoken); + + rc = ssh_buffer_unpack(packet, "S", &client_pubkey); + if (rc == SSH_ERROR) { + ssh_set_error(session, SSH_FATAL, "No public key in client request"); + goto error; + } + + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + server_pubkey = dh_init(session); + if (server_pubkey == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not generate a DH keypair"); + goto error; + } + rc = dh_import_peer_key(session, client_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not import client pubkey"); + goto error; + } + rc = ssh_dh_compute_shared_secret(crypto->dh_ctx, + DH_SERVER_KEYPAIR, + DH_CLIENT_KEYPAIR, + &crypto->shared_secret); + ssh_dh_debug_crypto(crypto); + break; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + rc = ssh_ecdh_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not generate an ECDH keypair"); + goto error; + } + crypto->ecdh_client_pubkey = ssh_string_copy(client_pubkey); + server_pubkey = ssh_string_copy(crypto->ecdh_server_pubkey); + rc = ecdh_build_k(session); + break; + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not generate a Curve25519 keypair"); + goto error; + } + server_pubkey = ssh_string_new(CURVE25519_PUBKEY_SIZE); + if (server_pubkey == NULL) { + ssh_set_error_oom(session); + goto error; + } + rc = ssh_string_fill(server_pubkey, + crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to copy Curve25519 pubkey"); + goto error; + } + if (ssh_string_len(client_pubkey) != CURVE25519_PUBKEY_SIZE) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect length of received client Curve25519 pubkey"); + goto error; + } + memcpy(crypto->curve25519_client_pubkey, + ssh_string_data(client_pubkey), + CURVE25519_PUBKEY_SIZE); + rc = ssh_curve25519_build_k(session); + break; + default: + ssh_set_error(session, SSH_FATAL, "Unsupported GSSAPI KEX method"); + goto error; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not derive shared secret"); + goto error; + } + + /* Also imports next_crypto->server_pubkey + * Can give error when using null hostkey */ + ssh_get_key_params(session, &privkey, &digest); + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + if (strcmp(crypto->kex_methods[SSH_HOSTKEYS], "null") != 0) { + rc = + ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(session->out_buffer, + "bS", + SSH2_MSG_KEXGSS_HOSTKEY, + server_pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Sent SSH2_MSG_KEXGSS_HOSTKEY"); + SSH_STRING_FREE(server_pubkey_blob); + } + + rc = ssh_gssapi_init(session); + if (rc == SSH_ERROR) { + goto error; + } + + hostname = ssh_get_local_hostname(); + if (hostname == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error getting hostname: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + goto error; + } + + rc = ssh_gssapi_import_name(session->gssapi, hostname); + SAFE_FREE(hostname); + if (rc != SSH_OK) { + goto error; + } + + maj_stat = gss_acquire_cred(&min_stat, + session->gssapi->client.server_name, + 0, + GSS_C_NO_OID_SET, + GSS_C_ACCEPT, + &session->gssapi->server_creds, + NULL, + NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_gssapi_log_error(SSH_LOG_TRACE, + "acquiring credentials", + maj_stat, + min_stat); + goto error; + } + + maj_stat = gss_accept_sec_context(&min_stat, + &session->gssapi->ctx, + session->gssapi->server_creds, + &input_token, + GSS_C_NO_CHANNEL_BINDINGS, + &client_name, + NULL /*mech_oid*/, + &output_token, + &ret_flags, + NULL /*time*/, + &session->gssapi->client_creds); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "accepting token failed", + maj_stat, + min_stat); + goto error; + } + SSH_STRING_FREE(otoken); + if (client_name != GSS_C_NO_NAME) { + session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); + } + gss_release_name(&min_stat, &client_name); + if (!(ret_flags & GSS_C_INTEG_FLAG) || !(ret_flags & GSS_C_MUTUAL_FLAG)) { + SSH_LOG(SSH_LOG_WARN, + "GSSAPI(accept) integrity and mutual flags were not set"); + gss_release_buffer(&min_stat, &output_token); + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "token accepted"); + + msg.length = session->next_crypto->digest_len; + msg.value = session->next_crypto->secret_hash; + maj_stat = gss_get_mic(&min_stat, + session->gssapi->ctx, + GSS_C_QOP_DEFAULT, + &msg, + &mic); + if (GSS_ERROR(maj_stat)) { + ssh_gssapi_log_error(SSH_LOG_DEBUG, + "creating mic failed", + maj_stat, + min_stat); + gss_release_buffer(&min_stat, &output_token); + goto error; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bSdPbdP", + SSH2_MSG_KEXGSS_COMPLETE, + server_pubkey, + mic.length, + (size_t)mic.length, + mic.value, + 1, + output_token.length, + (size_t)output_token.length, + output_token.value); + gss_release_buffer(&min_stat, &output_token); + gss_release_buffer(&min_stat, &mic); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + ssh_buffer_reinit(session->out_buffer); + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Sent SSH2_MSG_KEXGSS_COMPLETE"); + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + /* Send the MSG_NEWKEYS */ + rc = ssh_packet_send_newkeys(session); + if (rc == SSH_ERROR) { + goto error; + } + + ssh_string_free(server_pubkey); + ssh_string_free(client_pubkey); + return SSH_OK; +error: + SSH_STRING_FREE(server_pubkey_blob); + ssh_string_free(server_pubkey); + ssh_string_free(client_pubkey); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_ERROR; +} + +/** @internal + * @brief parse an incoming SSH_MSG_KEXGSS_INIT packet and complete + * Diffie-Hellman key exchange + **/ +static SSH_PACKET_CALLBACK(ssh_packet_server_gss_kex_init) +{ + (void)type; + (void)user; + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_KEXGSS_INIT"); + ssh_packet_remove_callbacks(session, &ssh_gss_kex_server_callbacks); + ssh_server_gss_kex_process_init(session, packet); + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/src/kex.c b/src/libs/libssh-0.12.2/src/kex.c new file mode 100644 index 000000000000..4b0c8065ca43 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/kex.c @@ -0,0 +1,2073 @@ +/* + * kex.c - key exchange + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/dh.h" +#ifdef WITH_GEX +#include "libssh/dh-gex.h" +#endif /* WITH_GEX */ +#include "libssh/kex.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" +#include "libssh/string.h" +#include "libssh/curve25519.h" +#include "libssh/sntrup761.h" +#include "libssh/hybrid_mlkem.h" +#include "libssh/kex-gss.h" +#include "libssh/knownhosts.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/bignum.h" +#include "libssh/token.h" +#include "libssh/gssapi.h" + +#ifdef HAVE_BLOWFISH +# define BLOWFISH ",blowfish-cbc" +#else +# define BLOWFISH "" +#endif + +#ifdef HAVE_LIBGCRYPT +# define AES "aes256-gcm@openssh.com,aes128-gcm@openssh.com," \ + "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" + +#elif defined(HAVE_LIBMBEDCRYPTO) +# ifdef MBEDTLS_GCM_C +# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," +# else +# define GCM "" +# endif /* MBEDTLS_GCM_C */ +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# define DES_SUPPORTED ",3des-cbc" + +#elif defined(HAVE_LIBCRYPTO) +# ifdef HAVE_OPENSSL_AES_H +# define GCM "aes256-gcm@openssh.com,aes128-gcm@openssh.com," +# define AES GCM "aes256-ctr,aes192-ctr,aes128-ctr" +# define AES_CBC ",aes256-cbc,aes192-cbc,aes128-cbc" +# else /* HAVE_OPENSSL_AES_H */ +# define AES "" +# define AES_CBC "" +# endif /* HAVE_OPENSSL_AES_H */ + +# define DES_SUPPORTED ",3des-cbc" +#endif /* HAVE_LIBCRYPTO */ + +#ifdef WITH_ZLIB +#define ZLIB "none,zlib@openssh.com,zlib" +#define ZLIB_DEFAULT "none,zlib@openssh.com" +#else +#define ZLIB "none" +#define ZLIB_DEFAULT "none" +#endif /* WITH_ZLIB */ + +#ifdef HAVE_CURVE25519 +#define CURVE25519 "curve25519-sha256,curve25519-sha256@libssh.org," +#else +#define CURVE25519 "" +#endif /* HAVE_CURVE25519 */ + +#ifdef HAVE_SNTRUP761 +#define SNTRUP761X25519 "sntrup761x25519-sha512,sntrup761x25519-sha512@openssh.com," +#else +#define SNTRUP761X25519 "" +#endif /* HAVE_SNTRUP761 */ + +#ifdef HAVE_MLKEM1024 +#define HYBRID_MLKEM "mlkem768x25519-sha256," \ + "mlkem768nistp256-sha256," \ + "mlkem1024nistp384-sha384," +#else +#define HYBRID_MLKEM "mlkem768x25519-sha256," \ + "mlkem768nistp256-sha256," +#endif /* HAVE_MLKEM1024 */ + +#ifdef HAVE_ECC +#define ECDH "ecdh-sha2-nistp256,ecdh-sha2-nistp384,ecdh-sha2-nistp521," +#define EC_HOSTKEYS "ecdsa-sha2-nistp521," \ + "ecdsa-sha2-nistp384," \ + "ecdsa-sha2-nistp256," +#define EC_SK_HOSTKEYS "sk-ecdsa-sha2-nistp256@openssh.com," +#define EC_FIPS_PUBLIC_KEY_ALGOS "ecdsa-sha2-nistp521-cert-v01@openssh.com," \ + "ecdsa-sha2-nistp384-cert-v01@openssh.com," \ + "ecdsa-sha2-nistp256-cert-v01@openssh.com," +#define EC_PUBLIC_KEY_ALGORITHMS EC_FIPS_PUBLIC_KEY_ALGOS \ + "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com," +#else +#define ECDH "" +#define EC_HOSTKEYS "" +#define EC_SK_HOSTKEYS "" +#define EC_FIPS_PUBLIC_KEY_ALGOS "" +#define EC_PUBLIC_KEY_ALGORITHMS "" +#endif /* HAVE_ECC */ + +#ifdef WITH_INSECURE_NONE +#define NONE ",none" +#else +#define NONE +#endif /* WITH_INSECURE_NONE */ + +#define HOSTKEYS "ssh-ed25519," \ + EC_HOSTKEYS \ + "sk-ssh-ed25519@openssh.com," \ + EC_SK_HOSTKEYS \ + "rsa-sha2-512," \ + "rsa-sha2-256," \ + "ssh-rsa" +#define DEFAULT_HOSTKEYS "ssh-ed25519," \ + EC_HOSTKEYS \ + "sk-ssh-ed25519@openssh.com," \ + EC_SK_HOSTKEYS \ + "rsa-sha2-512," \ + "rsa-sha2-256" + +#define PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ + "sk-ssh-ed25519-cert-v01@openssh.com," \ + EC_PUBLIC_KEY_ALGORITHMS \ + "rsa-sha2-512-cert-v01@openssh.com," \ + "rsa-sha2-256-cert-v01@openssh.com," \ + "ssh-rsa-cert-v01@openssh.com," \ + HOSTKEYS +#define DEFAULT_PUBLIC_KEY_ALGORITHMS "ssh-ed25519-cert-v01@openssh.com," \ + EC_PUBLIC_KEY_ALGORITHMS \ + "rsa-sha2-512-cert-v01@openssh.com," \ + "rsa-sha2-256-cert-v01@openssh.com," \ + DEFAULT_HOSTKEYS + +#ifdef WITH_GEX +#define GEX_SHA256 "diffie-hellman-group-exchange-sha256," +#define GEX_SHA1 "diffie-hellman-group-exchange-sha1," +#else +#define GEX_SHA256 +#define GEX_SHA1 +#endif /* WITH_GEX */ + +#define CHACHA20 "chacha20-poly1305@openssh.com," + +#define DEFAULT_KEY_EXCHANGE \ + HYBRID_MLKEM \ + SNTRUP761X25519 \ + CURVE25519 \ + ECDH \ + "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512," \ + GEX_SHA256 \ + "diffie-hellman-group14-sha256" \ + +#define KEY_EXCHANGE_SUPPORTED \ + GEX_SHA1 \ + DEFAULT_KEY_EXCHANGE \ + ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + +/* RFC 8308 */ +#define KEX_EXTENSION_CLIENT "ext-info-c" +/* Strict kex mitigation against CVE-2023-48795 */ +#define KEX_STRICT_CLIENT "kex-strict-c-v00@openssh.com" +#define KEX_STRICT_SERVER "kex-strict-s-v00@openssh.com" + +/* Allowed algorithms in FIPS mode */ +#define FIPS_ALLOWED_CIPHERS "aes256-gcm@openssh.com,"\ + "aes256-ctr,"\ + "aes256-cbc,"\ + "aes128-gcm@openssh.com,"\ + "aes128-ctr,"\ + "aes128-cbc" + +#define FIPS_ALLOWED_HOSTKEYS EC_HOSTKEYS \ + "rsa-sha2-512," \ + "rsa-sha2-256" + +#define FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS EC_FIPS_PUBLIC_KEY_ALGOS \ + "rsa-sha2-512-cert-v01@openssh.com," \ + "rsa-sha2-256-cert-v01@openssh.com," \ + FIPS_ALLOWED_HOSTKEYS + +#ifdef HAVE_MLKEM1024 +#define FIPS_MLKEM_KEX "mlkem768nistp256-sha256," \ + "mlkem1024nistp384-sha384," +#else +#define FIPS_MLKEM_KEX "mlkem768nistp256-sha256," +#endif + +#define FIPS_ALLOWED_KEX FIPS_MLKEM_KEX \ + "ecdh-sha2-nistp256,"\ + "ecdh-sha2-nistp384,"\ + "ecdh-sha2-nistp521,"\ + "diffie-hellman-group-exchange-sha256,"\ + "diffie-hellman-group14-sha256,"\ + "diffie-hellman-group16-sha512,"\ + "diffie-hellman-group18-sha512" + +#define FIPS_ALLOWED_MACS "hmac-sha2-256-etm@openssh.com,"\ + "hmac-sha1-etm@openssh.com,"\ + "hmac-sha2-512-etm@openssh.com,"\ + "hmac-sha2-256,"\ + "hmac-sha1,"\ + "hmac-sha2-512" + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *fips_methods[] = { + FIPS_ALLOWED_KEX, + FIPS_ALLOWED_PUBLIC_KEY_ALGORITHMS, + FIPS_ALLOWED_CIPHERS, + FIPS_ALLOWED_CIPHERS, + FIPS_ALLOWED_MACS, + FIPS_ALLOWED_MACS, + ZLIB_DEFAULT, + ZLIB_DEFAULT, + "", + "", + NULL +}; + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *default_methods[] = { + DEFAULT_KEY_EXCHANGE, + DEFAULT_PUBLIC_KEY_ALGORITHMS, + CHACHA20 AES, + CHACHA20 AES, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha2-256,hmac-sha2-512", + ZLIB_DEFAULT, + ZLIB_DEFAULT, + "", + "", + NULL +}; + +/* NOTE: This is a fixed API and the index is defined by ssh_kex_types_e */ +static const char *supported_methods[] = { + KEY_EXCHANGE_SUPPORTED, + PUBLIC_KEY_ALGORITHMS, + CHACHA20 AES AES_CBC BLOWFISH DES_SUPPORTED NONE, + CHACHA20 AES AES_CBC BLOWFISH DES_SUPPORTED NONE, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1" NONE, + "hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com,hmac-sha1-etm@openssh.com,hmac-sha2-256,hmac-sha2-512,hmac-sha1" NONE, + ZLIB, + ZLIB, + "", + "", + NULL +}; + +/* descriptions of the key exchange packet */ +static const char *ssh_kex_descriptions[] = { + "kex algos", + "server host key algo", + "encryption client->server", + "encryption server->client", + "mac algo client->server", + "mac algo server->client", + "compression algo client->server", + "compression algo server->client", + "languages client->server", + "languages server->client", + NULL +}; + +const char *ssh_kex_get_default_methods(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } + + return default_methods[type]; +} +const char *ssh_kex_get_supported_method(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } + + return supported_methods[type]; +} + +const char *ssh_kex_get_description(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } + + return ssh_kex_descriptions[type]; +} + +const char *ssh_kex_get_fips_methods(enum ssh_kex_types_e type) +{ + if (type >= SSH_KEX_METHODS) { + return NULL; + } + + return fips_methods[type]; +} + +/** + * @brief Get a list of supported algorithms of a given type. This respects the + * FIPS mode status. + * + * @param[in] type The type of the algorithm to query (SSH_KEX, SSH_MAC_C_S, + * ...). + * + * @return The list of supported methods as comma-separated string, or NULL for + * unknown type. + */ +const char *ssh_get_supported_methods(enum ssh_kex_types_e type) +{ + if (ssh_fips_mode()) { + return ssh_kex_get_fips_methods(type); + } else { + return ssh_kex_get_supported_method(type); + } +} + +/** + * @internal + * @brief returns whether the first client key exchange algorithm or + * hostkey type matches its server counterpart + * @returns whether the first client key exchange algorithm or hostkey type + * matches its server counterpart + */ +static int cmp_first_kex_algo(const char *client_str, + const char *server_str) { + size_t client_kex_len; + size_t server_kex_len; + + const char *colon = NULL; + + int is_wrong = 1; + + if (client_str == NULL || server_str == NULL) { + return is_wrong; + } + + colon = strchr(client_str, ','); + if (colon == NULL) { + client_kex_len = strlen(client_str); + } else { + client_kex_len = colon - client_str; + } + + colon = strchr(server_str, ','); + if (colon == NULL) { + server_kex_len = strlen(server_str); + } else { + server_kex_len = colon - server_str; + } + + if (client_kex_len != server_kex_len) { + return is_wrong; + } + + is_wrong = (strncmp(client_str, server_str, client_kex_len) != 0); + + return is_wrong; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexinit) +{ + int i, ok; + struct ssh_crypto_struct *crypto = session->next_crypto; + int server_kex = session->server; + ssh_string str = NULL; + char *strings[SSH_KEX_METHODS] = {0}; + int rc = SSH_ERROR; + size_t len; + + uint8_t first_kex_packet_follows = 0; + uint32_t kexinit_reserved = 0; + + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_TRACE, "KEXINIT received"); + + if (session->session_state == SSH_SESSION_STATE_AUTHENTICATED) { + if (session->dh_handshake_state == DH_STATE_FINISHED) { + SSH_LOG(SSH_LOG_DEBUG, "Peer initiated key re-exchange"); + /* Reset the sent flag if the re-kex was initiated by the peer */ + session->flags &= ~SSH_SESSION_FLAG_KEXINIT_SENT; + } else if (session->flags & SSH_SESSION_FLAG_KEXINIT_SENT && + session->dh_handshake_state == DH_STATE_INIT_SENT) { + /* This happens only when we are sending our-guessed first kex + * packet right after our KEXINIT packet. */ + SSH_LOG(SSH_LOG_DEBUG, "Received peer kexinit answer."); + } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { + ssh_set_error(session, SSH_FATAL, + "SSH_KEXINIT received in wrong state"); + goto error; + } + } else if (session->session_state != SSH_SESSION_STATE_INITIAL_KEX) { + ssh_set_error(session, SSH_FATAL, + "SSH_KEXINIT received in wrong state"); + goto error; + } + + if (server_kex) { +#ifdef WITH_SERVER + len = ssh_buffer_get_data(packet, crypto->client_kex.cookie, 16); + if (len != 16) { + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + + ok = ssh_hashbufin_add_cookie(session, crypto->client_kex.cookie); + if (ok < 0) { + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + + ok = server_set_kex(session); + if (ok == SSH_ERROR) { + goto error; + } +#endif /* WITH_SERVER */ + } else { + len = ssh_buffer_get_data(packet, crypto->server_kex.cookie, 16); + if (len != 16) { + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: no cookie in packet"); + goto error; + } + + ok = ssh_hashbufin_add_cookie(session, crypto->server_kex.cookie); + if (ok < 0) { + ssh_set_error(session, SSH_FATAL, + "ssh_packet_kexinit: adding cookie failed"); + goto error; + } + + ok = ssh_set_client_kex(session); + if (ok == SSH_ERROR) { + goto error; + } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + str = ssh_buffer_get_ssh_string(packet); + if (str == NULL) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->in_hashbuf, str); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Error adding string in hash buffer"); + goto error; + } + + strings[i] = ssh_string_to_char(str); + if (strings[i] == NULL) { + ssh_set_error_oom(session); + goto error; + } + SSH_STRING_FREE(str); + str = NULL; + } + + /* copy the peer kex info into an array of strings */ + if (server_kex) { +#ifdef WITH_SERVER + for (i = 0; i < SSH_KEX_METHODS; i++) { + crypto->client_kex.methods[i] = strings[i]; + } +#endif /* WITH_SERVER */ + } else { /* client */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + crypto->server_kex.methods[i] = strings[i]; + } + } + + /* + * Handle the two final fields for the KEXINIT message (RFC 4253 7.1): + * + * boolean first_kex_packet_follows + * uint32 0 (reserved for future extension) + * + * Notably if clients set 'first_kex_packet_follows', it is expected + * that its value is included when computing the session ID (see + * 'make_sessionid'). + */ + + rc = ssh_buffer_get_u8(packet, &first_kex_packet_follows); + if (rc != 1) { + goto error; + } + + rc = ssh_buffer_add_u8(session->in_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_u32(session->in_hashbuf, kexinit_reserved); + if (rc < 0) { + goto error; + } + + /* + * Remember whether 'first_kex_packet_follows' was set and the client + * guess was wrong: in this case the next SSH_MSG_KEXDH_INIT message + * must be ignored on the server side. + * Client needs to start the Key exchange over with the correct method + */ + if (first_kex_packet_follows || session->send_first_kex_follows) { + char **client_methods = crypto->client_kex.methods; + char **server_methods = crypto->server_kex.methods; + session->first_kex_follows_guess_wrong = + cmp_first_kex_algo(client_methods[SSH_KEX], + server_methods[SSH_KEX]) || + cmp_first_kex_algo(client_methods[SSH_HOSTKEYS], + server_methods[SSH_HOSTKEYS]); + SSH_LOG(SSH_LOG_DEBUG, "The initial guess was %s.", + session->first_kex_follows_guess_wrong ? "wrong" : "right"); + } + + /* + * handle the "strict KEX" feature. If supported by peer, then set up the + * flag and verify packet sequence numbers. + */ + if (server_kex) { + ok = match_group(crypto->client_kex.methods[SSH_KEX], + KEX_STRICT_CLIENT); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Client supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } else { + /* client kex */ + ok = match_group(crypto->server_kex.methods[SSH_KEX], + KEX_STRICT_SERVER); + if (ok) { + SSH_LOG(SSH_LOG_DEBUG, "Server supports strict kex, enabling."); + session->flags |= SSH_SESSION_FLAG_KEX_STRICT; + } + } +#ifdef WITH_SERVER + if (server_kex) { + /* + * If client sent a ext-info-c message in the kex list, it supports + * RFC 8308 extension negotiation. + */ + ok = match_group(crypto->client_kex.methods[SSH_KEX], + KEX_EXTENSION_CLIENT); + if (ok) { + const char *hostkeys = NULL, *wanted_hostkeys = NULL; + + /* The client supports extension negotiation */ + session->extensions |= SSH_EXT_NEGOTIATION; + /* + * RFC 8332 Section 3.1: Use for Server Authentication + * Check what algorithms were provided in the SSH_HOSTKEYS list + * by the client and enable the respective extensions to provide + * correct signature in the next packet if RSA is negotiated + */ + hostkeys = crypto->client_kex.methods[SSH_HOSTKEYS]; + wanted_hostkeys = session->opts.wanted_methods[SSH_HOSTKEYS]; + ok = match_group(hostkeys, "rsa-sha2-512"); + if (ok) { + /* Check if rsa-sha2-512 is allowed by config */ + if (wanted_hostkeys != NULL) { + char *is_allowed = ssh_find_matching(wanted_hostkeys, + "rsa-sha2-512"); + if (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } + SAFE_FREE(is_allowed); + } + } + ok = match_group(hostkeys, "rsa-sha2-256"); + if (ok) { + /* Check if rsa-sha2-256 is allowed by config */ + if (wanted_hostkeys != NULL) { + char *is_allowed = ssh_find_matching(wanted_hostkeys, + "rsa-sha2-256"); + if (is_allowed != NULL) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } + SAFE_FREE(is_allowed); + } + } + + /* + * Ensure that the client preference is honored for the case + * both signature types are enabled. + */ + if ((session->extensions & SSH_EXT_SIG_RSA_SHA256) && + (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { + char *rsa_sig_ext = NULL; + session->extensions &= ~(SSH_EXT_SIG_RSA_SHA256 | SSH_EXT_SIG_RSA_SHA512); + rsa_sig_ext = ssh_find_matching("rsa-sha2-512,rsa-sha2-256", + hostkeys); + if (rsa_sig_ext == NULL) { + goto error; /* should never happen */ + } else if (strcmp(rsa_sig_ext, "rsa-sha2-512") == 0) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } else if (strcmp(rsa_sig_ext, "rsa-sha2-256") == 0) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } else { + SAFE_FREE(rsa_sig_ext); + goto error; /* should never happen */ + } + SAFE_FREE(rsa_sig_ext); + } + + SSH_LOG(SSH_LOG_DEBUG, "The client supports extension " + "negotiation. Enabled signature algorithms: %s%s", + session->extensions & SSH_EXT_SIG_RSA_SHA256 ? "SHA256" : "", + session->extensions & SSH_EXT_SIG_RSA_SHA512 ? " SHA512" : ""); + } + } +#endif /* WITH_SERVER */ + + /* Note, that his overwrites authenticated state in case of rekeying */ + session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED; + /* if we already sent our initial key exchange packet, do not reset the + * DH state. We will know if we were right with our guess only in + * dh_handshake_state() */ + if (session->send_first_kex_follows == false) { + session->dh_handshake_state = DH_STATE_INIT; + } + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + +error: + SSH_STRING_FREE(str); + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (server_kex) { +#ifdef WITH_SERVER + session->next_crypto->client_kex.methods[i] = NULL; +#endif /* WITH_SERVER */ + } else { /* client */ + session->next_crypto->server_kex.methods[i] = NULL; + } + SAFE_FREE(strings[i]); + } + + session->session_state = SSH_SESSION_STATE_ERROR; + + return SSH_PACKET_USED; +} + +void ssh_list_kex(struct ssh_kex_struct *kex) { + int i = 0; + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("session cookie", kex->cookie, 16); +#endif + + for(i = 0; i < SSH_KEX_METHODS; i++) { + if (kex->methods[i] == NULL) { + continue; + } + SSH_LOG(SSH_LOG_FUNCTIONS, "%s: %s", + ssh_kex_descriptions[i], kex->methods[i]); + } +} + +/** + * @internal + * + * @brief selects the hostkey mechanisms to be chosen for the key exchange, + * as some hostkey mechanisms may be present in known_hosts files. + * + * @returns a cstring containing a comma-separated list of hostkey methods. + * NULL if no method matches + */ +char *ssh_client_select_hostkeys(ssh_session session) +{ + const char *wanted = NULL; + char *wanted_without_certs = NULL; + char *known_hosts_algorithms = NULL; + char *known_hosts_ordered = NULL; + char *new_hostkeys = NULL; + char *fips_hostkeys = NULL; + + wanted = session->opts.wanted_methods[SSH_HOSTKEYS]; + if (wanted == NULL) { + if (ssh_fips_mode()) { + wanted = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + wanted = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + + /* This removes the certificate types, unsupported for now */ + wanted_without_certs = ssh_find_all_matching(HOSTKEYS, wanted); + if (wanted_without_certs == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "List of allowed host key algorithms is empty or contains only " + "unsupported algorithms"); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Order of wanted host keys: \"%s\"", + wanted_without_certs); + + known_hosts_algorithms = ssh_known_hosts_get_algorithms_names(session); + if (known_hosts_algorithms == NULL) { + SSH_LOG(SSH_LOG_DEBUG, + "No key found in known_hosts; " + "changing host key method to \"%s\"", + wanted_without_certs); + + return wanted_without_certs; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Algorithms found in known_hosts files: \"%s\"", + known_hosts_algorithms); + + /* Filter and order the keys from known_hosts according to wanted list */ + known_hosts_ordered = ssh_find_all_matching(known_hosts_algorithms, + wanted_without_certs); + SAFE_FREE(known_hosts_algorithms); + if (known_hosts_ordered == NULL) { + SSH_LOG(SSH_LOG_DEBUG, + "No key found in known_hosts is allowed; " + "changing host key method to \"%s\"", + wanted_without_certs); + + return wanted_without_certs; + } + + /* Append the other supported keys after the preferred ones + * This function tolerates NULL pointers in parameters */ + new_hostkeys = ssh_append_without_duplicates(known_hosts_ordered, + wanted_without_certs); + SAFE_FREE(known_hosts_ordered); + SAFE_FREE(wanted_without_certs); + if (new_hostkeys == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (ssh_fips_mode()) { + /* Filter out algorithms not allowed in FIPS mode */ + fips_hostkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, new_hostkeys); + SAFE_FREE(new_hostkeys); + if (fips_hostkeys == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "None of the wanted host keys or keys in known_hosts files " + "is allowed in FIPS mode."); + return NULL; + } + new_hostkeys = fips_hostkeys; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Changing host key method to \"%s\"", + new_hostkeys); + + return new_hostkeys; +} + +/** + * @brief sets the key exchange parameters to be sent to the server, + * in function of the options and available methods. + */ +int ssh_set_client_kex(ssh_session session) +{ + struct ssh_kex_struct *client = &session->next_crypto->client_kex; + const char *wanted = NULL; + int ok; + int i; + bool gssapi_null_alg = false; + char *hostkeys = NULL; + + /* Skip if already set, for example for the rekey or when we do the guessing + * it could have been already used to make some protocol decisions. */ + if (client->methods[0] != NULL) { + return SSH_OK; + } + + ok = ssh_get_random(client->cookie, 16, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return SSH_ERROR; + } +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + char *gssapi_algs = NULL; + + ok = ssh_gssapi_init(session); + if (ok != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + ok = ssh_gssapi_import_name(session->gssapi, session->opts.host); + if (ok != SSH_OK) { + return SSH_ERROR; + } + + gssapi_algs = ssh_gssapi_kex_mechs(session); + if (gssapi_algs == NULL) { + return SSH_ERROR; + } + + /* Prefix the default algorithms with gsskex algs */ + if (ssh_fips_mode()) { + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(fips_methods[SSH_KEX], + gssapi_algs); + } else { + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(default_methods[SSH_KEX], + gssapi_algs); + } + + gssapi_null_alg = true; + + SAFE_FREE(gssapi_algs); + } +#endif + + /* Set the list of allowed algorithms in order of preference, if it hadn't + * been set yet. */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (i == SSH_HOSTKEYS) { + /* Set the hostkeys in the following order: + * - First: keys present in known_hosts files ordered by preference + * - Next: other wanted algorithms ordered by preference */ + client->methods[i] = ssh_client_select_hostkeys(session); + if (client->methods[i] == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + if (gssapi_null_alg) { + hostkeys = + ssh_append_without_duplicates(client->methods[i], "null"); + if (hostkeys == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + SAFE_FREE(client->methods[i]); + client->methods[i] = hostkeys; + } + continue; + } + + wanted = session->opts.wanted_methods[i]; + if (wanted == NULL) { + if (ssh_fips_mode()) { + wanted = fips_methods[i]; + } else { + wanted = default_methods[i]; + } + } + client->methods[i] = strdup(wanted); + if (client->methods[i] == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + + /* For rekeying, skip the extension negotiation */ + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + return SSH_OK; + } + + ok = ssh_kex_append_extensions(session, client); + if (ok != SSH_OK){ + return ok; + } + + return SSH_OK; +} + +int ssh_kex_append_extensions(ssh_session session, struct ssh_kex_struct *pkex) +{ + char *kex = NULL; + char *kex_tmp = NULL; + size_t kex_len, len; + + /* Here we append ext-info-c and kex-strict-c-v00@openssh.com for client + * and kex-strict-s-v00@openssh.com for server to the list of kex algorithms + */ + kex = pkex->methods[SSH_KEX]; + len = strlen(kex); + if (session->server) { + /* Comma, nul byte */ + kex_len = len + 1 + strlen(KEX_STRICT_SERVER) + 1; + } else { + /* Comma, comma, nul byte */ + kex_len = len + 1 + strlen(KEX_EXTENSION_CLIENT) + 1 + + strlen(KEX_STRICT_CLIENT) + 1; + } + if (kex_len >= MAX_PACKET_LEN) { + /* Overflow */ + return SSH_ERROR; + } + kex_tmp = realloc(kex, kex_len); + if (kex_tmp == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + if (session->server){ + snprintf(kex_tmp + len, kex_len - len, ",%s", KEX_STRICT_SERVER); + } else { + snprintf(kex_tmp + len, + kex_len - len, + ",%s,%s", + KEX_EXTENSION_CLIENT, + KEX_STRICT_CLIENT); + } + pkex->methods[SSH_KEX] = kex_tmp; + return SSH_OK; +} + +static const char *ssh_find_aead_hmac(const char *cipher) +{ + if (cipher == NULL) { + return NULL; + } else if (strcmp(cipher, "chacha20-poly1305@openssh.com") == 0) { + return "aead-poly1305"; + } else if (strcmp(cipher, "aes256-gcm@openssh.com") == 0) { + return "aead-gcm"; + } else if (strcmp(cipher, "aes128-gcm@openssh.com") == 0) { + return "aead-gcm"; + } + return NULL; +} + +static enum ssh_key_exchange_e +kex_select_kex_type(const char *kex) +{ + if (strcmp(kex, "diffie-hellman-group1-sha1") == 0) { + return SSH_KEX_DH_GROUP1_SHA1; + } else if (strncmp(kex, "gss-group14-sha256-", 19) == 0) { + return SSH_GSS_KEX_DH_GROUP14_SHA256; + } else if (strncmp(kex, "gss-group16-sha512-", 19) == 0) { + return SSH_GSS_KEX_DH_GROUP16_SHA512; + } else if (strncmp(kex, "gss-nistp256-sha256-", 20) == 0) { + return SSH_GSS_KEX_ECDH_NISTP256_SHA256; + } else if (strncmp(kex, "gss-curve25519-sha256-", 22) == 0) { + return SSH_GSS_KEX_CURVE25519_SHA256; + } else if (strcmp(kex, "diffie-hellman-group14-sha1") == 0) { + return SSH_KEX_DH_GROUP14_SHA1; + } else if (strcmp(kex, "diffie-hellman-group14-sha256") == 0) { + return SSH_KEX_DH_GROUP14_SHA256; + } else if (strcmp(kex, "diffie-hellman-group16-sha512") == 0) { + return SSH_KEX_DH_GROUP16_SHA512; + } else if (strcmp(kex, "diffie-hellman-group18-sha512") == 0) { + return SSH_KEX_DH_GROUP18_SHA512; +#ifdef WITH_GEX + } else if (strcmp(kex, "diffie-hellman-group-exchange-sha1") == 0) { + return SSH_KEX_DH_GEX_SHA1; + } else if (strcmp(kex, "diffie-hellman-group-exchange-sha256") == 0) { + return SSH_KEX_DH_GEX_SHA256; +#endif /* WITH_GEX */ + } else if (strcmp(kex, "ecdh-sha2-nistp256") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP256; + } else if (strcmp(kex, "ecdh-sha2-nistp384") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP384; + } else if (strcmp(kex, "ecdh-sha2-nistp521") == 0) { + return SSH_KEX_ECDH_SHA2_NISTP521; + } else if (strcmp(kex, "curve25519-sha256@libssh.org") == 0) { + return SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG; + } else if (strcmp(kex, "curve25519-sha256") == 0) { + return SSH_KEX_CURVE25519_SHA256; + } else if (strcmp(kex, "sntrup761x25519-sha512@openssh.com") == 0) { + return SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM; + } else if (strcmp(kex, "sntrup761x25519-sha512") == 0) { + return SSH_KEX_SNTRUP761X25519_SHA512; + } else if (strcmp(kex, "mlkem768x25519-sha256") == 0) { + return SSH_KEX_MLKEM768X25519_SHA256; + } else if (strcmp(kex, "mlkem768nistp256-sha256") == 0) { + return SSH_KEX_MLKEM768NISTP256_SHA256; +#ifdef HAVE_MLKEM1024 + } else if (strcmp(kex, "mlkem1024nistp384-sha384") == 0) { + return SSH_KEX_MLKEM1024NISTP384_SHA384; +#endif + } + /* should not happen. We should be getting only valid names at this stage */ + return 0; +} + + +/** @internal + * @brief Reverts guessed callbacks set during the dh_handshake() + * @param session session handle + * @returns void + */ +static void revert_kex_callbacks(ssh_session session) +{ + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + ssh_client_dh_remove_callbacks(session); + break; + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: +#ifdef WITH_GSSAPI + ssh_client_gss_kex_remove_callbacks(session); +#endif /* WITH_GSSAPI */ + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + ssh_client_dhgex_remove_callbacks(session); + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + ssh_client_ecdh_remove_callbacks(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + ssh_client_curve25519_remove_callbacks(session); + break; +#endif +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + ssh_client_sntrup761x25519_remove_callbacks(session); + break; +#endif + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ssh_client_hybrid_mlkem_remove_callbacks(session); + break; + } +} + +/** @brief Select the different methods on basis of client's and + * server's kex messages, and watches out if a match is possible. + */ +int ssh_kex_select_methods (ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + struct ssh_kex_struct *server = &crypto->server_kex; + struct ssh_kex_struct *client = &crypto->client_kex; + char *ext_start = NULL; + const char *aead_hmac = NULL; + enum ssh_key_exchange_e kex_type; + int i; + + /* Here we should drop the extensions from the list so we avoid matching. + * it. We added it to the end, so we can just truncate the string here */ + if (session->client) { + ext_start = strstr(client->methods[SSH_KEX], "," KEX_EXTENSION_CLIENT); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } + } + if (session->server) { + ext_start = strstr(server->methods[SSH_KEX], "," KEX_STRICT_SERVER); + if (ext_start != NULL) { + ext_start[0] = '\0'; + } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + crypto->kex_methods[i] = ssh_find_matching(server->methods[i], + client->methods[i]); + + if (i == SSH_MAC_C_S || i == SSH_MAC_S_C) { + aead_hmac = ssh_find_aead_hmac(crypto->kex_methods[i - 2]); + if (aead_hmac) { + free(crypto->kex_methods[i]); + crypto->kex_methods[i] = strdup(aead_hmac); + } + } + if (crypto->kex_methods[i] == NULL && i < SSH_LANG_C_S) { + ssh_set_error(session, SSH_FATAL, + "kex error : no match for method %s: server [%s], " + "client [%s]", ssh_kex_descriptions[i], + server->methods[i], client->methods[i]); + return SSH_ERROR; + } else if ((i >= SSH_LANG_C_S) && (crypto->kex_methods[i] == NULL)) { + /* we can safely do that for languages */ + crypto->kex_methods[i] = strdup(""); + } + } + + /* We can not set this value directly as the old value is needed to revert + * callbacks if we are client */ + kex_type = kex_select_kex_type(crypto->kex_methods[SSH_KEX]); + if (session->client && session->first_kex_follows_guess_wrong) { + SSH_LOG(SSH_LOG_DEBUG, "Our guess was wrong. Restarting the KEX"); + /* We need to remove the wrong callbacks and start kex again */ + revert_kex_callbacks(session); + session->dh_handshake_state = DH_STATE_INIT; + session->first_kex_follows_guess_wrong = false; + } + crypto->kex_type = kex_type; + + SSH_LOG(SSH_LOG_DEBUG, "Negotiated %s,%s,%s,%s,%s,%s,%s,%s,%s,%s", + session->next_crypto->kex_methods[SSH_KEX], + session->next_crypto->kex_methods[SSH_HOSTKEYS], + session->next_crypto->kex_methods[SSH_CRYPT_C_S], + session->next_crypto->kex_methods[SSH_CRYPT_S_C], + session->next_crypto->kex_methods[SSH_MAC_C_S], + session->next_crypto->kex_methods[SSH_MAC_S_C], + session->next_crypto->kex_methods[SSH_COMP_C_S], + session->next_crypto->kex_methods[SSH_COMP_S_C], + session->next_crypto->kex_methods[SSH_LANG_C_S], + session->next_crypto->kex_methods[SSH_LANG_S_C] + ); + return SSH_OK; +} + + +/* this function only sends the predefined set of kex methods */ +int ssh_send_kex(ssh_session session) +{ + struct ssh_kex_struct *kex = (session->server ? + &session->next_crypto->server_kex : + &session->next_crypto->client_kex); + ssh_string str = NULL; + int i; + int rc; + int first_kex_packet_follows = 0; + + /* Only client can initiate the handshake methods we implement. If we + * already received the peer mechanisms, there is no point in guessing */ + if (session->client && + session->session_state != SSH_SESSION_STATE_KEXINIT_RECEIVED && + session->send_first_kex_follows) { + first_kex_packet_follows = 1; + } + + SSH_LOG(SSH_LOG_TRACE, + "Sending KEXINIT packet, first_kex_packet_follows = %d", + first_kex_packet_follows); + + rc = ssh_buffer_pack(session->out_buffer, + "bP", + SSH2_MSG_KEXINIT, + (size_t)16, + kex->cookie); /* cookie */ + if (rc != SSH_OK) + goto error; + if (ssh_hashbufout_add_cookie(session) < 0) { + goto error; + } + + ssh_list_kex(kex); + + for (i = 0; i < SSH_KEX_METHODS; i++) { + str = ssh_string_from_char(kex->methods[i]); + if (str == NULL) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_hashbuf, str); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_ssh_string(session->out_buffer, str); + if (rc < 0) { + goto error; + } + SSH_STRING_FREE(str); + str = NULL; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + first_kex_packet_follows, + 0); + if (rc != SSH_OK) { + goto error; + } + + /* Prepare also the first_kex_packet_follows and reserved to 0 */ + rc = ssh_buffer_add_u8(session->out_hashbuf, first_kex_packet_follows); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(session->out_hashbuf, 0); + if (rc < 0) { + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return -1; + } + + session->flags |= SSH_SESSION_FLAG_KEXINIT_SENT; + SSH_LOG(SSH_LOG_PACKET, "SSH_MSG_KEXINIT sent"); + + /* If we indicated that we are sending the guessed key exchange packet, + * do it now. The packet is simple, but we need to do some preparations */ + if (first_kex_packet_follows == 1) { + char *list = kex->methods[SSH_KEX]; + const char *colon = strchr(list, ','); + size_t kex_name_len = colon ? (size_t)(colon - list) : strlen(list); + char *kex_name = calloc(kex_name_len + 1, 1); + if (kex_name == NULL) { + ssh_set_error_oom(session); + goto error; + } + snprintf(kex_name, kex_name_len + 1, "%.*s", (int)kex_name_len, list); + SSH_LOG(SSH_LOG_TRACE, "Sending the first kex packet for %s", kex_name); + + session->next_crypto->kex_type = kex_select_kex_type(kex_name); + free(kex_name); + + /* run the first step of the DH handshake */ + session->dh_handshake_state = DH_STATE_INIT; + if (dh_handshake(session) == SSH_ERROR) { + goto error; + } + } + return 0; + +error: + ssh_buffer_reinit(session->out_buffer); + ssh_buffer_reinit(session->out_hashbuf); + SSH_STRING_FREE(str); + + return -1; +} + +/* + * Key re-exchange (rekey) is triggered by this function. + * It can not be called again after the rekey is initialized! + */ +int ssh_send_rekex(ssh_session session) +{ + int rc; + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + /* Rekey/Key exchange is already in progress */ + SSH_LOG(SSH_LOG_PACKET, "Attempting rekey in bad state"); + return SSH_ERROR; + } + + if (session->current_crypto == NULL) { + /* No current crypto used -- can not exchange it */ + SSH_LOG(SSH_LOG_PACKET, "No crypto to rekey"); + return SSH_ERROR; + } + + if (session->client) { + rc = ssh_set_client_kex(session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Failed to set client kex"); + return rc; + } + } else { +#ifdef WITH_SERVER + rc = server_set_kex(session); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_PACKET, "Failed to set server kex"); + return rc; + } +#else + SSH_LOG(SSH_LOG_PACKET, "Invalid session state."); + return SSH_ERROR; +#endif /* WITH_SERVER */ + } + + session->dh_handshake_state = DH_STATE_INIT; + rc = ssh_send_kex(session); + if (rc < 0) { + SSH_LOG(SSH_LOG_PACKET, "Failed to send kex"); + return rc; + } + + /* Reset the handshake state */ + session->dh_handshake_state = DH_STATE_INIT_SENT; + return SSH_OK; +} + +/* returns a copy of the provided list if everything is supported, + * otherwise a new list of the supported algorithms */ +char *ssh_keep_known_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + return ssh_find_all_matching(supported_methods[algo], list); +} + +/** + * @internal + * + * @brief Return a newly allocated string containing only the FIPS allowed + * algorithms from the list. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be filtered + * + * @return A newly allocated list containing only the FIPS allowed algorithms from + * the list; NULL in case of error. + */ +char *ssh_keep_fips_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + return ssh_find_all_matching(fips_methods[algo], list); +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms plus the algorithms specified in list. If the system + * runs in fips mode, this will add only fips approved algorithms. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be appended + * + * @return A newly allocated list containing the default algorithms and the + * algorithms in list at the end; NULL in case of error. + */ +char *ssh_add_to_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *tmp = NULL, *ret = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_append_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_append_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms excluding the algorithms specified in list. If the system + * runs in fips mode, this will remove from the fips_methods list. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be exclude + * + * @return A newly allocated list containing the default algorithms without the + * algorithms in list; NULL in case of error. + */ +char *ssh_remove_from_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + if (algo > SSH_LANG_S_C) { + return NULL; + } + + if (list == NULL || list[0] == '\0') { + if (ssh_fips_mode()) { + return strdup(fips_methods[algo]); + } else { + return strdup(default_methods[algo]); + } + } + + if (ssh_fips_mode()) { + return ssh_remove_all_matching(fips_methods[algo], list); + } else { + return ssh_remove_all_matching(default_methods[algo], list); + } +} + +/** + * @internal + * + * @brief Return a newly allocated string containing the default + * algorithms with prioritized algorithms specified in list. If the + * algorithms are present in the default list they get prioritized, if not + * they are added to the front of the default list. If the system + * runs in fips mode, this will work with the fips_methods list. + * Empty list will cause error. + * + * @param[in] algo The type of the methods to filter + * @param[in] list The list to be pushed to priority + * + * @return A newly allocated list containing the default algorithms prioritized + * with the algorithms in list at the beginning of the list; NULL in case + * of error. + */ +char *ssh_prefix_default_algos(enum ssh_kex_types_e algo, const char *list) +{ + char *ret = NULL, *tmp = NULL; + + if (algo > SSH_LANG_S_C || list == NULL || list[0] == '\0') { + return NULL; + } + + if (ssh_fips_mode()) { + tmp = ssh_prefix_without_duplicates(fips_methods[algo], list); + ret = ssh_find_all_matching(fips_methods[algo], tmp); + } else { + tmp = ssh_prefix_without_duplicates(default_methods[algo], list); + ret = ssh_find_all_matching(supported_methods[algo], tmp); + } + + free(tmp); + return ret; +} + +int ssh_make_sessionid(ssh_session session) +{ + ssh_string num = NULL; + ssh_buffer server_hash = NULL; + ssh_buffer client_hash = NULL; + ssh_buffer buf = NULL; + ssh_string server_pubkey_blob = NULL; +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum client_pubkey, server_pubkey; +#else + bignum client_pubkey = NULL, server_pubkey = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ +#ifdef WITH_GEX +#if !defined(HAVE_LIBCRYPTO) || OPENSSL_VERSION_NUMBER < 0x30000000L + const_bignum modulus, generator; +#else + bignum modulus = NULL, generator = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* WITH_GEX */ + int rc = SSH_ERROR; + + buf = ssh_buffer_new(); + if (buf == NULL) { + ssh_set_error_oom(session); + return rc; + } + + rc = ssh_buffer_pack(buf, + "ss", + session->clientbanner, + session->serverbanner); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack client and server banner"); + goto error; + } + + if (session->client) { + server_hash = session->in_hashbuf; + client_hash = session->out_hashbuf; + } else { + server_hash = session->out_hashbuf; + client_hash = session->in_hashbuf; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to get next server pubkey blob"); + goto error; + } + + if (server_pubkey_blob == NULL) { + if ((session->server && ssh_kex_is_gss(session->next_crypto)) || + session->opts.gssapi_key_exchange) { + server_pubkey_blob = ssh_string_new(0); + if (server_pubkey_blob == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + } + } + + rc = ssh_buffer_pack(buf, + "dPdPS", + ssh_buffer_get_len(client_hash), + (size_t)ssh_buffer_get_len(client_hash), + ssh_buffer_get(client_hash), + ssh_buffer_get_len(server_hash), + (size_t)ssh_buffer_get_len(server_hash), + ssh_buffer_get(server_hash), + server_pubkey_blob); + SSH_STRING_FREE(server_pubkey_blob); + if (rc != SSH_OK){ + ssh_set_error(session, + SSH_FATAL, + "Failed to pack hashes and pubkey blob"); + goto error; + } + + switch(session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, NULL, &client_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_SERVER_KEYPAIR, NULL, &server_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(buf, + "BB", + client_pubkey, + server_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack DH pubkeys"); + goto error; + } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ + break; +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_CLIENT_KEYPAIR, NULL, &client_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_keypair_get_keys(session->next_crypto->dh_ctx, + DH_SERVER_KEYPAIR, NULL, &server_pubkey); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_dh_get_parameters(session->next_crypto->dh_ctx, + &modulus, &generator); + if (rc != SSH_OK) { + goto error; + } + rc = ssh_buffer_pack(buf, + "dddBBBB", + session->next_crypto->dh_pmin, + session->next_crypto->dh_pn, + session->next_crypto->dh_pmax, + modulus, + generator, + client_pubkey, + server_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack DH GEX params"); + goto error; + } +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(modulus); + bignum_safe_free(generator); +#endif /* OPENSSL_VERSION_NUMBER */ + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + if (session->next_crypto->ecdh_client_pubkey == NULL || + session->next_crypto->ecdh_server_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "ECDH parameter missing"); + goto error; + } + rc = ssh_buffer_pack(buf, + "SS", + session->next_crypto->ecdh_client_pubkey, + session->next_crypto->ecdh_server_pubkey); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack ECDH pubkeys"); + goto error; + } + break; +#endif /* HAVE_ECDH */ +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + case SSH_GSS_KEX_CURVE25519_SHA256: + rc = ssh_buffer_pack(buf, + "dPdP", + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_server_pubkey); + + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack Curve25519 pubkeys"); + goto error; + } + break; +#endif /* HAVE_CURVE25519 */ +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + rc = ssh_buffer_pack(buf, + "dPPdPP", + SNTRUP761_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE, + (size_t)SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_client_pubkey, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey, + SNTRUP761_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE, + (size_t)SNTRUP761_CIPHERTEXT_SIZE, + session->next_crypto->sntrup761_ciphertext, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_server_pubkey); + + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack SNTRU Prime params"); + goto error; + } + break; +#endif /* HAVE_SNTRUP761 */ + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_buffer_pack(buf, + "SS", + session->next_crypto->hybrid_client_init, + session->next_crypto->hybrid_server_reply); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to pack ML-KEM individual components"); + goto error; + } + break; + default: + /* Handle unsupported kex types - this should not happen in normal operation */ + rc = SSH_ERROR; + ssh_set_error(session, SSH_FATAL, "Unsupported KEX algorithm"); + goto error; + } + switch (session->next_crypto->kex_type) { + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + rc = ssh_buffer_pack(buf, + "F", + session->next_crypto->shared_secret, + SHA512_DIGEST_LEN); + break; + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + rc = ssh_buffer_pack(buf, "S", session->next_crypto->hybrid_shared_secret); + break; + default: + rc = ssh_buffer_pack(buf, "B", session->next_crypto->shared_secret); + break; + } + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to pack shared secret"); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("hash buffer", ssh_buffer_get(buf), ssh_buffer_get_len(buf)); +#endif + + /* Set rc for the following switch statement in case we goto error. */ + rc = SSH_ERROR; + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: +#endif /* WITH_GEX */ + session->next_crypto->digest_len = SHA_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA1; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha1(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA256: +#endif /* WITH_GEX */ + session->next_crypto->digest_len = SHA256_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA256; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha256(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_ECDH_SHA2_NISTP384: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + session->next_crypto->digest_len = SHA384_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA384; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha384(ssh_buffer_get(buf), ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + case SSH_KEX_ECDH_SHA2_NISTP521: + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + session->next_crypto->digest_len = SHA512_DIGEST_LENGTH; + session->next_crypto->digest_type = SSH_KDF_SHA512; + session->next_crypto->secret_hash = malloc(session->next_crypto->digest_len); + if (session->next_crypto->secret_hash == NULL) { + ssh_set_error_oom(session); + goto error; + } + sha512(ssh_buffer_get(buf), + ssh_buffer_get_len(buf), + session->next_crypto->secret_hash); + break; + default: + /* Handle unsupported kex types - this should not happen in normal operation */ + ssh_set_error(session, SSH_FATAL, "Unsupported KEX algorithm for hash computation"); + rc = SSH_ERROR; + goto error; + } + + /* During the first kex, secret hash and session ID are equal. However, after + * a key re-exchange, a new secret hash is calculated. This hash will not replace + * but complement existing session id. + */ + if (!session->next_crypto->session_id) { + session->next_crypto->session_id = malloc(session->next_crypto->digest_len); + if (session->next_crypto->session_id == NULL) { + ssh_set_error_oom(session); + rc = SSH_ERROR; + goto error; + } + memcpy(session->next_crypto->session_id, session->next_crypto->secret_hash, + session->next_crypto->digest_len); + /* Initial length is the same as secret hash */ + session->next_crypto->session_id_len = session->next_crypto->digest_len; + } +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_DEBUG, "Session hash: \n"); + ssh_log_hexdump("secret hash", session->next_crypto->secret_hash, session->next_crypto->digest_len); + ssh_log_hexdump("session id", session->next_crypto->session_id, session->next_crypto->session_id_len); +#endif /* DEBUG_CRYPTO */ + + rc = SSH_OK; +error: + SSH_BUFFER_FREE(buf); + SSH_BUFFER_FREE(client_hash); + SSH_BUFFER_FREE(server_hash); + + session->in_hashbuf = NULL; + session->out_hashbuf = NULL; + + SSH_STRING_FREE(num); +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(client_pubkey); + bignum_safe_free(server_pubkey); +#endif /* OPENSSL_VERSION_NUMBER */ + + return rc; +} + +int ssh_hashbufout_add_cookie(ssh_session session) +{ + int rc; + + session->out_hashbuf = ssh_buffer_new(); + if (session->out_hashbuf == NULL) { + return -1; + } + + rc = ssh_buffer_allocate_size(session->out_hashbuf, + sizeof(uint8_t) + 16); + if (rc < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + + if (ssh_buffer_add_u8(session->out_hashbuf, 20) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + + if (session->server) { + if (ssh_buffer_add_data(session->out_hashbuf, + session->next_crypto->server_kex.cookie, 16) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + } else { + if (ssh_buffer_add_data(session->out_hashbuf, + session->next_crypto->client_kex.cookie, 16) < 0) { + ssh_buffer_reinit(session->out_hashbuf); + return -1; + } + } + + return 0; +} + +int ssh_hashbufin_add_cookie(ssh_session session, unsigned char *cookie) +{ + int rc; + + session->in_hashbuf = ssh_buffer_new(); + if (session->in_hashbuf == NULL) { + return -1; + } + + rc = ssh_buffer_allocate_size(session->in_hashbuf, + sizeof(uint8_t) + 20 + 16); + if (rc < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + + if (ssh_buffer_add_u8(session->in_hashbuf, 20) < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + if (ssh_buffer_add_data(session->in_hashbuf,cookie, 16) < 0) { + ssh_buffer_reinit(session->in_hashbuf); + return -1; + } + + return 0; +} + +int ssh_generate_session_keys(ssh_session session) +{ + ssh_string k_string = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + unsigned char *key = NULL; + unsigned char *IV_cli_to_srv = NULL; + unsigned char *IV_srv_to_cli = NULL; + unsigned char *enckey_cli_to_srv = NULL; + unsigned char *enckey_srv_to_cli = NULL; + unsigned char *intkey_cli_to_srv = NULL; + unsigned char *intkey_srv_to_cli = NULL; + size_t key_len = 0; + size_t IV_len = 0; + size_t enckey_cli_to_srv_len = 0; + size_t enckey_srv_to_cli_len = 0; + size_t intkey_cli_to_srv_len = 0; + size_t intkey_srv_to_cli_len = 0; + int rc = -1; + + switch (session->next_crypto->kex_type) { + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + k_string = ssh_make_padded_bignum_string(crypto->shared_secret, + crypto->digest_len); + break; + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + k_string = ssh_string_copy(crypto->hybrid_shared_secret); + break; + default: + k_string = ssh_make_bignum_string(crypto->shared_secret); + break; + } + if (k_string == NULL) { + ssh_set_error_oom(session); + goto error; + } + /* See RFC4251 Section 5 for the definition of mpint which is the + * encoding we need to use for key in the SSH KDF */ + key = (unsigned char *)k_string; + key_len = ssh_string_len(k_string) + 4; + + IV_len = crypto->digest_len; + if (session->client) { + enckey_cli_to_srv_len = crypto->out_cipher->keysize / 8; + enckey_srv_to_cli_len = crypto->in_cipher->keysize / 8; + intkey_cli_to_srv_len = hmac_digest_len(crypto->out_hmac); + intkey_srv_to_cli_len = hmac_digest_len(crypto->in_hmac); + } else { + enckey_cli_to_srv_len = crypto->in_cipher->keysize / 8; + enckey_srv_to_cli_len = crypto->out_cipher->keysize / 8; + intkey_cli_to_srv_len = hmac_digest_len(crypto->in_hmac); + intkey_srv_to_cli_len = hmac_digest_len(crypto->out_hmac); + } + + IV_cli_to_srv = malloc(IV_len); + IV_srv_to_cli = malloc(IV_len); + enckey_cli_to_srv = malloc(enckey_cli_to_srv_len); + enckey_srv_to_cli = malloc(enckey_srv_to_cli_len); + intkey_cli_to_srv = malloc(intkey_cli_to_srv_len); + intkey_srv_to_cli = malloc(intkey_srv_to_cli_len); + if (IV_cli_to_srv == NULL || IV_srv_to_cli == NULL || + enckey_cli_to_srv == NULL || enckey_srv_to_cli == NULL || + intkey_cli_to_srv == NULL || intkey_srv_to_cli == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* IV */ + rc = ssh_kdf(crypto, key, key_len, 'A', IV_cli_to_srv, IV_len); + if (rc < 0) { + goto error; + } + rc = ssh_kdf(crypto, key, key_len, 'B', IV_srv_to_cli, IV_len); + if (rc < 0) { + goto error; + } + /* Encryption Key */ + rc = ssh_kdf(crypto, key, key_len, 'C', enckey_cli_to_srv, + enckey_cli_to_srv_len); + if (rc < 0) { + goto error; + } + rc = ssh_kdf(crypto, key, key_len, 'D', enckey_srv_to_cli, + enckey_srv_to_cli_len); + if (rc < 0) { + goto error; + } + /* Integrity Key */ + rc = ssh_kdf(crypto, key, key_len, 'E', intkey_cli_to_srv, + intkey_cli_to_srv_len); + if (rc < 0) { + goto error; + } + rc = ssh_kdf(crypto, key, key_len, 'F', intkey_srv_to_cli, + intkey_srv_to_cli_len); + if (rc < 0) { + goto error; + } + + if (session->client) { + crypto->encryptIV = IV_cli_to_srv; + crypto->decryptIV = IV_srv_to_cli; + crypto->encryptkey = enckey_cli_to_srv; + crypto->decryptkey = enckey_srv_to_cli; + crypto->encryptMAC = intkey_cli_to_srv; + crypto->decryptMAC = intkey_srv_to_cli; + } else { + crypto->encryptIV = IV_srv_to_cli; + crypto->decryptIV = IV_cli_to_srv; + crypto->encryptkey = enckey_srv_to_cli; + crypto->decryptkey = enckey_cli_to_srv; + crypto->encryptMAC = intkey_srv_to_cli; + crypto->decryptMAC = intkey_cli_to_srv; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Client to Server IV", IV_cli_to_srv, IV_len); + ssh_log_hexdump("Server to Client IV", IV_srv_to_cli, IV_len); + ssh_log_hexdump("Client to Server Encryption Key", enckey_cli_to_srv, + enckey_cli_to_srv_len); + ssh_log_hexdump("Server to Client Encryption Key", enckey_srv_to_cli, + enckey_srv_to_cli_len); + ssh_log_hexdump("Client to Server Integrity Key", intkey_cli_to_srv, + intkey_cli_to_srv_len); + ssh_log_hexdump("Server to Client Integrity Key", intkey_srv_to_cli, + intkey_srv_to_cli_len); +#endif /* DEBUG_CRYPTO */ + + rc = 0; +error: + ssh_string_burn(k_string); + SSH_STRING_FREE(k_string); + if (rc != 0) { + free(IV_cli_to_srv); + free(IV_srv_to_cli); + free(enckey_cli_to_srv); + free(enckey_srv_to_cli); + free(intkey_cli_to_srv); + free(intkey_srv_to_cli); + } + + return rc; +} + +/** @internal + * @brief Check if a given crypto context has a GSSAPI KEX set + * + * @param[in] crypto The SSH crypto context + * @return true if the KEX of the context is a GSSAPI KEX, false otherwise + */ +bool ssh_kex_is_gss(struct ssh_crypto_struct *crypto) +{ + switch (crypto->kex_type) { + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: + return true; + default: + return false; + } +} diff --git a/src/libs/libssh-0.12.2/src/known_hosts.c b/src/libs/libssh-0.12.2/src/known_hosts.c new file mode 100644 index 000000000000..701576ce8c13 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/known_hosts.c @@ -0,0 +1,589 @@ +/* + * keyfiles.c - private and public key handling for authentication. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/options.h" +#include "libssh/knownhosts.h" +/*todo: remove this include */ +#include "libssh/string.h" +#include "libssh/token.h" + +#ifndef _WIN32 +# include +# include +#endif + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 4096 +#endif + +/** + * @addtogroup libssh_session + * + * @{ + */ + +/** + * @internal + * + * @brief Return one line of known host file. + * + * This will return a token array containing (host|ip), keytype and key. + * + * @param[out] file A pointer to the known host file. Could be pointing to + * NULL at start. + * + * @param[in] filename The filename of the known host file. + * + * @param[out] found_type A pointer to a string to be set with the found key + * type. + * + * @returns The found_type type of key (ie "ssh-rsa"). Don't + * free that value. NULL if no match was found or the file + * was not found. + */ +static struct ssh_tokens_st *ssh_get_knownhost_line(FILE **file, + const char *filename, + const char **found_type) +{ + char buffer[MAX_LINE_SIZE] = {0}; + char *ptr = NULL; + struct ssh_tokens_st *tokens = NULL; + + if (*file == NULL) { + *file = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (*file == NULL) { + return NULL; + } + } + + while (fgets(buffer, sizeof(buffer), *file)) { + ptr = strchr(buffer, '\n'); + if (ptr) { + *ptr = '\0'; + } + + ptr = strchr(buffer,'\r'); + if (ptr) { + *ptr = '\0'; + } + + if (buffer[0] == '\0' || buffer[0] == '#') { + continue; /* skip empty lines */ + } + + tokens = ssh_tokenize(buffer, ' '); + if (tokens == NULL) { + fclose(*file); + *file = NULL; + + return NULL; + } + + if (tokens->tokens[0] == NULL || + tokens->tokens[1] == NULL || + tokens->tokens[2] == NULL) + { + /* it should have at least 3 tokens */ + ssh_tokens_free(tokens); + continue; + } + + *found_type = tokens->tokens[1]; + + return tokens; + } + + fclose(*file); + *file = NULL; + + /* we did not find anything, end of file*/ + return NULL; +} + +/** + * @internal + * + * @brief Check the public key in the known host line matches the public key of + * the currently connected server. + * + * @param[in] session The SSH session to use. + * + * @param[in] tokens A list of tokens in the known_hosts line. + * + * @returns 1 if the key matches, 0 if the key doesn't match and -1 + * on error. + */ +static int check_public_key(ssh_session session, char **tokens) { + ssh_string pubkey_blob = NULL; + ssh_buffer pubkey_buffer; + char *pubkey_64 = NULL; + int rc; + + /* ssh-rsa, ssh-ed25519, .. */ + pubkey_64 = tokens[2]; + pubkey_buffer = base64_to_bin(pubkey_64); + + if (pubkey_buffer == NULL) { + ssh_set_error(session, SSH_FATAL, + "Verifying that server is a known host: base64 error"); + return -1; + } + + rc = ssh_dh_get_current_server_publickey_blob(session, &pubkey_blob); + if (rc != 0) { + ssh_buffer_free(pubkey_buffer); + return -1; + } + + if (ssh_buffer_get_len(pubkey_buffer) != ssh_string_len(pubkey_blob)) { + ssh_string_free(pubkey_blob); + ssh_buffer_free(pubkey_buffer); + return 0; + } + + /* now test that they are identical */ + if (memcmp(ssh_buffer_get(pubkey_buffer), ssh_string_data(pubkey_blob), + ssh_buffer_get_len(pubkey_buffer)) != 0) { + ssh_string_free(pubkey_blob); + ssh_buffer_free(pubkey_buffer); + return 0; + } + + ssh_string_free(pubkey_blob); + ssh_buffer_free(pubkey_buffer); + return 1; +} + +/** + * @internal + * @brief Check if a hostname matches a openssh-style hashed known host. + * + * @param[in] host The host to check. + * + * @param[in] hashed The hashed value. + * + * @returns 1 if it matches, 0 otherwise. + */ +static int match_hashed_host(const char *host, const char *sourcehash) +{ + /* Openssh hash structure : + * |1|base64 encoded salt|base64 encoded hash + * hash is produced that way : + * hash := HMAC_SHA1(key=salt,data=host) + */ + unsigned char buffer[256] = {0}; + ssh_buffer salt = NULL; + ssh_buffer hash = NULL; + HMACCTX mac = NULL; + char *source = NULL; + char *b64hash = NULL; + int match, rc; + size_t size; + + if (strncmp(sourcehash, "|1|", 3) != 0) { + return 0; + } + + source = strdup(sourcehash + 3); + if (source == NULL) { + return 0; + } + + b64hash = strchr(source, '|'); + if (b64hash == NULL) { + /* Invalid hash */ + SAFE_FREE(source); + + return 0; + } + + *b64hash = '\0'; + b64hash++; + + salt = base64_to_bin(source); + if (salt == NULL) { + SAFE_FREE(source); + + return 0; + } + + hash = base64_to_bin(b64hash); + SAFE_FREE(source); + if (hash == NULL) { + ssh_buffer_free(salt); + + return 0; + } + + mac = hmac_init(ssh_buffer_get(salt), ssh_buffer_get_len(salt), SSH_HMAC_SHA1); + if (mac == NULL) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + size = sizeof(buffer); + rc = hmac_update(mac, host, strlen(host)); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + rc = hmac_final(mac, buffer, &size); + if (rc != 1) { + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + return 0; + } + + if (size == ssh_buffer_get_len(hash) && + memcmp(buffer, ssh_buffer_get(hash), size) == 0) { + match = 1; + } else { + match = 0; + } + + ssh_buffer_free(salt); + ssh_buffer_free(hash); + + SSH_LOG(SSH_LOG_PACKET, + "Matching a hashed host: %s match=%d", host, match); + + return match; +} + +/* How it's working : + * 1- we open the known host file and bitch if it doesn't exist + * 2- we need to examine each line of the file, until going on state SSH_SERVER_KNOWN_OK: + * - there's a match. if the key is good, state is SSH_SERVER_KNOWN_OK, + * else it's SSH_SERVER_KNOWN_CHANGED (or SSH_SERVER_FOUND_OTHER) + * - there's no match : no change + */ + +/** + * @brief This function is deprecated + * + * @deprecated Please use ssh_session_is_known_server() + * @see ssh_session_is_known_server() + */ +int ssh_is_server_known(ssh_session session) +{ + FILE *file = NULL; + char *host = NULL; + char *hostport = NULL; + const char *type = NULL; + int match; + int i = 0; + char *files[3] = {0}; + + struct ssh_tokens_st *tokens = NULL; + + int ret = SSH_SERVER_NOT_KNOWN; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return SSH_SERVER_FILE_NOT_FOUND; + } + } + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't verify host in known hosts if the hostname isn't known"); + + return SSH_SERVER_ERROR; + } + + if (session->current_crypto == NULL){ + ssh_set_error(session, SSH_FATAL, + "ssh_is_host_known called without cryptographic context"); + + return SSH_SERVER_ERROR; + } + + host = ssh_lowercase(session->opts.host); + hostport = ssh_hostport(host, session->opts.port > 0 ? session->opts.port : 22); + if (host == NULL || hostport == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(host); + SAFE_FREE(hostport); + + return SSH_SERVER_ERROR; + } + + /* Set the list of known hosts files */ + i = 0; + if (session->opts.global_knownhosts != NULL){ + files[i++] = session->opts.global_knownhosts; + } + files[i++] = session->opts.knownhosts; + files[i] = NULL; + i = 0; + + do { + tokens = ssh_get_knownhost_line(&file, + files[i], + &type); + + /* End of file, return the current state or use next file */ + if (tokens == NULL) { + ++i; + if(files[i] == NULL) + break; + else + continue; + } + match = match_hashed_host(host, tokens->tokens[0]); + if (match == 0){ + match = match_hostname(hostport, tokens->tokens[0], + strlen(tokens->tokens[0])); + } + if (match == 0) { + match = match_hostname(host, tokens->tokens[0], + strlen(tokens->tokens[0])); + } + if (match == 0) { + match = match_hashed_host(hostport, tokens->tokens[0]); + } + if (match) { + ssh_key pubkey = ssh_dh_get_current_server_publickey(session); + const char *pubkey_type = ssh_key_type_to_char(ssh_key_type(pubkey)); + + /* We got a match. Now check the key type */ + if (strcmp(pubkey_type, type) != 0) { + SSH_LOG(SSH_LOG_PACKET, + "ssh_is_server_known: server type [%s] doesn't match the " + "type [%s] in known_hosts file", + pubkey_type, + type); + /* Different type. We don't override the known_changed error which is + * more important */ + if (ret != SSH_SERVER_KNOWN_CHANGED) + ret = SSH_SERVER_FOUND_OTHER; + ssh_tokens_free(tokens); + continue; + } + /* so we know the key type is good. We may get a good key or a bad key. */ + match = check_public_key(session, tokens->tokens); + ssh_tokens_free(tokens); + + if (match < 0) { + ret = SSH_SERVER_ERROR; + break; + } else if (match == 1) { + ret = SSH_SERVER_KNOWN_OK; + break; + } else if(match == 0) { + /* We override the status with the wrong key state */ + ret = SSH_SERVER_KNOWN_CHANGED; + } + } else { + ssh_tokens_free(tokens); + } + } while (1); + + if ((ret == SSH_SERVER_NOT_KNOWN) && + (session->opts.StrictHostKeyChecking == 0)) { + int rv = ssh_session_update_known_hosts(session); + if (rv != SSH_OK) { + ret = SSH_SERVER_ERROR; + } else { + ret = SSH_SERVER_KNOWN_OK; + } + } + + SAFE_FREE(host); + SAFE_FREE(hostport); + if (file != NULL) { + fclose(file); + } + + /* Return the current state at end of file */ + return ret; +} + +/** + * @deprecated Please use ssh_session_export_known_hosts_entry() + * @brief This function is deprecated. + */ +char *ssh_dump_knownhost(ssh_session session) +{ + ssh_key server_pubkey = NULL; + char *host = NULL; + char *hostport = NULL; + char *buffer = NULL; + char *b64_key = NULL; + int rc; + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't write host in known hosts if the hostname isn't known"); + return NULL; + } + + host = ssh_lowercase(session->opts.host); + /* If using a nonstandard port, save the host in the [host]:port format */ + if (session->opts.port > 0 && session->opts.port != 22) { + hostport = ssh_hostport(host, session->opts.port); + SAFE_FREE(host); + if (hostport == NULL) { + return NULL; + } + host = hostport; + hostport = NULL; + } + + if (session->current_crypto==NULL) { + ssh_set_error(session, SSH_FATAL, "No current crypto context"); + SAFE_FREE(host); + return NULL; + } + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL){ + ssh_set_error(session, SSH_FATAL, "No public key present"); + SAFE_FREE(host); + return NULL; + } + + buffer = calloc (1, MAX_LINE_SIZE); + if (!buffer) { + SAFE_FREE(host); + return NULL; + } + + rc = ssh_pki_export_pubkey_base64(server_pubkey, &b64_key); + if (rc < 0) { + SAFE_FREE(buffer); + SAFE_FREE(host); + return NULL; + } + + snprintf(buffer, MAX_LINE_SIZE, + "%s %s %s\n", + host, + server_pubkey->type_c, + b64_key); + + SAFE_FREE(host); + SAFE_FREE(b64_key); + + return buffer; +} + +/** + * @deprecated Please use ssh_session_update_known_hosts() + * @brief This function is deprecated + */ +int ssh_write_knownhost(ssh_session session) +{ + FILE *file = NULL; + char *buffer = NULL; + char *dir = NULL; + int rc; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file"); + return SSH_ERROR; + } + } + + errno = 0; + file = fopen(session->opts.knownhosts, "a"); + if (file == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + if (errno == ENOENT) { + dir = ssh_dirname(session->opts.knownhosts); + if (dir == NULL) { + ssh_set_error(session, SSH_FATAL, + "%s", ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + rc = ssh_mkdirs(dir, 0700); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Cannot create %s directory: %s", + dir, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + SAFE_FREE(dir); + return SSH_ERROR; + } + SAFE_FREE(dir); + + errno = 0; + file = fopen(session->opts.knownhosts, "a"); + if (file == NULL) { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s" + " for appending: %s", + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + } else { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s for appending: %s", + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + } + + rc = ssh_session_export_known_hosts_entry(session, &buffer); + if (rc != SSH_OK) { + fclose(file); + return SSH_ERROR; + } + + if (fwrite(buffer, strlen(buffer), 1, file) != 1 || ferror(file)) { + SAFE_FREE(buffer); + fclose(file); + return -1; + } + + SAFE_FREE(buffer); + fclose(file); + return 0; +} + +#define KNOWNHOSTS_MAXTYPES 10 + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/knownhosts.c b/src/libs/libssh-0.12.2/src/knownhosts.c new file mode 100644 index 000000000000..e495c4036791 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/knownhosts.c @@ -0,0 +1,1339 @@ +/* + * known_hosts: Host and public key verification. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009-2017 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/dh.h" +#include "libssh/session.h" +#include "libssh/options.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/libssh.h" +#include "libssh/dh.h" +#include "libssh/knownhosts.h" +#include "libssh/token.h" + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 8192 +#endif + +/** + * @addtogroup libssh_session + * + * @{ + */ + +static int hash_hostname(const char *name, + unsigned char *salt, + unsigned int salt_size, + unsigned char **hash, + size_t *hash_size) +{ + int rc; + HMACCTX mac_ctx = NULL; + + mac_ctx = hmac_init(salt, salt_size, SSH_HMAC_SHA1); + if (mac_ctx == NULL) { + return SSH_ERROR; + } + + rc = hmac_update(mac_ctx, name, strlen(name)); + if (rc != 1) + return SSH_ERROR; + + rc = hmac_final(mac_ctx, *hash, hash_size); + if (rc != 1) + return SSH_ERROR; + + return SSH_OK; +} + +static int match_hashed_hostname(const char *host, const char *hashed_host) +{ + char *hashed = NULL; + char *b64_hash = NULL; + ssh_buffer salt = NULL; + ssh_buffer hash = NULL; + unsigned char hashed_buf[256] = {0}; + unsigned char *hashed_buf_ptr = hashed_buf; + size_t hashed_buf_size = sizeof(hashed_buf); + int cmp; + int rc; + int match = 0; + + cmp = strncmp(hashed_host, "|1|", 3); + if (cmp != 0) { + return 0; + } + + hashed = strdup(hashed_host + 3); + if (hashed == NULL) { + return 0; + } + + b64_hash = strchr(hashed, '|'); + if (b64_hash == NULL) { + goto error; + } + *b64_hash = '\0'; + b64_hash++; + + salt = base64_to_bin(hashed); + if (salt == NULL) { + goto error; + } + + hash = base64_to_bin(b64_hash); + if (hash == NULL) { + goto error; + } + + rc = hash_hostname(host, + ssh_buffer_get(salt), + ssh_buffer_get_len(salt), + &hashed_buf_ptr, + &hashed_buf_size); + if (rc != SSH_OK) { + goto error; + } + + if (hashed_buf_size != ssh_buffer_get_len(hash)) { + goto error; + } + + cmp = memcmp(hashed_buf, ssh_buffer_get(hash), hashed_buf_size); + if (cmp == 0) { + match = 1; + } + +error: + free(hashed); + SSH_BUFFER_FREE(salt); + SSH_BUFFER_FREE(hash); + + return match; +} + +/** + * @brief Free an allocated ssh_knownhosts_entry. + * + * Use SSH_KNOWNHOSTS_ENTRY_FREE() to set the pointer to NULL. + * + * @param[in] entry The entry to free. + */ +void ssh_knownhosts_entry_free(struct ssh_knownhosts_entry *entry) +{ + if (entry == NULL) { + return; + } + + SAFE_FREE(entry->hostname); + SAFE_FREE(entry->unparsed); + ssh_key_free(entry->publickey); + SAFE_FREE(entry->comment); + SAFE_FREE(entry); +} + +static int known_hosts_read_line(FILE *fp, + char *buf, + size_t buf_size, + size_t *buf_len, + size_t *lineno) +{ + while (fgets(buf, (int)buf_size, fp) != NULL) { + size_t len; + if (buf[0] == '\0') { + continue; + } + + *lineno += 1; + len = strlen(buf); + if (buf_len != NULL) { + *buf_len = len; + } + if (buf[len - 1] == '\n' || feof(fp)) { + return 0; + } else { + errno = E2BIG; + return -1; + } + } + + return -1; +} + +static int +ssh_known_hosts_entries_compare(struct ssh_knownhosts_entry *k1, + struct ssh_knownhosts_entry *k2) +{ + int cmp; + + if (k1 == NULL || k2 == NULL) { + return 1; + } + + cmp = strcmp(k1->hostname, k2->hostname); + if (cmp != 0) { + return cmp; + } + + cmp = ssh_key_cmp(k1->publickey, k2->publickey, SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + return cmp; + } + + return 0; +} + +/** + * @internal + * + * @brief Read entries from filename to provided list + * + * This method reads the known_hosts file referenced by the path + * in filename argument, and entries matching the match argument + * will be added to the list in entries argument. + * If the entries list is NULL, it will allocate a new list. Caller + * is responsible to free it even if an error occurs. + * + * @param match[in] The host name (with port) to match against + * @param filename[in] The known hosts file to parse + * @param entries[in,out] The list of entries to append matching ones + * @return `SSH_OK` on missing file or success parsing, + * `SSH_ERROR` on error + */ +static int ssh_known_hosts_read_entries(const char *match, + const char *filename, + struct ssh_list **entries) +{ + char line[MAX_LINE_SIZE]; + size_t lineno = 0; + size_t len = 0; + FILE *fp = NULL; + int rc; + + fp = ssh_strict_fopen(filename, SSH_MAX_CONFIG_FILE_SIZE); + if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_TRACE, "Failed to open the known_hosts file '%s': %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + /* The missing file is not an error here */ + return SSH_OK; + } + + if (*entries == NULL) { + *entries = ssh_list_new(); + if (*entries == NULL) { + fclose(fp); + return SSH_ERROR; + } + } + + for (rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno); + rc == 0; + rc = known_hosts_read_line(fp, line, sizeof(line), &len, &lineno)) { + struct ssh_knownhosts_entry *entry = NULL; + struct ssh_iterator *it = NULL; + char *p = NULL; + + if (line[len] != '\n') { + len = strcspn(line, "\n"); + } + line[len] = '\0'; + + /* Skip leading spaces */ + for (p = line; isspace((int)p[0]); p++); + + /* Skip comments and empty lines */ + if (p[0] == '\0' || p[0] == '#') { + continue; + } + + /* Skip lines starting with markers (@cert-authority, @revoked): + * we do not completely support them anyway */ + if (p[0] == '@') { + continue; + } + + rc = ssh_known_hosts_parse_line(match, + line, + &entry); + if (rc == SSH_AGAIN) { + continue; + } else if (rc != SSH_OK) { + goto error; + } + + /* Check for duplicates */ + for (it = ssh_list_get_iterator(*entries); + it != NULL; + it = it->next) { + struct ssh_knownhosts_entry *entry2 = NULL; + int cmp; + entry2 = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + cmp = ssh_known_hosts_entries_compare(entry, entry2); + if (cmp == 0) { + ssh_knownhosts_entry_free(entry); + entry = NULL; + break; + } + } + if (entry != NULL) { + rc = ssh_list_append(*entries, entry); + if (rc != SSH_OK) { + ssh_knownhosts_entry_free(entry); + goto error; + } + } + } + + fclose(fp); + return SSH_OK; +error: + fclose(fp); + return SSH_ERROR; +} + +static char *ssh_session_get_host_port(ssh_session session) +{ + char *host_port = NULL; + char *host = NULL; + + if (session->opts.host == NULL) { + ssh_set_error(session, + SSH_FATAL, + "Can't verify server in known hosts if the host we " + "should connect to has not been set"); + + return NULL; + } + + host = ssh_lowercase(session->opts.host); + if (host == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (session->opts.port == 0 || session->opts.port == 22) { + host_port = host; + } else { + host_port = ssh_hostport(host, session->opts.port); + SAFE_FREE(host); + if (host_port == NULL) { + ssh_set_error_oom(session); + return NULL; + } + } + + return host_port; +} + +/** + * @internal + * + * @brief Free known hosts entries list + * + * @param[in] entry_list The list of ssh_knownhosts_entry items + */ +static void ssh_knownhosts_entries_free(struct ssh_list *entry_list) +{ + struct ssh_iterator *it = NULL; + + if (entry_list == NULL) { + return; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); +} +/** + * @internal + * + * @brief Check which host keys should be preferred for the session. + * + * This checks the known_hosts file to find out which algorithms should be + * preferred for the connection we are going to establish. + * + * @param[in] session The ssh session to use. + * + * @return A list of supported key types, NULL on error. + */ +struct ssh_list *ssh_known_hosts_get_algorithms(ssh_session session) +{ + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + char *host_port = NULL; + size_t count; + struct ssh_list *list = NULL; + int list_error = 0; + int rc; + + if (session->opts.knownhosts == NULL || + session->opts.global_knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return NULL; + } + } + + list = ssh_list_new(); + if (list == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + goto error; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + goto error; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); + SAFE_FREE(host_port); + if (rc != 0) { + goto error; + } + + if (entry_list == NULL) { + goto error; + } + + count = ssh_list_count(entry_list); + if (count == 0) { + goto error; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) { + struct ssh_iterator *it2 = NULL; + struct ssh_knownhosts_entry *entry = NULL; + const char *algo = NULL; + bool present = false; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + algo = entry->publickey->type_c; + + /* Check for duplicates */ + for (it2 = ssh_list_get_iterator(list); + it2 != NULL; + it2 = it2->next) { + char *alg2 = ssh_iterator_value(char *, it2); + int cmp = strcmp(alg2, algo); + if (cmp == 0) { + present = true; + break; + } + } + + /* Add to the new list only if it is unique */ + if (!present) { + rc = ssh_list_append(list, algo); + if (rc != SSH_OK) { + list_error = 1; + } + } + + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + ssh_list_free(entry_list); + if (list_error) { + goto error; + } + + return list; +error: + ssh_knownhosts_entries_free(entry_list); + ssh_list_free(list); + return NULL; +} + +/** + * @internal + * + * @brief Returns a static string containing a list of the signature types the + * given key type can generate. + * + * @returns A static cstring containing the signature types the key is able to + * generate separated by commas; NULL in case of error + */ +static const char *ssh_known_host_sigs_from_hostkey_type(enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA: + return "rsa-sha2-512,rsa-sha2-256,ssh-rsa"; + case SSH_KEYTYPE_ED25519: + return "ssh-ed25519"; + case SSH_KEYTYPE_SK_ED25519: + return "sk-ssh-ed25519@openssh.com"; +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA_P256: + return "ecdsa-sha2-nistp256"; + case SSH_KEYTYPE_ECDSA_P384: + return "ecdsa-sha2-nistp384"; + case SSH_KEYTYPE_ECDSA_P521: + return "ecdsa-sha2-nistp521"; + case SSH_KEYTYPE_SK_ECDSA: + return "sk-ecdsa-sha2-nistp256@openssh.com"; +#else + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + SSH_LOG(SSH_LOG_WARN, "ECDSA keys are not supported by this build"); + break; +#endif + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, + "The given type %d is not a base private key type " + "or is unsupported", + type); + } + + return NULL; +} + +/** + * @internal + * + * @brief Get the host keys algorithms identifiers from the known_hosts files + * + * This expands the signatures types that can be generated from the keys types + * present in the known_hosts files + * + * @param[in] session The ssh session to use. + * + * @return A newly allocated cstring containing a list of signature algorithms + * that can be generated by the host using the keys listed in the known_hosts + * files, NULL on error. + */ +char *ssh_known_hosts_get_algorithms_names(ssh_session session) +{ + char methods_buffer[256 + 1] = {0}; + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + char *host_port = NULL; + size_t count; + bool needcomma = false; + char *names = NULL; + + int rc; + + if (session->opts.knownhosts == NULL || + session->opts.global_knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return NULL; + } + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return NULL; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + ssh_knownhosts_entries_free(entry_list); + return NULL; + } + + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); + SAFE_FREE(host_port); + if (rc != 0) { + ssh_knownhosts_entries_free(entry_list); + return NULL; + } + + if (entry_list == NULL) { + return NULL; + } + + count = ssh_list_count(entry_list); + if (count == 0) { + ssh_list_free(entry_list); + return NULL; + } + + for (it = ssh_list_get_iterator(entry_list); + it != NULL; + it = ssh_list_get_iterator(entry_list)) + { + struct ssh_knownhosts_entry *entry = NULL; + const char *algo = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + algo = ssh_known_host_sigs_from_hostkey_type(entry->publickey->type); + if (algo == NULL) { + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + continue; + } + + if (needcomma) { + strncat(methods_buffer, + ",", + sizeof(methods_buffer) - strlen(methods_buffer) - 1); + } + + strncat(methods_buffer, + algo, + sizeof(methods_buffer) - strlen(methods_buffer) - 1); + needcomma = true; + + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entry_list, it); + } + + ssh_list_free(entry_list); + + names = ssh_remove_duplicates(methods_buffer); + + return names; +} + +/** + * @brief Parse a line from a known_hosts entry into a structure + * + * This parses a known_hosts entry into a structure with the key in a libssh + * consumeable form. You can use the PKI key function to further work with it. + * + * @param[in] hostname The hostname to match the line to + * + * @param[in] line The line to compare and parse if we have a hostname + * match. + * + * @param[in] entry A pointer to store the allocated known_hosts + * entry structure. The user needs to free the memory + * using SSH_KNOWNHOSTS_ENTRY_FREE(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_known_hosts_parse_line(const char *hostname, + const char *line, + struct ssh_knownhosts_entry **entry) +{ + struct ssh_knownhosts_entry *e = NULL; + char *known_host = NULL; + char *p = NULL; + const char *cp = NULL; + char *save_tok = NULL; + enum ssh_keytypes_e key_type; + int match = 0; + int rc = SSH_OK; + + known_host = strdup(line); + if (known_host == NULL) { + return SSH_ERROR; + } + + /* match pattern for hostname or hashed hostname */ + p = strtok_r(known_host, " ", &save_tok); + if (p == NULL ) { + free(known_host); + return SSH_ERROR; + } + + e = calloc(1, sizeof(struct ssh_knownhosts_entry)); + if (e == NULL) { + free(known_host); + return SSH_ERROR; + } + + if (hostname != NULL) { + char *host_port = NULL; + char *q = NULL; + + /* Hashed */ + if (p[0] == '|') { + match = match_hashed_hostname(hostname, p); + } + + save_tok = NULL; + + for (q = strtok_r(p, ",", &save_tok); + q != NULL; + q = strtok_r(NULL, ",", &save_tok)) { + int cmp; + + if (q[0] == '[' && hostname[0] != '[') { + /* Corner case: We have standard port so we do not have + * hostname in square braces. But the pattern is enclosed + * in braces with, possibly standard or wildcard, port. + * We need to test against [host]:port pair here. + */ + if (host_port == NULL) { + host_port = ssh_hostport(hostname, 22); + if (host_port == NULL) { + rc = SSH_ERROR; + goto out; + } + } + + cmp = match_hostname(host_port, q, strlen(q)); + } else { + cmp = match_hostname(hostname, q, strlen(q)); + } + if (cmp == 1) { + match = 1; + break; + } + } + free(host_port); + + if (match == 0) { + rc = SSH_AGAIN; + goto out; + } + + e->hostname = strdup(hostname); + if (e->hostname == NULL) { + rc = SSH_ERROR; + goto out; + } + } + + /* Restart parsing */ + SAFE_FREE(known_host); + known_host = strdup(line); + if (known_host == NULL) { + rc = SSH_ERROR; + goto out; + } + + save_tok = NULL; + + p = strtok_r(known_host, " ", &save_tok); + if (p == NULL ) { + rc = SSH_ERROR; + goto out; + } + + e->unparsed = strdup(p); + if (e->unparsed == NULL) { + rc = SSH_ERROR; + goto out; + } + + /* pubkey type */ + p = strtok_r(NULL, " ", &save_tok); + if (p == NULL) { + rc = SSH_ERROR; + goto out; + } + + key_type = ssh_key_type_from_name(p); + if (key_type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "key type '%s' unknown!", p); + rc = SSH_ERROR; + goto out; + } + + /* public key */ + p = strtok_r(NULL, " ", &save_tok); + if (p == NULL) { + rc = SSH_ERROR; + goto out; + } + + rc = ssh_pki_import_pubkey_base64(p, + key_type, + &e->publickey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to parse %s key for entry: %s!", + ssh_key_type_to_char(key_type), + e->unparsed); + goto out; + } + + /* comment */ + p = strtok_r(NULL, " ", &save_tok); + if (p != NULL) { + cp = strstr(line, p); + if (cp != NULL) { + e->comment = strdup(cp); + if (e->comment == NULL) { + rc = SSH_ERROR; + goto out; + } + } + } + + *entry = e; + SAFE_FREE(known_host); + + return SSH_OK; +out: + SAFE_FREE(known_host); + ssh_knownhosts_entry_free(e); + return rc; +} + +/** + * @brief Check if the set hostname and port match an entry in known_hosts. + * + * This check if the set hostname and port have an entry in the known_hosts file. + * You need to set at least the hostname using ssh_options_set(). + * + * @param[in] session The session with the values set to check. + * + * @return A ssh_known_hosts_e return value. + */ +enum ssh_known_hosts_e ssh_session_has_known_hosts_entry(ssh_session session) +{ + struct ssh_list *entry_list = NULL; + char *host_port = NULL; + bool global_known_hosts_found = false; + bool known_hosts_found = false; + int rc; + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Cannot find a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + } + + if (session->opts.knownhosts == NULL && + session->opts.global_knownhosts == NULL) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "No path set for a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + + if (session->opts.knownhosts != NULL) { + known_hosts_found = ssh_file_readaccess_ok(session->opts.knownhosts); + if (!known_hosts_found) { + SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", + session->opts.knownhosts); + } + } + + if (session->opts.global_knownhosts != NULL) { + global_known_hosts_found = + ssh_file_readaccess_ok(session->opts.global_knownhosts); + if (!global_known_hosts_found) { + SSH_LOG(SSH_LOG_TRACE, "Cannot access file %s", + session->opts.global_knownhosts); + } + } + + if ((!known_hosts_found) && (!global_known_hosts_found)) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Cannot find a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return SSH_KNOWN_HOSTS_ERROR; + } + + if (known_hosts_found) { + rc = ssh_known_hosts_read_entries(host_port, + session->opts.knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + ssh_knownhosts_entries_free(entry_list); + return SSH_KNOWN_HOSTS_ERROR; + } + } + + if (global_known_hosts_found) { + rc = ssh_known_hosts_read_entries(host_port, + session->opts.global_knownhosts, + &entry_list); + if (rc != 0) { + SAFE_FREE(host_port); + ssh_knownhosts_entries_free(entry_list); + return SSH_KNOWN_HOSTS_ERROR; + } + } + + SAFE_FREE(host_port); + + if (ssh_list_count(entry_list) == 0) { + ssh_list_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + ssh_knownhosts_entries_free(entry_list); + + return SSH_KNOWN_HOSTS_OK; +} + +/** + * @brief Export the current session information to a known_hosts string. + * + * This exports the current information of a session which is connected so a + * ssh server into an entry line which can be added to a known_hosts file. + * + * @param[in] session The session with information to export. + * + * @param[in] pentry_string A pointer to a string to store the allocated + * line of the entry. The user must free it using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_session_export_known_hosts_entry(ssh_session session, + char **pentry_string) +{ + ssh_key server_pubkey = NULL; + char *host = NULL; + char entry_buf[MAX_LINE_SIZE] = {0}; + char *b64_key = NULL; + int rc; + + if (pentry_string == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + if (session->opts.host == NULL) { + ssh_set_error(session, SSH_FATAL, + "Can't create known_hosts entry - hostname unknown"); + return SSH_ERROR; + } + + host = ssh_session_get_host_port(session); + if (host == NULL) { + return SSH_ERROR; + } + + if (session->current_crypto == NULL) { + ssh_set_error(session, SSH_FATAL, + "No current crypto context, please connect first"); + SAFE_FREE(host); + return SSH_ERROR; + } + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL){ + ssh_set_error(session, SSH_FATAL, "No public key present"); + SAFE_FREE(host); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_base64(server_pubkey, &b64_key); + if (rc < 0) { + SAFE_FREE(host); + return SSH_ERROR; + } + + snprintf(entry_buf, sizeof(entry_buf), + "%s %s %s\n", + host, + server_pubkey->type_c, + b64_key); + + SAFE_FREE(host); + SAFE_FREE(b64_key); + + *pentry_string = strdup(entry_buf); + if (*pentry_string == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Adds the currently connected server to the user known_hosts file. + * + * This adds the currently connected server to the known_hosts file by + * appending a new line at the end. The global known_hosts file is considered + * read-only so it is not touched by this function. + * + * @param[in] session The session to use to write the entry. + * + * @return SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_session_update_known_hosts(ssh_session session) +{ + FILE *fp = NULL; + char *entry = NULL; + char *dir = NULL; + size_t nwritten; + size_t len; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + if (session->opts.knownhosts == NULL) { + rc = ssh_options_apply(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Can't find a known_hosts file"); + return SSH_ERROR; + } + } + + errno = 0; + fp = fopen(session->opts.knownhosts, "a"); + if (fp == NULL) { + if (errno == ENOENT) { + dir = ssh_dirname(session->opts.knownhosts); + if (dir == NULL) { + ssh_set_error(session, SSH_FATAL, "%s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + rc = ssh_mkdirs(dir, 0700); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Cannot create %s directory: %s", + dir, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + SAFE_FREE(dir); + return SSH_ERROR; + } + SAFE_FREE(dir); + + errno = 0; + fp = fopen(session->opts.knownhosts, "a"); + if (fp == NULL) { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s" + " for appending: %s", + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + } else { + ssh_set_error(session, SSH_FATAL, + "Couldn't open known_hosts file %s for appending: %s", + session->opts.knownhosts, strerror(errno)); + return SSH_ERROR; + } + } + + rc = ssh_session_export_known_hosts_entry(session, &entry); + if (rc != SSH_OK) { + fclose(fp); + return rc; + } + + len = strlen(entry); + nwritten = fwrite(entry, sizeof(char), len, fp); + SAFE_FREE(entry); + if (nwritten != len || ferror(fp)) { + ssh_set_error(session, SSH_FATAL, + "Couldn't append to known_hosts file %s: %s", + session->opts.knownhosts, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + fclose(fp); + return SSH_ERROR; + } + + fclose(fp); + return SSH_OK; +} + +static enum ssh_known_hosts_e +ssh_known_hosts_check_server_key(const char *hosts_entry, + const char *filename, + ssh_key server_key, + struct ssh_knownhosts_entry **pentry) +{ + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; + int rc; + + rc = ssh_known_hosts_read_entries(hosts_entry, + filename, + &entry_list); + if (rc != 0) { + ssh_knownhosts_entries_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + it = ssh_list_get_iterator(entry_list); + if (it == NULL) { + ssh_knownhosts_entries_free(entry_list); + return SSH_KNOWN_HOSTS_UNKNOWN; + } + + for (;it != NULL; it = it->next) { + struct ssh_knownhosts_entry *entry = NULL; + int cmp; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + + cmp = ssh_key_cmp(server_key, entry->publickey, SSH_KEY_CMP_PUBLIC); + if (cmp == 0) { + found = SSH_KNOWN_HOSTS_OK; + if (pentry != NULL) { + *pentry = entry; + ssh_list_remove(entry_list, it); + } + break; + } + + if (ssh_key_type(server_key) == ssh_key_type(entry->publickey)) { + found = SSH_KNOWN_HOSTS_CHANGED; + continue; + } + + if (found != SSH_KNOWN_HOSTS_CHANGED) { + found = SSH_KNOWN_HOSTS_OTHER; + } + } + + ssh_knownhosts_entries_free(entry_list); + + return found; +} + +/** + * @brief Get the known_hosts entry for the currently connected session. + * + * @param[in] session The session to validate. + * + * @param[in] pentry A pointer to store the allocated known hosts entry. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * Note: When GSSAPI key exchange is used, + * host keys may change frequently and + * without advance warning per RFC 4462. + * Clients SHOULD NOT issue strong warnings + * or abort when this occurs with GSSAPI + * key exchange.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct. + * This is also returned when GSSAPI key + * exchange with null hostkey is negotiated, + * as there is no host key to verify, or when + * the server does not send the host key despite + * negotiating a non-null host key algorithm.\n + * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The + * host is thus unknown. File will be + * created if host key is accepted.\n + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. + * + * @see ssh_knownhosts_entry_free() + */ +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry(ssh_session session, + struct ssh_knownhosts_entry **pentry) +{ + enum ssh_known_hosts_e old_rv, rv = SSH_KNOWN_HOSTS_UNKNOWN; + + if (pentry != NULL) { + *pentry = NULL; + } + + if (session->opts.knownhosts == NULL) { + if (ssh_options_apply(session) < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Can't find a known_hosts file"); + + return SSH_KNOWN_HOSTS_NOT_FOUND; + } + } + + rv = ssh_session_get_known_hosts_entry_file(session, + session->opts.knownhosts, + pentry); + if (rv == SSH_KNOWN_HOSTS_OK) { + /* We already found a match in the first file: return */ + return rv; + } + + old_rv = rv; + rv = ssh_session_get_known_hosts_entry_file(session, + session->opts.global_knownhosts, + pentry); + + /* If we did not find any match at all: we report the previous result */ + if (rv == SSH_KNOWN_HOSTS_UNKNOWN) { + if (session->opts.StrictHostKeyChecking == 0) { + return SSH_KNOWN_HOSTS_OK; + } + return old_rv; + } + + /* We found some match: return it */ + return rv; + +} + +/** + * @internal + * + * @brief Get the known_hosts entry for the current connected session + * from the given known_hosts file. + * + * @param[in] session The session to validate. + * + * @param[in] filename The filename to parse. + * + * @param[in] pentry A pointer to store the allocated known hosts entry. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct.\n + * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The + * host is thus unknown. File will be + * created if host key is accepted.\n + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. + * + * @see ssh_knownhosts_entry_free() + */ +enum ssh_known_hosts_e +ssh_session_get_known_hosts_entry_file(ssh_session session, + const char *filename, + struct ssh_knownhosts_entry **pentry) +{ + ssh_key server_pubkey = NULL; + char *host_port = NULL; + enum ssh_known_hosts_e found = SSH_KNOWN_HOSTS_UNKNOWN; + + server_pubkey = ssh_dh_get_current_server_publickey(session); + if (server_pubkey == NULL) { +#ifdef WITH_GSSAPI + /* After GSSAPI key exchange, sending the host key is optional + * because the server is already authenticated via Kerberos. + * Return SSH_KNOWN_HOSTS_UNKNOWN if there is no host key + * to let the application decide how to handle this case. */ + if (ssh_session_kex_is_gss(session)) { + return SSH_KNOWN_HOSTS_UNKNOWN; + } +#endif + ssh_set_error(session, + SSH_FATAL, + "ssh_session_is_known_host called without a " + "server_key!"); + + return SSH_KNOWN_HOSTS_ERROR; + } + + host_port = ssh_session_get_host_port(session); + if (host_port == NULL) { + return SSH_KNOWN_HOSTS_ERROR; + } + + found = ssh_known_hosts_check_server_key(host_port, + filename, + server_pubkey, + pentry); + SAFE_FREE(host_port); + + return found; +} + +/** + * @brief Check if the servers public key for the connected session is known. + * + * This checks if we already know the public key of the server we want to + * connect to. This allows to detect if there is a MITM attach going on + * of if there have been changes on the server we don't know about. + * + * @param[in] session The SSH to validate. + * + * @returns SSH_KNOWN_HOSTS_OK: The server is known and has not changed.\n + * SSH_KNOWN_HOSTS_CHANGED: The server key has changed. Either you + * are under attack or the administrator + * changed the key. You HAVE to warn the + * user about a possible attack.\n + * SSH_KNOWN_HOSTS_OTHER: The server gave use a key of a type while + * we had an other type recorded. It is a + * possible attack.\n + * SSH_KNOWN_HOSTS_UNKNOWN: The server is unknown. User should + * confirm the public key hash is correct.\n + * SSH_KNOWN_HOSTS_NOT_FOUND: The known host file does not exist. The + * host is thus unknown. File will be + * created if host key is accepted.\n + * SSH_KNOWN_HOSTS_ERROR: There had been an error checking the host. + */ +enum ssh_known_hosts_e ssh_session_is_known_server(ssh_session session) +{ + return ssh_session_get_known_hosts_entry(session, NULL); +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/legacy.c b/src/libs/libssh-0.12.2/src/legacy.c new file mode 100644 index 000000000000..5fad635f9fdb --- /dev/null +++ b/src/libs/libssh-0.12.2/src/legacy.c @@ -0,0 +1,790 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** functions in that file are wrappers to the newly named functions. All + * of them are depreciated, but these wrappers will avoid breaking backward + * compatibility + */ + +#include "config.h" + +#include +#include + +#include +#include +#include +#include +#include +#include +#include "libssh/pki_priv.h" +#include +#include +#include "libssh/options.h" + +/* AUTH FUNCTIONS */ +int ssh_auth_list(ssh_session session) { + return ssh_userauth_list(session, NULL); +} + +int ssh_userauth_offer_pubkey(ssh_session session, const char *username, + int type, ssh_string publickey) +{ + ssh_key key = NULL; + int rc; + + (void) type; /* unused */ + + rc = ssh_pki_import_pubkey_blob(publickey, &key); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Failed to convert public key"); + return SSH_AUTH_ERROR; + } + + rc = ssh_userauth_try_publickey(session, username, key); + ssh_key_free(key); + + return rc; +} + +int ssh_userauth_pubkey(ssh_session session, + const char *username, + ssh_string publickey, + ssh_private_key privatekey) +{ + ssh_key key = NULL; + int rc; + + (void) publickey; /* unused */ + + key = ssh_key_new(); + if (key == NULL) { + return SSH_AUTH_ERROR; + } + + key->type = privatekey->type; + key->type_c = ssh_key_type_to_char(key->type); + key->flags = SSH_KEY_FLAG_PRIVATE|SSH_KEY_FLAG_PUBLIC; +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = privatekey->rsa_priv; +#elif defined(HAVE_LIBCRYPTO) + key->key = privatekey->key_priv; +#else + key->rsa = privatekey->rsa_priv; +#endif /* HAVE_LIBCRYPTO */ + + rc = ssh_userauth_publickey(session, username, key); +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + key->key = NULL; +#else + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + ssh_key_free(key); + + return rc; +} + +int ssh_userauth_autopubkey(ssh_session session, const char *passphrase) { + return ssh_userauth_publickey_auto(session, NULL, passphrase); +} + +int ssh_userauth_privatekey_file(ssh_session session, + const char *username, + const char *filename, + const char *passphrase) { + char *pubkeyfile = NULL; + ssh_string pubkey = NULL; + ssh_private_key privkey = NULL; + int type = 0; + int rc = SSH_AUTH_ERROR; + size_t klen = strlen(filename) + 4 + 1; + + pubkeyfile = malloc(klen); + if (pubkeyfile == NULL) { + ssh_set_error_oom(session); + + return SSH_AUTH_ERROR; + } + snprintf(pubkeyfile, klen, "%s.pub", filename); + + pubkey = publickey_from_file(session, pubkeyfile, &type); + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_RARE, "Public key file %s not found. Trying to generate it.", pubkeyfile); + /* auto-detect the key type with type=0 */ + privkey = privatekey_from_file(session, filename, 0, passphrase); + } else { + SSH_LOG(SSH_LOG_RARE, "Public key file %s loaded.", pubkeyfile); + privkey = privatekey_from_file(session, filename, type, passphrase); + } + if (privkey == NULL) { + goto error; + } + /* ssh_userauth_pubkey is responsible for taking care of null-pubkey */ + rc = ssh_userauth_pubkey(session, username, pubkey, privkey); + privatekey_free(privkey); + +error: + SAFE_FREE(pubkeyfile); + ssh_string_free(pubkey); + + return rc; +} + +/* BUFFER FUNCTIONS */ + +void buffer_free(ssh_buffer buffer){ + ssh_buffer_free(buffer); +} +void *buffer_get(ssh_buffer buffer){ + return ssh_buffer_get(buffer); +} +uint32_t buffer_get_len(ssh_buffer buffer){ + return ssh_buffer_get_len(buffer); +} +ssh_buffer buffer_new(void){ + return ssh_buffer_new(); +} + +ssh_channel channel_accept_x11(ssh_channel channel, int timeout_ms){ + return ssh_channel_accept_x11(channel, timeout_ms); +} + +int channel_change_pty_size(ssh_channel channel,int cols,int rows){ + return ssh_channel_change_pty_size(channel,cols,rows); +} + +ssh_channel channel_forward_accept(ssh_session session, int timeout_ms){ + return ssh_channel_open_forward_port(session, timeout_ms, NULL, NULL, NULL); +} + +int channel_close(ssh_channel channel){ + return ssh_channel_close(channel); +} + +int channel_forward_cancel(ssh_session session, const char *address, int port){ + return ssh_channel_cancel_forward(session, address, port); +} + +int channel_forward_listen(ssh_session session, const char *address, + int port, int *bound_port){ + return ssh_channel_listen_forward(session, address, port, bound_port); +} + +void channel_free(ssh_channel channel){ + ssh_channel_free(channel); +} + +int channel_get_exit_status(ssh_channel channel){ + return ssh_channel_get_exit_status(channel); +} + +ssh_session channel_get_session(ssh_channel channel){ + return ssh_channel_get_session(channel); +} + +int channel_is_closed(ssh_channel channel){ + return ssh_channel_is_closed(channel); +} + +int channel_is_eof(ssh_channel channel){ + return ssh_channel_is_eof(channel); +} + +int channel_is_open(ssh_channel channel){ + return ssh_channel_is_open(channel); +} + +ssh_channel channel_new(ssh_session session){ + return ssh_channel_new(session); +} + +int channel_open_forward(ssh_channel channel, const char *remotehost, + int remoteport, const char *sourcehost, int localport){ + return ssh_channel_open_forward(channel, remotehost, remoteport, + sourcehost,localport); +} + +int channel_open_session(ssh_channel channel){ + return ssh_channel_open_session(channel); +} + +int channel_poll(ssh_channel channel, int is_stderr){ + return ssh_channel_poll(channel, is_stderr); +} + +int channel_read(ssh_channel channel, void *dest, uint32_t count, int is_stderr){ + return ssh_channel_read(channel, dest, count, is_stderr); +} + +/* + * This function will completely be depreciated. The old implementation was not + * renamed. + * int channel_read_buffer(ssh_channel channel, ssh_buffer buffer, uint32_t count, + * int is_stderr); + */ + +int channel_read_nonblocking(ssh_channel channel, void *dest, uint32_t count, + int is_stderr){ + return ssh_channel_read_nonblocking(channel, dest, count, is_stderr); +} + +int channel_request_env(ssh_channel channel, const char *name, const char *value){ + return ssh_channel_request_env(channel, name, value); +} + +int channel_request_exec(ssh_channel channel, const char *cmd){ + return ssh_channel_request_exec(channel, cmd); +} + +int channel_request_pty(ssh_channel channel){ + return ssh_channel_request_pty(channel); +} + +int channel_request_pty_size(ssh_channel channel, const char *term, + int cols, int rows){ + return ssh_channel_request_pty_size(channel, term, cols, rows); +} + +int channel_request_shell(ssh_channel channel){ + return ssh_channel_request_shell(channel); +} + +int channel_request_send_signal(ssh_channel channel, const char *signum){ + return ssh_channel_request_send_signal(channel, signum); +} + +int channel_request_sftp(ssh_channel channel){ + return ssh_channel_request_sftp(channel); +} + +int channel_request_subsystem(ssh_channel channel, const char *subsystem){ + return ssh_channel_request_subsystem(channel, subsystem); +} + +int channel_request_x11(ssh_channel channel, int single_connection, const char *protocol, + const char *cookie, int screen_number){ + return ssh_channel_request_x11(channel, single_connection, protocol, cookie, + screen_number); +} + +int channel_send_eof(ssh_channel channel){ + return ssh_channel_send_eof(channel); +} + +int channel_select(ssh_channel *readchans, ssh_channel *writechans, ssh_channel *exceptchans, struct + timeval * timeout){ + return ssh_channel_select(readchans, writechans, exceptchans, timeout); +} + +void channel_set_blocking(ssh_channel channel, int blocking){ + ssh_channel_set_blocking(channel, blocking); +} + +int channel_write(ssh_channel channel, const void *data, uint32_t len){ + return ssh_channel_write(channel, data, len); +} + +/* + * These functions have to be wrapped around the pki.c functions. + +void privatekey_free(ssh_private_key prv); +ssh_private_key privatekey_from_file(ssh_session session, const char *filename, + int type, const char *passphrase); +int ssh_publickey_to_file(ssh_session session, const char *file, + ssh_string pubkey, int type); +ssh_string publickey_to_string(ssh_public_key key); + * + */ + +void string_burn(ssh_string str){ + ssh_string_burn(str); +} + +ssh_string string_copy(ssh_string str){ + return ssh_string_copy(str); +} + +void *string_data(ssh_string str){ + return ssh_string_data(str); +} + +int string_fill(ssh_string str, const void *data, size_t len){ + return ssh_string_fill(str,data,len); +} + +void string_free(ssh_string str){ + ssh_string_free(str); +} + +ssh_string string_from_char(const char *what){ + return ssh_string_from_char(what); +} + +size_t string_len(ssh_string str){ + return ssh_string_len(str); +} + +ssh_string string_new(size_t size){ + return ssh_string_new(size); +} + +char *string_to_char(ssh_string str){ + return ssh_string_to_char(str); +} + +/* OLD PKI FUNCTIONS */ + +void publickey_free(ssh_public_key key) { + if (key == NULL) { + return; + } + + switch(key->type) { + case SSH_KEYTYPE_RSA: +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(key->rsa_pub); +#elif defined HAVE_LIBCRYPTO + EVP_PKEY_free(key->key_pub); +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_pk_free(key->rsa_pub); + SAFE_FREE(key->rsa_pub); +#endif /* HAVE_LIBGCRYPT */ + break; + default: + break; + } + SAFE_FREE(key); +} + +ssh_public_key publickey_from_privatekey(ssh_private_key prv) +{ + struct ssh_public_key_struct *p = NULL; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + + privkey = ssh_key_new(); + if (privkey == NULL) { + return NULL; + } + + privkey->type = prv->type; + privkey->type_c = ssh_key_type_to_char(privkey->type); + privkey->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->pk = prv->rsa_priv; +#elif defined(HAVE_LIBCRYPTO) + privkey->key = prv->key_priv; +#else + privkey->rsa = prv->rsa_priv; +#endif /* HAVE_LIBCRYPTO */ + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + privkey->key = NULL; +#else + privkey->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + ssh_key_free(privkey); + if (rc < 0) { + return NULL; + } + + p = ssh_pki_convert_key_to_publickey(pubkey); + ssh_key_free(pubkey); + + return p; +} + +ssh_private_key privatekey_from_file(ssh_session session, + const char *filename, + int type, + const char *passphrase) { + ssh_auth_callback auth_fn = NULL; + void *auth_data = NULL; + ssh_private_key privkey = NULL; + ssh_key key = NULL; + int rc; + + (void) type; /* unused */ + + if (session->common.callbacks) { + auth_fn = session->common.callbacks->auth_function; + auth_data = session->common.callbacks->userdata; + } + + + rc = ssh_pki_import_privkey_file(filename, + passphrase, + auth_fn, + auth_data, + &key); + if (rc != SSH_OK) { + return NULL; + } + + privkey = malloc(sizeof(struct ssh_private_key_struct)); + if (privkey == NULL) { + ssh_key_free(key); + return NULL; + } + + privkey->type = key->type; +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->rsa_priv = key->pk; + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + privkey->key_priv = key->key; + key->key = NULL; +#else + privkey->rsa_priv = key->rsa; + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + + ssh_key_free(key); + + return privkey; +} + +enum ssh_keytypes_e ssh_privatekey_type(ssh_private_key privatekey){ + if (privatekey==NULL) + return SSH_KEYTYPE_UNKNOWN; + return privatekey->type; +} + +void privatekey_free(ssh_private_key prv) { + if (prv == NULL) { + return; + } + +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(prv->rsa_priv); +#elif defined HAVE_LIBCRYPTO + EVP_PKEY_free(prv->key_priv); +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_pk_free(prv->rsa_priv); + SAFE_FREE(prv->rsa_priv); +#endif /* HAVE_LIBGCRYPT */ + memset(prv, 0, sizeof(struct ssh_private_key_struct)); + SAFE_FREE(prv); +} + +ssh_string publickey_from_file(ssh_session session, const char *filename, + int *type) { + ssh_key key = NULL; + ssh_string key_str = NULL; + int rc; + + (void) session; /* unused */ + + rc = ssh_pki_import_pubkey_file(filename, &key); + if (rc < 0) { + return NULL; + } + + rc = ssh_pki_export_pubkey_blob(key, &key_str); + if (rc < 0) { + ssh_key_free(key); + return NULL; + } + + if (type) { + *type = key->type; + } + ssh_key_free(key); + + return key_str; +} + +const char *ssh_type_to_char(int type) { + return ssh_key_type_to_char(type); +} + +int ssh_type_from_name(const char *name) { + return ssh_key_type_from_name(name); +} + +ssh_public_key publickey_from_string(ssh_session session, ssh_string pubkey_s) +{ + struct ssh_public_key_struct *pubkey = NULL; + ssh_key key = NULL; + int rc; + + (void) session; /* unused */ + + rc = ssh_pki_import_pubkey_blob(pubkey_s, &key); + if (rc < 0) { + return NULL; + } + + pubkey = malloc(sizeof(struct ssh_public_key_struct)); + if (pubkey == NULL) { + ssh_key_free(key); + return NULL; + } + + pubkey->type = key->type; + pubkey->type_c = key->type_c; + +#if defined(HAVE_LIBMBEDCRYPTO) + pubkey->rsa_pub = key->pk; + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + pubkey->key_pub = key->key; + key->key = NULL; +#else + pubkey->rsa_pub = key->rsa; + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + + ssh_key_free(key); + + return pubkey; +} + +ssh_string publickey_to_string(ssh_public_key pubkey) +{ + ssh_key key = NULL; + ssh_string key_blob = NULL; + int rc; + + if (pubkey == NULL) { + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + return NULL; + } + + key->type = pubkey->type; + key->type_c = pubkey->type_c; + +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = pubkey->rsa_pub; +#elif defined(HAVE_LIBCRYPTO) + key->key = pubkey->key_pub; +#else + key->rsa = pubkey->rsa_pub; +#endif /* HAVE_LIBCRYPTO */ + + rc = ssh_pki_export_pubkey_blob(key, &key_blob); + if (rc < 0) { + key_blob = NULL; + } + +#if defined(HAVE_LIBMBEDCRYPTO) + key->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + key->key = NULL; +#else + key->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + ssh_key_free(key); + + return key_blob; +} + +int ssh_publickey_to_file(ssh_session session, + const char *file, + ssh_string pubkey, + int type) +{ + FILE *fp = NULL; + char *user = NULL; + char buffer[1024]; + char *host = NULL; + unsigned char *pubkey_64 = NULL; + size_t len; + + if(session==NULL) + return SSH_ERROR; + if(file==NULL || pubkey==NULL){ + ssh_set_error(session, SSH_FATAL, "Invalid parameters"); + return SSH_ERROR; + } + pubkey_64 = bin_to_base64(ssh_string_data(pubkey), ssh_string_len(pubkey)); + if (pubkey_64 == NULL) { + return SSH_ERROR; + } + + user = ssh_get_local_username(); + if (user == NULL) { + SAFE_FREE(pubkey_64); + return SSH_ERROR; + } + + host = ssh_get_local_hostname(); + if (host == NULL) { + SAFE_FREE(user); + SAFE_FREE(pubkey_64); + return SSH_ERROR; + } + + snprintf(buffer, sizeof(buffer), "%s %s %s@%s\n", + ssh_type_to_char(type), + pubkey_64, + user, + host); + + SAFE_FREE(pubkey_64); + SAFE_FREE(user); + SAFE_FREE(host); + + SSH_LOG(SSH_LOG_RARE, "Trying to write public key file: %s", file); + SSH_LOG(SSH_LOG_PACKET, "public key file content: %s", buffer); + + fp = fopen(file, "w+"); + if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Error opening %s: %s", + file, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + len = strlen(buffer); + if (fwrite(buffer, len, 1, fp) != 1 || ferror(fp)) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Unable to write to %s", file); + fclose(fp); + unlink(file); + return SSH_ERROR; + } + + fclose(fp); + return SSH_OK; +} + +int ssh_try_publickey_from_file(ssh_session session, + const char *keyfile, + ssh_string *publickey, + int *type) { + char *pubkey_file = NULL; + size_t len; + ssh_string pubkey_string = NULL; + int pubkey_type; + + if (session == NULL || keyfile == NULL || publickey == NULL || type == NULL) { + return -1; + } + + if (session->opts.sshdir == NULL) { + if (ssh_options_apply(session) < 0) { + return -1; + } + } + + SSH_LOG(SSH_LOG_PACKET, "Trying to open privatekey %s", keyfile); + if (!ssh_file_readaccess_ok(keyfile)) { + SSH_LOG(SSH_LOG_PACKET, "Failed to open privatekey %s", keyfile); + return -1; + } + + len = strlen(keyfile) + 5; + pubkey_file = malloc(len); + if (pubkey_file == NULL) { + return -1; + } + snprintf(pubkey_file, len, "%s.pub", keyfile); + + SSH_LOG(SSH_LOG_PACKET, "Trying to open publickey %s", + pubkey_file); + if (!ssh_file_readaccess_ok(pubkey_file)) { + SSH_LOG(SSH_LOG_PACKET, "Failed to open publickey %s", + pubkey_file); + SAFE_FREE(pubkey_file); + return 1; + } + + SSH_LOG(SSH_LOG_PACKET, "Success opening public and private key"); + + /* + * We are sure both the private and public key file is readable. We return + * the public as a string, and the private filename as an argument + */ + pubkey_string = publickey_from_file(session, pubkey_file, &pubkey_type); + if (pubkey_string == NULL) { + SSH_LOG(SSH_LOG_PACKET, + "Wasn't able to open public key file %s: %s", + pubkey_file, + ssh_get_error(session)); + SAFE_FREE(pubkey_file); + return -1; + } + + SAFE_FREE(pubkey_file); + + *publickey = pubkey_string; + *type = pubkey_type; + + return 0; +} + +ssh_string ssh_get_pubkey(ssh_session session) +{ + ssh_string pubkey_blob = NULL; + int rc; + + if (session == NULL || + session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + return NULL; + } + + rc = ssh_dh_get_current_server_publickey_blob(session, + &pubkey_blob); + if (rc != 0) { + return NULL; + } + + return pubkey_blob; +} + +/**************************************************************************** + * SERVER SUPPORT + ****************************************************************************/ + +#ifdef WITH_SERVER +int ssh_accept(ssh_session session) { + return ssh_handle_key_exchange(session); +} + +int channel_write_stderr(ssh_channel channel, const void *data, uint32_t len) { + return ssh_channel_write_stderr(channel, data, len); +} + +/** @deprecated + * @brief Interface previously exported by error. + */ +ssh_message ssh_message_retrieve(ssh_session session, uint32_t packettype){ + (void) packettype; + ssh_set_error(session, SSH_FATAL, "ssh_message_retrieve: obsolete libssh call"); + return NULL; +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/src/libcrypto-compat.h b/src/libs/libssh-0.12.2/src/libcrypto-compat.h new file mode 100644 index 000000000000..0f2dc1847de7 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libcrypto-compat.h @@ -0,0 +1,14 @@ +#ifndef LIBCRYPTO_COMPAT_H +#define LIBCRYPTO_COMPAT_H + +#include + +#define NISTP256 "P-256" +#define NISTP384 "P-384" +#define NISTP521 "P-521" + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#define EVP_PKEY_eq EVP_PKEY_cmp +#endif /* OPENSSL_VERSION_NUMBER */ + +#endif /* LIBCRYPTO_COMPAT_H */ diff --git a/src/libs/libssh-0.12.2/src/libcrypto.c b/src/libs/libssh-0.12.2/src/libcrypto.c new file mode 100644 index 000000000000..849b539cbb16 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libcrypto.c @@ -0,0 +1,1668 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/libcrypto.h" +#include "libssh/pki.h" +#ifdef HAVE_OPENSSL_EVP_CHACHA20 +#include "libssh/bytearray.h" +#include "libssh/chacha20-poly1305-common.h" +#endif + +#ifdef HAVE_LIBCRYPTO +#ifdef LIBRESSL_VERSION_NUMBER +#include +#endif +#include +#include +#include +#include +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#include +#include +#else +#include +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ +#include +#if defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER) +#include +#endif + +#include "libcrypto-compat.h" + +#ifdef HAVE_OPENSSL_AES_H +#define HAS_AES +#include +#endif /* HAVE_OPENSSL_AES_H */ +#ifdef HAVE_OPENSSL_DES_H +#define HAS_DES +#include +#endif /* HAVE_OPENSSL_DES_H */ + +#if (defined(HAVE_VALGRIND_VALGRIND_H) && defined(HAVE_OPENSSL_IA32CAP_LOC)) +#include +#define CAN_DISABLE_AESNI +#endif + +#include "libssh/crypto.h" + +#ifdef HAVE_OPENSSL_EVP_KDF_CTX +#include +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +#include +#include +#endif /* OPENSSL_VERSION_NUMBER */ +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ + +#include "libssh/crypto.h" + +static int libcrypto_initialized = 0; + + +void ssh_reseed(void){ +#ifndef _WIN32 + struct timeval tv; + gettimeofday(&tv, NULL); + RAND_add(&tv, sizeof(tv), 0.0); +#endif +} + +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) +static OSSL_PROVIDER *provider = NULL; +static bool pkcs11_provider_failed = false; + +int pki_load_pkcs11_provider(void) +{ + if (OSSL_PROVIDER_available(NULL, "pkcs11") == 1) { + /* the provider is already available. + * Loaded through a configuration file? */ + return SSH_OK; + } + + if (pkcs11_provider_failed) { + /* the loading failed previously -- do not retry */ + return SSH_ERROR; + } + + provider = OSSL_PROVIDER_try_load(NULL, "pkcs11", 1); + if (provider != NULL) { + return SSH_OK; + } + + SSH_LOG(SSH_LOG_TRACE, + "Failed to load the pkcs11 provider: %s", + ERR_error_string(ERR_get_error(), NULL)); + /* Do not attempt to load it again */ + pkcs11_provider_failed = true; + return SSH_ERROR; +} +#else +static ENGINE *engine = NULL; + +ENGINE *pki_get_engine(void) +{ + int ok; + + if (engine == NULL) { + ENGINE_load_builtin_engines(); + + engine = ENGINE_by_id("pkcs11"); + if (engine == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Could not load the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + SSH_LOG(SSH_LOG_DEBUG, "Engine loaded successfully"); + + ok = ENGINE_init(engine); + if (!ok) { + SSH_LOG(SSH_LOG_TRACE, + "Could not initialize the engine: %s", + ERR_error_string(ERR_get_error(), NULL)); + ENGINE_free(engine); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, "Engine init success"); + } + return engine; +} +#endif /* defined(WITH_PKCS11_PROVIDER) */ +#endif /* defined(WITH_PKCS11_URI) */ + +#ifdef HAVE_OPENSSL_EVP_KDF_CTX +#if OPENSSL_VERSION_NUMBER < 0x30000000L +static const EVP_MD *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) +{ + switch (digest_type) { + case SSH_KDF_SHA1: + return EVP_sha1(); + case SSH_KDF_SHA256: + return EVP_sha256(); + case SSH_KDF_SHA384: + return EVP_sha384(); + case SSH_KDF_SHA512: + return EVP_sha512(); + } + return NULL; +} +#else +static const char *sshkdf_digest_to_md(enum ssh_kdf_digest digest_type) +{ + switch (digest_type) { + case SSH_KDF_SHA1: + return SN_sha1; + case SSH_KDF_SHA256: + return SN_sha256; + case SSH_KDF_SHA384: + return SN_sha384; + case SSH_KDF_SHA512: + return SN_sha512; + } + return NULL; +} +#endif /* OPENSSL_VERSION_NUMBER */ + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len) +{ + int ret = SSH_ERROR, rv; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EVP_KDF_CTX *ctx = EVP_KDF_CTX_new_id(EVP_KDF_SSHKDF); +#else + EVP_KDF_CTX *ctx = NULL; + OSSL_PARAM_BLD *param_bld = NULL; + OSSL_PARAM *params = NULL; + const char *md = NULL; + EVP_KDF *kdf = NULL; + + md = sshkdf_digest_to_md(crypto->digest_type); + if (md == NULL) { + return -1; + } + + kdf = EVP_KDF_fetch(NULL, "SSHKDF", NULL); + if (kdf == NULL) { + return -1; + } + ctx = EVP_KDF_CTX_new(kdf); + EVP_KDF_free(kdf); + + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + EVP_KDF_CTX_free(ctx); + return -1; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + if (ctx == NULL) { + goto out; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + rv = EVP_KDF_ctrl(ctx, + EVP_KDF_CTRL_SET_MD, + sshkdf_digest_to_md(crypto->digest_type)); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_KEY, key, key_len); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_ctrl(ctx, + EVP_KDF_CTRL_SET_SSHKDF_XCGHASH, + crypto->secret_hash, + crypto->digest_len); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_ctrl(ctx, EVP_KDF_CTRL_SET_SSHKDF_TYPE, key_type); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_ctrl(ctx, + EVP_KDF_CTRL_SET_SSHKDF_SESSION_ID, + crypto->session_id, + crypto->session_id_len); + if (rv != 1) { + goto out; + } + rv = EVP_KDF_derive(ctx, output, requested_len); + if (rv != 1) { + goto out; + } +#else + rv = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_KDF_PARAM_DIGEST, + md, + strlen(md)); + if (rv != 1) { + goto out; + } + rv = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_KEY, + key, + key_len); + if (rv != 1) { + goto out; + } + rv = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_XCGHASH, + crypto->secret_hash, + crypto->digest_len); + if (rv != 1) { + goto out; + } + rv = OSSL_PARAM_BLD_push_octet_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_SESSION_ID, + crypto->session_id, + crypto->session_id_len); + if (rv != 1) { + goto out; + } + rv = OSSL_PARAM_BLD_push_utf8_string(param_bld, + OSSL_KDF_PARAM_SSHKDF_TYPE, + (const char *)&key_type, + 1); + if (rv != 1) { + goto out; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + goto out; + } + + rv = EVP_KDF_derive(ctx, output, requested_len, params); + if (rv != 1) { + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + ret = SSH_OK; + +out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD_free(param_bld); + OSSL_PARAM_free(params); +#endif + EVP_KDF_CTX_free(ctx); + if (ret < 0) { + return ret; + } + return 0; +} + +#else +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len) +{ + return sshkdf_derive_key(crypto, key, key_len, + key_type, output, requested_len); +} +#endif /* HAVE_OPENSSL_EVP_KDF_CTX */ + +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) +{ + HMACCTX ctx = NULL; + EVP_PKEY *pkey = NULL; + int rc = -1; + + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + return NULL; + } + + pkey = EVP_PKEY_new_mac_key(EVP_PKEY_HMAC, NULL, key, (int)len); + if (pkey == NULL) { + goto error; + } + + switch (type) { + case SSH_HMAC_SHA1: + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha1(), NULL, pkey); + break; + case SSH_HMAC_SHA256: + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha256(), NULL, pkey); + break; + case SSH_HMAC_SHA512: + rc = EVP_DigestSignInit(ctx, NULL, EVP_sha512(), NULL, pkey); + break; + case SSH_HMAC_MD5: + rc = EVP_DigestSignInit(ctx, NULL, EVP_md5(), NULL, pkey); + break; + default: + rc = -1; + break; + } + + EVP_PKEY_free(pkey); + if (rc != 1) { + goto error; + } + return ctx; + +error: + EVP_MD_CTX_free(ctx); + return NULL; +} + +int hmac_update(HMACCTX ctx, const void *data, size_t len) +{ + return EVP_DigestSignUpdate(ctx, data, len); +} + +int hmac_final(HMACCTX ctx, unsigned char *hashmacbuf, size_t *len) +{ + size_t res = *len; + int rc; + rc = EVP_DigestSignFinal(ctx, hashmacbuf, &res); + EVP_MD_CTX_free(ctx); + if (rc == 1) { + *len = res; + } + + return rc; +} + +static void evp_cipher_init(struct ssh_cipher_struct *cipher) +{ + if (cipher->ctx == NULL) { + cipher->ctx = EVP_CIPHER_CTX_new(); + } else { + EVP_CIPHER_CTX_reset(cipher->ctx); + } + + switch(cipher->ciphertype){ + case SSH_AES128_CBC: + cipher->cipher = EVP_aes_128_cbc(); + break; + case SSH_AES192_CBC: + cipher->cipher = EVP_aes_192_cbc(); + break; + case SSH_AES256_CBC: + cipher->cipher = EVP_aes_256_cbc(); + break; + case SSH_AES128_CTR: + cipher->cipher = EVP_aes_128_ctr(); + break; + case SSH_AES192_CTR: + cipher->cipher = EVP_aes_192_ctr(); + break; + case SSH_AES256_CTR: + cipher->cipher = EVP_aes_256_ctr(); + break; + case SSH_AEAD_AES128_GCM: + cipher->cipher = EVP_aes_128_gcm(); + break; + case SSH_AEAD_AES256_GCM: + cipher->cipher = EVP_aes_256_gcm(); + break; + case SSH_3DES_CBC: + SSH_LOG(SSH_LOG_WARNING, "The DES cipher cannot be handled here"); + break; +#ifdef HAVE_BLOWFISH + case SSH_BLOWFISH_CBC: + cipher->cipher = EVP_bf_cbc(); + break; + /* ciphers not using EVP */ +#endif /* HAVE_BLOWFISH */ + case SSH_AEAD_CHACHA20_POLY1305: + SSH_LOG(SSH_LOG_TRACE, "The ChaCha cipher cannot be handled here"); + break; + case SSH_NO_CIPHER: + SSH_LOG(SSH_LOG_TRACE, "No valid ciphertype found"); + break; + } +} + +static int evp_cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, void *IV) +{ + int rc; + + evp_cipher_init(cipher); + + rc = EVP_EncryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptInit_ex failed"); + return SSH_ERROR; + } + + /* For AES-GCM we need to set IV in specific way */ + if (cipher->ciphertype == SSH_AEAD_AES128_GCM || + cipher->ciphertype == SSH_AEAD_AES256_GCM) { + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_SET_IV_FIXED, + -1, + (uint8_t *)IV); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + return SSH_ERROR; + } + } + + EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); + + return SSH_OK; +} + +static int evp_cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, + void *key, void *IV) { + int rc; + + evp_cipher_init(cipher); + + rc = EVP_DecryptInit_ex(cipher->ctx, cipher->cipher, NULL, key, IV); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptInit_ex failed"); + return SSH_ERROR; + } + + /* For AES-GCM we need to set IV in specific way */ + if (cipher->ciphertype == SSH_AEAD_AES128_GCM || + cipher->ciphertype == SSH_AEAD_AES256_GCM) { + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_SET_IV_FIXED, + -1, + (uint8_t *)IV); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_IV_FIXED failed"); + return SSH_ERROR; + } + } + + EVP_CIPHER_CTX_set_padding(cipher->ctx, 0); + + return SSH_OK; +} + +/* EVP wrapper function for encrypt/decrypt */ +static void evp_cipher_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + int outlen = 0; + int rc = 0; + + rc = EVP_EncryptUpdate(cipher->ctx, + (unsigned char *)out, + &outlen, + (unsigned char *)in, + (int)len); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); + return; + } + if (outlen != (int)len){ + SSH_LOG(SSH_LOG_DEBUG, + "EVP_EncryptUpdate: output size %d for %zu in", + outlen, + len); + return; + } +} + +static void evp_cipher_decrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + int outlen = 0; + int rc = 0; + + rc = EVP_DecryptUpdate(cipher->ctx, + (unsigned char *)out, + &outlen, + (unsigned char *)in, + (int)len); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate failed"); + return; + } + if (outlen != (int)len){ + SSH_LOG(SSH_LOG_DEBUG, + "EVP_DecryptUpdate: output size %d for %zu in", + outlen, + len); + return; + } +} + +static void evp_cipher_cleanup(struct ssh_cipher_struct *cipher) { + if (cipher->ctx != NULL) { + EVP_CIPHER_CTX_free(cipher->ctx); + } +} + +static int evp_cipher_aead_get_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + (void)cipher; + (void)seq; + + /* The length is not encrypted: Copy it to the result buffer */ + memcpy(out, in, len); + + return SSH_OK; +} + +static void evp_cipher_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + size_t authlen, aadlen; + uint8_t lastiv[1]; + int tmplen = 0; + size_t outlen; + int rc; + + (void) seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv); + if (rc == 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); + return; + } + + /* Pass over the authenticated data (not encrypted) */ + rc = EVP_EncryptUpdate(cipher->ctx, + NULL, + &tmplen, + (unsigned char *)in, + (int)aadlen); + outlen = tmplen; + if (rc == 0 || outlen != aadlen) { + SSH_LOG(SSH_LOG_TRACE, "Failed to pass authenticated data"); + return; + } + memcpy(out, in, aadlen); + + /* Encrypt the rest of the data */ + rc = EVP_EncryptUpdate(cipher->ctx, + (unsigned char *)out + aadlen, + &tmplen, + (unsigned char *)in + aadlen, + (int)(len - aadlen)); + outlen = tmplen; + if (rc != 1 || outlen != (int)len - aadlen) { + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); + return; + } + + /* compute tag */ + rc = EVP_EncryptFinal(cipher->ctx, NULL, &tmplen); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal failed: Failed to create a tag"); + return; + } + + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_GET_TAG, + (int)authlen, + (unsigned char *)tag); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_GET_TAG failed"); + return; + } +} + +static int evp_cipher_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + size_t authlen, aadlen; + uint8_t lastiv[1]; + int outlen = 0; + int rc = 0; + + (void)seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, EVP_CTRL_GCM_IV_GEN, 1, lastiv); + if (rc == 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_IV_GEN failed"); + return SSH_ERROR; + } + + /* set tag for authentication */ + rc = EVP_CIPHER_CTX_ctrl(cipher->ctx, + EVP_CTRL_GCM_SET_TAG, + (int)authlen, + (unsigned char *)complete_packet + aadlen + + encrypted_size); + if (rc == 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CTRL_GCM_SET_TAG failed"); + return SSH_ERROR; + } + + /* Pass over the authenticated data (not encrypted) */ + rc = EVP_DecryptUpdate(cipher->ctx, + NULL, + &outlen, + (unsigned char *)complete_packet, + (int)aadlen); + if (rc == 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to pass authenticated data"); + return SSH_ERROR; + } + /* Do not copy the length to the target buffer, because it is already processed */ + //memcpy(out, complete_packet, aadlen); + + /* Decrypt the rest of the data */ + rc = EVP_DecryptUpdate(cipher->ctx, + (unsigned char *)out, + &outlen, + (unsigned char *)complete_packet + aadlen, + (int)encrypted_size /* already subtracted aadlen */); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DecryptUpdate failed"); + return SSH_ERROR; + } + + if (outlen != (int)encrypted_size) { + SSH_LOG(SSH_LOG_TRACE, + "EVP_DecryptUpdate: output size %d for %zd in", + outlen, + encrypted_size); + return SSH_ERROR; + } + + /* verify tag */ + rc = EVP_DecryptFinal(cipher->ctx, NULL, &outlen); + if (rc != 1 || outlen != 0) { + SSH_LOG(SSH_LOG_TRACE, + "EVP_DecryptFinal failed: Failed authentication"); + return SSH_ERROR; + } + + return SSH_OK; +} + +#ifdef HAVE_OPENSSL_EVP_CHACHA20 + +struct chacha20_poly1305_keysched { + /* cipher handle used for encrypting the packets */ + EVP_CIPHER_CTX *main_evp; + /* cipher handle used for encrypting the length field */ + EVP_CIPHER_CTX *header_evp; +#if defined(LIBRESSL_VERSION_NUMBER) + /* LibreSSL Poly1305 context */ + poly1305_context poly_ctx; +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + /* mac handle used for authenticating the packets */ + EVP_PKEY_CTX *pctx; + /* Poly1305 key */ + EVP_PKEY *key; + /* MD context for digesting data in poly1305 */ + EVP_MD_CTX *mctx; +#else + /* MAC context used to do poly1305 */ + EVP_MAC_CTX *mctx; +#endif /* OPENSSL_VERSION_NUMBER */ +}; + +static void chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + EVP_CIPHER_CTX_free(ctx->main_evp); + ctx->main_evp = NULL; + EVP_CIPHER_CTX_free(ctx->header_evp); + ctx->header_evp = NULL; +#if defined(LIBRESSL_VERSION_NUMBER) + /* nothing to free */ +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + /* ctx->pctx is freed as part of MD context */ + EVP_PKEY_free(ctx->key); + ctx->key = NULL; + EVP_MD_CTX_free(ctx->mctx); + ctx->mctx = NULL; +#else + EVP_MAC_CTX_free(ctx->mctx); + ctx->mctx = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + SAFE_FREE(cipher->chacha20_schedule); +} + +static int chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, + void *key, + UNUSED_PARAM(void *IV)) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + uint8_t *u8key = key; + int ret = SSH_ERROR, rv; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC *mac = NULL; +#endif + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + /* ChaCha20 initialization */ + /* K2 uses the first half of the key */ + ctx->main_evp = EVP_CIPHER_CTX_new(); + if (ctx->main_evp == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CIPHER_CTX_new failed"); + goto out; + } + rv = EVP_EncryptInit_ex(ctx->main_evp, EVP_chacha20(), NULL, u8key, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit failed"); + goto out; + } + /* K1 uses the second half of the key */ + ctx->header_evp = EVP_CIPHER_CTX_new(); + if (ctx->header_evp == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CIPHER_CTX_new failed"); + goto out; + } + rv = EVP_EncryptInit_ex(ctx->header_evp, EVP_chacha20(), NULL, + u8key + CHACHA20_KEYLEN, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit failed"); + goto out; + } + + /* The Poly1305 key initialization is delayed to the time we know + * the actual key for packet so we do not need to create a bogus keys + */ +#if defined(LIBRESSL_VERSION_NUMBER) + /* nothing, poly1305_context is stack based */ +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + ctx->mctx = EVP_MD_CTX_new(); + if (ctx->mctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MD_CTX_new failed"); + return SSH_ERROR; + } +#else + mac = EVP_MAC_fetch(NULL, SN_poly1305, NULL); + if (mac == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_fetch failed"); + goto out; + } + ctx->mctx = EVP_MAC_CTX_new(mac); + if (ctx->mctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_CTX_new failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + ret = SSH_OK; +out: +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MAC_free(mac); +#endif + if (ret != SSH_OK) { + chacha20_poly1305_cleanup(cipher); + } + return ret; +} + +static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; + +static int chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, + uint64_t seq, + int do_encrypt) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t seqbuf[16] = {0}; + int ret; + + /* Prepare the IV for OpenSSL -- it needs to be 128 b long. First 32 b is + * counter the rest is nonce. The memory is initialized to zeros + * (counter starts from 0) and we set the sequence number in the second half + */ + PUSH_BE_U64(seqbuf, 8, seq); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("seqbuf (chacha20 IV)", seqbuf, sizeof(seqbuf)); +#endif /* DEBUG_CRYPTO */ + + ret = EVP_CipherInit_ex(ctx->header_evp, NULL, NULL, NULL, seqbuf, do_encrypt); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit_ex(header_evp) failed"); + return SSH_ERROR; + } + + ret = EVP_CipherInit_ex(ctx->main_evp, NULL, NULL, NULL, seqbuf, do_encrypt); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherInit_ex(main_evp) failed"); + return SSH_ERROR; + } + + return SSH_OK; +} + +static int chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, + uint64_t seq, + int do_encrypt) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + int ret = SSH_ERROR, len, rv; + + /* The initialization for decrypt was already done with the length block */ + if (do_encrypt) { + rv = chacha20_poly1305_set_iv(cipher, seq, do_encrypt); + if (rv != SSH_OK) { + return SSH_ERROR; + } + } + + /* Output full ChaCha block so that counter increases by one for + * next step. */ + rv = EVP_CipherUpdate(ctx->main_evp, poly_key, &len, + (unsigned char *)zero_block, sizeof(zero_block)); + if (rv != 1 || len != CHACHA20_BLOCKSIZE) { + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptUpdate failed"); + goto out; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("poly_key", poly_key, POLY1305_KEYLEN); +#endif /* DEBUG_CRYPTO */ + +/* LibreSSL path: use direct Poly1305 implementation */ +#if defined(LIBRESSL_VERSION_NUMBER) + CRYPTO_poly1305_init(&ctx->poly_ctx, poly_key); +/* Set the Poly1305 key */ +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + if (ctx->key == NULL) { + /* Poly1305 Initialization needs to know the actual key */ + ctx->key = EVP_PKEY_new_mac_key(EVP_PKEY_POLY1305, + NULL, + poly_key, + POLY1305_KEYLEN); + if (ctx->key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_new_mac_key failed"); + goto out; + } + rv = EVP_DigestSignInit(ctx->mctx, &ctx->pctx, NULL, NULL, ctx->key); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignInit failed"); + goto out; + } + } else { + /* Updating the key is easier but less obvious */ + rv = EVP_PKEY_CTX_ctrl(ctx->pctx, + -1, + EVP_PKEY_OP_SIGNCTX, + EVP_PKEY_CTRL_SET_MAC_KEY, + POLY1305_KEYLEN, + (void *)poly_key); + if (rv <= 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_PKEY_CTX_ctrl failed"); + goto out; + } + } +#else + rv = EVP_MAC_init(ctx->mctx, poly_key, POLY1305_KEYLEN, NULL); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_init failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + ret = SSH_OK; +out: + ssh_burn(poly_key, sizeof(poly_key)); + return ret; +} + +static int +chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int rv, outlen; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", (uint8_t *)in, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* Set IV for the header EVP */ + rv = chacha20_poly1305_set_iv(cipher, seq, 0); + if (rv != SSH_OK) { + return SSH_ERROR; + } + + rv = EVP_CipherUpdate(ctx->header_evp, out, &outlen, in, (int)len); + if (rv != 1 || outlen != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("deciphered length", out, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + rv = EVP_CipherFinal_ex(ctx->header_evp, out + outlen, &outlen); + if (rv != 1 || outlen != 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherFinal_ex failed"); + return SSH_ERROR; + } + + return SSH_OK; +} + +static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t *mac = + (uint8_t *)complete_packet + sizeof(uint32_t) + encrypted_size; + uint8_t tag[POLY1305_TAGLEN] = {0}; + int ret = SSH_ERROR; + int rv, cmp, len = 0; +#if !defined(LIBRESSL_VERSION_NUMBER) + size_t taglen = POLY1305_TAGLEN; +#endif + + /* Prepare the Poly1305 key */ + rv = chacha20_poly1305_packet_setup(cipher, seq, 0); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("received mac", mac, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Calculate MAC of received data */ +#if defined(LIBRESSL_VERSION_NUMBER) + CRYPTO_poly1305_update(&ctx->poly_ctx, + complete_packet, + encrypted_size + sizeof(uint32_t)); + CRYPTO_poly1305_finish(&ctx->poly_ctx, tag); + +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + rv = EVP_DigestSignUpdate(ctx->mctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed"); + goto out; + } + + rv = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "poly1305 verify error"); + goto out; + } +#else + rv = EVP_MAC_update(ctx->mctx, + complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed"); + goto out; + } + + rv = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_final failed"); + goto out; + } +#endif /* OPENSSL_VERSION_NUMBER */ + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("calculated mac", tag, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Verify the calculated MAC matches the attached MAC */ + cmp = CRYPTO_memcmp(tag, mac, POLY1305_TAGLEN); + if (cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + return SSH_ERROR; + } + + /* Decrypt the message */ + rv = EVP_CipherUpdate(ctx->main_evp, + out, + &len, + (uint8_t *)complete_packet + sizeof(uint32_t), + (int)encrypted_size); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); + goto out; + } + + rv = EVP_CipherFinal_ex(ctx->main_evp, out + len, &len); + if (rv != 1 || len != 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherFinal_ex failed"); + goto out; + } + + ret = SSH_OK; +out: + return ret; +} + +static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; +#if !defined(LIBRESSL_VERSION_NUMBER) + size_t taglen = POLY1305_TAGLEN; +#endif + int ret, outlen = 0; + + /* Prepare the Poly1305 key */ + ret = chacha20_poly1305_packet_setup(cipher, seq, 1); + if (ret != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); + return; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("plaintext length", + (unsigned char *)&in_packet->length, + sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + /* step 2, encrypt length field */ + ret = EVP_CipherUpdate(ctx->header_evp, + (unsigned char *)&out_packet->length, + &outlen, + (unsigned char *)&in_packet->length, + sizeof(uint32_t)); + if (ret != 1 || outlen != sizeof(uint32_t)) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); + return; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", + (unsigned char *)&out_packet->length, + outlen); +#endif /* DEBUG_CRYPTO */ + ret = EVP_CipherFinal_ex(ctx->header_evp, (uint8_t *)out + outlen, &outlen); + if (ret != 1 || outlen != 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_EncryptFinal_ex failed"); + return; + } + + /* step 3, encrypt packet payload (main_evp counter == 1) */ + /* We already did encrypt one block so the counter should be in the correct position */ + ret = EVP_CipherUpdate(ctx->main_evp, + out_packet->payload, + &outlen, + in_packet->payload, + (int)(len - sizeof(uint32_t))); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_CipherUpdate failed"); + return; + } + + /* step 4, compute the MAC */ +#if defined(LIBRESSL_VERSION_NUMBER) + + CRYPTO_poly1305_update(&ctx->poly_ctx, + (const unsigned char *)out_packet, + len); + CRYPTO_poly1305_finish(&ctx->poly_ctx, tag); +#elif OPENSSL_VERSION_NUMBER < 0x30000000L + ret = EVP_DigestSignUpdate(ctx->mctx, out_packet, len); + if (ret <= 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignUpdate failed"); + return; + } + ret = EVP_DigestSignFinal(ctx->mctx, tag, &taglen); + if (ret <= 0) { + SSH_LOG(SSH_LOG_TRACE, "EVP_DigestSignFinal failed"); + return; + } +#else + ret = EVP_MAC_update(ctx->mctx, (void *)out_packet, len); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_update failed"); + return; + } + + ret = EVP_MAC_final(ctx->mctx, tag, &taglen, POLY1305_TAGLEN); + if (ret != 1) { + SSH_LOG(SSH_LOG_TRACE, "EVP_MAC_final failed"); + return; + } +#endif /* OPENSSL_VERSION_NUMBER */ +} +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ + +#ifdef WITH_INSECURE_NONE +static void none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) +{ + memcpy(out, in, len); +} +#endif /* WITH_INSECURE_NONE */ + +/* + * The table of supported ciphers + */ +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef HAVE_BLOWFISH + { + .name = "blowfish-cbc", + .blocksize = 8, + .ciphertype = SSH_BLOWFISH_CBC, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, +#endif /* HAVE_BLOWFISH */ +#ifdef HAS_AES + { + .name = "aes128-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES128_CTR, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes192-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES192_CTR, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes256-ctr", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES256_CTR, + .keysize = 256, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes128-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES128_CBC, + .keysize = 128, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes192-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES192_CBC, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes256-cbc", + .blocksize = AES_BLOCK_SIZE, + .ciphertype = SSH_AES256_CBC, + .keysize = 256, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes128-gcm@openssh.com", + .blocksize = AES_BLOCK_SIZE, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .ciphertype = SSH_AEAD_AES128_GCM, + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .aead_encrypt = evp_cipher_aead_encrypt, + .aead_decrypt_length = evp_cipher_aead_get_length, + .aead_decrypt = evp_cipher_aead_decrypt, + .cleanup = evp_cipher_cleanup, + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = AES_BLOCK_SIZE, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .ciphertype = SSH_AEAD_AES256_GCM, + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .aead_encrypt = evp_cipher_aead_encrypt, + .aead_decrypt_length = evp_cipher_aead_get_length, + .aead_decrypt = evp_cipher_aead_decrypt, + .cleanup = evp_cipher_cleanup, + }, +#endif /* HAS_AES */ +#ifdef HAS_DES + { + .name = "3des-cbc", + .blocksize = 8, + .ciphertype = SSH_3DES_CBC, + .keysize = 192, + .set_encrypt_key = evp_cipher_set_encrypt_key, + .set_decrypt_key = evp_cipher_set_decrypt_key, + .encrypt = evp_cipher_encrypt, + .decrypt = evp_cipher_decrypt, + .cleanup = evp_cipher_cleanup, + }, +#endif /* HAS_DES */ + { +#ifdef HAVE_OPENSSL_EVP_CHACHA20 + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = CHACHA20_BLOCKSIZE / 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 2 * CHACHA20_KEYLEN * 8, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_poly1305_set_key, + .set_decrypt_key = chacha20_poly1305_set_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_poly1305_cleanup +#else + .name = "chacha20-poly1305@openssh.com" +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ + }, +#ifdef WITH_INSECURE_NONE + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt, + }, +#endif /* WITH_INSECURE_NONE */ + { + .name = NULL, + }, +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +/** + * @internal + * @brief Initialize libcrypto's subsystem + */ +int ssh_crypto_init(void) +{ +#ifndef HAVE_OPENSSL_EVP_CHACHA20 + size_t i; +#endif + + if (libcrypto_initialized) { + return SSH_OK; + } + if (OpenSSL_version_num() != OPENSSL_VERSION_NUMBER) { + SSH_LOG(SSH_LOG_DEBUG, + "libssh compiled with %s " + "headers, currently running with %s.", + OPENSSL_VERSION_TEXT, + OpenSSL_version(OpenSSL_version_num())); + } +#ifdef CAN_DISABLE_AESNI + /* + * disable AES-NI when running within Valgrind, because they generate + * too many "uninitialized memory access" false positives + */ + if (RUNNING_ON_VALGRIND) { + SSH_LOG(SSH_LOG_INFO, "Running within Valgrind, disabling AES-NI"); + /* Bit #57 denotes AES-NI instruction set extension */ + OPENSSL_ia32cap &= ~(1LL << 57); + } +#endif /* CAN_DISABLE_AESNI */ + +#ifndef HAVE_OPENSSL_EVP_CHACHA20 + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + ssh_get_chacha20poly1305_cipher(), + sizeof(struct ssh_cipher_struct)); + break; + } + } +#endif /* HAVE_OPENSSL_EVP_CHACHA20 */ + + libcrypto_initialized = 1; + + return SSH_OK; +} + +/** + * @internal + * @brief Finalize libcrypto's subsystem + */ +void ssh_crypto_finalize(void) +{ + if (!libcrypto_initialized) { + return; + } + +/* TODO this should finalize engine if it was started, but during atexit calls, + * we are crashing. AFAIK this is related to the dlopened pkcs11 modules calling + * the crypto cleanups earlier. */ +#if 0 + if (engine != NULL) { + ENGINE_finish(engine); + ENGINE_free(engine); + engine = NULL; + } +#endif +#if defined(WITH_PKCS11_URI) +#if defined(WITH_PKCS11_PROVIDER) + if (provider != NULL) { + OSSL_PROVIDER_unload(provider); + provider = NULL; + } +#endif /* WITH_PKCS11_PROVIDER */ +#endif /* WITH_PKCS11_URI */ + + libcrypto_initialized = 0; +} + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L +/** + * @internal + * @brief Create EVP_PKEY from parameters + * + * @param[in] name Algorithm to use. For more info see manpage of + * EVP_PKEY_CTX_new_from_name + * + * @param[in] param_bld Constructed param builder for the pkey + * + * @param[out] pkey Created EVP_PKEY variable + * + * @param[in] selection Reference selections at man EVP_PKEY_FROMDATA + * + * @return 0 on success, -1 on error + */ +int evp_build_pkey(const char *name, + OSSL_PARAM_BLD *param_bld, + EVP_PKEY **pkey, + int selection) +{ + int rc; + EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + OSSL_PARAM *params = NULL; + + if (ctx == NULL) { + return -1; + } + + params = OSSL_PARAM_BLD_to_param(param_bld); + if (params == NULL) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, pkey, selection, params); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + return -1; + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +/** + * @brief creates a copy of EVP_PKEY + * + * @param[in] name Algorithm to use. For more info see manpage of + * EVP_PKEY_CTX_new_from_name + * + * @param[in] key Key being duplicated from + * + * @param[in] demote Same as at pki_key_dup, only the public + * part of the key gets duplicated if true + * + * @param[out] new_key The key where the duplicate is saved + * + * @return 0 on success, -1 on error + */ +static int +evp_dup_pkey(const char *name, const ssh_key key, int demote, ssh_key new_key) +{ + int rc; + EVP_PKEY_CTX *ctx = NULL; + OSSL_PARAM *params = NULL; + + /* The simple case -- just reference the existing key */ + if (!demote || (key->flags & SSH_KEY_FLAG_PRIVATE) == 0) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + return -1; + } + new_key->key = key->key; + return SSH_OK; + } + + /* demote == 1 */ + ctx = EVP_PKEY_CTX_new_from_name(NULL, name, NULL); + if (ctx == NULL) { + return -1; + } + + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + return -1; + } + + if (strcmp(name, "EC") == 0) { + OSSL_PARAM *locate_param = NULL; + /* For ECC keys provided by engine or provider, we need to have the + * explicit public part available, otherwise the key will not be + * usable */ + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); + if (locate_param == NULL) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + } + rc = EVP_PKEY_fromdata_init(ctx); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + rc = EVP_PKEY_fromdata(ctx, &(new_key->key), EVP_PKEY_PUBLIC_KEY, params); + if (rc != 1) { + EVP_PKEY_CTX_free(ctx); + OSSL_PARAM_free(params); + return -1; + } + + OSSL_PARAM_free(params); + EVP_PKEY_CTX_free(ctx); + + return SSH_OK; +} + +int evp_dup_rsa_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey(SN_rsa, key, demote, new_key); +} + +int evp_dup_ecdsa_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey("EC", key, demote, new_key); +} + +int evp_dup_ed25519_pkey(const ssh_key key, ssh_key new_key, int demote) +{ + return evp_dup_pkey(SN_ED25519, key, demote, new_key); +} + +#endif /* OPENSSL_VERSION_NUMBER */ + +ssh_string pki_key_make_ecpoint_string(const EC_GROUP *g, const EC_POINT *p) +{ + ssh_string s = NULL; + size_t len; + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + NULL, + 0, + NULL); + if (len == 0) { + return NULL; + } + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + len = EC_POINT_point2oct(g, + p, + POINT_CONVERSION_UNCOMPRESSED, + ssh_string_data(s), + ssh_string_len(s), + NULL); + if (len != ssh_string_len(s)) { + SSH_STRING_FREE(s); + return NULL; + } + + return s; +} + +int pki_key_ecgroup_name_to_nid(const char *group) +{ + if (strcmp(group, NISTP256) == 0 || strcmp(group, "secp256r1") == 0 || + strcmp(group, SN_X9_62_prime256v1) == 0) { + return NID_X9_62_prime256v1; + } else if (strcmp(group, NISTP384) == 0 || + strcmp(group, SN_secp384r1) == 0) { + return NID_secp384r1; + } else if (strcmp(group, NISTP521) == 0 || + strcmp(group, SN_secp521r1) == 0) { + return NID_secp521r1; + } + return -1; +} +#endif /* LIBCRYPTO */ diff --git a/src/libs/libssh-0.12.2/src/libgcrypt.c b/src/libs/libssh-0.12.2/src/libgcrypt.c new file mode 100644 index 000000000000..8cce1a9f27a2 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libgcrypt.c @@ -0,0 +1,1011 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/string.h" +#include "libssh/misc.h" +#ifdef HAVE_GCRYPT_CHACHA_POLY +#include "libssh/chacha20-poly1305-common.h" +#endif + +#ifdef HAVE_LIBGCRYPT +#include + +#ifdef HAVE_GCRYPT_CHACHA_POLY + +struct chacha20_poly1305_keysched { + bool initialized; + /* cipher handle used for encrypting the packets */ + gcry_cipher_hd_t main_hd; + /* cipher handle used for encrypting the length field */ + gcry_cipher_hd_t header_hd; + /* mac handle used for authenticating the packets */ + gcry_mac_hd_t mac_hd; +}; + +static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; +#endif /* HAVE_GCRYPT_CHACHA_POLY */ + +static int libgcrypt_initialized = 0; + +static int alloc_key(struct ssh_cipher_struct *cipher) { + cipher->key = malloc(cipher->keylen); + if (cipher->key == NULL) { + return -1; + } + + return 0; +} + +void ssh_reseed(void){ +} + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len) +{ + return sshkdf_derive_key(crypto, key, key_len, + key_type, output, requested_len); +} + +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) { + HMACCTX c = NULL; + + switch(type) { + case SSH_HMAC_SHA1: + gcry_md_open(&c, GCRY_MD_SHA1, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_SHA256: + gcry_md_open(&c, GCRY_MD_SHA256, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_SHA512: + gcry_md_open(&c, GCRY_MD_SHA512, GCRY_MD_FLAG_HMAC); + break; + case SSH_HMAC_MD5: + gcry_md_open(&c, GCRY_MD_MD5, GCRY_MD_FLAG_HMAC); + break; + default: + c = NULL; + } + + gcry_md_setkey(c, key, len); + + return c; +} + +int hmac_update(HMACCTX c, const void *data, size_t len) { + gcry_md_write(c, data, len); + return 1; +} + +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) { + unsigned int tmp = gcry_md_get_algo_dlen(gcry_md_get_algo(c)); + *len = (size_t)tmp; + memcpy(hashmacbuf, gcry_md_read(c, 0), *len); + gcry_md_close(c); + return 1; +} + +#ifdef HAVE_BLOWFISH +/* the wrapper functions for blowfish */ +static int blowfish_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV){ + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_BLOWFISH, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 16)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + } + + return 0; +} + +static void blowfish_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, size_t len) { + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void blowfish_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, size_t len) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} +#endif /* HAVE_BLOWFISH */ + +static int aes_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { + int mode=GCRY_CIPHER_MODE_CBC; + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if(strstr(cipher->name,"-ctr")) + mode=GCRY_CIPHER_MODE_CTR; + if (strstr(cipher->name, "-gcm")) + mode = GCRY_CIPHER_MODE_GCM; + switch (cipher->keysize) { + case 128: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES128, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + case 192: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES192, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + case 256: + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_AES256, + mode, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unsupported key length %u.", cipher->keysize); + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, cipher->keysize / 8)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + if(mode == GCRY_CIPHER_MODE_CBC){ + if (gcry_cipher_setiv(cipher->key[0], IV, 16)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + } else if (mode == GCRY_CIPHER_MODE_GCM) { + /* Store the IV so we can handle the packet counter increments later + * The IV is passed to the cipher context later. + */ + memcpy(cipher->last_iv, IV, AES_GCM_IVLEN); + } else { + if(gcry_cipher_setctr(cipher->key[0],IV,16)){ + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + } + } + + return 0; +} + +static void aes_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void aes_decrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +static int +aes_aead_get_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + (void)cipher; + (void)seq; + + /* The length is not encrypted: Copy it to the result buffer */ + memcpy(out, in, len); + + return SSH_OK; +} + +static void +aes_gcm_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + gpg_error_t err; + size_t aadlen, authlen; + + (void)seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + err = gcry_cipher_setiv(cipher->key[0], + cipher->last_iv, + AES_GCM_IVLEN); + /* This actually does not increment the packet counter for the + * current encryption operation, but for the next one. The first + * operation needs to be completed with the derived IV. + * + * The IV buffer has the following structure: + * [ 4B static IV ][ 8B packet counter ][ 4B block counter ] + */ + uint64_inc(cipher->last_iv + 4); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + return; + } + + /* Pass the authenticated data (packet_length) */ + err = gcry_cipher_authenticate(cipher->key[0], in, aadlen); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_authenticate failed: %s", + gpg_strerror(err)); + return; + } + memcpy(out, in, aadlen); + + /* Encrypt the rest of the data */ + err = gcry_cipher_encrypt(cipher->key[0], + (unsigned char *)out + aadlen, + len - aadlen, + (unsigned char *)in + aadlen, + len - aadlen); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + return; + } + + /* Calculate the tag */ + err = gcry_cipher_gettag(cipher->key[0], + (void *)tag, + authlen); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_gettag failed: %s", + gpg_strerror(err)); + return; + } +} + +static int +aes_gcm_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + gpg_error_t err; + size_t aadlen, authlen; + + (void)seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* increment IV */ + err = gcry_cipher_setiv(cipher->key[0], + cipher->last_iv, + AES_GCM_IVLEN); + /* This actually does not increment the packet counter for the + * current encryption operation, but for the next one. The first + * operation needs to be completed with the derived IV. + * + * The IV buffer has the following structure: + * [ 4B static IV ][ 8B packet counter ][ 4B block counter ] + */ + uint64_inc(cipher->last_iv + 4); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + /* Pass the authenticated data (packet_length) */ + err = gcry_cipher_authenticate(cipher->key[0], + complete_packet, + aadlen); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_authenticate failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + /* Do not copy the length to the target buffer, because it is already processed */ + //memcpy(out, complete_packet, aadlen); + + /* Encrypt the rest of the data */ + err = gcry_cipher_decrypt(cipher->key[0], + out, + encrypted_size, + (unsigned char *)complete_packet + aadlen, + encrypted_size); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + /* Check the tag */ + err = gcry_cipher_checktag(cipher->key[0], + (unsigned char *)complete_packet + aadlen + encrypted_size, + authlen); + if (gpg_err_code(err) == GPG_ERR_CHECKSUM) { + SSH_LOG(SSH_LOG_DEBUG, "The authentication tag does not match"); + return SSH_ERROR; + } else if (err != GPG_ERR_NO_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "General error while decryption: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + return SSH_OK; +} + +static int des3_set_key(struct ssh_cipher_struct *cipher, void *key, void *IV) { + if (cipher->key == NULL) { + if (alloc_key(cipher) < 0) { + return -1; + } + if (gcry_cipher_open(&cipher->key[0], GCRY_CIPHER_3DES, + GCRY_CIPHER_MODE_CBC, 0)) { + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setkey(cipher->key[0], key, 24)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + if (gcry_cipher_setiv(cipher->key[0], IV, 8)) { + gcry_cipher_close(cipher->key[0]); + SAFE_FREE(cipher->key); + return -1; + } + } + + return 0; +} + +static void des3_encrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, size_t len) { + gcry_cipher_encrypt(cipher->key[0], out, len, in, len); +} + +static void des3_decrypt(struct ssh_cipher_struct *cipher, void *in, + void *out, size_t len) { + gcry_cipher_decrypt(cipher->key[0], out, len, in, len); +} + +#ifdef HAVE_GCRYPT_CHACHA_POLY +static void chacha20_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + if (ctx->initialized) { + gcry_cipher_close(ctx->main_hd); + gcry_cipher_close(ctx->header_hd); + gcry_mac_close(ctx->mac_hd); + ctx->initialized = false; + } + + SAFE_FREE(cipher->chacha20_schedule); +} + +static int chacha20_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + UNUSED_PARAM(void *IV)) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + uint8_t *u8key = key; + gpg_error_t err; + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + if (!ctx->initialized) { + /* Open cipher/mac handles. */ + err = gcry_cipher_open(&ctx->main_hd, GCRY_CIPHER_CHACHA20, + GCRY_CIPHER_MODE_STREAM, 0); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_open failed: %s", + gpg_strerror(err)); + SAFE_FREE(cipher->chacha20_schedule); + return -1; + } + err = gcry_cipher_open(&ctx->header_hd, GCRY_CIPHER_CHACHA20, + GCRY_CIPHER_MODE_STREAM, 0); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_open failed: %s", + gpg_strerror(err)); + gcry_cipher_close(ctx->main_hd); + SAFE_FREE(cipher->chacha20_schedule); + return -1; + } + err = gcry_mac_open(&ctx->mac_hd, GCRY_MAC_POLY1305, 0, NULL); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_open failed: %s", + gpg_strerror(err)); + gcry_cipher_close(ctx->main_hd); + gcry_cipher_close(ctx->header_hd); + SAFE_FREE(cipher->chacha20_schedule); + return -1; + } + + ctx->initialized = true; + } + + err = gcry_cipher_setkey(ctx->main_hd, u8key, CHACHA20_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setkey failed: %s", + gpg_strerror(err)); + chacha20_cleanup(cipher); + return -1; + } + + err = gcry_cipher_setkey(ctx->header_hd, u8key + CHACHA20_KEYLEN, + CHACHA20_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setkey failed: %s", + gpg_strerror(err)); + chacha20_cleanup(cipher); + return -1; + } + + return 0; +} + +static void chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + size_t taglen = POLY1305_TAGLEN; + gpg_error_t err; + + seq = htonll(seq); + + /* step 1, prepare the poly1305 key */ + err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + goto out; + } + /* Output full ChaCha block so that counter increases by one for + * payload encryption step. */ + err = gcry_cipher_encrypt(ctx->main_hd, + poly_key, + sizeof(poly_key), + zero_block, + sizeof(zero_block)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_setkey failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 2, encrypt length field */ + err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_cipher_encrypt(ctx->header_hd, + (uint8_t *)&out_packet->length, + sizeof(uint32_t), + (uint8_t *)&in_packet->length, + sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 3, encrypt packet payload (main_hd counter == 1) */ + err = gcry_cipher_encrypt(ctx->main_hd, + out_packet->payload, + len - sizeof(uint32_t), + in_packet->payload, + len - sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 4, compute the MAC */ + err = gcry_mac_write(ctx->mac_hd, (uint8_t *)out_packet, len); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_write failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_read(ctx->mac_hd, tag, &taglen); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_read failed: %s", + gpg_strerror(err)); + goto out; + } + +out: + ssh_burn(poly_key, sizeof(poly_key)); +} + +static int chacha20_poly1305_aead_decrypt_length( + struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + gpg_error_t err; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + seq = htonll(seq); + + err = gcry_cipher_setiv(ctx->header_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + err = gcry_cipher_decrypt(ctx->header_hd, + out, + sizeof(uint32_t), + in, + sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + return SSH_OK; +} + +static int chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + + encrypted_size; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + int ret = SSH_ERROR; + gpg_error_t err; + + seq = htonll(seq); + + /* step 1, prepare the poly1305 key */ + err = gcry_cipher_setiv(ctx->main_hd, (uint8_t *)&seq, sizeof(seq)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_setiv failed: %s", + gpg_strerror(err)); + goto out; + } + /* Output full ChaCha block so that counter increases by one for + * decryption step. */ + err = gcry_cipher_encrypt(ctx->main_hd, + poly_key, + sizeof(poly_key), + zero_block, + sizeof(zero_block)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_encrypt failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_setkey(ctx->mac_hd, poly_key, POLY1305_KEYLEN); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_setkey failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 2, check MAC */ + err = gcry_mac_write(ctx->mac_hd, (uint8_t *)complete_packet, + encrypted_size + sizeof(uint32_t)); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_write failed: %s", + gpg_strerror(err)); + goto out; + } + err = gcry_mac_verify(ctx->mac_hd, mac, POLY1305_TAGLEN); + if (gpg_err_code(err) == GPG_ERR_CHECKSUM) { + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + goto out; + } else if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_mac_verify failed: %s", + gpg_strerror(err)); + goto out; + } + + /* step 3, decrypt packet payload (main_hd counter == 1) */ + err = gcry_cipher_decrypt(ctx->main_hd, + out, + encrypted_size, + (uint8_t *)complete_packet + sizeof(uint32_t), + encrypted_size); + if (err != 0) { + SSH_LOG(SSH_LOG_TRACE, "gcry_cipher_decrypt failed: %s", + gpg_strerror(err)); + goto out; + } + + ret = SSH_OK; + +out: + ssh_burn(poly_key, sizeof(poly_key)); + return ret; +} +#endif /* HAVE_GCRYPT_CHACHA_POLY */ + +#ifdef WITH_INSECURE_NONE +static void +none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) +{ + memcpy(out, in, len); +} +#endif /* WITH_INSECURE_NONE */ + +/* the table of supported ciphers */ +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef HAVE_BLOWFISH + { + .name = "blowfish-cbc", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = blowfish_set_key, + .set_decrypt_key = blowfish_set_key, + .encrypt = blowfish_encrypt, + .decrypt = blowfish_decrypt + }, +#endif /* HAVE_BLOWFISH */ + { + .name = "aes128-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_encrypt + }, + { + .name = "aes192-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_encrypt + }, + { + .name = "aes256-ctr", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_encrypt + }, + { + .name = "aes128-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_decrypt + }, + { + .name = "aes192-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_decrypt + }, + { + .name = "aes256-cbc", + .blocksize = 16, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .encrypt = aes_encrypt, + .decrypt = aes_decrypt + }, + { + .name = "aes128-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .aead_encrypt = aes_gcm_encrypt, + .aead_decrypt_length = aes_aead_get_length, + .aead_decrypt = aes_gcm_decrypt, + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .set_encrypt_key = aes_set_key, + .set_decrypt_key = aes_set_key, + .aead_encrypt = aes_gcm_encrypt, + .aead_decrypt_length = aes_aead_get_length, + .aead_decrypt = aes_gcm_decrypt, + }, + { + .name = "3des-cbc", + .blocksize = 8, + .keylen = sizeof(gcry_cipher_hd_t), + .key = NULL, + .keysize = 192, + .set_encrypt_key = des3_set_key, + .set_decrypt_key = des3_set_key, + .encrypt = des3_encrypt, + .decrypt = des3_decrypt + }, + { +#ifdef HAVE_GCRYPT_CHACHA_POLY + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 2 * CHACHA20_KEYLEN * 8, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_set_encrypt_key, + .set_decrypt_key = chacha20_set_encrypt_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_cleanup +#else + .name = "chacha20-poly1305@openssh.com" +#endif + }, +#ifdef WITH_INSECURE_NONE + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt + }, +#endif /* WITH_INSECURE_NONE */ + { + .name = NULL, + .blocksize = 0, + .keylen = 0, + .key = NULL, + .keysize = 0, + .set_encrypt_key = NULL, + .set_decrypt_key = NULL, + .encrypt = NULL, + .decrypt = NULL + } +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +/* + * Extract an MPI from the given s-expression SEXP named NAME which is + * encoded using INFORMAT and store it in a newly allocated ssh_string + * encoded using OUTFORMAT. + */ +ssh_string ssh_sexp_extract_mpi(const gcry_sexp_t sexp, + const char *name, + enum gcry_mpi_format informat, + enum gcry_mpi_format outformat) +{ + gpg_error_t err; + ssh_string result = NULL; + gcry_sexp_t fragment = NULL; + gcry_mpi_t mpi = NULL; + size_t size; + + fragment = gcry_sexp_find_token(sexp, name, 0); + if (fragment == NULL) { + goto fail; + } + + mpi = gcry_sexp_nth_mpi(fragment, 1, informat); + if (mpi == NULL) { + goto fail; + } + + err = gcry_mpi_print(outformat, NULL, 0, &size, mpi); + if (err != 0) { + goto fail; + } + + result = ssh_string_new(size); + if (result == NULL) { + goto fail; + } + + err = gcry_mpi_print(outformat, ssh_string_data(result), size, NULL, mpi); + if (err != 0) { + ssh_string_burn(result); + SSH_STRING_FREE(result); + result = NULL; + goto fail; + } + +fail: + gcry_sexp_release(fragment); + gcry_mpi_release(mpi); + return result; +} + + +/** + * @internal + * + * @brief Initialize libgcrypt's subsystem + */ +int ssh_crypto_init(void) +{ + UNUSED_VAR(size_t i); + + if (libgcrypt_initialized) { + return SSH_OK; + } + + gcry_check_version(NULL); + + /* While the secure memory is not set up */ + gcry_control (GCRYCTL_SUSPEND_SECMEM_WARN); + + if (!gcry_control(GCRYCTL_INITIALIZATION_FINISHED_P, 0)) { + gcry_control(GCRYCTL_USE_SECURE_RNDPOOL); + gcry_control(GCRYCTL_INIT_SECMEM, 32768, 0); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); + } + + /* Re-enable warning */ + gcry_control (GCRYCTL_RESUME_SECMEM_WARN); + +#ifndef HAVE_GCRYPT_CHACHA_POLY + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + ssh_get_chacha20poly1305_cipher(), + sizeof(struct ssh_cipher_struct)); + break; + } + } +#endif + + libgcrypt_initialized = 1; + + return SSH_OK; +} + +/** + * @internal + * + * @brief Finalize libgcrypt's subsystem + */ +void ssh_crypto_finalize(void) +{ + if (!libgcrypt_initialized) { + return; + } + + gcry_control(GCRYCTL_TERM_SECMEM); + + libgcrypt_initialized = 0; +} + +#endif diff --git a/src/libs/libssh-0.12.2/src/libmbedcrypto.c b/src/libs/libssh-0.12.2/src/libmbedcrypto.c new file mode 100644 index 000000000000..d52ec3dfe811 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libmbedcrypto.c @@ -0,0 +1,1120 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/wrapper.h" +#include "libssh/crypto.h" +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "mbedcrypto-compat.h" +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) +#include "libssh/bytearray.h" +#include "libssh/chacha20-poly1305-common.h" +#include +#include +#endif + +#ifdef HAVE_LIBMBEDCRYPTO +#include +#ifdef MBEDTLS_GCM_C +#include +#endif /* MBEDTLS_GCM_C */ + +static mbedtls_entropy_context ssh_mbedtls_entropy; +extern mbedtls_ctr_drbg_context ssh_mbedtls_ctr_drbg; + +static int libmbedcrypto_initialized = 0; + +void ssh_reseed(void) +{ + mbedtls_ctr_drbg_reseed(&ssh_mbedtls_ctr_drbg, NULL, 0); +} + +int ssh_kdf(struct ssh_crypto_struct *crypto, + unsigned char *key, size_t key_len, + uint8_t key_type, unsigned char *output, + size_t requested_len) +{ + return sshkdf_derive_key(crypto, key, key_len, + key_type, output, requested_len); +} + +HMACCTX hmac_init(const void *key, size_t len, enum ssh_hmac_e type) +{ + HMACCTX ctx = NULL; + const mbedtls_md_info_t *md_info = NULL; + int rc; + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + switch (type) { + case SSH_HMAC_SHA1: + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + break; + case SSH_HMAC_SHA256: + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + break; + case SSH_HMAC_SHA512: + md_info = mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + break; + default: + goto error; + } + + mbedtls_md_init(ctx); + + if (md_info == NULL) { + goto error; + } + + rc = mbedtls_md_setup(ctx, md_info, 1); + if (rc != 0) { + goto error; + } + + rc = mbedtls_md_hmac_starts(ctx, key, len); + if (rc != 0) { + goto error; + } + + return ctx; + +error: + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return NULL; +} + +/* mbedtls returns 0 on success, but in this context + * success is 1 */ +int hmac_update(HMACCTX c, const void *data, size_t len) +{ + return !mbedtls_md_hmac_update(c, data, len); +} + +int hmac_final(HMACCTX c, unsigned char *hashmacbuf, size_t *len) +{ + int rc; + *len = (unsigned int)mbedtls_md_get_size(c->MBEDTLS_PRIVATE(md_info)); + rc = !mbedtls_md_hmac_finish(c, hashmacbuf); + mbedtls_md_free(c); + SAFE_FREE(c); + return rc; +} + +static int +cipher_init(struct ssh_cipher_struct *cipher, + mbedtls_operation_t operation, + void *key, + void *IV) +{ + const mbedtls_cipher_info_t *cipher_info = NULL; + mbedtls_cipher_context_t *ctx = NULL; + size_t key_bitlen = 0; + size_t iv_size = 0; + int rc; + + if (operation == MBEDTLS_ENCRYPT) { + ctx = &cipher->encrypt_ctx; + } else if (operation == MBEDTLS_DECRYPT) { + ctx = &cipher->decrypt_ctx; + } else { + SSH_LOG(SSH_LOG_TRACE, "unknown operation"); + return 1; + } + + mbedtls_cipher_init(ctx); + cipher_info = mbedtls_cipher_info_from_type(cipher->type); + + rc = mbedtls_cipher_setup(ctx, cipher_info); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_setup failed"); + goto error; + } + + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_cipher_setkey(ctx, key, key_bitlen, operation); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_setkey failed"); + goto error; + } + + iv_size = mbedtls_cipher_info_get_iv_size(cipher_info); + rc = mbedtls_cipher_set_iv(ctx, IV, iv_size); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_iv failed"); + goto error; + } + + return 0; +error: + mbedtls_cipher_free(ctx); + return 1; +} + +static int +cipher_set_encrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); + goto error; + } + + rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + return SSH_ERROR; +} + +static int +cipher_set_encrypt_key_cbc(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_ENCRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); + goto error; + } + + /* libssh only encrypts and decrypts packets that are multiples of a block + * size, and no padding is used */ + rc = mbedtls_cipher_set_padding_mode(&cipher->encrypt_ctx, + MBEDTLS_PADDING_NONE); + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_padding_mode failed"); + goto error; + } + + rc = mbedtls_cipher_reset(&cipher->encrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + mbedtls_cipher_free(&cipher->encrypt_ctx); + return SSH_ERROR; +} + +#ifdef MBEDTLS_GCM_C +static int +cipher_set_key_gcm(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + const mbedtls_cipher_info_t *cipher_info = NULL; + size_t key_bitlen = 0; + int rc; + + mbedtls_gcm_init(&cipher->gcm_ctx); + cipher_info = mbedtls_cipher_info_from_type(cipher->type); + + key_bitlen = mbedtls_cipher_info_get_key_bitlen(cipher_info); + rc = mbedtls_gcm_setkey(&cipher->gcm_ctx, MBEDTLS_CIPHER_ID_AES, + key, key_bitlen); + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_setkey failed"); + goto error; + } + + /* Store the IV so we can increment the packet counter later */ + memcpy(cipher->last_iv, IV, AES_GCM_IVLEN); + + return 0; +error: + mbedtls_gcm_free(&cipher->gcm_ctx); + return 1; +} +#endif /* MBEDTLS_GCM_C */ + +static int +cipher_set_decrypt_key(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); + goto error; + } + + mbedtls_cipher_reset(&cipher->decrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + mbedtls_cipher_free(&cipher->decrypt_ctx); + return SSH_ERROR; +} + +static int +cipher_set_decrypt_key_cbc(struct ssh_cipher_struct *cipher, + void *key, + void *IV) +{ + int rc; + + rc = cipher_init(cipher, MBEDTLS_DECRYPT, key, IV); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "cipher_init failed"); + goto error; + } + + rc = mbedtls_cipher_set_padding_mode(&cipher->decrypt_ctx, + MBEDTLS_PADDING_NONE); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_set_padding_mode failed"); + goto error; + } + + mbedtls_cipher_reset(&cipher->decrypt_ctx); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed"); + goto error; + } + + return SSH_OK; +error: + mbedtls_cipher_free(&cipher->decrypt_ctx); + return SSH_ERROR; +} + +static void cipher_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + size_t outlen = 0; + size_t total_len = 0; + int rc = 0; + rc = mbedtls_cipher_update(&cipher->encrypt_ctx, in, len, out, &outlen); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during encryption"); + return; + } + + total_len += outlen; + + if (total_len == len) { + return; + } + + rc = mbedtls_cipher_finish(&cipher->encrypt_ctx, (unsigned char *) out + outlen, + &outlen); + + total_len += outlen; + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_finish failed during encryption"); + return; + } + + if (total_len != len) { + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +static void cipher_encrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void *out, + size_t len) +{ + size_t outlen = 0; + int rc = 0; + rc = mbedtls_cipher_update(&cipher->encrypt_ctx, in, len, out, &outlen); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during encryption"); + return; + } + + if (outlen != len) { + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +static void cipher_decrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len) +{ + size_t outlen = 0; + int rc = 0; + size_t total_len = 0; + + rc = mbedtls_cipher_update(&cipher->decrypt_ctx, in, len, out, &outlen); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during decryption"); + return; + } + + total_len += outlen; + + if (total_len == len) { + return; + } + + rc = mbedtls_cipher_finish(&cipher->decrypt_ctx, (unsigned char *) out + + outlen, &outlen); + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed during decryption"); + return; + } + + total_len += outlen; + + if (total_len != len) { + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +static void cipher_decrypt_cbc(struct ssh_cipher_struct *cipher, void *in, void *out, + size_t len) +{ + size_t outlen = 0; + int rc = 0; + rc = mbedtls_cipher_update(&cipher->decrypt_ctx, in, len, out, &outlen); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_update failed during decryption"); + return; + } + + /* MbedTLS caches the last block when decrypting with cbc. + * By calling finish the block is flushed to out, however the unprocessed + * data counter is not reset. + * Calling mbedtls_cipher_reset resets the unprocessed data counter. + */ + if (outlen == 0) { + rc = mbedtls_cipher_finish(&cipher->decrypt_ctx, out, &outlen); + } else if (outlen == len) { + return; + } else { + rc = mbedtls_cipher_finish(&cipher->decrypt_ctx, (unsigned char *) out + + outlen , &outlen); + } + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_finish failed during decryption"); + return; + } + + rc = mbedtls_cipher_reset(&cipher->decrypt_ctx); + + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_cipher_reset failed during decryption"); + return; + } + + if (outlen != len) { + SSH_LOG(SSH_LOG_DEBUG, "mbedtls_cipher_update: output size %zu for %zu", + outlen, len); + return; + } + +} + +#ifdef MBEDTLS_GCM_C +static int +cipher_gcm_get_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + (void)cipher; + (void)seq; + + /* The length is not encrypted: Copy it to the result buffer */ + memcpy(out, in, len); + + return SSH_OK; +} + +static void +cipher_encrypt_gcm(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + size_t authlen, aadlen; + int rc; + + (void) seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + /* The length is not encrypted */ + memcpy(out, in, aadlen); + rc = mbedtls_gcm_crypt_and_tag(&cipher->gcm_ctx, + MBEDTLS_GCM_ENCRYPT, + len - aadlen, /* encrypted data len */ + cipher->last_iv, /* IV */ + AES_GCM_IVLEN, + in, /* aad */ + aadlen, + (const unsigned char *)in + aadlen, /* input */ + (unsigned char *)out + aadlen, /* output */ + authlen, + tag); /* tag */ + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_crypt_and_tag failed"); + return; + } + + /* Increment the IV for the next invocation */ + uint64_inc(cipher->last_iv + 4); +} + +static int +cipher_decrypt_gcm(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + size_t authlen, aadlen; + int rc; + + (void) seq; + + aadlen = cipher->lenfield_blocksize; + authlen = cipher->tag_size; + + rc = mbedtls_gcm_auth_decrypt(&cipher->gcm_ctx, + encrypted_size, /* encrypted data len */ + cipher->last_iv, /* IV */ + AES_GCM_IVLEN, + complete_packet, /* aad */ + aadlen, + (const uint8_t *)complete_packet + aadlen + encrypted_size, /* tag */ + authlen, + (const uint8_t *)complete_packet + aadlen, /* input */ + (unsigned char *)out); /* output */ + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_gcm_auth_decrypt failed"); + return SSH_ERROR; + } + + /* Increment the IV for the next invocation */ + uint64_inc(cipher->last_iv + 4); + + return SSH_OK; +} +#endif /* MBEDTLS_GCM_C */ + +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) + +struct chacha20_poly1305_keysched { + bool initialized; + /* cipher handle used for encrypting the packets */ + mbedtls_chacha20_context main_ctx; + /* cipher handle used for encrypting the length field */ + mbedtls_chacha20_context header_ctx; + /* Poly1305 key */ + mbedtls_poly1305_context poly_ctx; +}; + +static void +chacha20_poly1305_cleanup(struct ssh_cipher_struct *cipher) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + + if (cipher->chacha20_schedule == NULL) { + return; + } + + ctx = cipher->chacha20_schedule; + + if (ctx->initialized) { + mbedtls_chacha20_free(&ctx->main_ctx); + mbedtls_chacha20_free(&ctx->header_ctx); + mbedtls_poly1305_free(&ctx->poly_ctx); + ctx->initialized = false; + } + + SAFE_FREE(cipher->chacha20_schedule); +} + +static int +chacha20_poly1305_set_key(struct ssh_cipher_struct *cipher, + void *key, + UNUSED_PARAM(void *IV)) +{ + struct chacha20_poly1305_keysched *ctx = NULL; + uint8_t *u8key = key; + int ret = SSH_ERROR, rv; + + if (cipher->chacha20_schedule == NULL) { + ctx = calloc(1, sizeof(*ctx)); + if (ctx == NULL) { + return -1; + } + cipher->chacha20_schedule = ctx; + } else { + ctx = cipher->chacha20_schedule; + } + + if (!ctx->initialized) { + mbedtls_chacha20_init(&ctx->main_ctx); + mbedtls_chacha20_init(&ctx->header_ctx); + mbedtls_poly1305_init(&ctx->poly_ctx); + ctx->initialized = true; + } + + /* ChaCha20 keys initialization */ + /* K2 uses the first half of the key */ + rv = mbedtls_chacha20_setkey(&ctx->main_ctx, u8key); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_setkey(main_ctx) failed"); + goto out; + } + + /* K1 uses the second half of the key */ + rv = mbedtls_chacha20_setkey(&ctx->header_ctx, u8key + CHACHA20_KEYLEN); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_setkey(header_ctx) failed"); + goto out; + } + + ret = SSH_OK; +out: + if (ret != SSH_OK) { + chacha20_poly1305_cleanup(cipher); + } + return ret; +} + +static const uint8_t zero_block[CHACHA20_BLOCKSIZE] = {0}; + +static int +chacha20_poly1305_set_iv(struct ssh_cipher_struct *cipher, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t seqbuf[12] = {0}; + int ret; + + /* The nonce in mbedTLS is 96 b long. The counter is passed through separate + * parameter of 32 b size. + * Encode the sequence number into the last 8 bytes. + */ + PUSH_BE_U64(seqbuf, 4, seq); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("seqbuf (chacha20 IV)", seqbuf, sizeof(seqbuf)); +#endif /* DEBUG_CRYPTO */ + + ret = mbedtls_chacha20_starts(&ctx->header_ctx, seqbuf, 0); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_starts(header_ctx) failed"); + return SSH_ERROR; + } + + ret = mbedtls_chacha20_starts(&ctx->main_ctx, seqbuf, 0); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_starts(main_ctx) failed"); + return SSH_ERROR; + } + + return SSH_OK; +} + +static int +chacha20_poly1305_packet_setup(struct ssh_cipher_struct *cipher, + uint64_t seq, + int do_encrypt) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t poly_key[CHACHA20_BLOCKSIZE]; + int ret = SSH_ERROR, rv; + + /* The initialization for decrypt was already done with the length block */ + if (do_encrypt) { + rv = chacha20_poly1305_set_iv(cipher, seq); + if (rv != SSH_OK) { + return SSH_ERROR; + } + } + + /* Output full ChaCha block so that counter increases by one for + * next step. */ + rv = mbedtls_chacha20_update(&ctx->main_ctx, sizeof(zero_block), + zero_block, poly_key); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + goto out; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("poly_key", poly_key, POLY1305_KEYLEN); +#endif /* DEBUG_CRYPTO */ + + /* Set the Poly1305 key */ + rv = mbedtls_poly1305_starts(&ctx->poly_ctx, poly_key); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_starts failed"); + goto out; + } + + ret = SSH_OK; +out: + ssh_burn(poly_key, sizeof(poly_key)); + return ret; +} + +static int +chacha20_poly1305_aead_decrypt_length(struct ssh_cipher_struct *cipher, + void *in, + uint8_t *out, + size_t len, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int rv; + + if (len < sizeof(uint32_t)) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", (uint8_t *)in, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* Set IV for the header context */ + rv = chacha20_poly1305_set_iv(cipher, seq); + if (rv != SSH_OK) { + return SSH_ERROR; + } + + rv = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), in, out); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("deciphered length", out, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + return SSH_OK; +} + +static int +chacha20_poly1305_aead_decrypt(struct ssh_cipher_struct *cipher, + void *complete_packet, + uint8_t *out, + size_t encrypted_size, + uint64_t seq) +{ + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + uint8_t *mac = (uint8_t *)complete_packet + sizeof(uint32_t) + + encrypted_size; + uint8_t tag[POLY1305_TAGLEN] = {0}; + int ret = SSH_ERROR; + int rv, cmp = 0; + + /* Prepare the Poly1305 key */ + rv = chacha20_poly1305_packet_setup(cipher, seq, 0); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("received mac", mac, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Calculate MAC of received data */ + rv = mbedtls_poly1305_update(&ctx->poly_ctx, complete_packet, + encrypted_size + sizeof(uint32_t)); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_update failed"); + goto out; + } + + rv = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_finish failed"); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("calculated mac", tag, POLY1305_TAGLEN); +#endif /* DEBUG_CRYPTO */ + + /* Verify the calculated MAC matches the attached MAC */ + cmp = secure_memcmp(tag, mac, POLY1305_TAGLEN); + if (cmp != 0) { + /* mac error */ + SSH_LOG(SSH_LOG_PACKET, "poly1305 verify error"); + return SSH_ERROR; + } + + /* Decrypt the message */ + rv = mbedtls_chacha20_update(&ctx->main_ctx, encrypted_size, + (uint8_t *)complete_packet + sizeof(uint32_t), + out); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + goto out; + } + + ret = SSH_OK; +out: + return ret; +} + +static void +chacha20_poly1305_aead_encrypt(struct ssh_cipher_struct *cipher, + void *in, + void *out, + size_t len, + uint8_t *tag, + uint64_t seq) +{ + struct ssh_packet_header *in_packet = in, *out_packet = out; + struct chacha20_poly1305_keysched *ctx = cipher->chacha20_schedule; + int ret; + + /* Prepare the Poly1305 key */ + ret = chacha20_poly1305_packet_setup(cipher, seq, 1); + if (ret != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to setup packet"); + return; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("plaintext length", + (unsigned char *)&in_packet->length, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + /* step 2, encrypt length field */ + ret = mbedtls_chacha20_update(&ctx->header_ctx, sizeof(uint32_t), + (unsigned char *)&in_packet->length, + (unsigned char *)&out_packet->length); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + return; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("encrypted length", + (unsigned char *)&out_packet->length, sizeof(uint32_t)); +#endif /* DEBUG_CRYPTO */ + + /* step 3, encrypt packet payload (main_ctx counter == 1) */ + /* We already did encrypt one block so the counter should be in the correct position */ + ret = mbedtls_chacha20_update(&ctx->main_ctx, len - sizeof(uint32_t), + in_packet->payload, out_packet->payload); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_chacha20_update failed"); + return; + } + + /* step 4, compute the MAC */ + ret = mbedtls_poly1305_update(&ctx->poly_ctx, (const unsigned char *)out_packet, len); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_update failed"); + return; + } + ret = mbedtls_poly1305_finish(&ctx->poly_ctx, tag); + if (ret != 0) { + SSH_LOG(SSH_LOG_TRACE, "mbedtls_poly1305_finish failed"); + return; + } +} +#endif /* defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) */ + + +static void cipher_cleanup(struct ssh_cipher_struct *cipher) +{ + mbedtls_cipher_free(&cipher->encrypt_ctx); + mbedtls_cipher_free(&cipher->decrypt_ctx); +#ifdef MBEDTLS_GCM_C + mbedtls_gcm_free(&cipher->gcm_ctx); +#endif /* MBEDTLS_GCM_C */ +} + +#ifdef WITH_INSECURE_NONE +static void +none_crypt(UNUSED_PARAM(struct ssh_cipher_struct *cipher), + void *in, + void *out, + size_t len) +{ + memcpy(out, in, len); +} +#endif /* WITH_INSECURE_NONE */ + +static struct ssh_cipher_struct ssh_ciphertab[] = { +#ifdef HAVE_BLOWFISH + { + .name = "blowfish-cbc", + .blocksize = 8, + .keysize = 128, + .type = MBEDTLS_CIPHER_BLOWFISH_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, +#endif /* HAVE_BLOWFISH */ + { + .name = "aes128-ctr", + .blocksize = 16, + .keysize = 128, + .type = MBEDTLS_CIPHER_AES_128_CTR, + .set_encrypt_key = cipher_set_encrypt_key, + .set_decrypt_key = cipher_set_decrypt_key, + .encrypt = cipher_encrypt, + .decrypt = cipher_decrypt, + .cleanup = cipher_cleanup + }, + { + .name = "aes192-ctr", + .blocksize = 16, + .keysize = 192, + .type = MBEDTLS_CIPHER_AES_192_CTR, + .set_encrypt_key = cipher_set_encrypt_key, + .set_decrypt_key = cipher_set_decrypt_key, + .encrypt = cipher_encrypt, + .decrypt = cipher_decrypt, + .cleanup = cipher_cleanup + }, + { + .name = "aes256-ctr", + .blocksize = 16, + .keysize = 256, + .type = MBEDTLS_CIPHER_AES_256_CTR, + .set_encrypt_key = cipher_set_encrypt_key, + .set_decrypt_key = cipher_set_decrypt_key, + .encrypt = cipher_encrypt, + .decrypt = cipher_decrypt, + .cleanup = cipher_cleanup + }, + { + .name = "aes128-cbc", + .blocksize = 16, + .keysize = 128, + .type = MBEDTLS_CIPHER_AES_128_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, + { + .name = "aes192-cbc", + .blocksize = 16, + .keysize = 192, + .type = MBEDTLS_CIPHER_AES_192_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, + { + .name = "aes256-cbc", + .blocksize = 16, + .keysize = 256, + .type = MBEDTLS_CIPHER_AES_256_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, +#ifdef MBEDTLS_GCM_C + { + .name = "aes128-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keysize = 128, + .tag_size = AES_GCM_TAGLEN, + .type = MBEDTLS_CIPHER_AES_128_GCM, + .set_encrypt_key = cipher_set_key_gcm, + .set_decrypt_key = cipher_set_key_gcm, + .aead_encrypt = cipher_encrypt_gcm, + .aead_decrypt_length = cipher_gcm_get_length, + .aead_decrypt = cipher_decrypt_gcm, + .cleanup = cipher_cleanup + }, + { + .name = "aes256-gcm@openssh.com", + .blocksize = 16, + .lenfield_blocksize = 4, /* not encrypted, but authenticated */ + .keysize = 256, + .tag_size = AES_GCM_TAGLEN, + .type = MBEDTLS_CIPHER_AES_256_GCM, + .set_encrypt_key = cipher_set_key_gcm, + .set_decrypt_key = cipher_set_key_gcm, + .aead_encrypt = cipher_encrypt_gcm, + .aead_decrypt_length = cipher_gcm_get_length, + .aead_decrypt = cipher_decrypt_gcm, + .cleanup = cipher_cleanup + }, +#endif /* MBEDTLS_GCM_C */ + { + .name = "3des-cbc", + .blocksize = 8, + .keysize = 192, + .type = MBEDTLS_CIPHER_DES_EDE3_CBC, + .set_encrypt_key = cipher_set_encrypt_key_cbc, + .set_decrypt_key = cipher_set_decrypt_key_cbc, + .encrypt = cipher_encrypt_cbc, + .decrypt = cipher_decrypt_cbc, + .cleanup = cipher_cleanup + }, + { +#if defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C) + .ciphertype = SSH_AEAD_CHACHA20_POLY1305, + .name = "chacha20-poly1305@openssh.com", + .blocksize = 8, + .lenfield_blocksize = 4, + .keylen = sizeof(struct chacha20_poly1305_keysched), + .keysize = 2 * CHACHA20_KEYLEN * 8, + .tag_size = POLY1305_TAGLEN, + .set_encrypt_key = chacha20_poly1305_set_key, + .set_decrypt_key = chacha20_poly1305_set_key, + .aead_encrypt = chacha20_poly1305_aead_encrypt, + .aead_decrypt_length = chacha20_poly1305_aead_decrypt_length, + .aead_decrypt = chacha20_poly1305_aead_decrypt, + .cleanup = chacha20_poly1305_cleanup +#else + .name = "chacha20-poly1305@openssh.com" +#endif + }, +#ifdef WITH_INSECURE_NONE + { + .name = "none", + .blocksize = 8, + .keysize = 0, + .encrypt = none_crypt, + .decrypt = none_crypt, + }, +#endif /* WITH_INSECURE_NONE */ + { + .name = NULL, + .blocksize = 0, + .keysize = 0, + .set_encrypt_key = NULL, + .set_decrypt_key = NULL, + .encrypt = NULL, + .decrypt = NULL, + .cleanup = NULL + } +}; + +struct ssh_cipher_struct *ssh_get_ciphertab(void) +{ + return ssh_ciphertab; +} + +int ssh_crypto_init(void) +{ + UNUSED_VAR(size_t i); + int rc; + + if (libmbedcrypto_initialized) { + return SSH_OK; + } + + mbedtls_entropy_init(&ssh_mbedtls_entropy); + mbedtls_ctr_drbg_init(&ssh_mbedtls_ctr_drbg); + + rc = mbedtls_ctr_drbg_seed(&ssh_mbedtls_ctr_drbg, mbedtls_entropy_func, + &ssh_mbedtls_entropy, NULL, 0); + if (rc != 0) { + mbedtls_ctr_drbg_free(&ssh_mbedtls_ctr_drbg); + } + +#if !(defined(MBEDTLS_CHACHA20_C) && defined(MBEDTLS_POLY1305_C)) + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + int cmp; + + cmp = strcmp(ssh_ciphertab[i].name, "chacha20-poly1305@openssh.com"); + if (cmp == 0) { + memcpy(&ssh_ciphertab[i], + ssh_get_chacha20poly1305_cipher(), + sizeof(struct ssh_cipher_struct)); + break; + } + } +#endif + + libmbedcrypto_initialized = 1; + + return SSH_OK; +} + +mbedtls_ctr_drbg_context *ssh_get_mbedtls_ctr_drbg_context(void) +{ + return &ssh_mbedtls_ctr_drbg; +} + +void ssh_crypto_finalize(void) +{ + if (!libmbedcrypto_initialized) { + return; + } + + mbedtls_ctr_drbg_free(&ssh_mbedtls_ctr_drbg); + mbedtls_entropy_free(&ssh_mbedtls_entropy); + + libmbedcrypto_initialized = 0; +} + +#endif /* HAVE_LIBMBEDCRYPTO */ diff --git a/src/libs/libssh-0.12.2/src/libssh.map b/src/libs/libssh-0.12.2/src/libssh.map new file mode 100644 index 000000000000..81e80d759169 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/libssh.map @@ -0,0 +1,516 @@ +# This map file was updated with abimap-0.4.0 + +LIBSSH_4_5_0 # Released +{ + global: + _ssh_log; + buffer_free; + buffer_get; + buffer_get_len; + buffer_new; + channel_accept_x11; + channel_change_pty_size; + channel_close; + channel_forward_accept; + channel_forward_cancel; + channel_forward_listen; + channel_free; + channel_get_exit_status; + channel_get_session; + channel_is_closed; + channel_is_eof; + channel_is_open; + channel_new; + channel_open_forward; + channel_open_session; + channel_poll; + channel_read; + channel_read_buffer; + channel_read_nonblocking; + channel_request_env; + channel_request_exec; + channel_request_pty; + channel_request_pty_size; + channel_request_send_signal; + channel_request_sftp; + channel_request_shell; + channel_request_subsystem; + channel_request_x11; + channel_select; + channel_send_eof; + channel_set_blocking; + channel_write; + channel_write_stderr; + privatekey_free; + privatekey_from_file; + publickey_free; + publickey_from_file; + publickey_from_privatekey; + publickey_to_string; + sftp_async_read; + sftp_async_read_begin; + sftp_attributes_free; + sftp_canonicalize_path; + sftp_chmod; + sftp_chown; + sftp_client_message_free; + sftp_client_message_get_data; + sftp_client_message_get_filename; + sftp_client_message_get_flags; + sftp_client_message_get_type; + sftp_client_message_set_filename; + sftp_close; + sftp_closedir; + sftp_dir_eof; + sftp_extension_supported; + sftp_extensions_get_count; + sftp_extensions_get_data; + sftp_extensions_get_name; + sftp_file_set_blocking; + sftp_file_set_nonblocking; + sftp_free; + sftp_fstat; + sftp_fstatvfs; + sftp_fsync; + sftp_get_client_message; + sftp_get_error; + sftp_handle; + sftp_handle_alloc; + sftp_handle_remove; + sftp_init; + sftp_lstat; + sftp_mkdir; + sftp_new; + sftp_new_channel; + sftp_open; + sftp_opendir; + sftp_read; + sftp_readdir; + sftp_readlink; + sftp_rename; + sftp_reply_attr; + sftp_reply_data; + sftp_reply_handle; + sftp_reply_name; + sftp_reply_names; + sftp_reply_names_add; + sftp_reply_status; + sftp_rewind; + sftp_rmdir; + sftp_seek; + sftp_seek64; + sftp_send_client_message; + sftp_server_init; + sftp_server_new; + sftp_server_version; + sftp_setstat; + sftp_stat; + sftp_statvfs; + sftp_statvfs_free; + sftp_symlink; + sftp_tell; + sftp_tell64; + sftp_unlink; + sftp_utimes; + sftp_write; + ssh_accept; + ssh_add_channel_callbacks; + ssh_auth_list; + ssh_basename; + ssh_bind_accept; + ssh_bind_accept_fd; + ssh_bind_fd_toaccept; + ssh_bind_free; + ssh_bind_get_fd; + ssh_bind_listen; + ssh_bind_new; + ssh_bind_options_set; + ssh_bind_set_blocking; + ssh_bind_set_callbacks; + ssh_bind_set_fd; + ssh_blocking_flush; + ssh_buffer_add_data; + ssh_buffer_free; + ssh_buffer_get; + ssh_buffer_get_data; + ssh_buffer_get_len; + ssh_buffer_new; + ssh_buffer_reinit; + ssh_channel_accept_forward; + ssh_channel_accept_x11; + ssh_channel_cancel_forward; + ssh_channel_change_pty_size; + ssh_channel_close; + ssh_channel_free; + ssh_channel_get_exit_status; + ssh_channel_get_session; + ssh_channel_is_closed; + ssh_channel_is_eof; + ssh_channel_is_open; + ssh_channel_listen_forward; + ssh_channel_new; + ssh_channel_open_auth_agent; + ssh_channel_open_forward; + ssh_channel_open_reverse_forward; + ssh_channel_open_session; + ssh_channel_open_x11; + ssh_channel_poll; + ssh_channel_poll_timeout; + ssh_channel_read; + ssh_channel_read_nonblocking; + ssh_channel_read_timeout; + ssh_channel_request_auth_agent; + ssh_channel_request_env; + ssh_channel_request_exec; + ssh_channel_request_pty; + ssh_channel_request_pty_size; + ssh_channel_request_send_break; + ssh_channel_request_send_exit_signal; + ssh_channel_request_send_exit_status; + ssh_channel_request_send_signal; + ssh_channel_request_sftp; + ssh_channel_request_shell; + ssh_channel_request_subsystem; + ssh_channel_request_x11; + ssh_channel_select; + ssh_channel_send_eof; + ssh_channel_set_blocking; + ssh_channel_set_counter; + ssh_channel_window_size; + ssh_channel_write; + ssh_channel_write_stderr; + ssh_clean_pubkey_hash; + ssh_connect; + ssh_connector_free; + ssh_connector_new; + ssh_connector_set_in_channel; + ssh_connector_set_in_fd; + ssh_connector_set_out_channel; + ssh_connector_set_out_fd; + ssh_copyright; + ssh_dirname; + ssh_disconnect; + ssh_dump_knownhost; + ssh_event_add_connector; + ssh_event_add_fd; + ssh_event_add_session; + ssh_event_dopoll; + ssh_event_free; + ssh_event_new; + ssh_event_remove_connector; + ssh_event_remove_fd; + ssh_event_remove_session; + ssh_execute_message_callbacks; + ssh_finalize; + ssh_forward_accept; + ssh_forward_cancel; + ssh_forward_listen; + ssh_free; + ssh_get_cipher_in; + ssh_get_cipher_out; + ssh_get_clientbanner; + ssh_get_disconnect_message; + ssh_get_error; + ssh_get_error_code; + ssh_get_fd; + ssh_get_hexa; + ssh_get_hmac_in; + ssh_get_hmac_out; + ssh_get_issue_banner; + ssh_get_kex_algo; + ssh_get_log_callback; + ssh_get_log_level; + ssh_get_log_userdata; + ssh_get_openssh_version; + ssh_get_poll_flags; + ssh_get_pubkey; + ssh_get_pubkey_hash; + ssh_get_publickey; + ssh_get_publickey_hash; + ssh_get_random; + ssh_get_server_publickey; + ssh_get_serverbanner; + ssh_get_status; + ssh_get_version; + ssh_getpass; + ssh_gssapi_get_creds; + ssh_gssapi_set_creds; + ssh_handle_key_exchange; + ssh_init; + ssh_is_blocking; + ssh_is_connected; + ssh_is_server_known; + ssh_key_cmp; + ssh_key_free; + ssh_key_is_private; + ssh_key_is_public; + ssh_key_new; + ssh_key_type; + ssh_key_type_from_name; + ssh_key_type_to_char; + ssh_known_hosts_parse_line; + ssh_knownhosts_entry_free; + ssh_log; + ssh_message_auth_interactive_request; + ssh_message_auth_kbdint_is_response; + ssh_message_auth_password; + ssh_message_auth_pubkey; + ssh_message_auth_publickey; + ssh_message_auth_publickey_state; + ssh_message_auth_reply_pk_ok; + ssh_message_auth_reply_pk_ok_simple; + ssh_message_auth_reply_success; + ssh_message_auth_set_methods; + ssh_message_auth_user; + ssh_message_channel_request_channel; + ssh_message_channel_request_command; + ssh_message_channel_request_env_name; + ssh_message_channel_request_env_value; + ssh_message_channel_request_open_destination; + ssh_message_channel_request_open_destination_port; + ssh_message_channel_request_open_originator; + ssh_message_channel_request_open_originator_port; + ssh_message_channel_request_open_reply_accept; + ssh_message_channel_request_pty_height; + ssh_message_channel_request_pty_pxheight; + ssh_message_channel_request_pty_pxwidth; + ssh_message_channel_request_pty_term; + ssh_message_channel_request_pty_width; + ssh_message_channel_request_reply_success; + ssh_message_channel_request_subsystem; + ssh_message_channel_request_x11_auth_cookie; + ssh_message_channel_request_x11_auth_protocol; + ssh_message_channel_request_x11_screen_number; + ssh_message_channel_request_x11_single_connection; + ssh_message_free; + ssh_message_get; + ssh_message_global_request_address; + ssh_message_global_request_port; + ssh_message_global_request_reply_success; + ssh_message_reply_default; + ssh_message_retrieve; + ssh_message_service_reply_success; + ssh_message_service_service; + ssh_message_subtype; + ssh_message_type; + ssh_mkdir; + ssh_new; + ssh_options_copy; + ssh_options_get; + ssh_options_get_port; + ssh_options_getopt; + ssh_options_parse_config; + ssh_options_set; + ssh_pcap_file_close; + ssh_pcap_file_free; + ssh_pcap_file_new; + ssh_pcap_file_open; + ssh_pki_copy_cert_to_privkey; + ssh_pki_export_privkey_file; + ssh_pki_export_privkey_to_pubkey; + ssh_pki_export_pubkey_base64; + ssh_pki_export_pubkey_file; + ssh_pki_generate; + ssh_pki_import_cert_base64; + ssh_pki_import_cert_file; + ssh_pki_import_privkey_base64; + ssh_pki_import_privkey_file; + ssh_pki_import_pubkey_base64; + ssh_pki_import_pubkey_file; + ssh_pki_key_ecdsa_name; + ssh_print_hexa; + ssh_privatekey_type; + ssh_publickey_to_file; + ssh_remove_channel_callbacks; + ssh_scp_accept_request; + ssh_scp_close; + ssh_scp_deny_request; + ssh_scp_free; + ssh_scp_init; + ssh_scp_leave_directory; + ssh_scp_new; + ssh_scp_pull_request; + ssh_scp_push_directory; + ssh_scp_push_file; + ssh_scp_push_file64; + ssh_scp_read; + ssh_scp_request_get_filename; + ssh_scp_request_get_permissions; + ssh_scp_request_get_size; + ssh_scp_request_get_size64; + ssh_scp_request_get_warning; + ssh_scp_write; + ssh_select; + ssh_send_debug; + ssh_send_ignore; + ssh_send_keepalive; + ssh_server_init_kex; + ssh_service_request; + ssh_session_export_known_hosts_entry; + ssh_session_has_known_hosts_entry; + ssh_session_is_known_server; + ssh_session_update_known_hosts; + ssh_set_agent_channel; + ssh_set_agent_socket; + ssh_set_auth_methods; + ssh_set_blocking; + ssh_set_callbacks; + ssh_set_channel_callbacks; + ssh_set_counters; + ssh_set_fd_except; + ssh_set_fd_toread; + ssh_set_fd_towrite; + ssh_set_log_callback; + ssh_set_log_level; + ssh_set_log_userdata; + ssh_set_message_callback; + ssh_set_pcap_file; + ssh_set_server_callbacks; + ssh_silent_disconnect; + ssh_string_burn; + ssh_string_copy; + ssh_string_data; + ssh_string_fill; + ssh_string_free; + ssh_string_free_char; + ssh_string_from_char; + ssh_string_get_char; + ssh_string_len; + ssh_string_new; + ssh_string_to_char; + ssh_threads_get_noop; + ssh_threads_get_pthread; + ssh_threads_set_callbacks; + ssh_try_publickey_from_file; + ssh_userauth_agent; + ssh_userauth_agent_pubkey; + ssh_userauth_autopubkey; + ssh_userauth_gssapi; + ssh_userauth_kbdint; + ssh_userauth_kbdint_getanswer; + ssh_userauth_kbdint_getinstruction; + ssh_userauth_kbdint_getname; + ssh_userauth_kbdint_getnanswers; + ssh_userauth_kbdint_getnprompts; + ssh_userauth_kbdint_getprompt; + ssh_userauth_kbdint_setanswer; + ssh_userauth_list; + ssh_userauth_none; + ssh_userauth_offer_pubkey; + ssh_userauth_password; + ssh_userauth_privatekey_file; + ssh_userauth_pubkey; + ssh_userauth_publickey; + ssh_userauth_publickey_auto; + ssh_userauth_try_publickey; + ssh_version; + ssh_write_knownhost; + string_burn; + string_copy; + string_data; + string_fill; + string_free; + string_from_char; + string_len; + string_new; + string_to_char; + local: + *; +} ; + +LIBSSH_4_6_0 # Released +{ + global: + ssh_print_hash; +} LIBSSH_4_5_0; + +LIBSSH_4_7_0 # Released +{ + global: + sftp_client_message_get_submessage; + ssh_get_fingerprint_hash; + ssh_pki_export_privkey_base64; +} LIBSSH_4_6_0; + +LIBSSH_4_8_0 # Released +{ + global: + sftp_server_free; + ssh_bind_options_parse_config; + ssh_channel_open_forward_unix; + ssh_message_channel_request_open_reply_accept_channel; +} LIBSSH_4_7_0; + +LIBSSH_4_8_1 # Released +{ + global: + ssh_session_get_known_hosts_entry; + ssh_threads_get_default; +} LIBSSH_4_8_0; + +LIBSSH_4_9_0 # Released +{ + global: + ssh_channel_open_forward_port; + ssh_key_dup; + ssh_send_issue_banner; + ssh_session_set_disconnect_message; + ssh_userauth_publickey_auto_get_current_identity; + ssh_vlog; +} LIBSSH_4_8_1; + +LIBSSH_4_10_0 # Released +{ + global: + sftp_aio_begin_read; + sftp_aio_begin_write; + sftp_aio_free; + sftp_aio_wait_read; + sftp_aio_wait_write; + sftp_channel_default_data_callback; + sftp_channel_default_subsystem_request; + sftp_expand_path; + sftp_hardlink; + sftp_home_directory; + sftp_limits; + sftp_limits_free; + sftp_lsetstat; + ssh_channel_get_exit_state; + ssh_channel_request_pty_size_modes; + ssh_pki_export_privkey_base64_format; + ssh_pki_export_privkey_file_format; + ssh_request_no_more_sessions; +} LIBSSH_4_9_0; + +LIBSSH_4_11_0 # Released +{ + global: + sftp_get_users_groups_by_id; + sftp_name_id_map_free; + sftp_name_id_map_new; + ssh_get_supported_methods; + ssh_key_get_sk_application; + ssh_key_get_sk_flags; + ssh_key_get_sk_user_id; + ssh_pki_ctx_free; + ssh_pki_ctx_get_sk_attestation_buffer; + ssh_pki_ctx_new; + ssh_pki_ctx_options_set; + ssh_pki_ctx_set_sk_pin_callback; + ssh_pki_ctx_sk_callbacks_option_set; + ssh_pki_ctx_sk_callbacks_options_clear; + ssh_pki_generate_key; + ssh_sk_resident_keys_load; + ssh_string_cmp; + ssh_string_from_data; + sshsig_sign; + sshsig_verify; +} LIBSSH_4_10_0; + +LIBSSH_4_12_0 # Released +{ + global: + ssh_session_kex_is_gss; + ssh_userauth_gssapi_keyex; +} LIBSSH_4_11_0; + diff --git a/src/libs/libssh-0.12.2/src/log.c b/src/libs/libssh-0.12.2/src/log.c new file mode 100644 index 000000000000..8bc8ccabdee9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/log.c @@ -0,0 +1,262 @@ +/* + * log.c - logging and debugging functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#ifdef HAVE_SYS_UTIME_H +#include +#endif /* HAVE_SYS_UTIME_H */ +#include + +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +#ifndef LOG_SIZE +#define LOG_SIZE 1024 +#endif + +static LIBSSH_THREAD int ssh_log_level; +static LIBSSH_THREAD ssh_logging_callback ssh_log_cb; +static LIBSSH_THREAD void *ssh_log_userdata = NULL; + +/** + * @defgroup libssh_log The SSH logging functions + * @ingroup libssh + * + * Logging functions for debugging and problem resolving. + * + * @{ + */ + +static int current_timestring(int hires, char *buf, size_t len) +{ + char tbuf[64]; + struct timeval tv; + struct tm tm, *tm_ptr = NULL; + time_t t; + + gettimeofday(&tv, NULL); + t = (time_t) tv.tv_sec; + + tm_ptr = localtime_r(&t, &tm); + if (tm_ptr == NULL) { + return -1; + } + + if (hires) { + strftime(tbuf, sizeof(tbuf), "%Y/%m/%d %H:%M:%S", &tm); + snprintf(buf, len, "%s.%06ld", tbuf, (long)tv.tv_usec); + } else { + strftime(tbuf, sizeof(tbuf), "%Y/%m/%d %H:%M:%S", &tm); + snprintf(buf, len, "%s", tbuf); + } + + return 0; +} + +static void ssh_log_stderr(int verbosity, + const char *function, + const char *buffer) +{ + char date[128] = {0}; + int rc; + + rc = current_timestring(1, date, sizeof(date)); + if (rc == 0) { + fprintf(stderr, "[%s, %d] %s:", date, verbosity, function); + } else { + fprintf(stderr, "[%d] %s", verbosity, function); + } + + fprintf(stderr, " %s\n", buffer); +} + +static void ssh_log_custom(ssh_logging_callback log_fn, + int verbosity, + const char *function, + const char *buffer) +{ + char buf[LOG_SIZE + 64]; + + snprintf(buf, sizeof(buf), "%s: %s", function, buffer); + log_fn(verbosity, function, buf, ssh_get_log_userdata()); +} + +void ssh_log_function(int verbosity, + const char *function, + const char *buffer) +{ + ssh_logging_callback log_fn = ssh_get_log_callback(); + + if (log_fn) { + ssh_log_custom(log_fn, verbosity, function, buffer); + return; + } + + ssh_log_stderr(verbosity, function, buffer); +} + +void ssh_vlog(int verbosity, + const char *function, + const char *format, + va_list *va) +{ + char buffer[LOG_SIZE]; + + vsnprintf(buffer, sizeof(buffer), format, *va); + ssh_log_function(verbosity, function, buffer); +} + +void _ssh_log(int verbosity, + const char *function, + const char *format, ...) +{ + va_list va; + + if (verbosity <= ssh_get_log_level()) { + va_start(va, format); + ssh_vlog(verbosity, function, format, &va); + va_end(va); + } +} + +/* LEGACY */ + +void ssh_log(ssh_session session, + int verbosity, + const char *format, ...) +{ + va_list va; + + if (verbosity <= session->common.log_verbosity) { + va_start(va, format); + ssh_vlog(verbosity, "", format, &va); + va_end(va); + } +} + +/** @internal + * @brief log a SSH event with a common pointer + * @param common The SSH/bind session. + * @param verbosity The verbosity of the event. + * @param format The format string of the log entry. + */ +void ssh_log_common(struct ssh_common_struct *common, + int verbosity, + const char *function, + const char *format, ...) +{ + va_list va; + + if (verbosity <= common->log_verbosity) { + va_start(va, format); + ssh_vlog(verbosity, function, format, &va); + va_end(va); + } +} + + +/* PUBLIC */ + +/** + * @brief Set the log level of the library. + * + * @param[in] level The level to set. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_set_log_level(int level) { + if (level < 0) { + return SSH_ERROR; + } + + ssh_log_level = level; + + return SSH_OK; +} + +/** + * @brief Get the log level of the library. + * + * @return The value of the log level. + */ +int ssh_get_log_level(void) { + return ssh_log_level; +} + +int ssh_set_log_callback(ssh_logging_callback cb) { + if (cb == NULL) { + return SSH_ERROR; + } + + ssh_log_cb = cb; + + return SSH_OK; +} + +void +_ssh_reset_log_cb(void) +{ + ssh_log_cb = NULL; +} + +ssh_logging_callback ssh_get_log_callback(void) { + return ssh_log_cb; +} + +/** + * @brief Get the userdata of the logging function. + * + * @return The userdata if set or NULL. + */ +void *ssh_get_log_userdata(void) +{ + if (ssh_log_userdata == NULL) { + return NULL; + } + + return ssh_log_userdata; +} + +/** + * @brief Set the userdata for the logging function. + * + * @param[in] data The userdata to set. + * + * @return SSH_OK on success. + */ +int ssh_set_log_userdata(void *data) +{ + ssh_log_userdata = data; + + return 0; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/match.c b/src/libs/libssh-0.12.2/src/match.c new file mode 100644 index 000000000000..40b9c0f8f491 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/match.c @@ -0,0 +1,615 @@ +/* + * Author: Tatu Ylonen + * Copyright (c) 1995 Tatu Ylonen , Espoo, Finland + * All rights reserved + * Simple pattern matching, with '*' and '?' as wildcards. + * + * As far as I am concerned, the code I have written for this software + * can be used freely for any purpose. Any derived versions of this + * software must be clearly marked as such, and if the derived work is + * incompatible with the protocol description in the RFC file, it must be + * called by a name other than "ssh" or "Secure Shell". + */ + +/* + * Copyright (c) 2000 Markus Friedl. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR + * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. + * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, + * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT + * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF + * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" + +#include +#include +#include +#ifndef _WIN32 +#include +#include +#include +#endif + +/* for systems without IPv6 support matching should still work */ +#ifndef INET6_ADDRSTRLEN +#define INET6_ADDRSTRLEN 46 +#endif + +#include "libssh/priv.h" + +/** + * @brief Compare a string with a pattern containing wildcards `*` and `?` + * + * This function is an iterative replacement for the previously recursive + * implementation to avoid exponential complexity (DoS) with specific patterns. + * + * @param[in] s The string to match. + * @param[in] pattern The pattern to match against. + * + * @return 1 if the pattern matches, 0 otherwise. + */ +static int match_pattern(const char *s, const char *pattern) +{ + const char *s_star = NULL; /* Position in s when last `*` was met */ + const char *p_star = NULL; /* Position in pattern after last `*` */ + + if (s == NULL || pattern == NULL) { + return 0; + } + + while (*s) { + /* Case 1: Exact match or '?' wildcard */ + if (*pattern == *s || *pattern == '?') { + s++; + pattern++; + continue; + } + + /* Case 2: '*' wildcard */ + if (*pattern == '*') { + /* Record the position of the star and the current string position. + * We optimistically assume * matches 0 characters first. + */ + p_star = ++pattern; + s_star = s; + continue; + } + + /* Case 3: Mismatch */ + if (p_star) { + /* If we have seen a star previously, backtrack. + * We restore the pattern to just after the star, + * but advance the string position (consume one more char for the + * star). + * No need to backtrack to previous stars as any match of the last + * star could be eaten the same way by the previous star. + */ + pattern = p_star; + s = ++s_star; + continue; + } + + /* Case 4: Mismatch and no star to backtrack to */ + return 0; + } + + /* Handle trailing stars in the pattern + * (e.g., pattern "abc*" matching "abc") */ + while (*pattern == '*') { + pattern++; + } + + /* If we reached the end of the pattern, it's a match */ + return (*pattern == '\0'); +} + +/* + * Tries to match the string against the comma-separated sequence of subpatterns + * (each possibly preceded by ! to indicate negation). + * Returns -1 if negation matches, 1 if there is a positive match, 0 if there is + * no match at all. + */ +int match_pattern_list(const char *string, const char *pattern, + size_t len, int dolower) { + char sub[1024]; + int negated; + int got_positive; + size_t i, subi; + + got_positive = 0; + for (i = 0; i < len;) { + /* Check if the subpattern is negated. */ + if (pattern[i] == '!') { + negated = 1; + i++; + } else { + negated = 0; + } + + /* + * Extract the subpattern up to a comma or end. Convert the + * subpattern to lowercase. + */ + for (subi = 0; + i < len && subi < sizeof(sub) - 1 && pattern[i] != ','; + subi++, i++) { + sub[subi] = dolower && isupper(pattern[i]) ? + (char)tolower(pattern[i]) : pattern[i]; + } + + /* If subpattern too long, return failure (no match). */ + if (subi >= sizeof(sub) - 1) { + return 0; + } + + /* If the subpattern was terminated by a comma, skip the comma. */ + if (i < len && pattern[i] == ',') { + i++; + } + + /* Null-terminate the subpattern. */ + sub[subi] = '\0'; + + /* Try to match the subpattern against the string. */ + if (match_pattern(string, sub)) { + if (negated) { + return -1; /* Negative */ + } else { + got_positive = 1; /* Positive */ + } + } + } + + /* + * Return success if got a positive match. If there was a negative + * match, we have already returned -1 and never get here. + */ + return got_positive; +} + +/* + * Tries to match the host name (which must be in all lowercase) against the + * comma-separated sequence of subpatterns (each possibly preceded by ! to + * indicate negation). + * Returns -1 if negation matches, 1 if there is a positive match, 0 if there + * is no match at all. + */ +int +match_hostname(const char *host, const char *pattern, size_t len) +{ + return match_pattern_list(host, pattern, len, 1); +} + +#ifndef _WIN32 +/** + * @brief Tries to match the host IPv6 address against a given network address + * with specified prefix length in CIDR notation. + * + * @param[in] host_addr The host address to verify. + * + * @param[in] net_addr The network id address against which the match is + * being verified + * + * @param[in] bits The prefix length + * + * @return 0 on a negative match. + * @return 1 on a positive match. + */ +static int +cidr_match_6(struct in6_addr *host_addr, + struct in6_addr *net_addr, + unsigned int bits) +{ + const uint8_t *a = host_addr->s6_addr; + const uint8_t *b = net_addr->s6_addr; + + unsigned int byte_whole, bits_left; + + /* The number of a complete byte covered by the prefix */ + byte_whole = bits / 8; + + /* + * The number of bits remaining in the incomplete (last) byte + * covered by the prefix + */ + bits_left = bits % 8; + + if (byte_whole) { + if (memcmp(a, b, byte_whole) != 0) { + return 0; + } + } + + if (bits_left) { + if ((a[byte_whole] ^ b[byte_whole]) & (0xFFu << (8 - bits_left))) { + return 0; + } + } + + return 1; +} + +/** + * @brief Tries to match the host IPv4 address against a given network address + * with specified prefix length in CIDR notation. + * + * @param[in] host_addr The host address to verify. + * + * @param[in] net_addr The network id address against which the match is + * being verified + * + * @param[in] bits The prefix length + * + * @return 0 on a negative match. + * @return 1 on a positive match. + */ +static int +cidr_match_4(struct in_addr *host_addr, + struct in_addr *net_addr, + unsigned int bits) +{ + if (bits == 0) { + /* C99 6.5.7 (3): u32 << 32 is undefined behaviour */ + return 1; + } + + return !((host_addr->s_addr ^ net_addr->s_addr) & + htonl((0xFFFFFFFFu << (32 - bits)) & 0xFFFFFFFFu)); +} + +/** + * @brief Checks if the mask length is valid according to the address family + * (IPv4 or IPv6). + * + * @param[in] family The address family (e.g. AF_INET or AF_INET6) + * + * @param[in] mask The subnet mask (prefix) + * + * @return true if the mask length does not exceed the maximum valid length + * according to the address family (IPv4 or IPv6). + * @return false if the mask length exceeds the maximum valid length + * or there is no match with IPv4 or IPv6 address family. + */ +static bool +masklen_valid(int family, unsigned int mask) +{ + switch (family) { + case AF_INET: + return mask <= 32; + case AF_INET6: + return mask <= 128; + default: + return false; + } +} + +/** + * @brief Extracts address family given a network address. + * + * @param[in] address The network address. + * + * @return The value of the address family if no errors. + * @return -1 in case of errors. + */ +static int +get_address_family(const char *address) +{ + struct addrinfo hints, *ai = NULL; + int rc = -1, rv; + + ZERO_STRUCT(hints); + if (address == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad arguments"); + goto out; + } + + hints.ai_flags = AI_NUMERICHOST; + rv = getaddrinfo(address, NULL, &hints, &ai); + if (rv != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't get address information - getaddrinfo() failed: %s", + gai_strerror(rv)); + goto out; + } + + rc = ai->ai_family; + freeaddrinfo(ai); + +out: + return rc; +} + +/** + * @brief Tries to match the host address against a CIDR list provided + * by the user. If the host address family is unknown, it can be derived by + * passing -1 as sa_family argument. + * + * It can be also used to validate a CIDR list when the passed address is NULL + * and sa_family is -1. + * + * @param[in] address The host address to verify (NULL to validate CIDR list). + * + * @param[in] addrlist The CIDR list against which the match is being verified. + * The CIDR list can contain both IPv4 and IPv6 addresses + * and has to be comma separated + * (',' only, space after comma not allowed). + * + * @param[in] sa_family The socket address family (e.g. AF_INET or AF_INET6, + * -1 to validate CIDR list or unknown address family). + * + * @usage To validate CIDR list: match_cidr_address_list(NULL, addrlist, -1). + * @usage To verify a match with unknown address family: + * match_cidr_address_list(address, addrlist, -1). + * @return 1 only on positive match. + * @return 0 on negative match or valid CIDR list. + * @return -1 on errors or invalid CIDR list. + */ +int +match_cidr_address_list(const char *address, + const char *addrlist, + int sa_family) +{ + char *list = NULL, *cp = NULL, *a = NULL, *b = NULL, *sp = NULL; + char addr_buffer[64], addr[NI_MAXHOST]; + struct in_addr try_addr, match_addr; + struct in6_addr try_addr6, match_addr6; + unsigned long mask_len; + size_t addr_len, tmp_len; + int rc = 0, r, ai_family; + + ZERO_STRUCT(try_addr); + ZERO_STRUCT(try_addr6); + ZERO_STRUCT(match_addr); + ZERO_STRUCT(match_addr6); + + if (sa_family != AF_INET && sa_family != AF_INET6 && sa_family != -1) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid argument: sa_family %d is not valid", + sa_family); + return -1; + } + + if (address != NULL) { + strncpy(addr, address, NI_MAXHOST - 1); + + /* Remove interface in case of IPv6 address: addr%interface */ + a = strchr(addr, '%'); + if (a != NULL) { + *a = '\0'; + } + + /* + * If sa_family is set to -1 and address is not NULL then + * the socket address family should be derived + */ + if (sa_family == -1) { + r = get_address_family(addr); + if (r == -1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to derive address family for address " + "\"%.100s\"", + addr); + return -1; + } + sa_family = r; + } + + /* + * Translate host address from dot notation to binary network format + * according to family type, + * i.e. IPv4 (store in in_addr) or IPv6 (store in in6_addr) + */ + if (sa_family == AF_INET) { + if (inet_pton(AF_INET, addr, &try_addr) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv4 address \"%.100s\"", + addr); + return -1; + } + } else if (sa_family == AF_INET6) { + if (inet_pton(AF_INET6, addr, &try_addr6) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv6 address \"%.100s\"", + addr); + return -1; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Address family %d for address \"%.100s\" " + "is not recognized", + sa_family, + addr); + return -1; + } + } + + b = list = strdup(addrlist); + if (b == NULL) { + return -1; + } + + while ((cp = strsep(&list, ",")) != NULL) { + if (*cp == '\0') { + SSH_LOG(SSH_LOG_TRACE, "Empty entry in list \"%.100s\"", b); + rc = -1; + break; + } + + /* + * Stop junk from reaching address translation. +3 for the "/prefix". + * INET6_ADDRSTRLEN is 46 and includes space for '\0' terminator. The + * maximum IPv6 address printable is the one that carries IPv4 too. + * E.g. ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255 is 46 chars + * long ('\0' included) and the maximum prefix length possible is 96. + * This explains why +3. All the other IPv6 addresses with maximum /127 + * prefix length (39 + 4) are covered just by INET6_ADDRSTRLEN itself + */ + addr_len = strlen(cp); + if (addr_len > INET6_ADDRSTRLEN + 3) { + SSH_LOG(SSH_LOG_TRACE, + "List entry \"%.100s\" too long: %zu > %d (MAX ALLOWED)", + cp, + addr_len, + INET6_ADDRSTRLEN + 3); + rc = -1; + break; + } + +#define VALID_CIDR_CHARS "0123456789abcdefABCDEF.:/" + tmp_len = strspn(cp, VALID_CIDR_CHARS); + if (tmp_len != addr_len) { + SSH_LOG(SSH_LOG_TRACE, + "List entry \"%.100s\" contains invalid characters " + "-> \"%c\" is an invalid character", + cp, + cp[tmp_len]); + rc = -1; + break; + } +#undef VALID_CIDR_CHARS + + strncpy(addr_buffer, cp, sizeof(addr_buffer) - 1); + sp = strchr(addr_buffer, '/'); + if (sp != NULL) { + *sp = '\0'; + sp++; + mask_len = strtoul(sp, &cp, 10); + if (*sp < '0' || *sp > '9' || *cp != '\0') { + SSH_LOG(SSH_LOG_TRACE, "Error while parsing prefix: %s", sp); + rc = -1; + break; + } + if (mask_len > 128) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid prefix: %lu exceeds the maximum allowed " + "(>128)", + mask_len); + rc = -1; + break; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Missing prefix length for list entry \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + + ai_family = get_address_family(addr_buffer); + if (ai_family == -1) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't get address family for \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + + if (ai_family == AF_INET) { + if (inet_pton(AF_INET, addr_buffer, &match_addr) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv4 address \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + } else if (ai_family == AF_INET6) { + if (inet_pton(AF_INET6, addr_buffer, &match_addr6) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Couldn't parse IPv6 address \"%.100s\"", + addr_buffer); + rc = -1; + break; + } + } else { + SSH_LOG(SSH_LOG_TRACE, + "Address family %d for address \"%.100s\" " + "is not recognized", + ai_family, + addr_buffer); + rc = -1; + break; + } + + if (masklen_valid(ai_family, mask_len) != true) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid mask length %lu for list entry \"%.100s\"", + mask_len, + addr_buffer); + rc = -1; + break; + } + + /* Verify match between host address and network address*/ + if (((ai_family == AF_INET && sa_family == AF_INET) && + cidr_match_4(&try_addr, &match_addr, mask_len)) || + ((ai_family == AF_INET6 && sa_family == AF_INET6) && + cidr_match_6(&try_addr6, &match_addr6, mask_len))) { + rc = 1; + break; + } + } + SAFE_FREE(b); + + return rc; +} +#endif /* _WIN32 */ + +/** + * @brief Tries to match an object against a comma separated group of objects + * + * The characters '*' and '?' are NOT considered wildcards and an object in the + * group preceded by a ! does NOT indicate negation. The characters '*', '?' + * and '!' are treated normally like other characters, only ',' (comma) is + * treated specially and is considered as a delimiter that separates objects in + * the group. + * + * @param[in] group Group of objects (comma separated) to match against. + * + * @param[in] object Object to match. + * + * @returns 1 if there is a match, 0 if there is no match at all. + */ +int match_group(const char *group, const char *object) +{ + const char *a = NULL; + const char *z = NULL; + + if (group == NULL || object == NULL) { + return 0; + } + + z = group; + do { + a = strchr(z, ','); + if (a == NULL) { + if (strcmp(z, object) == 0) { + return 1; + } + return 0; + } else { + if (strncmp(z, object, a - z) == 0) { + return 1; + } + } + z = a + 1; + } while (1); + + /* not reached */ + return 0; +} diff --git a/src/libs/libssh-0.12.2/src/mbedcrypto-compat.h b/src/libs/libssh-0.12.2/src/mbedcrypto-compat.h new file mode 100644 index 000000000000..f028854fb8ee --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mbedcrypto-compat.h @@ -0,0 +1,56 @@ +#ifndef MBEDCRYPTO_COMPAT_H +#define MBEDCRYPTO_COMPAT_H + +/* mbedtls/version.h should be available for both v2 and v3 + * v3 defines the version inside build_info.h so if it isn't defined + * in version.h we should have v3 + */ +#include +#include + +#ifndef MBEDTLS_VERSION_MAJOR +#include +#endif /* MBEDTLS_VERSION_MAJOR */ + +#if MBEDTLS_VERSION_MAJOR < 3 + +static inline size_t +mbedtls_cipher_info_get_key_bitlen(const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return info->key_bitlen; +} + +static inline size_t +mbedtls_cipher_info_get_iv_size(const mbedtls_cipher_info_t *info) +{ + if (info == NULL) { + return 0; + } + return (size_t)info->iv_size; +} + +#define MBEDTLS_PRIVATE(X) X + +#ifdef HAVE_MBEDTLS_CURVE25519 +#include + +#define MBEDTLS_ECDH_PRIVATE(X) X +#define MBEDTLS_ECDH_PARAMS(X) X +typedef mbedtls_ecdh_context mbedtls_ecdh_params; +#endif /* HAVE_MBEDTLS_CURVE25519 */ + +#else /* MBEDTLS_VERSION_MAJOR < 3 */ + +#ifdef HAVE_MBEDTLS_CURVE25519 +#include + +#define MBEDTLS_ECDH_PRIVATE(X) MBEDTLS_PRIVATE(X) +#define MBEDTLS_ECDH_PARAMS(X) X.MBEDTLS_PRIVATE(ctx).MBEDTLS_PRIVATE(mbed_ecdh) +typedef mbedtls_ecdh_context_mbed mbedtls_ecdh_params; +#endif /* HAVE_MBEDTLS_CURVE25519 */ + +#endif /* MBEDTLS_VERSION_MAJOR < 3 */ +#endif /* MBEDCRYPTO_COMPAT_H */ diff --git a/src/libs/libssh-0.12.2/src/mbedcrypto_missing.c b/src/libs/libssh-0.12.2/src/mbedcrypto_missing.c new file mode 100644 index 000000000000..2c1a8d7ad148 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mbedcrypto_missing.c @@ -0,0 +1,180 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/libmbedcrypto.h" + +#ifdef HAVE_LIBMBEDCRYPTO +bignum ssh_mbedcry_bn_new(void) +{ + bignum bn; + + bn = malloc(sizeof(mbedtls_mpi)); + if (bn) { + mbedtls_mpi_init(bn); + } + + return bn; +} + +void ssh_mbedcry_bn_free(bignum bn) +{ + mbedtls_mpi_free(bn); + SAFE_FREE(bn); +} + +char *ssh_mbedcry_bn2num(const_bignum num, int radix) +{ + char *buf = NULL; + size_t olen; + int rc; + + rc = mbedtls_mpi_write_string(num, radix, buf, 0, &olen); + if (rc != 0 && rc != MBEDTLS_ERR_MPI_BUFFER_TOO_SMALL) { + return NULL; + } + + buf = mbedtls_calloc(1, olen); + if (buf == NULL) { + return NULL; + } + + rc = mbedtls_mpi_write_string(num, radix, buf, olen, &olen); + if (rc != 0) { + SAFE_FREE(buf); + return NULL; + } + + return buf; +} + +int ssh_mbedcry_rand(bignum rnd, int bits, int top, int bottom) +{ + size_t len; + int rc; + int i; + + if (bits <= 0) { + return 0; + } + + len = bits / 8 + 1; + /* FIXME weird bug: over 1024, fill_random function returns an error code + * MBEDTLS_ERR_MPI_BAD_INPUT_DATA -0x0004 + */ + if (len > 1024){ + len = 1024; + } + rc = mbedtls_mpi_fill_random(rnd, + len, + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + if (rc != 0) { + return 0; + } + + for (i = len * 8 - 1; i >= bits; i--) { + rc = mbedtls_mpi_set_bit(rnd, i, 0); + if (rc != 0) { + return 0; + } + } + + if (top == 0) { + rc = mbedtls_mpi_set_bit(rnd, bits - 1, 0); + if (rc != 0) { + return 0; + } + } + + if (top == 1) { + if (bits < 2) { + return 0; + } + + rc = mbedtls_mpi_set_bit(rnd, bits - 2, 0); + if (rc != 0) { + return 0; + } + } + + if (bottom) { + rc = mbedtls_mpi_set_bit(rnd, 0, 1); + if (rc != 0) { + return 0; + } + } + + return 1; +} + +int ssh_mbedcry_is_bit_set(bignum num, size_t pos) +{ + int bit; + bit = mbedtls_mpi_get_bit(num, pos); + return bit; +} + +/** @brief generates a random integer between 0 and max + * @returns 1 in case of success, 0 otherwise + */ +int ssh_mbedcry_rand_range(bignum dest, bignum max) +{ + size_t bits; + bignum rnd; + int rc; + + bits = bignum_num_bits(max) + 64; + rnd = bignum_new(); + if (rnd == NULL){ + return 0; + } + rc = bignum_rand(rnd, bits); + if (rc != 1) { + bignum_safe_free(rnd); + return rc; + } + mbedtls_mpi_mod_mpi(dest, rnd, max); + bignum_safe_free(rnd); + return 1; +} + +int ssh_mbedcry_hex2bn(bignum *dest, char *data) +{ + int rc; + + *dest = bignum_new(); + if (*dest == NULL){ + return 0; + } + rc = mbedtls_mpi_read_string(*dest, 16, data); + if (rc == 0) { + return 1; + } + + return 0; +} + +#endif diff --git a/src/libs/libssh-0.12.2/src/md_crypto.c b/src/libs/libssh-0.12.2/src/md_crypto.c new file mode 100644 index 000000000000..100f1079d6cf --- /dev/null +++ b/src/libs/libssh-0.12.2/src/md_crypto.c @@ -0,0 +1,373 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libcrypto-compat.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include +#include +#include +#include + +SHACTX +sha1_init(void) +{ + int rc; + SHACTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha1(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha1_ctx_free(SHACTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHACTX c = sha1_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha1_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha1_final(hash, c); +} + +SHA256CTX +sha256_init(void) +{ + int rc; + SHA256CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha256(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHA256CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA256CTX c = sha256_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha256_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha256_final(hash, c); +} + +SHA384CTX +sha384_init(void) +{ + int rc; + SHA384CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha384(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHA384CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA384CTX c = sha384_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha384_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha384_final(hash, c); +} + +SHA512CTX +sha512_init(void) +{ + int rc = 0; + SHA512CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_sha512(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHA512CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + SHA512CTX c = sha512_init(); + int rc; + + if (c == NULL) { + return SSH_ERROR; + } + rc = sha512_update(c, digest, len); + if (rc != SSH_OK) { + EVP_MD_CTX_free(c); + return SSH_ERROR; + } + return sha512_final(hash, c); +} + +MD5CTX +md5_init(void) +{ + int rc; + MD5CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + return NULL; + } + rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); + if (rc == 0) { + EVP_MD_CTX_free(c); + c = NULL; + } + return c; +} + +void +md5_ctx_free(MD5CTX c) +{ + EVP_MD_CTX_free(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + int rc = EVP_DigestUpdate(c, data, len); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + unsigned int mdlen = 0; + int rc = EVP_DigestFinal(c, md, &mdlen); + + EVP_MD_CTX_free(c); + if (rc != 1) { + return SSH_ERROR; + } + return SSH_OK; +} + +/** +* @ brief One-shot MD5. Not intended for use in security-relevant contexts. +*/ +int +md5(const unsigned char *digest, size_t len, unsigned char *hash) +{ + int rc, ret = SSH_ERROR; + unsigned int mdlen = 0; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD *md5 = NULL; +#endif + MD5CTX c = EVP_MD_CTX_new(); + if (c == NULL) { + goto out; + } + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + md5 = EVP_MD_fetch(NULL, "MD5", FIPS_FALLBACK_PROPQ); + if (md5 == NULL) { + goto out; + } + rc = EVP_DigestInit(c, md5); +#else + rc = EVP_DigestInit_ex(c, EVP_md5(), NULL); +#endif + if (rc == 0) { + goto out; + } + + rc = EVP_DigestUpdate(c, digest, len); + if (rc != 1) { + goto out; + } + + rc = EVP_DigestFinal(c, hash, &mdlen); + if (rc != 1) { + goto out; + } + + ret = SSH_OK; + +out: + EVP_MD_CTX_free(c); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EVP_MD_free(md5); +#endif + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/md_gcrypt.c b/src/libs/libssh-0.12.2/src/md_gcrypt.c new file mode 100644 index 000000000000..c8830773e254 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/md_gcrypt.c @@ -0,0 +1,252 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" + +#include + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA1, 0); + + return ctx; +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +void +sha1_ctx_free(SHACTX c) +{ + gcry_md_close(c); +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA1, hash, digest, len); + return SSH_OK; +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA256, 0); + + return ctx; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + gcry_md_close(c); +} + +int +sha256_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA256_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA256, hash, digest, len); + return SSH_OK; +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA384, 0); + + return ctx; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + gcry_md_close(c); +} + +int +sha384_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA384_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA384, hash, digest, len); + return SSH_OK; +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + gcry_md_open(&ctx, GCRY_MD_SHA512, 0); + + return ctx; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + gcry_md_close(c); +} + +int +sha512_update(SHACTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHACTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, SHA512_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_SHA512, hash, digest, len); + return SSH_OK; +} + +MD5CTX +md5_init(void) +{ + MD5CTX c = NULL; + gcry_md_open(&c, GCRY_MD_MD5, 0); + + return c; +} + +void +md5_ctx_free(MD5CTX c) +{ + gcry_md_close(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + gcry_md_write(c, data, len); + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + unsigned char *tmp = NULL; + + gcry_md_final(c); + tmp = gcry_md_read(c, 0); + if (tmp == NULL) { + gcry_md_close(c); + return SSH_ERROR; + } + memcpy(md, tmp, MD5_DIGEST_LEN); + gcry_md_close(c); + return SSH_OK; +} + +int md5(const unsigned char *digest, size_t len, unsigned char *hash) +{ + gcry_md_hash_buffer(GCRY_MD_MD5, hash, digest, len); + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/md_mbedcrypto.c b/src/libs/libssh-0.12.2/src/md_mbedcrypto.c new file mode 100644 index 000000000000..445f644da118 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/md_mbedcrypto.c @@ -0,0 +1,455 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "mbedcrypto-compat.h" + +#include + +SHACTX +sha1_init(void) +{ + SHACTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha1_ctx_free(SHACTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha1_update(SHACTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1_final(unsigned char *md, SHACTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha1_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha1(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA1); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA256CTX +sha256_init(void) +{ + SHA256CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha256_ctx_free(SHA256CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha256_update(SHA256CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256_final(unsigned char *md, SHA256CTX c) +{ + int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha256(const unsigned char *digest, size_t len, unsigned char *hash) +{ + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA256); + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA384CTX +sha384_init(void) +{ + SHA384CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha384_ctx_free(SHA384CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha384_update(SHA384CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384_final(unsigned char *md, SHA384CTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha384_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha384(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA384); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +SHA512CTX +sha512_init(void) +{ + SHA512CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +sha512_ctx_free(SHA512CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +sha512_update(SHA512CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512_final(unsigned char *md, SHA512CTX c) +{ + int rc = mbedtls_md_finish(c, md); + sha512_ctx_free(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +sha512(const unsigned char *digest, size_t len, unsigned char *hash) +{ + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_SHA512); + int rc; + + if (md_info == NULL) { + return SSH_ERROR; + } + rc = mbedtls_md(md_info, digest, len, hash); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +MD5CTX +md5_init(void) +{ + MD5CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + if (md_info == NULL) { + return NULL; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return NULL; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + SAFE_FREE(ctx); + return NULL; + } + + return ctx; +} + +void +md5_ctx_free(MD5CTX c) +{ + mbedtls_md_free(c); + SAFE_FREE(c); +} + +int +md5_update(MD5CTX c, const void *data, size_t len) +{ + int rc = mbedtls_md_update(c, data, len); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int +md5_final(unsigned char *md, MD5CTX c) +{ + int rc = mbedtls_md_finish(c, md); + mbedtls_md_free(c); + SAFE_FREE(c); + if (rc != 0) { + return SSH_ERROR; + } + return SSH_OK; +} + +int md5(const unsigned char *digest, size_t len, unsigned char *hash) +{ + MD5CTX ctx = NULL; + int rc; + const mbedtls_md_info_t *md_info = + mbedtls_md_info_from_type(MBEDTLS_MD_MD5); + if (md_info == NULL) { + return SSH_ERROR; + } + + ctx = malloc(sizeof(mbedtls_md_context_t)); + if (ctx == NULL) { + return SSH_ERROR; + } + + mbedtls_md_init(ctx); + + rc = mbedtls_md_setup(ctx, md_info, 0); + if (rc != 0) { + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return SSH_ERROR; + } + + rc = mbedtls_md_starts(ctx); + if (rc != 0) { + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return SSH_ERROR; + } + + rc = mbedtls_md_update(ctx, digest, len); + if (rc != 0) { + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + return SSH_ERROR; + } + + rc = mbedtls_md_finish(ctx, hash); + mbedtls_md_free(ctx); + SAFE_FREE(ctx); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/messages.c b/src/libs/libssh-0.12.2/src/messages.c new file mode 100644 index 000000000000..561879888d7b --- /dev/null +++ b/src/libs/libssh-0.12.2/src/messages.c @@ -0,0 +1,1980 @@ +/* + * messages.c - message parsing for client and server + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/messages.h" +#include "libssh/socket.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#include "libssh/gssapi.h" +#endif + +/** + * @defgroup libssh_messages The SSH message functions + * @ingroup libssh + * + * This file contains the message parsing utilities for client and server + * programs using libssh. + * + * On the server the main loop of the program will call + * ssh_message_get(session) to get messages as they come. They are not 1-1 with + * the protocol messages. Then, the user will know what kind of a message it is + * and use the appropriate functions to handle it (or use the default handlers + * if you don't know what to do). + * + * @{ + */ + +static ssh_message ssh_message_new(ssh_session session) +{ + ssh_message msg = calloc(1, sizeof(struct ssh_message_struct)); + if (msg == NULL) { + return NULL; + } + msg->session = session; + + /* Set states explicitly */ + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + + return msg; +} + +#ifndef WITH_SERVER + +/* Reduced version of the reply default that only replies with + * SSH_MSG_UNIMPLEMENTED + */ +static int ssh_message_reply_default(ssh_message msg) { + SSH_LOG(SSH_LOG_FUNCTIONS, "Reporting unknown packet"); + + if (ssh_buffer_add_u8(msg->session->out_buffer, SSH2_MSG_UNIMPLEMENTED) < 0) + goto error; + if (ssh_buffer_add_u32(msg->session->out_buffer, + htonl(msg->session->recv_seq-1)) < 0) + goto error; + return ssh_packet_send(msg->session); + error: + return SSH_ERROR; +} + +#endif + +static int ssh_send_disconnect(ssh_session session) +{ + int rc = SSH_ERROR; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->disconnect_message == NULL) { + session->disconnect_message = strdup("Bye Bye"); + if (session->disconnect_message == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + + if (session->socket != NULL && ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bdss", + SSH2_MSG_DISCONNECT, + SSH2_DISCONNECT_BY_APPLICATION, + session->disconnect_message, + ""); /* language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = ssh_packet_send(session); + ssh_session_socket_close(session); + } + + return rc; +} + +#ifdef WITH_SERVER + +static int ssh_execute_server_request(ssh_session session, ssh_message msg) +{ + ssh_channel channel = NULL; + int rc; + + switch(msg->type) { + case SSH_REQUEST_AUTH: + if (msg->auth_request.method == SSH_AUTH_METHOD_PASSWORD && + ssh_callbacks_exists(session->server_callbacks, auth_password_function)) { + rc = session->server_callbacks->auth_password_function(session, + msg->auth_request.username, msg->auth_request.password, + session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } else if(msg->auth_request.method == SSH_AUTH_METHOD_PUBLICKEY && + ssh_callbacks_exists(session->server_callbacks, auth_pubkey_function)) { + rc = session->server_callbacks->auth_pubkey_function(session, + msg->auth_request.username, msg->auth_request.pubkey, + msg->auth_request.signature_state, + session->server_callbacks->userdata); + if (msg->auth_request.signature_state != SSH_PUBLICKEY_STATE_NONE) { + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else { + ssh_message_reply_default(msg); + } + } else { + if (rc == SSH_AUTH_SUCCESS) { + ssh_message_auth_reply_pk_ok_simple(msg); + } else { + ssh_message_reply_default(msg); + } + } + + return SSH_OK; + } else if (msg->auth_request.method == SSH_AUTH_METHOD_NONE && + ssh_callbacks_exists(session->server_callbacks, auth_none_function)) { + rc = session->server_callbacks->auth_none_function(session, + msg->auth_request.username, session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL){ + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } else if (msg->auth_request.method == SSH_AUTH_METHOD_INTERACTIVE && + ssh_callbacks_exists(session->server_callbacks, auth_kbdint_function)) { + rc = session->server_callbacks->auth_kbdint_function(msg, + session, + session->server_callbacks->userdata); + if (rc == SSH_AUTH_SUCCESS || rc == SSH_AUTH_PARTIAL) { + ssh_message_auth_reply_success(msg, rc == SSH_AUTH_PARTIAL); + } else if (rc == SSH_AUTH_INFO) { + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + break; + case SSH_REQUEST_CHANNEL_OPEN: + if (msg->channel_request_open.type == SSH_CHANNEL_SESSION && + ssh_callbacks_exists(session->server_callbacks, channel_open_request_session_function)) { + channel = session->server_callbacks->channel_open_request_session_function(session, + session->server_callbacks->userdata); + if (channel != NULL) { + rc = ssh_message_channel_request_open_reply_accept_channel(msg, channel); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to send reply for accepting a channel " + "open"); + } + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; +#define CB channel_open_request_direct_tcpip_function + } else if (msg->channel_request_open.type == SSH_CHANNEL_DIRECT_TCPIP && + ssh_callbacks_exists(session->server_callbacks, CB)) { + struct ssh_channel_request_open *rq = &msg->channel_request_open; + channel = session->server_callbacks->CB(session, + rq->destination, + rq->destination_port, + rq->originator, + rq->originator_port, + session->server_callbacks->userdata); +#undef CB + if (channel != NULL) { + rc = ssh_message_channel_request_open_reply_accept_channel( + msg, + channel); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to send reply for accepting a channel " + "open"); + } + return SSH_OK; + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + + break; + case SSH_REQUEST_CHANNEL: + channel = msg->channel_request.channel; + + if (msg->channel_request.type == SSH_CHANNEL_REQUEST_PTY){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_pty_request_function) { + rc = ssh_callbacks_iterate_exec(channel_pty_request_function, + session, + channel, + msg->channel_request.TERM, + msg->channel_request.width, + msg->channel_request.height, + msg->channel_request.pxwidth, + msg->channel_request.pxheight); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_SHELL){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_shell_request_function) { + rc = ssh_callbacks_iterate_exec(channel_shell_request_function, + session, + channel); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_X11){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_x11_req_function) { + ssh_callbacks_iterate_exec(channel_x11_req_function, + session, + channel, + msg->channel_request.x11_single_connection, + msg->channel_request.x11_auth_protocol, + msg->channel_request.x11_auth_cookie, + msg->channel_request.x11_screen_number); + ssh_message_channel_request_reply_success(msg); + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_WINDOW_CHANGE){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_pty_window_change_function) { + rc = ssh_callbacks_iterate_exec(channel_pty_window_change_function, + session, + channel, + msg->channel_request.width, + msg->channel_request.height, + msg->channel_request.pxwidth, + msg->channel_request.pxheight); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to iterate callbacks for window change"); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_EXEC){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_exec_request_function) { + rc = ssh_callbacks_iterate_exec(channel_exec_request_function, + session, + channel, + msg->channel_request.command); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_ENV){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_env_request_function) { + rc = ssh_callbacks_iterate_exec(channel_env_request_function, + session, + channel, + msg->channel_request.var_name, + msg->channel_request.var_value); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } else if (msg->channel_request.type == SSH_CHANNEL_REQUEST_SUBSYSTEM){ + ssh_callbacks_iterate(channel->callbacks, + ssh_channel_callbacks, + channel_subsystem_request_function) { + rc = ssh_callbacks_iterate_exec(channel_subsystem_request_function, + session, + channel, + msg->channel_request.subsystem); + if (rc == 0) { + ssh_message_channel_request_reply_success(msg); + } else { + ssh_message_reply_default(msg); + } + + return SSH_OK; + } + ssh_callbacks_iterate_end(); + } + break; + case SSH_REQUEST_SERVICE: + if (ssh_callbacks_exists(session->server_callbacks, service_request_function)) { + rc = session->server_callbacks->service_request_function(session, + msg->service_request.service, session->server_callbacks->userdata); + if (rc == 0) { + ssh_message_reply_default(msg); + } else { + ssh_send_disconnect(session); + } + + return SSH_OK; + } + + return SSH_AGAIN; + case SSH_REQUEST_GLOBAL: + break; + } + + return SSH_AGAIN; +} + +static int ssh_reply_channel_open_request(ssh_message msg, ssh_channel channel) +{ + if (channel != NULL) { + return ssh_message_channel_request_open_reply_accept_channel(msg, channel); + } + + ssh_message_reply_default(msg); + + return SSH_OK; +} + +static int ssh_execute_client_request(ssh_session session, ssh_message msg) +{ + ssh_channel channel = NULL; + int rc = SSH_AGAIN; + + if (msg->type == SSH_REQUEST_CHANNEL_OPEN + && msg->channel_request_open.type == SSH_CHANNEL_X11 + && ssh_callbacks_exists(session->common.callbacks, channel_open_request_x11_function)) { + channel = session->common.callbacks->channel_open_request_x11_function (session, + msg->channel_request_open.originator, + msg->channel_request_open.originator_port, + session->common.callbacks->userdata); + + return ssh_reply_channel_open_request(msg, channel); + } else if (msg->type == SSH_REQUEST_CHANNEL_OPEN + && msg->channel_request_open.type == SSH_CHANNEL_AUTH_AGENT + && ssh_callbacks_exists(session->common.callbacks, channel_open_request_auth_agent_function)) { + channel = session->common.callbacks->channel_open_request_auth_agent_function (session, + session->common.callbacks->userdata); + + return ssh_reply_channel_open_request(msg, channel); + } else if (msg->type == SSH_REQUEST_CHANNEL_OPEN + && msg->channel_request_open.type == SSH_CHANNEL_FORWARDED_TCPIP + && ssh_callbacks_exists(session->common.callbacks, channel_open_request_forwarded_tcpip_function)) { + channel = session->common.callbacks->channel_open_request_forwarded_tcpip_function(session, + msg->channel_request_open.destination, + msg->channel_request_open.destination_port, + msg->channel_request_open.originator, + msg->channel_request_open.originator_port, + session->common.callbacks->userdata); + + return ssh_reply_channel_open_request(msg, channel); + } + + return rc; +} + +/** @internal + * Executes the callbacks defined in session->server_callbacks, out of an ssh_message + * I don't like ssh_message interface but it works. + * @returns SSH_OK if the message has been handled, or SSH_AGAIN otherwise. + */ +static int ssh_execute_server_callbacks(ssh_session session, ssh_message msg){ + int rc = SSH_AGAIN; + + if (session->server_callbacks != NULL){ + rc = ssh_execute_server_request(session, msg); + } else if (session->common.callbacks != NULL) { + /* This one is in fact a client callback... */ + rc = ssh_execute_client_request(session, msg); + } + + return rc; +} + +#endif /* WITH_SERVER */ + +static int ssh_execute_message_callback(ssh_session session, ssh_message msg) { + int ret; + if(session->ssh_message_callback != NULL) { + ret = session->ssh_message_callback(session, msg, + session->ssh_message_callback_data); + if(ret == 1) { + ret = ssh_message_reply_default(msg); + SSH_MESSAGE_FREE(msg); + if(ret != SSH_OK) { + return ret; + } + } else { + SSH_MESSAGE_FREE(msg); + } + } else { + ret = ssh_message_reply_default(msg); + SSH_MESSAGE_FREE(msg); + if(ret != SSH_OK) { + return ret; + } + } + return SSH_OK; +} + +/** + * @internal + * + * @brief Add a message to the current queue of messages to be parsed and/or call + * the various callback functions. + * + * @param[in] session The SSH session to add the message. + * + * @param[in] message The message to add to the queue. + */ +static void ssh_message_queue(ssh_session session, ssh_message message) +{ +#ifdef WITH_SERVER + int ret; +#endif + + if (message == NULL) { + return; + } + +#ifdef WITH_SERVER + /* probably not the best place to execute server callbacks, but still better + * than nothing. + */ + ret = ssh_execute_server_callbacks(session, message); + if (ret == SSH_OK) { + SSH_MESSAGE_FREE(message); + return; + } +#endif /* WITH_SERVER */ + + if (session->ssh_message_callback != NULL) { + /* This will transfer the message, do not free. */ + ssh_execute_message_callback(session, message); + return; + } + + if (session->server_callbacks != NULL) { + /* if we have server callbacks, but nothing was executed, it means we are + * in non-synchronous mode, and we just don't care about the message we + * received. Just send a default response. Do not queue it. + */ + ssh_message_reply_default(message); + SSH_MESSAGE_FREE(message); + return; + } + + if (session->ssh_message_list == NULL) { + session->ssh_message_list = ssh_list_new(); + if (session->ssh_message_list == NULL) { + /* + * If the message list couldn't be allocated, the message can't be + * enqueued + */ + ssh_message_reply_default(message); + ssh_set_error_oom(session); + SSH_MESSAGE_FREE(message); + return; + } + } + + /* This will transfer the message, do not free. */ + ssh_list_append(session->ssh_message_list, message); + return; +} + +/** + * @internal + * + * @brief Pop a message from the message list and dequeue it. + * + * @param[in] session The SSH session to pop the message. + * + * @returns The head message or NULL if it doesn't exist. + */ +ssh_message ssh_message_pop_head(ssh_session session) +{ + ssh_message msg = NULL; + struct ssh_iterator *i = NULL; + + if (session->ssh_message_list == NULL) + return NULL; + + i = ssh_list_get_iterator(session->ssh_message_list); + if (i != NULL) { + msg = ssh_iterator_value(ssh_message, i); + ssh_list_remove(session->ssh_message_list, i); + } + return msg; +} + +/* Returns 1 if there is a message available */ +static int ssh_message_termination(void *s) +{ + ssh_session session = s; + struct ssh_iterator *it = NULL; + + if (session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + + it = ssh_list_get_iterator(session->ssh_message_list); + if (!it) + return 0; + else + return 1; +} +/** + * @brief Retrieve a SSH message from a SSH session. + * + * @param[in] session The SSH session to get the message. + * + * @returns The SSH message received, NULL in case of error, or timeout + * elapsed. + * + * @warning This function blocks until a message has been received. Betterset up + * a callback if this behavior is unwanted. + */ +ssh_message ssh_message_get(ssh_session session) +{ + ssh_message msg = NULL; + int rc; + + msg = ssh_message_pop_head(session); + if (msg != NULL) { + return msg; + } + if (session->ssh_message_list == NULL) { + session->ssh_message_list = ssh_list_new(); + if (session->ssh_message_list == NULL) { + ssh_set_error_oom(session); + return NULL; + } + } + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_message_termination, session); + if (rc || session->session_state == SSH_SESSION_STATE_ERROR) { + return NULL; + } + msg = ssh_list_pop_head(ssh_message, session->ssh_message_list); + + return msg; +} + +/** + * @brief Get the type of the message. + * + * @param[in] msg The message to get the type from. + * + * @return The message type or -1 on error. + */ +int ssh_message_type(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + return msg->type; +} + +/** + * @brief Get the subtype of the message. + * + * @param[in] msg The message to get the subtype from. + * + * @return The message type or -1 on error. + */ +int ssh_message_subtype(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + return msg->auth_request.method; + case SSH_REQUEST_CHANNEL_OPEN: + return msg->channel_request_open.type; + case SSH_REQUEST_CHANNEL: + return msg->channel_request.type; + case SSH_REQUEST_GLOBAL: + return msg->global_request.type; + } + + return -1; +} + +/** + * @brief Free a SSH message. + * + * @param[in] msg The message to release the memory. + */ +void ssh_message_free(ssh_message msg){ + if (msg == NULL) { + return; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + SAFE_FREE(msg->auth_request.username); + SAFE_FREE(msg->auth_request.sigtype); + if (msg->auth_request.password) { + ssh_burn(msg->auth_request.password, + strlen(msg->auth_request.password)); + SAFE_FREE(msg->auth_request.password); + } + ssh_key_free(msg->auth_request.pubkey); + ssh_key_free(msg->auth_request.server_pubkey); + break; + case SSH_REQUEST_CHANNEL_OPEN: + SAFE_FREE(msg->channel_request_open.originator); + SAFE_FREE(msg->channel_request_open.destination); + break; + case SSH_REQUEST_CHANNEL: + SAFE_FREE(msg->channel_request.TERM); + SAFE_FREE(msg->channel_request.modes); + SAFE_FREE(msg->channel_request.var_name); + SAFE_FREE(msg->channel_request.var_value); + SAFE_FREE(msg->channel_request.command); + SAFE_FREE(msg->channel_request.subsystem); + switch (msg->channel_request.type) { + case SSH_CHANNEL_REQUEST_EXEC: + SAFE_FREE(msg->channel_request.command); + break; + case SSH_CHANNEL_REQUEST_ENV: + SAFE_FREE(msg->channel_request.var_name); + SAFE_FREE(msg->channel_request.var_value); + break; + case SSH_CHANNEL_REQUEST_PTY: + SAFE_FREE(msg->channel_request.TERM); + break; + case SSH_CHANNEL_REQUEST_SUBSYSTEM: + SAFE_FREE(msg->channel_request.subsystem); + break; + case SSH_CHANNEL_REQUEST_X11: + SAFE_FREE(msg->channel_request.x11_auth_protocol); + SAFE_FREE(msg->channel_request.x11_auth_cookie); + break; + } + break; + case SSH_REQUEST_SERVICE: + SAFE_FREE(msg->service_request.service); + break; + case SSH_REQUEST_GLOBAL: + SAFE_FREE(msg->global_request.bind_address); + break; + } + ZERO_STRUCTP(msg); + SAFE_FREE(msg); +} + +#ifdef WITH_SERVER + +SSH_PACKET_CALLBACK(ssh_packet_service_request) +{ + char *service_c = NULL; + ssh_message msg = NULL; + int rc; + + (void)type; + (void)user; + + rc = ssh_buffer_unpack(packet, + "s", + &service_c); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Invalid SSH_MSG_SERVICE_REQUEST packet"); + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received a SERVICE_REQUEST for service %s", + service_c); + + msg = ssh_message_new(session); + if (msg == NULL) { + SAFE_FREE(service_c); + goto error; + } + + msg->type = SSH_REQUEST_SERVICE; + msg->service_request.service = service_c; + + ssh_message_queue(session, msg); +error: + + return SSH_PACKET_USED; +} + + +/* + * This function concats in a buffer the values needed to do a signature + * verification. + */ +static ssh_buffer ssh_msg_userauth_build_digest(ssh_session session, + ssh_message msg, + const char *service, + ssh_string algo, + const char *method) +{ + struct ssh_crypto_struct *crypto = NULL; + ssh_buffer buffer = NULL; + ssh_string str = NULL; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + rc = ssh_pki_export_pubkey_blob(msg->auth_request.pubkey, &str); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dPbsssbsS", + (uint32_t)crypto->session_id_len, /* session ID string */ + crypto->session_id_len, + crypto->session_id, + SSH2_MSG_USERAUTH_REQUEST, /* type */ + msg->auth_request.username, + service, + method, + 1, /* has to be signed (true) */ + ssh_string_get_char(algo), /* pubkey algorithm */ + str); /* public key as a blob */ + + SSH_STRING_FREE(str); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(buffer); + return NULL; + } + + /* Add server public key for hostbound extension */ + if (strcmp(method, "publickey-hostbound-v00@openssh.com") == 0 && + msg->auth_request.server_pubkey != NULL) { + + rc = ssh_pki_export_pubkey_blob(msg->auth_request.server_pubkey, &str); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, str); + SSH_STRING_FREE(str); + if (rc < 0) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(buffer); + return NULL; + } + } + + return buffer; +} + +/** + * @internal + * + * @brief Handle a SSH_MSG_MSG_USERAUTH_REQUEST packet and queue a + * SSH Message + */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_request) +{ + ssh_message msg = NULL; + ssh_signature sig = NULL; + char *service = NULL; + char *method = NULL; + int cmp; + int rc; + + (void)user; + (void)type; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->type = SSH_REQUEST_AUTH; + rc = ssh_buffer_unpack(packet, + "sss", + &msg->auth_request.username, + &service, + &method); + + if (rc != SSH_OK) { + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Auth request for service %s, method %s for user '%s'", + service, + method, + msg->auth_request.username); + + cmp = strcmp(service, "ssh-connection"); + if (cmp != 0) { + SSH_LOG(SSH_LOG_TRACE, "Invalid service request: %s", service); + goto end; + } + + if (strcmp(method, "none") == 0) { + msg->auth_request.method = SSH_AUTH_METHOD_NONE; + goto end; + } + + if (strcmp(method, "password") == 0) { + uint8_t tmp; + + msg->auth_request.method = SSH_AUTH_METHOD_PASSWORD; + rc = ssh_buffer_unpack(packet, "bs", &tmp, &msg->auth_request.password); + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(method, "keyboard-interactive") == 0) { + ssh_string lang = NULL; + ssh_string submethods = NULL; + + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + lang = ssh_buffer_get_ssh_string(packet); + if (lang == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "The language tag is deprecated and SHOULD be the empty string." + */ + SSH_STRING_FREE(lang); + + submethods = ssh_buffer_get_ssh_string(packet); + if (submethods == NULL) { + goto error; + } + /* from the RFC 4256 + * 3.1. Initial Exchange + * "One possible implementation strategy of the submethods field on the + * server is that, unless the user may use multiple different + * submethods, the server ignores this field." + */ + SSH_STRING_FREE(submethods); + + goto end; + } + + if (strcmp(method, "publickey") == 0 || + strcmp(method, "publickey-hostbound-v00@openssh.com") == 0) { + ssh_string algo = NULL; + ssh_string pubkey_blob = NULL; + ssh_string server_pubkey_blob = NULL; + uint8_t has_sign; + + msg->auth_request.method = SSH_AUTH_METHOD_PUBLICKEY; + + rc = ssh_buffer_unpack(packet, "bSS", &has_sign, &algo, &pubkey_blob); + + if (rc != SSH_OK) { + goto error; + } + + cmp = strcmp(method, "publickey-hostbound-v00@openssh.com"); + if (cmp == 0) { + server_pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (server_pubkey_blob == NULL) { + SSH_STRING_FREE(pubkey_blob); + SSH_STRING_FREE(algo); + goto error; + } + + rc = ssh_pki_import_pubkey_blob(server_pubkey_blob, + &msg->auth_request.server_pubkey); + SSH_STRING_FREE(server_pubkey_blob); + + if (rc < 0) { + SSH_STRING_FREE(pubkey_blob); + SSH_STRING_FREE(algo); + goto error; + } + } + + rc = ssh_pki_import_pubkey_blob(pubkey_blob, &msg->auth_request.pubkey); + SSH_STRING_FREE(pubkey_blob); + pubkey_blob = NULL; + if (rc < 0) { + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_NONE; + msg->auth_request.sigtype = strdup(ssh_string_get_char(algo)); + if (msg->auth_request.sigtype == NULL) { + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + + // has a valid signature ? + if (has_sign) { + ssh_string sig_blob = NULL; + ssh_buffer digest = NULL; + + sig_blob = ssh_buffer_get_ssh_string(packet); + if (sig_blob == NULL) { + SSH_LOG(SSH_LOG_PACKET, "Invalid signature packet from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_ERROR; + SSH_STRING_FREE(algo); + algo = NULL; + goto error; + } + + digest = ssh_msg_userauth_build_digest(session, + msg, + service, + algo, + method); + SSH_STRING_FREE(algo); + algo = NULL; + if (digest == NULL) { + SSH_STRING_FREE(sig_blob); + SSH_LOG(SSH_LOG_PACKET, "Failed to get digest"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, + msg->auth_request.pubkey, + &sig); + if (rc == SSH_OK) { + /* Check if the signature from client matches server preferences + */ + if (session->opts.pubkey_accepted_types) { + cmp = match_group(session->opts.pubkey_accepted_types, + sig->type_c); + if (cmp != 1) { + ssh_set_error( + session, + SSH_FATAL, + "Public key from client (%s) doesn't match server " + "preference (%s)", + sig->type_c, + session->opts.pubkey_accepted_types); + rc = SSH_ERROR; + } + } + + if (rc == SSH_OK) { + rc = ssh_pki_signature_verify(session, + sig, + msg->auth_request.pubkey, + ssh_buffer_get(digest), + ssh_buffer_get_len(digest)); + } + } + SSH_STRING_FREE(sig_blob); + SSH_BUFFER_FREE(digest); + ssh_signature_free(sig); + if (rc < 0) { + SSH_LOG(SSH_LOG_PACKET, + "Received an invalid signature from peer"); + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, "Valid signature received"); + + cmp = strcmp(method, "publickey-hostbound-v00@openssh.com"); + if (cmp == 0) { + ssh_key server_key = NULL; + + if (msg->auth_request.server_pubkey == NULL) { + SSH_LOG(SSH_LOG_PACKET, + "Server public key not provided by client"); + msg->auth_request.signature_state = + SSH_PUBLICKEY_STATE_WRONG; + goto error; + } + + rc = ssh_get_server_publickey(session, &server_key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, + "Failed to get server public key for hostbound " + "verification"); + msg->auth_request.signature_state = + SSH_PUBLICKEY_STATE_ERROR; + ssh_key_free(server_key); + goto error; + } + + if (ssh_key_cmp(server_key, + msg->auth_request.server_pubkey, + SSH_KEY_CMP_PUBLIC) != 0) { + SSH_LOG(SSH_LOG_PACKET, + "Server public key doesn't match the one provided " + "by client"); + msg->auth_request.signature_state = + SSH_PUBLICKEY_STATE_WRONG; + ssh_key_free(server_key); + goto error; + } + ssh_key_free(server_key); + } + + msg->auth_request.signature_state = SSH_PUBLICKEY_STATE_VALID; + } + + SAFE_FREE(method); + SSH_STRING_FREE(algo); + goto end; + } +#ifdef WITH_GSSAPI + if (strcmp(method, "gssapi-with-mic") == 0) { + uint32_t n_oid; + ssh_string *oids = NULL; + ssh_string oid = NULL; + char *hexa = NULL; + int i; + ssh_buffer_get_u32(packet, &n_oid); + n_oid = ntohl(n_oid); + if (n_oid > 100) { + ssh_set_error( + session, + SSH_FATAL, + "USERAUTH_REQUEST: gssapi-with-mic OID count too big (%d)", + n_oid); + goto error; + } + SSH_LOG(SSH_LOG_PACKET, "gssapi: %d OIDs", n_oid); + oids = calloc(n_oid, sizeof(ssh_string)); + if (oids == NULL) { + ssh_set_error_oom(session); + goto error; + } + for (i = 0; i < (int)n_oid; ++i) { + oid = ssh_buffer_get_ssh_string(packet); + if (oid == NULL) { + for (i = i - 1; i >= 0; --i) { + SAFE_FREE(oids[i]); + } + SAFE_FREE(oids); + ssh_set_error(session, + SSH_LOG_PACKET, + "USERAUTH_REQUEST: gssapi-with-mic missing OID"); + goto error; + } + oids[i] = oid; + if (session->common.log_verbosity >= SSH_LOG_PACKET) { + hexa = ssh_get_hexa(ssh_string_data(oid), ssh_string_len(oid)); + SSH_LOG(SSH_LOG_PACKET, "gssapi: OID %d: %s", i, hexa); + SAFE_FREE(hexa); + } + } + ssh_gssapi_handle_userauth(session, + msg->auth_request.username, + n_oid, + oids); + + for (i = 0; i < (int)n_oid; ++i) { + SAFE_FREE(oids[i]); + } + SAFE_FREE(oids); + /* bypass the message queue thing */ + SAFE_FREE(service); + SAFE_FREE(method); + SSH_MESSAGE_FREE(msg); + + return SSH_PACKET_USED; + } + if (strcmp(method, "gssapi-keyex") == 0) { + gss_buffer_desc received_mic = GSS_C_EMPTY_BUFFER; + gss_buffer_desc mic_buf = GSS_C_EMPTY_BUFFER; + ssh_string mic_token_string = NULL; + OM_uint32 maj_stat, min_stat; + ssh_buffer buf = NULL; + ssh_server_callbacks callbacks = session->server_callbacks; + + if (!ssh_session_kex_is_gss(session)) { + ssh_set_error(session, + SSH_FATAL, + "Attempt to authenticate with gssapi-keyex without " + "doing GSSAPI Key Exchange."); + ssh_auth_reply_default(session, 0); + goto error; + } + + if (session->gssapi == NULL || session->gssapi->ctx == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + ssh_auth_reply_default(session, 0); + goto error; + } + + rc = ssh_buffer_unpack(packet, "S", &mic_token_string); + if (rc != SSH_OK) { + ssh_auth_reply_default(session, 0); + goto error; + } + received_mic.length = ssh_string_len(mic_token_string); + received_mic.value = ssh_string_data(mic_token_string); + + SAFE_FREE(session->gssapi->user); + session->gssapi->user = strdup(msg->auth_request.username); + buf = ssh_gssapi_build_mic(session, "gssapi-keyex"); + if (buf == NULL) { + ssh_set_error_oom(session); + SSH_STRING_FREE(mic_token_string); + ssh_auth_reply_default(session, 0); + goto error; + } + + mic_buf.length = ssh_buffer_get_len(buf); + mic_buf.value = ssh_buffer_get(buf); + + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &mic_buf, + &received_mic, + NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify MIC for gssapi-keyex auth"); + SSH_BUFFER_FREE(buf); + SSH_STRING_FREE(mic_token_string); + ssh_auth_reply_default(session, 0); + goto error; + } + + if (ssh_callbacks_exists(callbacks, auth_gssapi_mic_function)) { + rc = callbacks->auth_gssapi_mic_function(session, + session->gssapi->user, + session->gssapi->canonic_user, + callbacks->userdata); + switch (rc) { + case SSH_AUTH_SUCCESS: + ssh_auth_reply_success(session, 0); + break; + case SSH_AUTH_PARTIAL: + ssh_auth_reply_success(session, 1); + break; + default: + ssh_auth_reply_default(session, 0); + break; + } + } + + /* bypass the message queue thing */ + SAFE_FREE(service); + SAFE_FREE(method); + SSH_BUFFER_FREE(buf); + SSH_MESSAGE_FREE(msg); + SSH_STRING_FREE(mic_token_string); + + return SSH_PACKET_USED; + } +#endif + + msg->auth_request.method = SSH_AUTH_METHOD_UNKNOWN; + SAFE_FREE(method); + goto end; +error: + SAFE_FREE(service); + SAFE_FREE(method); + + SSH_MESSAGE_FREE(msg); + + return SSH_PACKET_USED; +end: + SAFE_FREE(service); + SAFE_FREE(method); + + ssh_message_queue(session, msg); + + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ +/** + * @internal + * + * @brief Handle a SSH_MSG_MSG_USERAUTH_INFO_RESPONSE packet and queue a + * SSH Message + */ +#ifndef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + (void)session; + (void)type; + (void)packet; + (void)user; + return SSH_PACKET_USED; +} +#else /* WITH_SERVER */ +SSH_PACKET_CALLBACK(ssh_packet_userauth_info_response){ + uint32_t nanswers; + uint32_t i; + ssh_string tmp = NULL; + int rc; + + ssh_message msg = NULL; + + /* GSSAPI_TOKEN has same packed number. XXX fix this */ +#ifdef WITH_GSSAPI + if (session->gssapi != NULL) { + return ssh_packet_userauth_gssapi_token(session, type, packet, user); + } +#endif + (void)user; + (void)type; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* HACK: we forge a message to be able to handle it in the + * same switch() as other auth methods */ + msg->type = SSH_REQUEST_AUTH; + msg->auth_request.method = SSH_AUTH_METHOD_INTERACTIVE; + msg->auth_request.kbdint_response = 1; +#if 0 // should we wipe the username ? + msg->auth_request.username = NULL; +#endif + + rc = ssh_buffer_unpack(packet, "d", &nanswers); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + goto error; + } + + if (session->kbdint == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Warning: Got a keyboard-interactive " + "response but it seems we didn't send the request."); + + session->kbdint = ssh_kbdint_new(); + if (session->kbdint == NULL) { + ssh_set_error_oom(session); + + goto error; + } + } else if (session->kbdint->answers != NULL) { + uint32_t n; + + for (n = 0; n < session->kbdint->nanswers; n++) { + ssh_burn(session->kbdint->answers[n], + strlen(session->kbdint->answers[n])); + SAFE_FREE(session->kbdint->answers[n]); + } + SAFE_FREE(session->kbdint->answers); + session->kbdint->nanswers = 0; + } + + SSH_LOG(SSH_LOG_PACKET,"kbdint: %" PRIu32 " answers", nanswers); + if (nanswers > KBDINT_MAX_PROMPT) { + ssh_set_error(session, SSH_FATAL, + "Too much answers received from client: %" PRIu32 " (0x%.4" PRIx32 ")", + nanswers, nanswers); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + + if(nanswers != session->kbdint->nprompts) { + /* warn but let the application handle this case */ + SSH_LOG(SSH_LOG_DEBUG, "Warning: Number of prompts and answers" + " mismatch: p=%" PRIu32 " a=%" PRIu32, session->kbdint->nprompts, nanswers); + } + session->kbdint->nanswers = nanswers; + + session->kbdint->answers = calloc(nanswers, sizeof(char *)); + if (session->kbdint->answers == NULL) { + session->kbdint->nanswers = 0; + ssh_set_error_oom(session); + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + + for (i = 0; i < nanswers; i++) { + tmp = ssh_buffer_get_ssh_string(packet); + if (tmp == NULL) { + ssh_set_error(session, SSH_FATAL, "Short INFO_RESPONSE packet"); + session->kbdint->nanswers = i; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + session->kbdint->answers[i] = ssh_string_to_char(tmp); + SSH_STRING_FREE(tmp); + if (session->kbdint->answers[i] == NULL) { + ssh_set_error_oom(session); + session->kbdint->nanswers = i; + ssh_kbdint_free(session->kbdint); + session->kbdint = NULL; + + goto error; + } + } + + ssh_message_queue(session,msg); + + return SSH_PACKET_USED; + +error: + SSH_MESSAGE_FREE(msg); + + return SSH_PACKET_USED; +} +#endif /* WITH_SERVER */ + +SSH_PACKET_CALLBACK(ssh_packet_channel_open){ + ssh_message msg = NULL; + char *type_c = NULL; + uint32_t originator_port, destination_port; + int rc; + + (void)type; + (void)user; + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + msg->type = SSH_REQUEST_CHANNEL_OPEN; + rc = ssh_buffer_unpack(packet, "s", &type_c); + if (rc != SSH_OK){ + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Clients wants to open a %s channel", type_c); + + rc = ssh_buffer_unpack(packet, + "ddd", + &msg->channel_request_open.sender, + &msg->channel_request_open.window, + &msg->channel_request_open.packet_size); + if (rc != SSH_OK){ + goto error; + } + + if (msg->channel_request_open.packet_size == 0) { + ssh_set_error(session, + SSH_FATAL, + "Invalid maximum packet size 0 in SSH2_MSG_CHANNEL_OPEN"); + goto error; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED){ + ssh_set_error(session,SSH_FATAL, "Invalid state when receiving channel open request (must be authenticated)"); + goto error; + } + + if (strcmp(type_c, "session") == 0) { + if (session->flags & SSH_SESSION_FLAG_NO_MORE_SESSIONS) { + ssh_session_set_disconnect_message(session, "No more sessions allowed!"); + ssh_set_error(session, SSH_FATAL, "No more sessions allowed!"); + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_send_disconnect(session); + goto error; + } + + msg->channel_request_open.type = SSH_CHANNEL_SESSION; + SAFE_FREE(type_c); + goto end; + } + + if (strcmp(type_c,"direct-tcpip") == 0) { + rc = ssh_buffer_unpack(packet, + "sdsd", + &msg->channel_request_open.destination, + &destination_port, + &msg->channel_request_open.originator, + &originator_port); + if (rc != SSH_OK) { + goto error; + } + + msg->channel_request_open.destination_port = (uint16_t) destination_port; + msg->channel_request_open.originator_port = (uint16_t) originator_port; + msg->channel_request_open.type = SSH_CHANNEL_DIRECT_TCPIP; + goto end; + } + + if (strcmp(type_c,"forwarded-tcpip") == 0) { + rc = ssh_buffer_unpack(packet, "sdsd", + &msg->channel_request_open.destination, + &destination_port, + &msg->channel_request_open.originator, + &originator_port + ); + if (rc != SSH_OK){ + goto error; + } + msg->channel_request_open.destination_port = (uint16_t) destination_port; + msg->channel_request_open.originator_port = (uint16_t) originator_port; + msg->channel_request_open.type = SSH_CHANNEL_FORWARDED_TCPIP; + goto end; + } + + if (strcmp(type_c,"x11") == 0) { + rc = ssh_buffer_unpack(packet, "sd", + &msg->channel_request_open.originator, + &originator_port); + if (rc != SSH_OK){ + goto error; + } + msg->channel_request_open.originator_port = (uint16_t) originator_port; + msg->channel_request_open.type = SSH_CHANNEL_X11; + goto end; + } + + if (strcmp(type_c,"auth-agent@openssh.com") == 0) { + msg->channel_request_open.type = SSH_CHANNEL_AUTH_AGENT; + goto end; + } + + msg->channel_request_open.type = SSH_CHANNEL_UNKNOWN; + goto end; + +error: + SSH_MESSAGE_FREE(msg); +end: + SAFE_FREE(type_c); + if(msg != NULL) + ssh_message_queue(session,msg); + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief This function accepts a channel open request for the specified channel. + * + * @param[in] msg The message. + * + * @param[in] chan The channel the request is made on. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +int ssh_message_channel_request_open_reply_accept_channel(ssh_message msg, + ssh_channel chan) +{ + ssh_session session = NULL; + int rc; + + if (msg == NULL) { + return SSH_ERROR; + } + + session = msg->session; + + chan->local_channel = ssh_channel_new_id(session); + chan->local_maxpacket = 35000; + chan->local_window = 32000; + chan->remote_channel = msg->channel_request_open.sender; + chan->remote_maxpacket = msg->channel_request_open.packet_size; + chan->remote_window = msg->channel_request_open.window; + chan->state = SSH_CHANNEL_STATE_OPEN; + chan->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND; + + rc = ssh_buffer_pack(session->out_buffer, + "bdddd", + SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, + chan->remote_channel, + chan->local_channel, + chan->local_window, + chan->local_maxpacket); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Accepting a channel request_open for chan %" PRIu32, + chan->remote_channel); + + rc = ssh_packet_send(session); + + return rc; +} + +/** + * @internal + * + * @brief This function accepts a channel open request. + * + * @param[in] msg The message. + * + * @returns a valid ssh_channel handle if the request is to be allowed + * + * @returns NULL in case of error + */ +ssh_channel ssh_message_channel_request_open_reply_accept(ssh_message msg) +{ + ssh_channel chan = NULL; + int rc; + + if (msg == NULL) { + return NULL; + } + + chan = ssh_channel_new(msg->session); + if (chan == NULL) { + return NULL; + } + rc = ssh_message_channel_request_open_reply_accept_channel(msg, chan); + if (rc < 0) { + ssh_channel_free(chan); + chan = NULL; + } + return chan; +} + +/** + * @internal + * + * @brief This function parses the last end of a channel request packet. + * + * This is normally converted to a SSH message and placed in the queue. + * + * @param[in] session The SSH session. + * + * @param[in] channel The channel the request is made on. + * + * @param[in] packet The rest of the packet to be parsed. + * + * @param[in] request The type of request. + * + * @param[in] want_reply The want_reply field from the request. + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +int +ssh_message_handle_channel_request(ssh_session session, + ssh_channel channel, + ssh_buffer packet, + const char *request, + uint8_t want_reply) +{ + ssh_message msg = NULL; + int rc; + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + + SSH_LOG(SSH_LOG_PACKET, + "Received a %s channel_request for channel (%" PRIu32 ":%" PRIu32 + ") (want_reply=%hhu)", + request, + channel->local_channel, + channel->remote_channel, + want_reply); + + msg->type = SSH_REQUEST_CHANNEL; + msg->channel_request.channel = channel; + msg->channel_request.want_reply = want_reply; + + if (strcmp(request, "pty-req") == 0) { + rc = ssh_buffer_unpack(packet, + "sddddS", + &msg->channel_request.TERM, + &msg->channel_request.width, + &msg->channel_request.height, + &msg->channel_request.pxwidth, + &msg->channel_request.pxheight, + &msg->channel_request.modes); + + msg->channel_request.type = SSH_CHANNEL_REQUEST_PTY; + + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "window-change") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_WINDOW_CHANGE; + rc = ssh_buffer_unpack(packet, + "dddd", + &msg->channel_request.width, + &msg->channel_request.height, + &msg->channel_request.pxwidth, + &msg->channel_request.pxheight); + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "subsystem") == 0) { + rc = ssh_buffer_unpack(packet, "s", &msg->channel_request.subsystem); + msg->channel_request.type = SSH_CHANNEL_REQUEST_SUBSYSTEM; + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "shell") == 0) { + msg->channel_request.type = SSH_CHANNEL_REQUEST_SHELL; + goto end; + } + + if (strcmp(request, "exec") == 0) { + rc = ssh_buffer_unpack(packet, "s", &msg->channel_request.command); + msg->channel_request.type = SSH_CHANNEL_REQUEST_EXEC; + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "env") == 0) { + rc = ssh_buffer_unpack(packet, + "ss", + &msg->channel_request.var_name, + &msg->channel_request.var_value); + msg->channel_request.type = SSH_CHANNEL_REQUEST_ENV; + if (rc != SSH_OK) { + goto error; + } + goto end; + } + + if (strcmp(request, "x11-req") == 0) { + rc = ssh_buffer_unpack(packet, + "bssd", + &msg->channel_request.x11_single_connection, + &msg->channel_request.x11_auth_protocol, + &msg->channel_request.x11_auth_cookie, + &msg->channel_request.x11_screen_number); + + msg->channel_request.type = SSH_CHANNEL_REQUEST_X11; + if (rc != SSH_OK) { + goto error; + } + + goto end; + } + + msg->channel_request.type = SSH_CHANNEL_REQUEST_UNKNOWN; +end: + ssh_message_queue(session, msg); + + return SSH_OK; +error: + SSH_MESSAGE_FREE(msg); + + return SSH_ERROR; +} + +/** @internal + * + * @brief Sends a successful channel request reply + * + * @param msg A message to reply to + * + * @returns SSH_OK on success, SSH_ERROR if an error occurred. + */ +int ssh_message_channel_request_reply_success(ssh_message msg) +{ + uint32_t channel; + int rc; + + if (msg == NULL) { + return SSH_ERROR; + } + + if (msg->channel_request.want_reply) { + channel = msg->channel_request.channel->remote_channel; + + SSH_LOG(SSH_LOG_PACKET, + "Sending a channel_request success to channel %" PRIu32, + channel); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_SUCCESS, + channel); + if (rc != SSH_OK) { + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + return ssh_packet_send(msg->session); + } + + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request succeeded"); + + return SSH_OK; +} + +#ifdef WITH_SERVER +SSH_PACKET_CALLBACK(ssh_packet_global_request) +{ + ssh_message msg = NULL; + char *request = NULL; + uint8_t want_reply; + int rc = SSH_PACKET_USED; + int r; + + (void)user; + (void)type; + (void)packet; + + SSH_LOG(SSH_LOG_DEBUG,"Received SSH_MSG_GLOBAL_REQUEST packet"); + r = ssh_buffer_unpack(packet, "sb", &request, &want_reply); + if (r != SSH_OK){ + goto error; + } + + msg = ssh_message_new(session); + if (msg == NULL) { + ssh_set_error_oom(session); + goto error; + } + msg->type = SSH_REQUEST_GLOBAL; + + if (strcmp(request, "tcpip-forward") == 0) { + + /* According to RFC4254, the client SHOULD reject this message */ + if (session->client) { + goto reply_with_failure; + } + + r = ssh_buffer_unpack(packet, + "sd", + &msg->global_request.bind_address, + &msg->global_request.bind_port); + if (r != SSH_OK){ + goto reply_with_failure; + } + msg->global_request.type = SSH_GLOBAL_REQUEST_TCPIP_FORWARD; + msg->global_request.want_reply = want_reply; + + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_MSG_GLOBAL_REQUEST %s %hhu %s:%d", + request, + want_reply, + msg->global_request.bind_address, + msg->global_request.bind_port); + + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + SSH_LOG(SSH_LOG_DEBUG, + "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %hhu %s:%d", + request, + want_reply, + msg->global_request.bind_address, + msg->global_request.bind_port); + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else { + SAFE_FREE(request); + ssh_message_queue(session, msg); + return rc; + } + } else if (strcmp(request, "cancel-tcpip-forward") == 0) { + + /* According to RFC4254, the client SHOULD reject this message */ + if (session->client) { + goto reply_with_failure; + } + + r = ssh_buffer_unpack(packet, + "sd", + &msg->global_request.bind_address, + &msg->global_request.bind_port); + if (r != SSH_OK){ + goto reply_with_failure; + } + msg->global_request.type = SSH_GLOBAL_REQUEST_CANCEL_TCPIP_FORWARD; + msg->global_request.want_reply = want_reply; + + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_MSG_GLOBAL_REQUEST %s %hhu %s:%d", + request, + want_reply, + msg->global_request.bind_address, + msg->global_request.bind_port); + + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else { + SAFE_FREE(request); + ssh_message_queue(session, msg); + return rc; + } + } else if(strcmp(request, "keepalive@openssh.com") == 0) { + msg->global_request.type = SSH_GLOBAL_REQUEST_KEEPALIVE; + msg->global_request.want_reply = want_reply; + SSH_LOG(SSH_LOG_DEBUG, + "Received keepalive@openssh.com %hhu", + want_reply); + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + SSH_LOG(SSH_LOG_DEBUG, + "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %hhu", + request, + want_reply); + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else if (want_reply) { + ssh_message_global_request_reply_success(msg, 0); + } + } else if (strcmp(request, "no-more-sessions@openssh.com") == 0) { + msg->global_request.type = SSH_GLOBAL_REQUEST_NO_MORE_SESSIONS; + msg->global_request.want_reply = want_reply; + + SSH_LOG(SSH_LOG_PROTOCOL, + "Received no-more-sessions@openssh.com %hhu", + want_reply); + if (ssh_callbacks_exists(session->common.callbacks, + global_request_function)) { + SSH_LOG(SSH_LOG_DEBUG, + "Calling callback for SSH_MSG_GLOBAL_REQUEST %s %hhu", + request, + want_reply); + session->common.callbacks->global_request_function( + session, + msg, + session->common.callbacks->userdata); + } else if (want_reply) { + ssh_message_global_request_reply_success(msg, 0); + } + + session->flags |= SSH_SESSION_FLAG_NO_MORE_SESSIONS; + } else { + SSH_LOG(SSH_LOG_DEBUG, + "UNKNOWN SSH_MSG_GLOBAL_REQUEST %s, want_reply = %hhu", + request, + want_reply); + goto reply_with_failure; + } + + SAFE_FREE(msg); + SAFE_FREE(request); + return rc; + +reply_with_failure: + /* Only report the failure if requested */ + if (want_reply) { + r = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_REQUEST_FAILURE); + if (r < 0) { + ssh_set_error_oom(session); + goto error; + } + + r = ssh_packet_send(session); + if (r != SSH_OK) { + goto error; + } + } else { + SSH_LOG(SSH_LOG_PACKET, + "The requester doesn't want to know the request failed!"); + } + + /* Consume the message to avoid sending UNIMPLEMENTED later */ + rc = SSH_PACKET_USED; +error: + SAFE_FREE(msg); + SAFE_FREE(request); + SSH_LOG(SSH_LOG_TRACE, "Invalid SSH_MSG_GLOBAL_REQUEST packet"); + return rc; +} + +#endif /* WITH_SERVER */ + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/misc.c b/src/libs/libssh-0.12.2/src/misc.c new file mode 100644 index 000000000000..f6f741081036 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/misc.c @@ -0,0 +1,2506 @@ +/* + * misc.c - useful client functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#ifndef _WIN32 +/* This is needed for a standard getpwuid_r on opensolaris */ +#define _POSIX_PTHREAD_SEMANTICS +#include +#include +#include +#include +#include +#include + +#endif /* _WIN32 */ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ + + +#ifdef _WIN32 + +#ifndef _WIN32_IE +# define _WIN32_IE 0x0501 // SHGetSpecialFolderPath +#endif + +#include // Must be the first to include +#include +#include +#include +#include + +#ifdef HAVE_IO_H +#include +#endif /* HAVE_IO_H */ + +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +#ifdef HAVE_LIBGCRYPT +#define GCRYPT_STRING "/gcrypt" +#else +#define GCRYPT_STRING "" +#endif + +#ifdef HAVE_LIBCRYPTO +#define CRYPTO_STRING "/openssl" +#else +#define CRYPTO_STRING "" +#endif + +#ifdef HAVE_LIBMBEDCRYPTO +#define MBED_STRING "/mbedtls" +#else +#define MBED_STRING "" +#endif + +#ifdef WITH_ZLIB +#define ZLIB_STRING "/zlib" +#else +#define ZLIB_STRING "" +#endif + +#define ARPA_DOMAIN_MAX_LEN 63 + +/** + * @defgroup libssh_misc The SSH helper functions + * @ingroup libssh + * + * Different helper functions used in the SSH Library. + * + * @{ + */ + +#ifdef _WIN32 +static char *ssh_get_user_home_dir_internal(void) +{ + char tmp[PATH_MAX] = {0}; + char *szPath = NULL; + + if (SHGetSpecialFolderPathA(NULL, tmp, CSIDL_PROFILE, TRUE)) { + szPath = malloc(strlen(tmp) + 1); + if (szPath == NULL) { + return NULL; + } + + strcpy(szPath, tmp); + return szPath; + } + + return NULL; +} + +/* we have read access on file */ +int ssh_file_readaccess_ok(const char *file) +{ + if (_access(file, 4) < 0) { + return 0; + } + + return 1; +} + +/** + * @brief Check if the given path is an existing directory and that is + * accessible for writing. + * + * @param[in] path Path to the directory to be checked + * + * @return Return 1 if the directory exists and is accessible; 0 otherwise + * */ +int ssh_dir_writeable(const char *path) +{ + struct _stat buffer; + int rc; + + rc = _stat(path, &buffer); + if (rc < 0) { + return 0; + } + + if ((buffer.st_mode & _S_IFDIR) && (buffer.st_mode & _S_IWRITE)) { + return 1; + } + + return 0; +} + +#define SSH_USEC_IN_SEC 1000000LL +#define SSH_SECONDS_SINCE_1601 11644473600LL + +int ssh_gettimeofday(struct timeval *__p, void *__t) +{ + union { + unsigned long long ns100; /* time since 1 Jan 1601 in 100ns units */ + FILETIME ft; + } now; + + GetSystemTimeAsFileTime (&now.ft); + __p->tv_usec = (long) ((now.ns100 / 10LL) % SSH_USEC_IN_SEC); + __p->tv_sec = (long)(((now.ns100 / 10LL ) / SSH_USEC_IN_SEC) - SSH_SECONDS_SINCE_1601); + + return (0); +} + +/** + * @internal + * + * @brief Convert time in seconds since the Epoch to broken-down local time + * + * This is a helper used to provide localtime_r() like function interface + * on Windows. + * + * @param timer Pointer to a location storing the time_t which + * represents the time in seconds since the Epoch. + * + * @param result Pointer to a location where the broken-down time + * (expressed as local time) should be stored. + * + * @returns A pointer to the structure pointed to by the parameter + * result on success, NULL on error with the errno + * set to indicate the error. + */ +struct tm *ssh_localtime(const time_t *timer, struct tm *result) +{ + errno_t rc; + rc = localtime_s(result, timer); + if (rc != 0) { + return NULL; + } + + return result; +} + +char *ssh_get_local_username(void) +{ + DWORD size = 0; + char *user = NULL; + int rc; + + /* get the size */ + GetUserName(NULL, &size); + + user = (char *)malloc(size); + if (user == NULL) { + return NULL; + } + + if (GetUserName(user, &size)) { + rc = ssh_check_username_syntax(user); + if (rc == SSH_OK) { + return user; + } + } + + free(user); + + return NULL; +} + +int ssh_is_ipaddr_v4(const char *str) +{ + struct sockaddr_storage ss; + int sslen = sizeof(ss); + int rc = SOCKET_ERROR; + + /* WSAStringToAddressA thinks that 0.0.0 is a valid IP */ + if (strlen(str) < 7) { + return 0; + } + + rc = WSAStringToAddressA((LPSTR) str, + AF_INET, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { + return 1; + } + + return 0; +} + +int ssh_is_ipaddr(const char *str) +{ + int rc = SOCKET_ERROR; + char *s = strdup(str); + + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { + struct sockaddr_storage ss; + int sslen = sizeof(ss); + char *network_interface = strchr(s, '%'); + + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = WSAStringToAddressA((LPSTR) s, + AF_INET6, + NULL, + (struct sockaddr*)&ss, + &sslen); + if (rc == 0) { + free(s); + return 1; + } + } + + free(s); + return ssh_is_ipaddr_v4(str); +} +#else /* _WIN32 */ + +#ifndef NSS_BUFLEN_PASSWD +#define NSS_BUFLEN_PASSWD 4096 +#endif /* NSS_BUFLEN_PASSWD */ + +static char *ssh_get_user_home_dir_internal(void) +{ + char *szPath = NULL; + struct passwd pwd; + struct passwd *pwdbuf = NULL; + char buf[NSS_BUFLEN_PASSWD] = {0}; + int rc; + + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0 || pwdbuf == NULL ) { + szPath = getenv("HOME"); + if (szPath == NULL) { + return NULL; + } + snprintf(buf, sizeof(buf), "%s", szPath); + return strdup(buf); + } + + szPath = strdup(pwd.pw_dir); + + return szPath; +} + +/* we have read access on file */ +int ssh_file_readaccess_ok(const char *file) +{ + if (access(file, R_OK) < 0) { + return 0; + } + + return 1; +} + +/** + * @brief Check if the given path is an existing directory and that is + * accessible for writing. + * + * @param[in] path Path to the directory to be checked + * + * @return Return 1 if the directory exists and is accessible; 0 otherwise + * */ +int ssh_dir_writeable(const char *path) +{ + struct stat buffer; + int rc; + + rc = stat(path, &buffer); + if (rc < 0) { + return 0; + } + + if (S_ISDIR(buffer.st_mode) && (buffer.st_mode & S_IWRITE)) { + return 1; + } + + return 0; +} + +char *ssh_get_local_username(void) +{ + struct passwd pwd; + struct passwd *pwdbuf = NULL; + char buf[NSS_BUFLEN_PASSWD]; + char *name = NULL; + int rc; + + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + if (rc != 0 || pwdbuf == NULL) { + return NULL; + } + + name = strdup(pwd.pw_name); + rc = ssh_check_username_syntax(name); + + if (rc != SSH_OK) { + free(name); + return NULL; + } + + return name; +} + +int ssh_is_ipaddr_v4(const char *str) +{ + int rc = -1; + struct in_addr dest; + + rc = inet_pton(AF_INET, str, &dest); + if (rc > 0) { + return 1; + } + + return 0; +} + +int ssh_is_ipaddr(const char *str) +{ + int rc = -1; + char *s = strdup(str); + + if (s == NULL) { + return -1; + } + if (strchr(s, ':')) { + struct in6_addr dest6; + char *network_interface = strchr(s, '%'); + + /* link-local (IP:v6:addr%ifname). */ + if (network_interface != NULL) { + rc = if_nametoindex(network_interface + 1); + if (rc == 0) { + free(s); + return 0; + } + *network_interface = '\0'; + } + rc = inet_pton(AF_INET6, s, &dest6); + if (rc > 0) { + free(s); + return 1; + } + } + + free(s); + return ssh_is_ipaddr_v4(str); +} + +#endif /* _WIN32 */ + +char *ssh_get_user_home_dir(ssh_session session) +{ + char *szPath = NULL; + + /* If used previously, reuse cached value */ + if (session != NULL && session->opts.homedir != NULL) { + return strdup(session->opts.homedir); + } + + szPath = ssh_get_user_home_dir_internal(); + if (szPath == NULL) { + return NULL; + } + + if (session != NULL) { + /* cache it: + * failure is not fatal -- at worst we will just not cache it */ + session->opts.homedir = strdup(szPath); + } + + return szPath; +} + +char *ssh_lowercase(const char* str) +{ + char *new = NULL, *p = NULL; + + if (str == NULL) { + return NULL; + } + + new = strdup(str); + if (new == NULL) { + return NULL; + } + + for (p = new; *p; p++) { + *p = tolower(*p); + } + + return new; +} + +char *ssh_hostport(const char *host, int port) +{ + char *dest = NULL; + size_t len; + + if (host == NULL) { + return NULL; + } + + /* 3 for []:, 5 for 65536 and 1 for nul */ + len = strlen(host) + 3 + 5 + 1; + dest = malloc(len); + if (dest == NULL) { + return NULL; + } + snprintf(dest, len, "[%s]:%d", host, port); + + return dest; +} + +static char * +ssh_get_hexa_internal(const unsigned char *what, size_t len, bool colons) +{ + const char h[] = "0123456789abcdef"; + char *hexa = NULL; + size_t i; + size_t bytes_per_byte = 2 + (colons ? 1 : 0); + size_t hlen = len * bytes_per_byte; + + if (what == NULL || len < 1 || len > (UINT_MAX - 1) / bytes_per_byte) { + return NULL; + } + + hexa = calloc(hlen + 1, sizeof(char)); + if (hexa == NULL) { + return NULL; + } + + for (i = 0; i < len; i++) { + hexa[i * bytes_per_byte] = h[(what[i] >> 4) & 0xF]; + hexa[i * bytes_per_byte + 1] = h[what[i] & 0xF]; + if (colons) { + hexa[i * bytes_per_byte + 2] = ':'; + } + } + if (colons) { + hexa[hlen - 1] = '\0'; + } + + return hexa; +} + +/** + * @brief Convert a buffer into a colon separated hex string. + * The caller has to free the memory. + * + * @param[in] what What should be converted to a hex string. + * + * @param[in] len Length of the buffer to convert. + * + * @return The hex string or NULL on error. The memory needs + * to be freed using ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_get_hexa(const unsigned char *what, size_t len) +{ + return ssh_get_hexa_internal(what, len, true); +} + +/** + * @deprecated Please use ssh_print_hash() instead + */ +void ssh_print_hexa(const char *descr, const unsigned char *what, size_t len) +{ + char *hexa = ssh_get_hexa(what, len); + + if (hexa == NULL) { + return; + } + fprintf(stderr, "%s: %s\n", descr, hexa); + + free(hexa); +} + +/** + * @brief Log the content of a buffer in hexadecimal format, similar to the + * output of 'hexdump -C' command. + * + * The first logged line is the given description followed by the length. + * Then the content of the buffer is logged 16 bytes per line in the following + * format: + * + * (offset) (first 8 bytes) (last 8 bytes) (the 16 bytes as ASCII char values) + * + * The output for a 16 bytes array containing values from 0x00 to 0x0f would be: + * + * "Example (16 bytes):" + * " 00000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f ................" + * + * The value for each byte as corresponding ASCII character is printed at the + * end if the value is printable. Otherwise, it is replaced with '.'. + * + * @param[in] descr A description for the content to be logged + * @param[in] what The buffer to be logged + * @param[in] len The length of the buffer given in what + * + * @note If a too long description is provided (which would result in a first + * line longer than 80 bytes), the function will fail. + */ +void ssh_log_hexdump(const char *descr, const unsigned char *what, size_t len) +{ + size_t i; + char ascii[17]; + const unsigned char *pc = NULL; + size_t count = 0; + ssize_t printed = 0; + + /* The required buffer size is calculated from: + * + * 2 bytes for spaces at the beginning + * 8 bytes for the offset + * 2 bytes for spaces + * 24 bytes to print the first 8 bytes + spaces + * 1 byte for an extra space + * 24 bytes to print next 8 bytes + spaces + * 2 bytes for extra spaces + * 16 bytes for the content as ASCII characters at the end + * 1 byte for the ending '\0' + * + * Resulting in 80 bytes. + * + * Except for the first line (description + size), all lines have fixed + * length. If a too long description is used, the function will fail. + * */ + char buffer[80]; + + /* Print description */ + if (descr != NULL) { + printed = snprintf(buffer, sizeof(buffer), "%s ", descr); + if (printed < 0) { + goto error; + } + count += printed; + } else { + printed = snprintf(buffer, sizeof(buffer), "(NULL description) "); + if (printed < 0) { + goto error; + } + count += printed; + } + + if (len == 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, + "(zero length):"); + if (printed < 0) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + return; + } else { + printed = snprintf(buffer + count, sizeof(buffer) - count, + "(%zu bytes):", len); + if (printed < 0) { + goto error; + } + count += printed; + } + + if (what == NULL) { + printed = snprintf(buffer + count, sizeof(buffer) - count, + "(NULL)"); + if (printed < 0) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + return; + } + + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + + /* Reset state */ + count = 0; + pc = what; + + for (i = 0; i < len; i++) { + /* Add one space after printing 8 bytes */ + if ((i % 8) == 0) { + if (i != 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, " "); + if (printed < 0) { + goto error; + } + count += printed; + } + } + + /* Log previous line and reset state for new line */ + if ((i % 16) == 0) { + if (i != 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, + " %s", ascii); + if (printed < 0) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + count = 0; + } + + /* Start a new line with the offset */ + printed = snprintf(buffer, sizeof(buffer), + " %08zx ", i); + if (printed < 0) { + goto error; + } + count += printed; + } + + /* Print the current byte hexadecimal representation */ + printed = snprintf(buffer + count, sizeof(buffer) - count, + " %02x", pc[i]); + if (printed < 0) { + goto error; + } + count += printed; + + /* If printable, store the ASCII character */ + if (isprint(pc[i])) { + ascii[i % 16] = pc[i]; + } else { + ascii[i % 16] = '.'; + } + ascii[(i % 16) + 1] = '\0'; + } + + /* Add padding if not exactly 16 characters */ + while ((i % 16) != 0) { + /* Add one space after printing 8 bytes */ + if ((i % 8) == 0) { + if (i != 0) { + printed = snprintf(buffer + count, sizeof(buffer) - count, " "); + if (printed < 0) { + goto error; + } + count += printed; + } + } + + printed = snprintf(buffer + count, sizeof(buffer) - count, " "); + if (printed < 0) { + goto error; + } + count += printed; + i++; + } + + /* Print the last printable part */ + printed = snprintf(buffer + count, sizeof(buffer) - count, + " %s", ascii); + if (printed < 0) { + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "%s", buffer); + + return; + +error: + SSH_LOG(SSH_LOG_DEBUG, "Could not print to buffer"); + return; +} + +/** + * @brief Check if libssh is the required version or get the version + * string. + * + * @param[in] req_version The version required. + * + * @return If the version of libssh is newer than the version + * required it will return a version string. + * NULL if the version is older. + * + * Example: + * + * @code + * if (ssh_version(SSH_VERSION_INT(0,2,1)) == NULL) { + * fprintf(stderr, "libssh version is too old!\n"); + * exit(1); + * } + * + * if (debug) { + * printf("libssh %s\n", ssh_version(0)); + * } + * @endcode + */ +const char *ssh_version(int req_version) +{ + if (req_version <= LIBSSH_VERSION_INT) { + return SSH_STRINGIFY(LIBSSH_VERSION) GCRYPT_STRING CRYPTO_STRING + MBED_STRING ZLIB_STRING; + } + + return NULL; +} + +struct ssh_list *ssh_list_new(void) +{ + struct ssh_list *ret = malloc(sizeof(struct ssh_list)); + if (ret == NULL) { + return NULL; + } + ret->root = ret->end = NULL; + return ret; +} + +void ssh_list_free(struct ssh_list *list) +{ + struct ssh_iterator *ptr = NULL, *next = NULL; + if (!list) + return; + ptr = list->root; + while (ptr) { + next = ptr->next; + SAFE_FREE(ptr); + ptr = next; + } + SAFE_FREE(list); +} + +struct ssh_iterator *ssh_list_get_iterator(const struct ssh_list *list) +{ + if (!list) + return NULL; + return list->root; +} + +struct ssh_iterator *ssh_list_find(const struct ssh_list *list, void *value) +{ + struct ssh_iterator *it = NULL; + + for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) + if (it->data == value) + return it; + return NULL; +} + +/** + * @brief Get the number of elements in the list + * + * @param[in] list The list to count. + * + * @return The number of elements in the list. + */ +size_t ssh_list_count(const struct ssh_list *list) +{ + struct ssh_iterator *it = NULL; + size_t count = 0; + + for (it = ssh_list_get_iterator(list); it != NULL ; it = it->next) { + count++; + } + + return count; +} + +static struct ssh_iterator *ssh_iterator_new(const void *data) +{ + struct ssh_iterator *iterator = malloc(sizeof(struct ssh_iterator)); + + if (iterator == NULL) { + return NULL; + } + iterator->next = NULL; + iterator->data = data; + return iterator; +} + +/** + * @internal + * + * @brief Appends an element to the end of the list. + * + * @param[in] list The list to append the element + * @param[in] data The element to append + * + * @return `SSH_OK` on success, `SSH_ERROR` on error + */ +int ssh_list_append(struct ssh_list *list, const void *data) +{ + struct ssh_iterator *iterator = NULL; + + if (list == NULL) { + return SSH_ERROR; + } + + iterator = ssh_iterator_new(data); + if (iterator == NULL) { + return SSH_ERROR; + } + + if(!list->end){ + /* list is empty */ + list->root=list->end=iterator; + } else { + /* put it on end of list */ + list->end->next=iterator; + list->end=iterator; + } + return SSH_OK; +} + +int ssh_list_prepend(struct ssh_list *list, const void *data) +{ + struct ssh_iterator *it = NULL; + + if (list == NULL) { + return SSH_ERROR; + } + + it = ssh_iterator_new(data); + if (it == NULL) { + return SSH_ERROR; + } + + if (list->end == NULL) { + /* list is empty */ + list->root = list->end = it; + } else { + /* set as new root */ + it->next = list->root; + list->root = it; + } + + return SSH_OK; +} + +void ssh_list_remove(struct ssh_list *list, struct ssh_iterator *iterator) +{ + struct ssh_iterator *ptr = NULL, *prev = NULL; + + if (list == NULL) { + return; + } + + prev = NULL; + ptr = list->root; + while (ptr && ptr != iterator) { + prev = ptr; + ptr = ptr->next; + } + if (!ptr) { + /* we did not find the element */ + return; + } + /* unlink it */ + if (prev) + prev->next = ptr->next; + /* if iterator was the head */ + if (list->root == iterator) + list->root = iterator->next; + /* if iterator was the tail */ + if (list->end == iterator) + list->end = prev; + SAFE_FREE(iterator); +} + +/** + * @internal + * + * @brief Removes the top element of the list and returns the data value + * attached to it. + * + * @param[in] list The ssh_list to remove the element. + * + * @returns A pointer to the element being stored in head, or NULL + * if the list is empty. + */ +const void *_ssh_list_pop_head(struct ssh_list *list) +{ + struct ssh_iterator *iterator = NULL; + const void *data = NULL; + + if (list == NULL) { + return NULL; + } + + iterator = list->root; + if (iterator == NULL) { + return NULL; + } + data=iterator->data; + list->root=iterator->next; + if(list->end==iterator) + list->end=NULL; + SAFE_FREE(iterator); + return data; +} + +/** + * @brief Parse directory component. + * + * dirname breaks a null-terminated pathname string into a directory component. + * In the usual case, ssh_dirname() returns the string up to, but not including, + * the final '/'. Trailing '/' characters are not counted as part of the + * pathname. The caller must free the memory using ssh_string_free_char(). + * + * @param[in] path The path to parse. + * + * @return The dirname of path or NULL if we can't allocate memory. + * If path does not contain a slash, c_dirname() returns + * the string ".". If path is a string "/", it returns + * the string "/". If path is NULL or an empty string, + * "." is returned. The memory needs to be freed using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_dirname (const char *path) +{ + char *new = NULL; + size_t len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + /* goto next slash */ + while(len > 0 && path[len - 1] != '/') --len; + + if (len == 0) { + return strdup("."); + } else if (len == 1) { + return strdup("/"); + } + + /* Remove slashes again */ + while(len > 0 && path[len - 1] == '/') --len; + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, path, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief basename - parse filename component. + * + * basename breaks a null-terminated pathname string into a filename component. + * ssh_basename() returns the component following the final '/'. Trailing '/' + * characters are not counted as part of the pathname. + * + * @param[in] path The path to parse. + * + * @return The filename of path or NULL if we can't allocate + * memory. If path is the string "/", basename returns + * the string "/". If path is NULL or an empty string, + * "." is returned. The caller needs to free this memory + * ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_basename (const char *path) +{ + char *new = NULL; + const char *s = NULL; + size_t len; + + if (path == NULL || *path == '\0') { + return strdup("."); + } + + len = strlen(path); + /* Remove trailing slashes */ + while(len > 0 && path[len - 1] == '/') --len; + + /* We have only slashes */ + if (len == 0) { + return strdup("/"); + } + + while(len > 0 && path[len - 1] != '/') --len; + + if (len > 0) { + s = path + len; + len = strlen(s); + + while(len > 0 && s[len - 1] == '/') --len; + } else { + return strdup(path); + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + + strncpy(new, s, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief Attempts to create a directory with the given pathname. + * + * This is the portable version of mkdir, mode is ignored on Windows systems. + * + * @param[in] pathname The path name to create the directory. + * + * @param[in] mode The permissions to use. + * + * @return 0 on success, < 0 on error with errno set. + */ +int ssh_mkdir(const char *pathname, mode_t mode) +{ + int r; +#ifdef _WIN32 + r = _mkdir(pathname); +#else + r = mkdir(pathname, mode); +#endif + + return r; +} + +/** + * @brief Attempts to create a directory with the given pathname. The missing + * directories in the given pathname are created recursively. + * + * @param[in] pathname The path name to create the directory. + * + * @param[in] mode The permissions to use. + * + * @return 0 on success, < 0 on error with errno set. + * + * @note mode is ignored on Windows systems. + */ +int ssh_mkdirs(const char *pathname, mode_t mode) +{ + int rc = 0; + char *parent = NULL; + + if (pathname == NULL || + pathname[0] == '\0' || + !strcmp(pathname, "/") || + !strcmp(pathname, ".")) + { + errno = EINVAL; + return -1; + } + + errno = 0; + +#ifdef _WIN32 + rc = _mkdir(pathname); +#else + rc = mkdir(pathname, mode); +#endif + + if (rc < 0) { + /* If a directory was missing, try to create the parent */ + if (errno == ENOENT) { + parent = ssh_dirname(pathname); + if (parent == NULL) { + errno = ENOMEM; + return -1; + } + + rc = ssh_mkdirs(parent, mode); + if (rc < 0) { + /* We could not create the parent */ + SAFE_FREE(parent); + return -1; + } + + SAFE_FREE(parent); + + /* Try again */ + errno = 0; +#ifdef _WIN32 + rc = _mkdir(pathname); +#else + rc = mkdir(pathname, mode); +#endif + } + } + + return rc; +} + +/** + * @brief Expand a directory starting with a tilde '~' + * + * @param[in] d The directory to expand. + * + * @return The expanded directory, NULL on error. The caller + * needs to free the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_path_expand_tilde(const char *d) +{ + char *h = NULL, *r = NULL; + const char *p = NULL; + size_t ld; + size_t lh = 0; + + if (d[0] != '~') { + return strdup(d); + } + d++; + + /* handle ~user/path */ + p = strchr(d, '/'); + if (p != NULL && p > d) { +#ifdef _WIN32 + return strdup(d); +#else + struct passwd *pw = NULL; + size_t s = p - d; + char u[128]; + + if (s >= sizeof(u)) { + return NULL; + } + memcpy(u, d, s); + u[s] = '\0'; + pw = getpwnam(u); + if (pw == NULL) { + return NULL; + } + ld = strlen(p); + h = strdup(pw->pw_dir); +#endif + } else { + ld = strlen(d); + p = (char *) d; + h = ssh_get_user_home_dir(NULL); + } + if (h == NULL) { + return NULL; + } + lh = strlen(h); + + r = malloc(ld + lh + 1); + if (r == NULL) { + SAFE_FREE(h); + return NULL; + } + + if (lh > 0) { + memcpy(r, h, lh); + } + SAFE_FREE(h); + memcpy(r + lh, p, ld + 1); + + return r; +} + +char *ssh_get_local_hostname(void) +{ + char host[NI_MAXHOST] = {0}; + int rc; + + rc = gethostname(host, sizeof(host)); + if (rc != 0) { + return NULL; + } + return strdup(host); +} + +static char *get_connection_hash(ssh_session session) +{ + unsigned char conn_hash[SHA_DIGEST_LENGTH]; + char *local_hostname = NULL; + SHACTX ctx = sha1_init(); + char strport[10] = {0}; + unsigned int port; + char *username = NULL; + int rc; + + if (session == NULL) { + return NULL; + } + + if (ctx == NULL) { + goto err; + } + + /* Local hostname %l */ + local_hostname = ssh_get_local_hostname(); + if (local_hostname == NULL) { + goto err; + } + rc = sha1_update(ctx, local_hostname, strlen(local_hostname)); + if (rc != SSH_OK) { + goto err; + } + SAFE_FREE(local_hostname); + + /* Remote hostname %h */ + if (session->opts.host == NULL) { + goto err; + } + rc = sha1_update(ctx, session->opts.host, strlen(session->opts.host)); + if (rc != SSH_OK) { + goto err; + } + + /* Remote port %p */ + ssh_options_get_port(session, &port); + snprintf(strport, sizeof(strport), "%d", port); + rc = sha1_update(ctx, strport, strlen(strport)); + if (rc != SSH_OK) { + goto err; + } + + /* The remote username %r */ + username = session->opts.username; + if (username == NULL) { + /* fallback to local username: it will be used if not explicitly set */ + username = ssh_get_local_username(); + if (username == NULL) { + goto err; + } + } + rc = sha1_update(ctx, username, strlen(username)); + if (username != session->opts.username) { + free(username); + } + if (rc != SSH_OK) { + goto err; + } + + /* ProxyJump */ + if (session->opts.proxy_jumps_str != NULL) { + rc = sha1_update(ctx, + session->opts.proxy_jumps_str, + strlen(session->opts.proxy_jumps_str)); + } + if (rc != SSH_OK) { + goto err; + } + + /* Frees context */ + rc = sha1_final(conn_hash, ctx); + if (rc != SSH_OK) { + goto err; + } + + return ssh_get_hexa_internal(conn_hash, SHA_DIGEST_LENGTH, false); + +err: + free(local_hostname); + sha1_ctx_free(ctx); + return NULL; +} + +/** @internal + * @brief expands a string in function of session options + * + * @param[in] s Format string to expand. Known parameters: + * - %d user home directory (~) + * - %h target host name + * - %u local username + * - %l local hostname + * - %r remote username + * - %p remote port + * - %j proxyjump string + * - %C Hash of %l%h%p%r%j + * + * @returns Expanded string. The caller needs to free the memory using + * ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_path_expand_escape(ssh_session session, const char *s) +{ + char *buf = NULL; + char *r = NULL; + char *x = NULL; + const char *p = NULL; + size_t i, l; + + r = ssh_path_expand_tilde(s); + if (r == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + if (strlen(r) > MAX_BUF_SIZE) { + ssh_set_error(session, SSH_FATAL, "string to expand too long"); + free(r); + return NULL; + } + + buf = malloc(MAX_BUF_SIZE); + if (buf == NULL) { + ssh_set_error_oom(session); + free(r); + return NULL; + } + + p = r; + buf[0] = '\0'; + + for (i = 0; *p != '\0'; p++) { + if (*p != '%') { + escape: + buf[i] = *p; + i++; + if (i >= MAX_BUF_SIZE) { + free(buf); + free(r); + return NULL; + } + buf[i] = '\0'; + continue; + } + + p++; + if (*p == '\0') { + break; + } + + switch (*p) { + case '%': + goto escape; + case 'd': + x = ssh_get_user_home_dir(session); + if (x == NULL) { + ssh_set_error(session, SSH_FATAL, "Cannot expand homedir"); + free(buf); + free(r); + return NULL; + } + break; + case 'u': + x = ssh_get_local_username(); + break; + case 'l': + x = ssh_get_local_hostname(); + break; + case 'h': + if (session->opts.host) { + x = strdup(session->opts.host); + } else { + ssh_set_error(session, SSH_FATAL, "Cannot expand host"); + free(buf); + free(r); + return NULL; + } + break; + case 'r': + if (session->opts.username) { + x = strdup(session->opts.username); + } else { + ssh_set_error(session, SSH_FATAL, "Cannot expand username"); + free(buf); + free(r); + return NULL; + } + break; + case 'p': { + char tmp[6]; + unsigned int port; + + ssh_options_get_port(session, &port); + snprintf(tmp, sizeof(tmp), "%u", port); + x = strdup(tmp); + break; + } + case 'j': + if (session->opts.proxy_jumps_str != NULL) { + x = strdup(session->opts.proxy_jumps_str); + } else { + x = strdup(""); + } + break; + case 'C': + x = get_connection_hash(session); + break; + default: + ssh_set_error(session, SSH_FATAL, "Wrong escape sequence detected"); + free(buf); + free(r); + return NULL; + } + + if (x == NULL) { + ssh_set_error_oom(session); + free(buf); + free(r); + return NULL; + } + + i += strlen(x); + if (i >= MAX_BUF_SIZE) { + ssh_set_error(session, SSH_FATAL, "String too long"); + free(buf); + free(x); + free(r); + return NULL; + } + l = strlen(buf); + strncpy(buf + l, x, MAX_BUF_SIZE - l - 1); + buf[i] = '\0'; + SAFE_FREE(x); + } + + free(r); + + /* strip the unused space by realloc */ + x = realloc(buf, strlen(buf) + 1); + if (x == NULL) { + ssh_set_error_oom(session); + free(buf); + } + return x; +} + +/** + * @internal + * + * @brief Analyze the SSH banner to extract version information. + * + * @param session The session to analyze the banner from. + * @param server 0 means we are a client, 1 a server. + * + * @return 0 on success, < 0 on error. + * + * @see ssh_get_issue_banner() + */ +int ssh_analyze_banner(ssh_session session, int server) +{ + const char *banner = NULL; + const char *openssh = NULL; + const char *ios = NULL; + + if (server) { + banner = session->clientbanner; + } else { + banner = session->serverbanner; + } + + if (banner == NULL) { + ssh_set_error(session, SSH_FATAL, "Invalid banner"); + return -1; + } + + /* + * Typical banners e.g. are: + * + * SSH-1.5-openSSH_5.4 + * SSH-1.99-openSSH_3.0 + * + * SSH-2.0-something + * 012345678901234567890 + */ + if (strlen(banner) < 6 || + strncmp(banner, "SSH-", 4) != 0) { + ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner); + return -1; + } + + SSH_LOG(SSH_LOG_DEBUG, "Analyzing banner: %s", banner); + + switch (banner[4]) { + case '2': + break; + case '1': + if (strlen(banner) > 6) { + if (banner[6] == '9') { + break; + } + } + FALL_THROUGH; + default: + ssh_set_error(session, SSH_FATAL, "Protocol mismatch: %s", banner); + return -1; + } + + /* Make a best-effort to extract OpenSSH version numbers. */ + openssh = strstr(banner, "OpenSSH"); + if (openssh != NULL) { + char *tmp = NULL; + unsigned long int major = 0UL; + unsigned long int minor = 0UL; + int off = 0; + + /* + * The banner is typical: + * OpenSSH_5.4 + * 012345678901234567890 + */ + if (strlen(openssh) > 9) { + errno = 0; + major = strtoul(openssh + 8, &tmp, 10); + if ((tmp == (openssh + 8)) || + ((errno == ERANGE) && (major == ULONG_MAX)) || + ((errno != 0) && (major == 0)) || + ((major < 1) || (major > 100))) { + /* invalid major */ + errno = 0; + goto done; + } + + errno = 0; + off = major >= 10 ? 11 : 10; + minor = strtoul(openssh + off, &tmp, 10); + if ((tmp == (openssh + off)) || + ((errno == ERANGE) && (major == ULONG_MAX)) || + ((errno != 0) && (major == 0)) || + (minor > 100)) { + /* invalid minor */ + errno = 0; + goto done; + } + + session->openssh = SSH_VERSION_INT(((int) major), ((int) minor), 0); + + SSH_LOG(SSH_LOG_DEBUG, + "We are talking to an OpenSSH %s version: %lu.%lu (%x)", + server ? "client" : "server", + major, minor, session->openssh); + } + } + /* Cisco devices have odd scp implementation which breaks */ + ios = strstr(banner, "Cisco"); + if (ios != NULL) { + session->flags |= SSH_SESSION_FLAG_SCP_QUOTING_BROKEN; + } + +done: + return 0; +} + +/* try the Monotonic clock if possible for perfs reasons */ +#ifdef _POSIX_MONOTONIC_CLOCK +#define CLOCK CLOCK_MONOTONIC +#else +#define CLOCK CLOCK_REALTIME +#endif + +/** + * @internal + * @brief initializes a timestamp to the current time + * @param[out] ts pointer to an allocated ssh_timestamp structure + */ +void ssh_timestamp_init(struct ssh_timestamp *ts) +{ +#ifdef HAVE_CLOCK_GETTIME + struct timespec tp; + clock_gettime(CLOCK, &tp); + ts->useconds = tp.tv_nsec / 1000; +#else + struct timeval tp; + gettimeofday(&tp, NULL); + ts->useconds = tp.tv_usec; +#endif + ts->seconds = tp.tv_sec; +} + +#undef CLOCK + +/** + * @internal + * @brief gets the time difference between two timestamps in ms + * @param[in] old older value + * @param[in] new newer value + * @returns difference in milliseconds + */ + +static int +ssh_timestamp_difference(struct ssh_timestamp *old, struct ssh_timestamp *new) +{ + long seconds, usecs, msecs; + seconds = new->seconds - old->seconds; + usecs = new->useconds - old->useconds; + if (usecs < 0){ + seconds--; + usecs += 1000000; + } + msecs = seconds * 1000 + usecs/1000; + return msecs; +} + +/** + * @internal + * @brief turn seconds and microseconds pair (as provided by user-set options) + * into millisecond value + * @param[in] sec number of seconds + * @param[in] usec number of microseconds + * @returns milliseconds, or 10000 if user supplied values are equal to zero + */ +int ssh_make_milliseconds(unsigned long sec, unsigned long usec) +{ + unsigned long res = usec ? (usec / 1000) : 0; + res += (sec * 1000); + if (res == 0) { + res = 10 * 1000; /* use a reasonable default value in case + * SSH_OPTIONS_TIMEOUT is not set in options. */ + } + + if (res > INT_MAX) { + return SSH_TIMEOUT_INFINITE; + } else { + return (int)res; + } +} + +/** + * @internal + * @brief Checks if a timeout is elapsed, in function of a previous + * timestamp and an assigned timeout + * @param[in] ts pointer to an existing timestamp + * @param[in] timeout timeout in milliseconds. Negative values mean infinite + * timeout + * @returns 1 if timeout is elapsed + * 0 otherwise + */ +int ssh_timeout_elapsed(struct ssh_timestamp *ts, int timeout) +{ + struct ssh_timestamp now; + + switch(timeout) { + case -2: /* + * -2 means user-defined timeout as available in + * session->timeout, session->timeout_usec. + */ + SSH_LOG(SSH_LOG_DEBUG, "ssh_timeout_elapsed called with -2. this needs to " + "be fixed. please set a breakpoint on misc.c:%d and " + "fix the caller\n", __LINE__); + return 0; + case -1: /* -1 means infinite timeout */ + return 0; + case 0: /* 0 means no timeout */ + return 1; + default: + break; + } + + ssh_timestamp_init(&now); + + return (ssh_timestamp_difference(ts,&now) >= timeout); +} + +/** + * @brief updates a timeout value so it reflects the remaining time + * @param[in] ts pointer to an existing timestamp + * @param[in] timeout timeout in milliseconds. Negative values mean infinite + * timeout + * @returns remaining time in milliseconds, 0 if elapsed, -1 if never. + */ +int ssh_timeout_update(struct ssh_timestamp *ts, int timeout) +{ + struct ssh_timestamp now; + int ms, ret; + if (timeout <= 0) { + return timeout; + } + ssh_timestamp_init(&now); + ms = ssh_timestamp_difference(ts,&now); + if(ms < 0) + ms = 0; + ret = timeout - ms; + return ret >= 0 ? ret: 0; +} + +/** + * @brief Securely free memory by overwriting it before deallocation + * + * Overwrites the memory region with zeros before calling free() to prevent + * sensitive data from remaining in memory after deallocation. + * + * @param[in] ptr Pointer to the memory region to securely free. + * Can be NULL (no operation performed). + * @param[in] len Length of the memory region in bytes. + * + */ +void burn_free(void *ptr, size_t len) +{ + if (ptr == NULL || len == 0) { + return; + } + + ssh_burn(ptr, len); + free(ptr); +} + +#if !defined(HAVE_STRNDUP) +char *strndup(const char *s, size_t n) +{ + char *x = NULL; + + if (n + 1 < n) { + return NULL; + } + + x = malloc(n + 1); + if (x == NULL) { + return NULL; + } + + memcpy(x, s, n); + x[n] = '\0'; + + return x; +} +#endif /* ! HAVE_STRNDUP */ + +/* Increment 64b integer in network byte order */ +void +uint64_inc(unsigned char *counter) +{ + int i; + + for (i = 7; i >= 0; i--) { + counter[i]++; + if (counter[i]) + return; + } +} + +/** + * @internal + * + * @brief Quote file name to be used on shell. + * + * Try to put the given file name between single quotes. There are special + * cases: + * + * - When the '\'' char is found in the file name, it is double quoted + * - example: + * input: a'b + * output: 'a'"'"'b' + * - When the '!' char is found in the file name, it is replaced by an unquoted + * verbatim char "\!" + * - example: + * input: a!b + * output 'a'\!'b' + * + * @param[in] file_name File name string to be quoted before used on shell + * @param[out] buf Buffer to receive the final quoted file name. Must + * have room for the final quoted string. The maximum + * output length would be (3 * strlen(file_name) + 1) + * since in the worst case each character would be + * replaced by 3 characters, plus the terminating '\0'. + * @param[in] buf_len The size of the provided output buffer + * + * @returns SSH_ERROR on error; length of the resulting string not counting the + * string terminator '\0' + * */ +int ssh_quote_file_name(const char *file_name, char *buf, size_t buf_len) +{ + const char *src = NULL; + char *dst = NULL; + size_t required_buf_len; + + enum ssh_quote_state_e state = NO_QUOTE; + + if (file_name == NULL || buf == NULL || buf_len == 0) { + SSH_LOG(SSH_LOG_TRACE, "Invalid parameter"); + return SSH_ERROR; + } + + /* Only allow file names smaller than 32kb. */ + if (strlen(file_name) > 32 * 1024) { + SSH_LOG(SSH_LOG_TRACE, "File name too long"); + return SSH_ERROR; + } + + /* Paranoia check */ + required_buf_len = (size_t)3 * strlen(file_name) + 1; + if (required_buf_len > buf_len) { + SSH_LOG(SSH_LOG_TRACE, "Buffer too small"); + return SSH_ERROR; + } + + src = file_name; + dst = buf; + + while ((*src != '\0')) { + switch (*src) { + + /* The '\'' char is double quoted */ + + case '\'': + switch (state) { + case NO_QUOTE: + /* Start a new double quoted string. The '\'' char will be + * copied to the beginning of it at the end of the loop. */ + *dst++ = '"'; + break; + case SINGLE_QUOTE: + /* Close the current single quoted string and start a new double + * quoted string. The '\'' char will be copied to the beginning + * of it at the end of the loop. */ + *dst++ = '\''; + *dst++ = '"'; + break; + case DOUBLE_QUOTE: + /* If already in the double quoted string, keep copying the + * sequence of chars. */ + break; + default: + /* Should never be reached */ + goto error; + } + + /* When the '\'' char is found, the resulting state will be + * DOUBLE_QUOTE in any case*/ + state = DOUBLE_QUOTE; + break; + + /* The '!' char is replaced by unquoted "\!" */ + + case '!': + switch (state) { + case NO_QUOTE: + /* The '!' char is interpreted in some shells (e.g. CSH) even + * when is quoted with single quotes. Replace it with unquoted + * "\!" which is correctly interpreted as the '!' character. */ + *dst++ = '\\'; + break; + case SINGLE_QUOTE: + /* Close the currently quoted string and replace '!' for unquoted + * "\!" */ + *dst++ = '\''; + *dst++ = '\\'; + break; + case DOUBLE_QUOTE: + /* Close currently quoted string and replace "!" for unquoted + * "\!" */ + *dst++ = '"'; + *dst++ = '\\'; + break; + default: + /* Should never be reached */ + goto error; + } + + /* When the '!' char is found, the resulting state will be NO_QUOTE + * in any case*/ + state = NO_QUOTE; + break; + + /* Ordinary chars are single quoted */ + + default: + switch (state) { + case NO_QUOTE: + /* Start a new single quoted string */ + *dst++ = '\''; + break; + case SINGLE_QUOTE: + /* If already in the single quoted string, keep copying the + * sequence of chars. */ + break; + case DOUBLE_QUOTE: + /* Close current double quoted string and start a new single + * quoted string. */ + *dst++ = '"'; + *dst++ = '\''; + break; + default: + /* Should never be reached */ + goto error; + } + + /* When an ordinary char is found, the resulting state will be + * SINGLE_QUOTE in any case*/ + state = SINGLE_QUOTE; + break; + } + + /* Copy the current char to output */ + *dst++ = *src++; + } + + /* Close the quoted string when necessary */ + + switch (state) { + case NO_QUOTE: + /* No open string */ + break; + case SINGLE_QUOTE: + /* Close current single quoted string */ + *dst++ = '\''; + break; + case DOUBLE_QUOTE: + /* Close current double quoted string */ + *dst++ = '"'; + break; + default: + /* Should never be reached */ + goto error; + } + + /* Put the string terminator */ + *dst = '\0'; + + return (int)(dst - buf); + +error: + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Given a string, encode existing newlines as the string "\\n" + * + * @param[in] string Input string + * @param[out] buf Output buffer. This buffer must be at least (2 * + * strlen(string)) + 1 long. In the worst case, + * each character can be encoded as 2 characters plus the + * terminating '\0'. + * @param[in] buf_len Size of the provided output buffer + * + * @returns SSH_ERROR on error; length of the resulting string not counting the + * terminating '\0' otherwise + */ +int ssh_newline_vis(const char *string, char *buf, size_t buf_len) +{ + const char *in = NULL; + char *out = NULL; + + if (string == NULL || buf == NULL || buf_len == 0) { + return SSH_ERROR; + } + + if ((2 * strlen(string) + 1) > buf_len) { + SSH_LOG(SSH_LOG_TRACE, "Buffer too small"); + return SSH_ERROR; + } + + out = buf; + for (in = string; *in != '\0'; in++) { + if (*in == '\n') { + *out++ = '\\'; + *out++ = 'n'; + } else { + *out++ = *in; + } + } + *out = '\0'; + + return (int)(out - buf); +} + +/** + * @internal + * + * @brief Replaces the last 6 characters of a string from 'X' to 6 random hexdigits. + * + * @param[in,out] name Any input string with last 6 characters as 'X'. + * @returns -1 as error when the last 6 characters of the input to be replaced are not 'X' + * 0 otherwise. + */ +int ssh_tmpname(char *name) +{ + char *tmp = NULL; + size_t i = 0; + int rc = 0; + uint8_t random[6]; + + if (name == NULL) { + goto err; + } + + tmp = name + strlen(name) - 6; + if (tmp < name) { + goto err; + } + + for (i = 0; i < 6; i++) { + if (tmp[i] != 'X') { + SSH_LOG(SSH_LOG_WARNING, + "Invalid input. Last six characters of the input must be \'X\'"); + goto err; + } + } + + rc = ssh_get_random(random, 6, 0); + if (!rc) { + SSH_LOG(SSH_LOG_WARNING, + "Could not generate random data\n"); + goto err; + } + + for (i = 0; i < 6; i++) { + /* Limit the random[i] < 32 */ + random[i] &= 0x1f; + /* For values from 0 to 9 use numbers, otherwise use letters */ + tmp[i] = random[i] > 9 ? random[i] + 'a' - 10 : random[i] + '0'; + } + + return 0; + +err: + errno = EINVAL; + return -1; +} + +/** + * @internal + * + * @brief Finds the first occurrence of a pattern in a string and replaces it. + * + * @param[in] src Source string containing the pattern to be replaced. + * @param[in] pattern Pattern to be replaced in the source string. + * Note: this function replaces the first occurrence of + * pattern only. + * @param[in] replace String to be replaced is stored in replace. + * + * @returns src_replaced a pointer that points to the replaced string. + * NULL if allocation fails or if src is NULL. The returned memory needs to be + * freed using ssh_string_free_char(). + * + * @see ssh_string_free_char() + */ +char *ssh_strreplace(const char *src, const char *pattern, const char *replace) +{ + const char *p = NULL; + char *src_replaced = NULL; + + if (src == NULL) { + return NULL; + } + + if (pattern == NULL || replace == NULL) { + return strdup(src); + } + + p = strstr(src, pattern); + + if (p != NULL) { + size_t offset = p - src; + size_t pattern_len = strlen(pattern); + size_t replace_len = strlen(replace); + size_t len = strlen(src); + size_t len_replaced = len + replace_len - pattern_len + 1; + + src_replaced = (char *)malloc(len_replaced); + + if (src_replaced == NULL) { + return NULL; + } + + memset(src_replaced, 0, len_replaced); + memcpy(src_replaced, src, offset); + memcpy(src_replaced + offset, replace, replace_len); + memcpy(src_replaced + offset + replace_len, src + offset + pattern_len, len - offset - pattern_len); + return src_replaced; /* free in the caller */ + } else { + return strdup(src); + } +} + +/** + * @internal + * + * @brief Processes errno into error string + * + * @param[in] err_num The errno value + * @param[out] buf Pointer to a place where the string could be saved + * @param[in] buflen The allocated size of buf + * + * @return error string + */ +char *ssh_strerror(int err_num, char *buf, size_t buflen) +{ +#if ((defined(__linux__) && defined(__GLIBC__)) || defined(__CYGWIN__)) && defined(_GNU_SOURCE) + /* GNU extension on Linux */ + return strerror_r(err_num, buf, buflen); +#else + int rv; + +#if defined(_WIN32) + rv = strerror_s(buf, buflen, err_num); +#else + /* POSIX version available for example on FreeBSD or in musl libc */ + rv = strerror_r(err_num, buf, buflen); +#endif /* _WIN32 */ + + /* make sure the buffer is initialized and terminated with NULL */ + if (-rv == ERANGE) { + buf[0] = '\0'; + } + return buf; +#endif /* ((defined(__linux__) && defined(__GLIBC__)) || defined(__CYGWIN__)) && defined(_GNU_SOURCE) */ +} + +/** + * @brief Read the requested number of bytes from a local file. + * + * A call to read() may perform a short read even when sufficient data is + * present in the file. This function can be used to avoid such short reads. + * + * This function tries to read the requested number of bytes from the file + * until one of the following occurs : + * - Requested number of bytes are read. + * - EOF is encountered before reading the requested number of bytes. + * - An error occurs. + * + * On encountering an error due to an interrupt, this function ignores that + * error and continues trying to read the data. + * + * @param[in] fd The file descriptor of the local file to read from. + * + * @param[out] buf Pointer to a buffer in which read data will be + * stored. + * + * @param[in] nbytes Number of bytes to read. + * + * @returns Number of bytes read on success, + * SSH_ERROR on error with errno set to indicate the + * error. + */ +ssize_t ssh_readn(int fd, void *buf, size_t nbytes) +{ + size_t total_bytes_read = 0; + ssize_t bytes_read; + + if (fd < 0 || buf == NULL || nbytes == 0) { + errno = EINVAL; + return SSH_ERROR; + } + + do { + bytes_read = read(fd, + ((char *)buf) + total_bytes_read, + nbytes - total_bytes_read); + if (bytes_read == -1) { + if (errno == EINTR) { + /* Ignoring errors due to signal interrupts */ + continue; + } + + return SSH_ERROR; + } + + if (bytes_read == 0) { + /* EOF encountered on the local file before reading nbytes */ + break; + } + + total_bytes_read += (size_t)bytes_read; + } while (total_bytes_read < nbytes); + + return total_bytes_read; +} + +/** + * @brief Write the requested number of bytes to a local file. + * + * A call to write() may perform a short write on a local file. This function + * can be used to avoid short writes. + * + * This function tries to write the requested number of bytes until those many + * bytes are written or some error occurs. + * + * On encountering an error due to an interrupt, this function ignores that + * error and continues trying to write the data. + * + * @param[in] fd The file descriptor of the local file to write to. + * + * @param[in] buf Pointer to a buffer in which data to write is stored. + * + * @param[in] nbytes Number of bytes to write. + * + * @returns Number of bytes written on success, + * SSH_ERROR on error with errno set to indicate the + * error. + */ +ssize_t ssh_writen(int fd, const void *buf, size_t nbytes) +{ + size_t total_bytes_written = 0; + ssize_t bytes_written; + + if (fd < 0 || buf == NULL || nbytes == 0) { + errno = EINVAL; + return SSH_ERROR; + } + + do { + bytes_written = write(fd, + ((const char *)buf) + total_bytes_written, + nbytes - total_bytes_written); + if (bytes_written == -1) { + if (errno == EINTR) { + /* Ignoring errors due to signal interrupts */ + continue; + } + + return SSH_ERROR; + } + + total_bytes_written += (size_t)bytes_written; + } while (total_bytes_written < nbytes); + + return total_bytes_written; +} + +/** + * @brief Checks syntax of a domain name + * + * The check is made based on the RFC1035 section 2.3.1 + * Allowed characters are: hyphen, period, digits (0-9) and letters (a-zA-Z) + * + * The label should be no longer than 63 characters + * The label should start with a letter and end with a letter or number + * The label in this implementation can start with a number to allow virtual + * URLs to pass. Note that this will make IPv4 addresses to pass + * this check too. + * + * @param hostname The domain name to be checked, has to be null terminated + * + * @return SSH_OK if the hostname passes syntax check + * SSH_ERROR otherwise or if hostname is NULL or empty string + */ +int ssh_check_hostname_syntax(const char *hostname) +{ + char *it = NULL, *s = NULL, *buf = NULL; + size_t it_len; + char c; + + if (hostname == NULL || strlen(hostname) == 0) { + return SSH_ERROR; + } + + /* strtok_r writes into the string, keep the input clean */ + s = strdup(hostname); + if (s == NULL) { + return SSH_ERROR; + } + + it = strtok_r(s, ".", &buf); + /* if the token has 0 length */ + if (it == NULL) { + free(s); + return SSH_ERROR; + } + do { + it_len = strlen(it); + if (it_len > ARPA_DOMAIN_MAX_LEN || + /* the first char must be a letter, but some virtual urls start + * with a number */ + isalnum(it[0]) == 0 || + isalnum(it[it_len - 1]) == 0) { + free(s); + return SSH_ERROR; + } + while (*it != '\0') { + c = *it; + /* the "." is allowed too, but tokenization removes it from the + * string */ + if (isalnum(c) == 0 && c != '-') { + free(s); + return SSH_ERROR; + } + it++; + } + } while ((it = strtok_r(NULL, ".", &buf)) != NULL); + + free(s); + + return SSH_OK; +} + +/** + * @brief Checks syntax of a username + * + * This check disallows metacharacters in the username + * + * @param username The username to be checked, has to be null terminated + * + * @return SSH_OK if the username passes syntax check + * SSH_ERROR otherwise or if username is NULL or empty string + */ +int ssh_check_username_syntax(const char *username) +{ + size_t username_len; + + if (username == NULL || *username == '-') { + return SSH_ERROR; + } + + username_len = strlen(username); + if (username_len == 0 || username[username_len - 1] == '\\' || + strpbrk(username, SSH_DANGEROUS_SHELL_CHARS) != NULL) { + return SSH_ERROR; + } + for (size_t i = 0; i < username_len; i++) { + if (isspace(username[i]) != 0 && username[i + 1] == '-') { + return SSH_ERROR; + } + } + + return SSH_OK; +} + +/** + * @brief Free proxy jump list + * + * Frees everything in a proxy jump list, but doesn't free the ssh_list + * + * @param proxy_jump_list + * + */ +void +ssh_proxyjumps_free(struct ssh_list *proxy_jump_list) +{ + struct ssh_jump_info_struct *jump = NULL; + + for (jump = + ssh_list_pop_head(struct ssh_jump_info_struct *, proxy_jump_list); + jump != NULL; + jump = ssh_list_pop_head(struct ssh_jump_info_struct *, + proxy_jump_list)) { + SAFE_FREE(jump->hostname); + SAFE_FREE(jump->username); + SAFE_FREE(jump); + } +} + +/** + * @brief Check if libssh proxy jumps is enabled + * + * If env variable OPENSSH_PROXYJUMP is set to 1 then proxyjump will be + * through the OpenSSH binary. + * + * @return false if OPENSSH_PROXYJUMP=1 + * true otherwise + */ +bool +ssh_libssh_proxy_jumps(void) +{ + const char *t = getenv("OPENSSH_PROXYJUMP"); + + return !(t != NULL && t[0] == '1'); +} + +/** + * @internal + * + * @brief Safely open a file containing some configuration. + * + * Runs checks if the file can be used as some configuration file (is regular + * file and is not too large). If so, returns the opened file (for reading). + * Otherwise logs error and returns `NULL`. + * + * @param filename The path to the file to open. + * @param max_file_size Maximum file size that is accepted. + * + * @returns the opened file or `NULL` on error. + */ +FILE *ssh_strict_fopen(const char *filename, size_t max_file_size) +{ + FILE *f = NULL; + struct stat sb; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + int r, fd; + + /* open first to avoid TOCTOU */ + fd = open(filename, O_RDONLY); + if (fd == -1) { + SSH_LOG(SSH_LOG_RARE, + "Failed to open a file %s for reading: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return NULL; + } + + /* Check the file is sensible for a configuration file */ + r = fstat(fd, &sb); + if (r != 0) { + SSH_LOG(SSH_LOG_RARE, + "Failed to stat %s: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + close(fd); + return NULL; + } + if ((sb.st_mode & S_IFMT) != S_IFREG) { + SSH_LOG(SSH_LOG_RARE, + "The file %s is not a regular file: skipping", + filename); + close(fd); + return NULL; + } + + if ((size_t)sb.st_size > max_file_size) { + SSH_LOG(SSH_LOG_RARE, + "The file %s is too large (%jd MB > %zu MB): skipping", + filename, + (intmax_t)sb.st_size / 1024 / 1024, + max_file_size / 1024 / 1024); + close(fd); + return NULL; + } + + f = fdopen(fd, "r"); + if (f == NULL) { + SSH_LOG(SSH_LOG_RARE, + "Failed to open a file %s for reading: %s", + filename, + ssh_strerror(r, err_msg, SSH_ERRNO_MSG_MAX)); + close(fd); + return NULL; + } + + /* the flcose() will close also the underlying fd */ + return f; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/mlkem.c b/src/libs/libssh-0.12.2/src/mlkem.c new file mode 100644 index 000000000000..967c4f11ea55 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mlkem.c @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Pavol Žáčik + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/mlkem.h" + +const struct mlkem_type_info *kex_type_to_mlkem_info(enum ssh_key_exchange_e kex_type) +{ + switch (kex_type) { + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: + return &MLKEM768_INFO; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + return &MLKEM1024_INFO; +#endif + default: + return NULL; + } +} diff --git a/src/libs/libssh-0.12.2/src/mlkem_crypto.c b/src/libs/libssh-0.12.2/src/mlkem_crypto.c new file mode 100644 index 000000000000..8f58b80bf7cb --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mlkem_crypto.c @@ -0,0 +1,279 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Pavol Žáčik + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/mlkem.h" +#include "libssh/session.h" + +#include +#include +#include + +const struct mlkem_type_info MLKEM768_INFO = { + .pubkey_size = OSSL_ML_KEM_768_PUBLIC_KEY_BYTES, + .ciphertext_size = OSSL_ML_KEM_768_CIPHERTEXT_BYTES, + .name = LN_ML_KEM_768, +}; + +const struct mlkem_type_info MLKEM1024_INFO = { + .pubkey_size = OSSL_ML_KEM_1024_PUBLIC_KEY_BYTES, + .ciphertext_size = OSSL_ML_KEM_1024_CIPHERTEXT_BYTES, + .name = LN_ML_KEM_1024, +}; + +static const char *ssh_mlkem_get_propq(const struct mlkem_type_info *mlkem_info) +{ + static const char *propq = NULL; + static bool is_cached = false; + EVP_KEM *kem = NULL; + + if (is_cached) { + return propq; + } + is_cached = true; + + if (!ssh_fips_mode()) { + return propq; + } + + kem = EVP_KEM_fetch(NULL, mlkem_info->name, NULL); + if (kem != NULL) { + EVP_KEM_free(kem); + return propq; + } + + propq = FIPS_FALLBACK_PROPQ; + return propq; +} + +int ssh_mlkem_init(ssh_session session) +{ + struct ssh_crypto_struct *crypto = session->next_crypto; + EVP_PKEY_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + int rc, ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string pubkey = NULL; + size_t pubkey_size; + const char *propq = NULL; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + propq = ssh_mlkem_get_propq(mlkem_info); + + ctx = EVP_PKEY_CTX_new_from_name(NULL, mlkem_info->name, propq); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_keygen_init(ctx); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to initialize ML-KEM keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_keygen(ctx, &pkey); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to perform ML-KEM keygen: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + EVP_PKEY_free(crypto->mlkem_privkey); + crypto->mlkem_privkey = pkey; + + pubkey_size = mlkem_info->pubkey_size; + pubkey = ssh_string_new(pubkey_size); + if (pubkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = EVP_PKEY_get_raw_public_key(pkey, + ssh_string_data(pubkey), + &pubkey_size); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to extract ML-KEM public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_client_pubkey); + crypto->mlkem_client_pubkey = pubkey; + pubkey = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(pubkey); + EVP_PKEY_CTX_free(ctx); + return ret; +} + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + EVP_PKEY *pkey = NULL; + EVP_PKEY_CTX *ctx = NULL; + int rc, ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + const unsigned char *pubkey = ssh_string_data(crypto->mlkem_client_pubkey); + const size_t pubkey_len = ssh_string_len(crypto->mlkem_client_pubkey); + size_t shared_secret_size = MLKEM_SHARED_SECRET_SIZE; + ssh_string ciphertext = NULL; + size_t ciphertext_size; + const char *propq = NULL; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + propq = ssh_mlkem_get_propq(mlkem_info); + + pkey = EVP_PKEY_new_raw_public_key_ex(NULL, + mlkem_info->name, + propq, + pubkey, + pubkey_len); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM public key from raw data: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, pkey, propq); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_encapsulate_init(ctx, NULL); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to initialize ML-KEM encapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ciphertext_size = mlkem_info->ciphertext_size; + ciphertext = ssh_string_new(ciphertext_size); + if (ciphertext == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + rc = EVP_PKEY_encapsulate(ctx, + ssh_string_data(ciphertext), + &ciphertext_size, + shared_secret, + &shared_secret_size); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to perform ML-KEM encapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_ciphertext); + crypto->mlkem_ciphertext = ciphertext; + ciphertext = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(ciphertext); + EVP_PKEY_free(pkey); + EVP_PKEY_CTX_free(ctx); + return ret; +} + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + EVP_PKEY_CTX *ctx = NULL; + int rc, ret = SSH_ERROR; + size_t shared_secret_size = MLKEM_SHARED_SECRET_SIZE; + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + const char *propq = NULL; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + propq = ssh_mlkem_get_propq(mlkem_info); + + ctx = EVP_PKEY_CTX_new_from_pkey(NULL, crypto->mlkem_privkey, propq); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to create ML-KEM context: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_decapsulate_init(ctx, NULL); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to initialize ML-KEM decapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + rc = EVP_PKEY_decapsulate(ctx, + shared_secret, + &shared_secret_size, + ssh_string_data(crypto->mlkem_ciphertext), + ssh_string_len(crypto->mlkem_ciphertext)); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to perform ML-KEM decapsulation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto cleanup; + } + + ret = SSH_OK; + +cleanup: + EVP_PKEY_CTX_free(ctx); + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/mlkem_gcrypt.c b/src/libs/libssh-0.12.2/src/mlkem_gcrypt.c new file mode 100644 index 000000000000..1898becbe3ed --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mlkem_gcrypt.c @@ -0,0 +1,207 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/mlkem.h" +#include "libssh/session.h" + +#include + +const struct mlkem_type_info MLKEM768_INFO = { + .pubkey_size = GCRY_KEM_MLKEM768_PUBKEY_LEN, + .privkey_size = GCRY_KEM_MLKEM768_SECKEY_LEN, + .ciphertext_size = GCRY_KEM_MLKEM768_CIPHER_LEN, + .alg = GCRY_KEM_MLKEM768, +}; + +const struct mlkem_type_info MLKEM1024_INFO = { + .pubkey_size = GCRY_KEM_MLKEM1024_PUBKEY_LEN, + .privkey_size = GCRY_KEM_MLKEM1024_SECKEY_LEN, + .ciphertext_size = GCRY_KEM_MLKEM1024_CIPHER_LEN, + .alg = GCRY_KEM_MLKEM1024, +}; + +int ssh_mlkem_init(ssh_session session) +{ + int ret = SSH_ERROR; + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + ssh_string pubkey = NULL; + unsigned char *privkey = NULL, *pubkey_data = NULL; + gcry_error_t err; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + privkey = malloc(mlkem_info->privkey_size); + if (privkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + pubkey = ssh_string_new(mlkem_info->pubkey_size); + if (pubkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + + pubkey_data = ssh_string_data(pubkey); + err = gcry_kem_keypair(mlkem_info->alg, + pubkey_data, + mlkem_info->pubkey_size, + privkey, + mlkem_info->privkey_size); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate ML-KEM key: %s", + gpg_strerror(err)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_client_pubkey); + crypto->mlkem_client_pubkey = pubkey; + pubkey = NULL; + + free(crypto->mlkem_privkey); + crypto->mlkem_privkey = privkey; + crypto->mlkem_privkey_len = mlkem_info->privkey_size; + privkey = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(pubkey); + if (privkey != NULL) { + ssh_burn(privkey, mlkem_info->privkey_size); + free(privkey); + } + return ret; +} + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + int ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + const unsigned char *pubkey_data = NULL; + unsigned char *ciphertext_data = NULL; + ssh_string ciphertext = NULL; + ssh_string pubkey = crypto->mlkem_client_pubkey; + gcry_error_t err; + + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing pubkey in session"); + return SSH_ERROR; + } + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + ciphertext = ssh_string_new(mlkem_info->ciphertext_size); + if (ciphertext == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + pubkey_data = ssh_string_data(pubkey); + ciphertext_data = ssh_string_data(ciphertext); + err = gcry_kem_encap(mlkem_info->alg, + pubkey_data, + mlkem_info->pubkey_size, + ciphertext_data, + mlkem_info->ciphertext_size, + shared_secret, + MLKEM_SHARED_SECRET_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to encapsulate ML-KEM shared secret: %s", + gpg_strerror(err)); + goto cleanup; + } + + ssh_string_free(crypto->mlkem_ciphertext); + crypto->mlkem_ciphertext = ciphertext; + ciphertext = NULL; + + ret = SSH_OK; + +cleanup: + ssh_string_free(ciphertext); + return ret; +} + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string ciphertext = NULL; + unsigned char *ciphertext_data = NULL; + gcry_error_t err; + + ciphertext = crypto->mlkem_ciphertext; + if (ciphertext == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing ciphertext in session"); + return SSH_ERROR; + } + + if (crypto->mlkem_privkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing ML-KEM private key in session"); + return SSH_ERROR; + } + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + ciphertext_data = ssh_string_data(ciphertext); + err = gcry_kem_decap(mlkem_info->alg, + crypto->mlkem_privkey, + mlkem_info->privkey_size, + ciphertext_data, + mlkem_info->ciphertext_size, + shared_secret, + MLKEM_SHARED_SECRET_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to decapsulate ML-KEM shared secret: %s", + gpg_strerror(err)); + return SSH_ERROR; + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/mlkem_native.c b/src/libs/libssh-0.12.2/src/mlkem_native.c new file mode 100644 index 000000000000..97b22a36b5a6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/mlkem_native.c @@ -0,0 +1,202 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/crypto.h" +#include "libssh/mlkem.h" +#include "libssh/mlkem_native.h" +#include "libssh/session.h" + +#define crypto_kem_mlkem768_PUBLICKEYBYTES 1184 +#define crypto_kem_mlkem768_SECRETKEYBYTES 2400 +#define crypto_kem_mlkem768_CIPHERTEXTBYTES 1088 + +const struct mlkem_type_info MLKEM768_INFO = { + .pubkey_size = crypto_kem_mlkem768_PUBLICKEYBYTES, + .privkey_size = crypto_kem_mlkem768_SECRETKEYBYTES, + .ciphertext_size = crypto_kem_mlkem768_CIPHERTEXTBYTES, +}; + +int ssh_mlkem_init(ssh_session session) +{ + int ret = SSH_ERROR; + struct ssh_crypto_struct *crypto = session->next_crypto; + const struct mlkem_type_info *mlkem_info = NULL; + unsigned char rnd[LIBCRUX_ML_KEM_KEY_PAIR_PRNG_LEN]; + struct libcrux_mlkem768_keypair keypair; + int err; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + goto cleanup; + } + + err = ssh_get_random(rnd, sizeof(rnd), 0); + if (err != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to generate random data for ML-KEM keygen"); + goto cleanup; + } + + keypair = libcrux_ml_kem_mlkem768_portable_generate_key_pair(rnd); + + if (ssh_string_len(crypto->mlkem_client_pubkey) < mlkem_info->pubkey_size) { + SSH_STRING_FREE(crypto->mlkem_client_pubkey); + } + if (crypto->mlkem_client_pubkey == NULL) { + crypto->mlkem_client_pubkey = ssh_string_new(mlkem_info->pubkey_size); + if (crypto->mlkem_client_pubkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + } + err = ssh_string_fill(crypto->mlkem_client_pubkey, + keypair.pk.value, + mlkem_info->pubkey_size); + if (err) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to fill the string with client pubkey"); + goto cleanup; + } + + if (crypto->mlkem_privkey == NULL) { + crypto->mlkem_privkey = malloc(mlkem_info->privkey_size); + if (crypto->mlkem_privkey == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + } + memcpy(crypto->mlkem_privkey, keypair.sk.value, mlkem_info->privkey_size); + crypto->mlkem_privkey_len = mlkem_info->privkey_size; + + ret = SSH_OK; + +cleanup: + ssh_burn(&keypair, sizeof(keypair)); + ssh_burn(rnd, sizeof(rnd)); + return ret; +} + +int ssh_mlkem_encapsulate(ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + int ret = SSH_ERROR; + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + const unsigned char *pubkey_data = NULL; + ssh_string pubkey = crypto->mlkem_client_pubkey; + struct libcrux_mlkem768_enc_result enc; + struct libcrux_mlkem768_pk mlkem_pub = {0}; + unsigned char rnd[LIBCRUX_ML_KEM_ENC_PRNG_LEN]; + int err; + + if (pubkey == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing pubkey in session"); + return SSH_ERROR; + } + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + pubkey_data = ssh_string_data(pubkey); + memcpy(mlkem_pub.value, pubkey_data, mlkem_info->pubkey_size); + err = libcrux_ml_kem_mlkem768_portable_validate_public_key(&mlkem_pub); + if (err == 0) { + SSH_LOG(SSH_LOG_WARNING, "Invalid public key"); + return SSH_ERROR; + } + + err = ssh_get_random(rnd, sizeof(rnd), 0); + if (err != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to generate random data for ML-KEM keygen"); + goto cleanup; + } + + enc = libcrux_ml_kem_mlkem768_portable_encapsulate(&mlkem_pub, rnd); + + if (ssh_string_len(crypto->mlkem_ciphertext) < mlkem_info->ciphertext_size) { + SSH_STRING_FREE(crypto->mlkem_ciphertext); + } + if (crypto->mlkem_ciphertext == NULL) { + crypto->mlkem_ciphertext = ssh_string_new(mlkem_info->ciphertext_size); + if (crypto->mlkem_ciphertext == NULL) { + ssh_set_error_oom(session); + goto cleanup; + } + } + err = ssh_string_fill(crypto->mlkem_ciphertext, + enc.fst.value, + sizeof(enc.fst.value)); + if (err != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, "Failed to fill the string with ciphertext"); + goto cleanup; + } + memcpy(shared_secret, enc.snd, sizeof(enc.snd)); + + ret = SSH_OK; + +cleanup: + ssh_burn(rnd, sizeof(rnd)); + ssh_burn(&enc, sizeof(enc)); + return ret; +} + +int ssh_mlkem_decapsulate(const ssh_session session, + ssh_mlkem_shared_secret shared_secret) +{ + const struct mlkem_type_info *mlkem_info = NULL; + struct ssh_crypto_struct *crypto = session->next_crypto; + ssh_string ciphertext = NULL; + unsigned char *ciphertext_data = NULL; + struct libcrux_mlkem768_sk mlkem_priv = {0}; + struct libcrux_mlkem768_ciphertext mlkem_ciphertext = {0}; + + mlkem_info = kex_type_to_mlkem_info(crypto->kex_type); + if (mlkem_info == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Unknown ML-KEM type"); + return SSH_ERROR; + } + + ciphertext = crypto->mlkem_ciphertext; + if (ciphertext == NULL) { + SSH_LOG(SSH_LOG_WARNING, "Missing ciphertext in session"); + return SSH_ERROR; + } + + ciphertext_data = ssh_string_data(ciphertext); + memcpy(mlkem_ciphertext.value, + ciphertext_data, + sizeof(mlkem_ciphertext.value)); + + memcpy(mlkem_priv.value, crypto->mlkem_privkey, crypto->mlkem_privkey_len); + + libcrux_ml_kem_mlkem768_portable_decapsulate(&mlkem_priv, + &mlkem_ciphertext, + shared_secret); + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/options.c b/src/libs/libssh-0.12.2/src/options.c new file mode 100644 index 000000000000..1563c907be08 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/options.c @@ -0,0 +1,3120 @@ +/* + * options.c - handle pre-connection options + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include +#ifndef _WIN32 +#include +#else +#include +#endif +#include "libssh/config.h" +#include "libssh/config_parser.h" +#include "libssh/misc.h" +#include "libssh/options.h" +#include "libssh/pki.h" +#include "libssh/pki_context.h" +#include "libssh/pki_priv.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include +#include "libssh/misc.h" +#include "libssh/options.h" +#include "libssh/config_parser.h" +#include "libssh/gssapi.h" +#include "libssh/token.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#include "libssh/bind.h" +#include "libssh/bind_config.h" +#endif + +/** + * @addtogroup libssh_session + * @{ + */ + +/** + * @brief Duplicate the options of a session structure. + * + * If you make several sessions with the same options this is useful. You + * cannot use twice the same option structure in ssh_connect. + * + * @param src The session to use to copy the options. + * + * @param dest A pointer to store the allocated session with duplicated + * options. You have to free the memory using ssh_free() + * + * @returns 0 on success, -1 on error with errno set. + * + * @see ssh_connect() + * @see ssh_free() + */ +int ssh_options_copy(ssh_session src, ssh_session *dest) +{ + ssh_session new = NULL; + struct ssh_iterator *it = NULL; + struct ssh_list *list = NULL; + char *id = NULL; + int i; + + if (src == NULL || dest == NULL) { + return -1; + } + + new = ssh_new(); + if (new == NULL) { + return -1; + } + + if (src->opts.username != NULL) { + new->opts.username = strdup(src->opts.username); + if (new->opts.username == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.host != NULL) { + new->opts.host = strdup(src->opts.host); + if (new->opts.host == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.bindaddr != NULL) { + new->opts.bindaddr = strdup(src->opts.bindaddr); + if (new->opts.bindaddr == NULL) { + ssh_free(new); + return -1; + } + } + + /* Remove the default identities */ + for (id = ssh_list_pop_head(char *, new->opts.identity_non_exp); + id != NULL; + id = ssh_list_pop_head(char *, new->opts.identity_non_exp)) { + SAFE_FREE(id); + } + /* Copy the new identities from the source list */ + list = new->opts.identity_non_exp; + it = ssh_list_get_iterator(src->opts.identity_non_exp); + for (i = 0; i < 2; i++) { + while (it) { + int rc; + + id = strdup((char *)it->data); + if (id == NULL) { + ssh_free(new); + return -1; + } + + rc = ssh_list_append(list, id); + if (rc < 0) { + free(id); + ssh_free(new); + return -1; + } + it = it->next; + } + + /* copy the identity list if there is any already */ + list = new->opts.identity; + it = ssh_list_get_iterator(src->opts.identity); + } + + list = new->opts.certificate_non_exp; + it = ssh_list_get_iterator(src->opts.certificate_non_exp); + for (i = 0; i < 2; i++) { + while (it) { + int rc; + + id = strdup((char *)it->data); + if (id == NULL) { + ssh_free(new); + return -1; + } + + rc = ssh_list_append(list, id); + if (rc < 0) { + free(id); + ssh_free(new); + return -1; + } + it = it->next; + } + + /* copy the certificate list if there is any already */ + list = new->opts.certificate; + it = ssh_list_get_iterator(src->opts.certificate); + } + + if (src->opts.sshdir != NULL) { + new->opts.sshdir = strdup(src->opts.sshdir); + if (new->opts.sshdir == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.knownhosts != NULL) { + new->opts.knownhosts = strdup(src->opts.knownhosts); + if (new->opts.knownhosts == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.global_knownhosts != NULL) { + new->opts.global_knownhosts = strdup(src->opts.global_knownhosts); + if (new->opts.global_knownhosts == NULL) { + ssh_free(new); + return -1; + } + } + + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (src->opts.wanted_methods[i] != NULL) { + new->opts.wanted_methods[i] = strdup(src->opts.wanted_methods[i]); + if (new->opts.wanted_methods[i] == NULL) { + ssh_free(new); + return -1; + } + } + } + + if (src->opts.ProxyCommand != NULL) { + new->opts.ProxyCommand = strdup(src->opts.ProxyCommand); + if (new->opts.ProxyCommand == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.pubkey_accepted_types != NULL) { + new->opts.pubkey_accepted_types = strdup(src->opts.pubkey_accepted_types); + if (new->opts.pubkey_accepted_types == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.gss_server_identity != NULL) { + new->opts.gss_server_identity = strdup(src->opts.gss_server_identity); + if (new->opts.gss_server_identity == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.gss_client_identity != NULL) { + new->opts.gss_client_identity = strdup(src->opts.gss_client_identity); + if (new->opts.gss_client_identity == NULL) { + ssh_free(new); + return -1; + } + } + + if (src->opts.control_path != NULL) { + new->opts.control_path = strdup(src->opts.control_path); + if (new->opts.control_path == NULL) { + ssh_free(new); + return -1; + } + } + + memcpy(new->opts.options_seen, src->opts.options_seen, + sizeof(new->opts.options_seen)); + + new->opts.fd = src->opts.fd; + new->opts.port = src->opts.port; + new->opts.timeout = src->opts.timeout; + new->opts.timeout_usec = src->opts.timeout_usec; + new->opts.compressionlevel = src->opts.compressionlevel; + new->opts.StrictHostKeyChecking = src->opts.StrictHostKeyChecking; + new->opts.gss_delegate_creds = src->opts.gss_delegate_creds; + new->opts.flags = src->opts.flags; + new->opts.nodelay = src->opts.nodelay; + new->opts.config_processed = src->opts.config_processed; + new->opts.control_master = src->opts.control_master; + new->opts.address_family = src->opts.address_family; + new->common.log_verbosity = src->common.log_verbosity; + new->common.callbacks = src->common.callbacks; + + SSH_PKI_CTX_FREE(new->pki_context); + if (src->pki_context != NULL) { + new->pki_context = ssh_pki_ctx_dup(src->pki_context); + if (new->pki_context == NULL) { + ssh_free(new); + return -1; + } + } + + *dest = new; + + return 0; +} + +int ssh_options_set_algo(ssh_session session, + enum ssh_kex_types_e algo, + const char *list, + char **place) +{ + /* When the list start with +,-,^ the filtration of unknown algorithms + * gets handled inside the helper functions, otherwise the list is taken + * as it is. */ + char *p = (char *)list; + + if (algo < SSH_COMP_C_S) { + if (list[0] == '+') { + p = ssh_add_to_default_algos(algo, list+1); + } else if (list[0] == '-') { + p = ssh_remove_from_default_algos(algo, list+1); + } else if (list[0] == '^') { + p = ssh_prefix_default_algos(algo, list+1); + } + } + + if (p == list) { + if (ssh_fips_mode()) { + p = ssh_keep_fips_algos(algo, list); + } else { + p = ssh_keep_known_algos(algo, list); + } + } + + if (p == NULL) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Setting method: no allowed algorithm for method \"%s\" (%s)", + ssh_kex_get_description(algo), list); + return -1; + } + + SAFE_FREE(*place); + *place = p; + + return 0; +} + +/* + * Map a public ssh_options_e onto the internal config opcode whose parser + * case applies it via ssh_options_set(). Used to mark a value as "seen" when + * an application sets it explicitly, so later config-file processing does not + * override it (OpenSSH "first obtained value wins" semantics). + * + * Returns SOC_UNKNOWN for options that must NOT be protected: + * - accumulative options (IdentityFile/CertificateFile and friends), + * - SSH_OPTIONS_HOST, the Host/Match lookup key that config HostName + * intentionally overrides during alias resolution, + * - operational settings such as log verbosity, + * - options that have no ssh_config equivalent, + * - getter-only options, which ssh_options_set() never accepts. + */ +static enum ssh_config_opcode_e ssh_opt_type_to_opcode(enum ssh_options_e type) +{ + switch (type) { + case SSH_OPTIONS_PORT: + case SSH_OPTIONS_PORT_STR: + return SOC_PORT; + case SSH_OPTIONS_USER: + return SOC_USERNAME; + case SSH_OPTIONS_KNOWNHOSTS: + return SOC_KNOWNHOSTS; + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + return SOC_GLOBALKNOWNHOSTSFILE; + case SSH_OPTIONS_TIMEOUT: + return SOC_TIMEOUT; + case SSH_OPTIONS_CIPHERS_C_S: + case SSH_OPTIONS_CIPHERS_S_C: + return SOC_CIPHERS; + case SSH_OPTIONS_COMPRESSION: + case SSH_OPTIONS_COMPRESSION_C_S: + case SSH_OPTIONS_COMPRESSION_S_C: + return SOC_COMPRESSION; + case SSH_OPTIONS_PROXYCOMMAND: + return SOC_PROXYCOMMAND; + case SSH_OPTIONS_PROXYJUMP: + return SOC_PROXYJUMP; + case SSH_OPTIONS_BINDADDR: + return SOC_BINDADDRESS; + case SSH_OPTIONS_STRICTHOSTKEYCHECK: + return SOC_STRICTHOSTKEYCHECK; + case SSH_OPTIONS_KEY_EXCHANGE: + return SOC_KEXALGORITHMS; + case SSH_OPTIONS_HOSTKEYS: + return SOC_HOSTKEYALGORITHMS; + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + return SOC_PUBKEYACCEPTEDKEYTYPES; + case SSH_OPTIONS_HMAC_C_S: + case SSH_OPTIONS_HMAC_S_C: + return SOC_MACS; + case SSH_OPTIONS_GSSAPI_SERVER_IDENTITY: + return SOC_GSSAPISERVERIDENTITY; + case SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY: + return SOC_GSSAPICLIENTIDENTITY; + case SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS: + return SOC_GSSAPIDELEGATECREDENTIALS; + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE: + return SOC_GSSAPIKEYEXCHANGE; + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + return SOC_GSSAPIKEXALGORITHMS; + case SSH_OPTIONS_PASSWORD_AUTH: + return SOC_PASSWORDAUTHENTICATION; + case SSH_OPTIONS_PUBKEY_AUTH: + return SOC_PUBKEYAUTHENTICATION; + case SSH_OPTIONS_KBDINT_AUTH: + return SOC_KBDINTERACTIVEAUTHENTICATION; + case SSH_OPTIONS_GSSAPI_AUTH: + return SOC_GSSAPIAUTHENTICATION; + case SSH_OPTIONS_REKEY_DATA: + case SSH_OPTIONS_REKEY_TIME: + return SOC_REKEYLIMIT; + case SSH_OPTIONS_RSA_MIN_SIZE: + return SOC_REQUIRED_RSA_SIZE; + case SSH_OPTIONS_IDENTITY_AGENT: + return SOC_IDENTITYAGENT; + case SSH_OPTIONS_IDENTITIES_ONLY: + return SOC_IDENTITIESONLY; + case SSH_OPTIONS_CONTROL_MASTER: + return SOC_CONTROLMASTER; + case SSH_OPTIONS_CONTROL_PATH: + return SOC_CONTROLPATH; + case SSH_OPTIONS_ADDRESS_FAMILY: + return SOC_ADDRESSFAMILY; + /* + * Accumulative options append to a list instead of replacing a value, so + * the "first value wins" precedence between config and the application does + * not apply to them. + */ + case SSH_OPTIONS_IDENTITY: + case SSH_OPTIONS_ADD_IDENTITY: + case SSH_OPTIONS_CERTIFICATE: + case SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND: + /* + * SSH_OPTIONS_HOST carries the destination as given by the application, + * which is OpenSSH's "host" (the Host/Match lookup key), not its + * "hostname". Config HostName resolves that key to the real hostname and + * must keep doing so. HostName has its own "first value wins" precedence + * between config entries, enforced independently via seen[SOC_HOSTNAME] + * while parsing the configuration. + */ + case SSH_OPTIONS_HOST: + /* + * Operational settings that applications and frameworks routinely set on + * their own, independent of the connection configuration. OpenSSH's config + * parser notably does not let a previously-set value suppress LogLevel, so + * we follow it and leave log verbosity unprotected. + */ + case SSH_OPTIONS_LOG_VERBOSITY: + case SSH_OPTIONS_LOG_VERBOSITY_STR: + /* + * Options with no OpenSSH ssh_config equivalent (or that are never applied + * from a config file), so there is no config value that could override the + * application's choice. + */ + case SSH_OPTIONS_FD: + case SSH_OPTIONS_SSH_DIR: + case SSH_OPTIONS_SSH1: + case SSH_OPTIONS_SSH2: + case SSH_OPTIONS_TIMEOUT_USEC: + case SSH_OPTIONS_COMPRESSION_LEVEL: + case SSH_OPTIONS_NODELAY: + case SSH_OPTIONS_PROCESS_CONFIG: + case SSH_OPTIONS_PKI_CONTEXT: + /* + * Getter-only options: ssh_options_set() rejects them, so they never reach + * the marking step. Listed to keep the switch exhaustive. + */ + case SSH_OPTIONS_NEXT_IDENTITY: + return SOC_UNKNOWN; + } + + return SOC_UNKNOWN; +} + +/** + * @brief This function can set all possible ssh options. + * + * @param session An allocated SSH session structure. + * + * @param type The option type to set. This could be one of the + * following: + * + * - SSH_OPTIONS_HOST: + * The hostname or ip address to connect to. It can be also in + * the format of URI, containing also username, such as + * [username@]hostname. The IPv6 addresses can be enclosed + * within square braces, for example [::1]. The IPv4 address + * supports any format supported by OS. The hostname needs to be + * encoded to match RFC1035, so for IDN it needs to be encoded + * in punycode. + * (const char *). + * + * - SSH_OPTIONS_PORT: + * The port to connect to (unsigned int). + * + * - SSH_OPTIONS_PORT_STR: + * The port to connect to (const char *). + * + * - SSH_OPTIONS_FD: + * The file descriptor to use (socket_t).\n + * \n + * If you wish to open the socket yourself for a reason + * or another, set the file descriptor and take care of closing + * it (this is new behavior in libssh 0.10). + * Don't forget to set the hostname as the hostname is used + * as a key in the known_host mechanism. + * + * - SSH_OPTIONS_BINDADDR: + * The address to bind the client to (const char *). + * + * - SSH_OPTIONS_USER: + * The username for authentication (const char *).\n + * \n + * If the value is NULL, the username is set to the + * default username. + * + * - SSH_OPTIONS_SSH_DIR: + * Set the ssh directory (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default ssh directory.\n + * \n + * The ssh directory is used for files like known_hosts + * and identity (private and public key). It may start + * with ~ which will be replaced by the user home + * directory. + * + * - SSH_OPTIONS_KNOWNHOSTS: + * Set the known hosts file name (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default known hosts file, normally + * ~/.ssh/known_hosts.\n + * \n + * The known hosts file is used to certify remote hosts + * are genuine. It may include "%d" which will be + * replaced by the user home directory. + * + * - SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + * Set the global known hosts file name (const char *,format string).\n + * \n + * If the value is NULL, the directory is set to the + * default global known hosts file, normally + * /etc/ssh/ssh_known_hosts.\n + * \n + * The known hosts file is used to certify remote hosts + * are genuine. + * + * - SSH_OPTIONS_ADD_IDENTITY (or SSH_OPTIONS_IDENTITY): + * Add a new identity file (const char *, format string) to + * the identity list.\n + * \n + * By default id_rsa, id_ecdsa and id_ed25519 files are used.\n + * If libssh is built with FIDO2/U2F support, id_ecdsa_sk and\n + * id_ed25519_sk files are also used by default.\n + * \n + * The identity used to authenticate with public key will be + * prepended to the list. + * It may include "%s" which will be replaced by the + * user home directory. + * + * - SSH_OPTIONS_CERTIFICATE: + * Add a new certificate file (const char *, format string) to + * the certificate list.\n + * \n + * By default id_rsa-cert.pub, id_ecdsa-cert.pub and + * id_ed25519-cert.pub files are used, when the underlying + * private key is present.\n + * \n + * The certificate itself can not be used to authenticate to + * remote server so it needs to be paired with private key + * (aka identity file) provided with separate option, from agent + * or from PKCS#11 token. + * It may include "%s" which will be replaced by the + * user home directory. + * + * - SSH_OPTIONS_TIMEOUT: + * Set a timeout for the connection in seconds (long). + * + * - SSH_OPTIONS_TIMEOUT_USEC: + * Set a timeout for the connection in micro seconds + * (long). + * + * - SSH_OPTIONS_SSH1: + * Deprecated + * + * - SSH_OPTIONS_SSH2: + * Unused + * + * - SSH_OPTIONS_LOG_VERBOSITY: + * Set the session logging verbosity (int).\n + * \n + * The verbosity of the messages. Every log smaller or + * equal to verbosity will be shown. + * - SSH_LOG_NOLOG: No logging + * - SSH_LOG_WARNING: Only warnings + * - SSH_LOG_PROTOCOL: High level protocol information + * - SSH_LOG_PACKET: Lower level protocol information, packet level + * - SSH_LOG_FUNCTIONS: Every function path + * The default is SSH_LOG_NOLOG. + * + * - SSH_OPTIONS_LOG_VERBOSITY_STR: + * Set the session logging verbosity via a + * string that will be converted to a numerical + * value (e.g. "3") and interpreted according + * to the values of + * SSH_OPTIONS_LOG_VERBOSITY above (const + * char *). + * + * - SSH_OPTIONS_CIPHERS_C_S: + * Set the symmetric cipher client to server (const char *, + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. + * + * - SSH_OPTIONS_CIPHERS_S_C: + * Set the symmetric cipher server to client (const char *, + * comma-separated list). The list can be prepended by +,-,^ + * which can append, remove or move to the beginning + * (prioritizing) of the default list respectively. Giving an + * empty list after + and ^ will cause error. + * + * - SSH_OPTIONS_KEY_EXCHANGE: + * Set the key exchange method to be used (const char *, + * comma-separated list). ex: + * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1,diffie-hellman-group1-sha1" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. + * + * - SSH_OPTIONS_HMAC_C_S: + * Set the Message Authentication Code algorithm client to server + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. + * + * - SSH_OPTIONS_HMAC_S_C: + * Set the Message Authentication Code algorithm server to client + * (const char *, comma-separated list). The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. + * + * - SSH_OPTIONS_HOSTKEYS: + * Set the preferred server host key types (const char *, + * comma-separated list). ex: + * "ssh-rsa,ecdh-sha2-nistp256". The list can be + * prepended by +,-,^ which will append, remove or move to + * the beginning (prioritizing) of the default list + * respectively. Giving an empty list after + and ^ will + * cause error. + * + * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + * Set the preferred public key algorithms to be used for + * authentication (const char *, comma-separated list). ex: + * "ssh-rsa,rsa-sha2-256,ecdh-sha2-nistp256" + * The list can be prepended by +,-,^ which will append, + * remove or move to the beginning (prioritizing) of the + * default list respectively. Giving an empty list + * after + and ^ will cause error. + * + * - SSH_OPTIONS_COMPRESSION_C_S: + * Set the compression to use for client to server + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION_S_C: + * Set the compression to use for server to client + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION: + * Set the compression to use for both directions + * communication (const char *, "yes", "no" or a specific + * algorithm name if needed ("zlib","zlib@openssh.com","none"). + * + * - SSH_OPTIONS_COMPRESSION_LEVEL: + * Set the compression level to use for zlib functions. (int, + * value from 1 to 9, 9 being the most efficient but slower). + * + * - SSH_OPTIONS_STRICTHOSTKEYCHECK: + * Set the parameter StrictHostKeyChecking to avoid + * asking about a fingerprint (int, 0 = false). + * + * - SSH_OPTIONS_PROXYCOMMAND: + * Set the command to be executed in order to connect to + * server (const char *). + * + * - SSH_OPTIONS_PROXYJUMP: + * Set the comma separated jump hosts in order to connect to + * server (const char *). Set to "none" to disable. + * Example: + * "alice@127.0.0.1:5555,bob@127.0.0.2" + * + * If environment variable OPENSSH_PROXYJUMP is set to 1 then proxyjump will be + * handled by the OpenSSH binary. + * + * - SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND: + * Append the callbacks struct for a jump in order of + * SSH_OPTIONS_PROXYJUMP. Append as many times + * as the number of jumps (struct ssh_jump_callbacks_struct *). + * + * - SSH_OPTIONS_GSSAPI_SERVER_IDENTITY + * Set it to specify the GSSAPI server identity that libssh + * should expect when connecting to the server (const char *). + * + * - SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY + * Set it to specify the GSSAPI client identity that libssh + * should expect when connecting to the server (const char *). + * + * - SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS + * Set it to specify that GSSAPI should delegate credentials + * to the server (int, 0 = false). + * + * - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE + * Set to true to allow GSSAPI key exchange (bool). + * + * - SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS + * Set the GSSAPI key exchange method to be used (const char *, + * comma-separated list). ex: + * "gss-curve25519-sha256-,gss-nistp256-sha256-" + * These will prefix the default algorithms if + * SSH_OPTIONS_GSSAPI_KEY_EXCHANGE is true. + * + * - SSH_OPTIONS_PASSWORD_AUTH + * Set it if password authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * Currently without effect (ssh_userauth_auto_pubkey doesn't use + * password authentication). + * + * - SSH_OPTIONS_PUBKEY_AUTH + * Set it if pubkey authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * + * - SSH_OPTIONS_KBDINT_AUTH + * Set it if keyboard-interactive authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * Currently without effect (ssh_userauth_auto_pubkey doesn't use + * keyboard-interactive authentication). + * + * - SSH_OPTIONS_GSSAPI_AUTH + * Set it if gssapi authentication should be used + * in ssh_userauth_auto_pubkey(). (int, 0=false). + * Currently without effect (ssh_userauth_auto_pubkey doesn't use + * gssapi authentication). + * + * - SSH_OPTIONS_NODELAY + * Set it to disable Nagle's Algorithm (TCP_NODELAY) on the + * session socket. (int, 0=false) + * + * - SSH_OPTIONS_PROCESS_CONFIG + * Set it to false to disable automatic processing of per-user + * and system-wide OpenSSH configuration files. LibSSH + * automatically uses these configuration files unless + * you provide it with this option or with different file (bool). + * + * - SSH_OPTIONS_REKEY_DATA + * Set the data limit that can be transferred with a single + * key in bytes. RFC 4253 Section 9 recommends 1GB of data, while + * RFC 4344 provides more specific restrictions, that are applied + * automatically. When specified, the lower value will be used. + * (uint64_t, 0=default) + * + * - SSH_OPTIONS_REKEY_TIME + * Set the time limit for a session before initializing a rekey + * in seconds. RFC 4253 Section 9 recommends one hour. + * (uint32_t, 0=off) + * + * - SSH_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by the + * client for both authentication and hostkey verification. + * The values under 1024 bits are not accepted even with this + * configuration option as they are considered completely broken. + * Setting 0 will revert the value to defaults. + * Default is 3072 bits or 2048 bits in FIPS mode. + * (int) + * + * - SSH_OPTIONS_IDENTITY_AGENT + * Set the path to the SSH agent socket. If unset, the + * SSH_AUTH_SOCK environment is consulted. + * (const char *) + * + * - SSH_OPTIONS_IDENTITIES_ONLY + * Use only keys specified in the SSH config, even if agent + * offers more. + * (bool) + * + * - SSH_OPTIONS_CONTROL_MASTER + * Set the option to enable the sharing of multiple sessions over a + * single network connection using connection multiplexing (int). + * + * The possible options are among the following: + * - SSH_CONTROL_MASTER_AUTO: enable connection sharing if possible + * - SSH_CONTROL_MASTER_YES: enable connection sharing unconditionally + * - SSH_CONTROL_MASTER_ASK: ask for confirmation if connection sharing is to be enabled + * - SSH_CONTROL_MASTER_AUTOASK: enable connection sharing if possible, + * but ask for confirmation + * - SSH_CONTROL_MASTER_NO: disable connection sharing unconditionally + * + * The default is SSH_CONTROL_MASTER_NO. + * + * - SSH_OPTIONS_CONTROL_PATH + * Set the path to the control socket used for connection sharing. + * Set to "none" to disable connection sharing. + * (const char *) + * + * - SSH_OPTIONS_PKI_CONTEXT + * Attach a previously created generic PKI context to the + * session. This allows supplying per-session PKI + * configuration options for PKI operations. + * All fields from the user's context are copied to the session's + * own context. The user retains ownership of the original + * context and can free it after this call. + * (ssh_pki_ctx) + * + * - SSH_OPTIONS_ADDRESS_FAMILY + * Specify which address family to use when connecting. + * + * Possible options: + * - SSH_ADDRESS_FAMILY_ANY: use any address family + * - SSH_ADDRESS_FAMILY_INET: IPv4 only + * - SSH_ADDRESS_FAMILY_INET6: IPv6 only + * + * @param value The value to set. This is a generic pointer and the + * datatype which is used should be set according to the + * type set. + * + * @return 0 on success, < 0 on error. + * + * @warning When the option value to set is represented via a pointer + * (e.g const char * in case of strings, ssh_key in case of a + * libssh key), the value parameter should be that pointer. + * Do NOT pass a pointer to a pointer (const char **, ssh_key *) + * + * @warning When the option value to set is not a pointer (e.g int, + * unsigned int, bool, long), the value parameter should be + * a pointer to the location storing the value to set (int *, + * unsigned int *, bool *, long *) + * + * @warning If the value parameter has an invalid type (e.g if its not a + * pointer when it should have been a pointer, or if its a pointer + * to a pointer when it should have just been a pointer), then the + * behaviour is undefined. + */ +int ssh_options_set(ssh_session session, enum ssh_options_e type, + const void *value) +{ + const char *v = NULL; + char *p = NULL, *q = NULL; + long int i; + unsigned int u; + int rc; + char **wanted_methods = session->opts.wanted_methods; + struct ssh_jump_callbacks_struct *j = NULL; + enum ssh_config_opcode_e opcode; + + if (session == NULL) { + return -1; + } + + switch (type) { + case SSH_OPTIONS_HOST: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + char *username = NULL, *hostname = NULL; + rc = ssh_config_parse_uri(value, &username, &hostname, NULL, true); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + return -1; + } + if (username != NULL) { + SAFE_FREE(session->opts.username); + session->opts.username = username; + } + if (hostname != NULL) { + SAFE_FREE(session->opts.host); + session->opts.host = hostname; + } + } + break; + case SSH_OPTIONS_PORT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x <= 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.port = *x & 0xffffU; + } + break; + case SSH_OPTIONS_PORT_STR: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(v); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No port number was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); + if (i <= 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.port = i & 0xffffU; + } + break; + case SSH_OPTIONS_FD: + if (value == NULL) { + session->opts.fd = SSH_INVALID_SOCKET; + ssh_set_error_invalid(session); + return -1; + } else { + socket_t *x = (socket_t *) value; + if (*x < 0) { + session->opts.fd = SSH_INVALID_SOCKET; + ssh_set_error_invalid(session); + return -1; + } + + session->opts.fd = *x & 0xffff; + } + break; + case SSH_OPTIONS_BINDADDR: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + + q = strdup(v); + if (q == NULL) { + return -1; + } + SAFE_FREE(session->opts.bindaddr); + session->opts.bindaddr = q; + break; + case SSH_OPTIONS_USER: + v = value; + SAFE_FREE(session->opts.username); + if (v == NULL) { + q = ssh_get_local_username(); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.username = q; + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { /* username provided */ + session->opts.username = strdup(value); + if (session->opts.username == NULL) { + ssh_set_error_oom(session); + return -1; + } + rc = ssh_check_username_syntax(session->opts.username); + if (rc != SSH_OK) { + ssh_set_error_invalid(session); + return -1; + } + } + break; + case SSH_OPTIONS_SSH_DIR: + v = value; + SAFE_FREE(session->opts.sshdir); + if (v == NULL) { + session->opts.sshdir = ssh_path_expand_tilde("~/.ssh"); + if (session->opts.sshdir == NULL) { + return -1; + } + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.sshdir = ssh_path_expand_tilde(v); + if (session->opts.sshdir == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_IDENTITY: + case SSH_OPTIONS_ADD_IDENTITY: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + q = strdup(v); + if (q == NULL) { + return -1; + } + if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) { + rc = ssh_list_append(session->opts.identity_non_exp, q); + } else { + rc = ssh_list_prepend(session->opts.identity_non_exp, q); + } + if (rc < 0) { + free(q); + return -1; + } + break; + case SSH_OPTIONS_CERTIFICATE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + q = strdup(v); + if (q == NULL) { + return -1; + } + rc = ssh_list_append(session->opts.certificate_non_exp, q); + if (rc < 0) { + free(q); + return -1; + } + break; + case SSH_OPTIONS_KNOWNHOSTS: + v = value; + SAFE_FREE(session->opts.knownhosts); + if (v == NULL) { + /* The default value will be set by the ssh_options_apply() */ + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.knownhosts = strdup(v); + if (session->opts.knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + } + break; + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + v = value; + SAFE_FREE(session->opts.global_knownhosts); + if (v == NULL) { + session->opts.global_knownhosts = + strdup(GLOBAL_CONF_DIR "/ssh_known_hosts"); + if (session->opts.global_knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.global_knownhosts = strdup(v); + if (session->opts.global_knownhosts == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS; + } + break; + case SSH_OPTIONS_TIMEOUT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + long *x = (long *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.timeout = *x & 0xffffffffU; + } + break; + case SSH_OPTIONS_TIMEOUT_USEC: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + long *x = (long *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->opts.timeout_usec = *x & 0xffffffffU; + } + break; + case SSH_OPTIONS_SSH1: + break; + case SSH_OPTIONS_SSH2: + break; + case SSH_OPTIONS_LOG_VERBOSITY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->common.log_verbosity = *x & 0xffffU; + ssh_set_log_level(*x & 0xffffU); + } + break; + case SSH_OPTIONS_LOG_VERBOSITY_STR: + v = value; + if (v == NULL || v[0] == '\0') { + session->common.log_verbosity = 0; + ssh_set_error_invalid(session); + return -1; + } else { + q = strdup(v); + if (q == NULL) { + ssh_set_error_oom(session); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); + if (i < 0) { + ssh_set_error_invalid(session); + return -1; + } + + session->common.log_verbosity = i & 0xffffU; + ssh_set_log_level(i & 0xffffU); + } + break; + case SSH_OPTIONS_CIPHERS_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_CIPHERS_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_KEY_EXCHANGE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_HOSTKEYS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + v, + &session->opts.pubkey_accepted_types); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_HMAC_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_HMAC_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_options_set_algo(session, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_COMPRESSION_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com"; + } + rc = ssh_options_set_algo(session, + SSH_COMP_C_S, + tmp, + &wanted_methods[SSH_COMP_C_S]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_COMPRESSION_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + const char *tmp = v; + if (strcasecmp(value, "yes") == 0){ + tmp = "zlib@openssh.com,none"; + } else if (strcasecmp(value, "no") == 0){ + tmp = "none,zlib@openssh.com"; + } + + rc = ssh_options_set_algo(session, + SSH_COMP_S_C, + tmp, + &wanted_methods[SSH_COMP_S_C]); + if (rc < 0) + return -1; + } + break; + case SSH_OPTIONS_COMPRESSION: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } + if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_C_S, v) < 0) + return -1; + if(ssh_options_set(session,SSH_OPTIONS_COMPRESSION_S_C, v) < 0) + return -1; + break; + case SSH_OPTIONS_COMPRESSION_LEVEL: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x < 1 || *x > 9) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.compressionlevel = *x & 0xff; + } + break; + case SSH_OPTIONS_STRICTHOSTKEYCHECK: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + + session->opts.StrictHostKeyChecking = (*x & 0xff) > 0 ? 1 : 0; + } + break; + case SSH_OPTIONS_PROXYCOMMAND: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.ProxyCommand); + /* Setting the command to 'none' disables this option. */ + rc = strcasecmp(v, "none"); + if (rc != 0) { + q = strdup(v); + if (q == NULL) { + return -1; + } + session->opts.ProxyCommand = q; + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_PROXYCOMMAND; + } + } + break; + case SSH_OPTIONS_PROXYJUMP: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_config_parse_proxy_jump(session, v, true); + if (rc != SSH_OK) { + return SSH_ERROR; + } + } + break; + case SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND: + j = (struct ssh_jump_callbacks_struct *)value; + if (j == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + rc = ssh_list_prepend(session->opts.proxy_jumps_user_cb, j); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + } + break; + case SSH_OPTIONS_GSSAPI_SERVER_IDENTITY: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.gss_server_identity); + session->opts.gss_server_identity = strdup(v); + if (session->opts.gss_server_identity == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.gss_client_identity); + session->opts.gss_client_identity = strdup(v); + if (session->opts.gss_client_identity == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int x = *(int *)value; + + session->opts.gss_delegate_creds = (x & 0xff); + } + break; +#ifdef WITH_GSSAPI + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.gssapi_key_exchange = *x; + } + break; + case SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + /* Check if algorithms are supported */ + char *ret = + ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, v); + if (ret == NULL) { + ssh_set_error(session, + SSH_FATAL, + "GSSAPI key exchange algorithms not " + "supported or invalid"); + return -1; + } + SAFE_FREE(session->opts.gssapi_key_exchange_algs); + session->opts.gssapi_key_exchange_algs = ret; + } + break; +#endif + case SSH_OPTIONS_PASSWORD_AUTH: + case SSH_OPTIONS_PUBKEY_AUTH: + case SSH_OPTIONS_KBDINT_AUTH: + case SSH_OPTIONS_GSSAPI_AUTH: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int x = *(int *)value; + u = type == SSH_OPTIONS_PASSWORD_AUTH ? + SSH_OPT_FLAG_PASSWORD_AUTH: + type == SSH_OPTIONS_PUBKEY_AUTH ? + SSH_OPT_FLAG_PUBKEY_AUTH: + type == SSH_OPTIONS_KBDINT_AUTH ? + SSH_OPT_FLAG_KBDINT_AUTH: + SSH_OPT_FLAG_GSSAPI_AUTH; + if (x != 0){ + session->opts.flags |= u; + } else { + session->opts.flags &= ~u; + } + } + break; + case SSH_OPTIONS_NODELAY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + session->opts.nodelay = (*x & 0xff) > 0 ? 1 : 0; + } + break; + case SSH_OPTIONS_PROCESS_CONFIG: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.config_processed = !(*x); + } + break; + case SSH_OPTIONS_REKEY_DATA: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + uint64_t *x = (uint64_t *)value; + session->opts.rekey_data = *x; + } + break; + case SSH_OPTIONS_REKEY_TIME: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + uint32_t *x = (uint32_t *)value; + if ((*x * 1000) < *x) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "The provided value (%" PRIu32 ") for rekey" + " time is too large", *x); + return -1; + } + session->opts.rekey_time = (*x) * 1000; + } + break; + case SSH_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + + if (*x < 0) { + ssh_set_error_invalid(session); + return -1; + } + + /* (*x == 0) is allowed as it is used to revert to default */ + + if (*x > 0 && *x < RSA_MIN_KEY_SIZE) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "The provided value (%d) for minimal RSA key " + "size is too small. Use at least %d bits.", + *x, + RSA_MIN_KEY_SIZE); + return -1; + } + session->opts.rsa_min_size = *x; + } + break; + case SSH_OPTIONS_IDENTITY_AGENT: + v = value; + SAFE_FREE(session->opts.agent_socket); + if (v == NULL) { + /* The default value will be set by the ssh_options_apply() */ + } else if (v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + session->opts.agent_socket = ssh_path_expand_tilde(v); + if (session->opts.agent_socket == NULL) { + ssh_set_error_oom(session); + return -1; + } + } + break; + case SSH_OPTIONS_IDENTITIES_ONLY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + bool *x = (bool *)value; + session->opts.identities_only = *x; + } + break; + case SSH_OPTIONS_CONTROL_MASTER: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *) value; + if (*x < SSH_CONTROL_MASTER_NO || *x > SSH_CONTROL_MASTER_AUTOASK) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.control_master = *x; + } + break; + case SSH_OPTIONS_CONTROL_PATH: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(session); + return -1; + } else { + SAFE_FREE(session->opts.control_path); + rc = strcasecmp(v, "none"); + if (rc != 0) { + session->opts.control_path = ssh_path_expand_tilde(v); + if (session->opts.control_path == NULL) { + ssh_set_error_oom(session); + return -1; + } + session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_CONTROL_PATH; + } + } + break; + case SSH_OPTIONS_PKI_CONTEXT: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + SSH_PKI_CTX_FREE(session->pki_context); + + session->pki_context = ssh_pki_ctx_dup((const ssh_pki_ctx)value); + if (session->pki_context == NULL) { + ssh_set_error_oom(session); + return -1; + } + break; + case SSH_OPTIONS_ADDRESS_FAMILY: + if (value == NULL) { + ssh_set_error_invalid(session); + return -1; + } else { + int *x = (int *)value; + if (*x < SSH_ADDRESS_FAMILY_ANY || + *x > SSH_ADDRESS_FAMILY_INET6) { + ssh_set_error_invalid(session); + return -1; + } + session->opts.address_family = *x; + } + break; + default: + ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return -1; + break; + } + + /* + * The option was set successfully. Mark config-backed options as + * explicitly set so that later processing of OpenSSH configuration files + * keeps the application's value (issue #365). Options that map to + * SOC_UNKNOWN are intentionally left unmarked. + */ + opcode = ssh_opt_type_to_opcode(type); + if (opcode != SOC_UNKNOWN) { + session->opts.options_seen[opcode] = 1; + } + + return 0; +} + +/** + * @brief This function returns the current algorithms used for algorithm + * negotiation. It is either libssh default, option manually set or option + * read from configuration file. + * + * This function will return NULL on error + * + * @param session An allocated SSH session structure. + * @param algo One of the ssh_kex_types_e values. + */ +char *ssh_options_get_algo(ssh_session session, + enum ssh_kex_types_e algo) +{ + char *value = NULL; + + /* Check session and algo values are valid */ + + if (session == NULL) { + return NULL; + } + + if (algo >= SSH_LANG_C_S) { + ssh_set_error_invalid(session); + return NULL; + } + + /* Get the option the user has set, if there is one */ + value = session->opts.wanted_methods[algo]; + if (value == NULL) { + /* The user has not set a value, return the appropriate default */ + if (ssh_fips_mode()) + value = (char *)ssh_kex_get_fips_methods(algo); + else + value = (char *)ssh_kex_get_default_methods(algo); + } + + return value; +} + + +/** + * @brief This function can get ssh the ssh port. It must only be used on + * a valid ssh session. This function is useful when the session + * options have been automatically inferred from the environment + * or configuration files and one + * + * @param session An allocated SSH session structure. + * + * @param port_target An unsigned integer into which the + * port will be set from the ssh session. + * + * @return 0 on success, < 0 on error. + * + */ +int ssh_options_get_port(ssh_session session, unsigned int* port_target) { + if (session == NULL) { + return -1; + } + + if (session->opts.port == 0) { + *port_target = 22; + return 0; + } + + *port_target = session->opts.port; + + return 0; +} + +/** + * @brief This function can get ssh options, it does not support all options provided for + * ssh options set, but mostly those which a user-space program may care about having + * trusted the ssh driver to infer these values from underlying configuration files. + * It operates only on those SSH_OPTIONS_* which return char*. If you wish to receive + * the port then please use ssh_options_get_port() which returns an unsigned int. + * + * @param session An allocated SSH session structure. + * + * @param type The option type to get. This could be one of the + * following: + * + * - SSH_OPTIONS_HOST: + * The hostname or ip address to connect to (const char *). + * + * - SSH_OPTIONS_USER: + * The username for authentication (const char *).\n + * \n when not explicitly set this will be inferred from the + * ~/.ssh/config file. + * + * - SSH_OPTIONS_IDENTITY: + * Get the first identity file name (const char *).\n + * \n + * By default `id_rsa`, `id_ecdsa`, `id_ed25519`, `id_ecdsa_sk` + * and `id_ed25519_sk` (when SK support is built in) files are + * used. + * + * - SSH_OPTIONS_NEXT_IDENTITY: + * Get the next identity file name (const char *).\n + * \n + * Repeat calls to get all key paths. SSH_EOF is returned when + * the end of list is reached. Another call will start another + * iteration over the same list. + * + * - SSH_OPTIONS_PROXYCOMMAND: + * Get the proxycommand necessary to log into the + * remote host. When not explicitly set, it will be read + * from the ~/.ssh/config file. + * + * - SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + * Get the path to the global known_hosts file being used. + * + * - SSH_OPTIONS_KNOWNHOSTS: + * Get the path to the known_hosts file being used. + * + * - SSH_OPTIONS_CONTROL_PATH: + * Get the path to the control socket being used for connection + * multiplexing. + * + * - SSH_OPTIONS_KEY_EXCHANGE: + * Get the key exchange methods to be used. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HOSTKEYS: + * Get the preferred server host key types. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + * Get the preferred public key algorithms to be used for + * authentication. + * + * - SSH_OPTIONS_CIPHERS_C_S: + * Get the symmetric cipher client to server. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_CIPHERS_S_C: + * Get the symmetric cipher server to client. If the option has + * not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_C_S: + * Get the Message Authentication Code algorithm client to server + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_HMAC_S_C: + * Get the Message Authentication Code algorithm server to client + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_C_S: + * Get the compression to use for client to server communication + * If the option has not been set, returns the defaults. + * + * - SSH_OPTIONS_COMPRESSION_S_C: + * Get the compression to use for server to client communication + * If the option has not been set, returns the defaults. + * + * @param value The value to get into. As a char**, space will be + * allocated by the function for the value, it is + * your responsibility to free the memory using + * ssh_string_free_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int ssh_options_get(ssh_session session, enum ssh_options_e type, char** value) +{ + char *src = NULL; + + if (session == NULL) { + return SSH_ERROR; + } + + if (value == NULL) { + ssh_set_error_invalid(session); + return SSH_ERROR; + } + + switch(type) + { + case SSH_OPTIONS_HOST: + src = session->opts.host; + break; + + case SSH_OPTIONS_USER: + src = session->opts.username; + break; + + case SSH_OPTIONS_IDENTITY: { + struct ssh_iterator *it = NULL; + it = ssh_list_get_iterator(session->opts.identity); + if (it == NULL) { + it = ssh_list_get_iterator(session->opts.identity_non_exp); + } + if (it == NULL) { + return SSH_ERROR; + } + src = ssh_iterator_value(char *, it); + break; + } + + case SSH_OPTIONS_NEXT_IDENTITY: { + if (session->opts.identity_it != NULL) { + /* Move to the next item */ + session->opts.identity_it = session->opts.identity_it->next; + if (session->opts.identity_it == NULL) { + *value = NULL; + return SSH_EOF; + } + } else { + /* Get iterator from opts */ + struct ssh_iterator *it = NULL; + it = ssh_list_get_iterator(session->opts.identity); + if (it == NULL) { + it = ssh_list_get_iterator(session->opts.identity_non_exp); + } + if (it == NULL) { + return SSH_ERROR; + } + session->opts.identity_it = it; + } + src = ssh_iterator_value(char *, session->opts.identity_it); + break; + } + + case SSH_OPTIONS_PROXYCOMMAND: + src = session->opts.ProxyCommand; + break; + + case SSH_OPTIONS_KNOWNHOSTS: + src = session->opts.knownhosts; + break; + + case SSH_OPTIONS_GLOBAL_KNOWNHOSTS: + src = session->opts.global_knownhosts; + break; + case SSH_OPTIONS_CONTROL_PATH: + src = session->opts.control_path; + break; + + case SSH_OPTIONS_CIPHERS_C_S: + src = ssh_options_get_algo(session, SSH_CRYPT_C_S); + break; + + case SSH_OPTIONS_CIPHERS_S_C: + src = ssh_options_get_algo(session, SSH_CRYPT_S_C); + break; + + case SSH_OPTIONS_KEY_EXCHANGE: + src = ssh_options_get_algo(session, SSH_KEX); + break; + + case SSH_OPTIONS_HOSTKEYS: + src = ssh_options_get_algo(session, SSH_HOSTKEYS); + break; + + case SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES: + src = session->opts.pubkey_accepted_types; + break; + + case SSH_OPTIONS_HMAC_C_S: + src = ssh_options_get_algo(session, SSH_MAC_C_S); + break; + + case SSH_OPTIONS_HMAC_S_C: + src = ssh_options_get_algo(session, SSH_MAC_S_C); + break; + + case SSH_OPTIONS_COMPRESSION_C_S: + src = ssh_options_get_algo(session, SSH_COMP_C_S); + break; + + case SSH_OPTIONS_COMPRESSION_S_C: + src = ssh_options_get_algo(session, SSH_COMP_S_C); + break; + + default: + ssh_set_error(session, SSH_REQUEST_DENIED, "Unknown ssh option %d", type); + return SSH_ERROR; + break; + } + if (src == NULL) { + return SSH_ERROR; + } + *value = strdup(src); + if (*value == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Parse command line arguments. + * + * This is a helper for your application to generate the appropriate + * options from the command line arguments.\n + * The argv array and argc value are changed so that the parsed + * arguments won't appear anymore in them.\n + * The single arguments (without switches) are not parsed. thus, + * myssh -l user localhost\n + * The command won't set the hostname value of options to localhost. + * + * @param session The session to configure. + * + * @param argcptr The pointer to the argument count. + * + * @param argv The arguments list pointer. + * + * @returns 0 on success, < 0 on error. + * + * @see ssh_session_new() + */ +int ssh_options_getopt(ssh_session session, int *argcptr, char **argv) +{ +#ifdef _MSC_VER + (void)session; + (void)argcptr; + (void)argv; + /* Not supported with a Microsoft compiler */ + return -1; +#else + char *user = NULL; + char *cipher = NULL; + char *identity = NULL; + char *port = NULL; + char **save = NULL; + char **tmp = NULL; + size_t i = 0; + int argc = *argcptr; + int debuglevel = 0; + int compress = 0; + int cont = 1; + size_t current = 0; + int opt_rc = 0; + int saveoptind = optind; /* need to save 'em */ + int saveopterr = opterr; + int opt; + + /* Nothing to do here */ + if (argc <= 1) { + return SSH_OK; + } + + opterr = 0; /* shut up getopt */ + while ((opt = getopt(argc, argv, "c:i:o:Cl:p:vb:r12")) != -1) { + switch(opt) { + case 'l': + user = optarg; + break; + case 'p': + port = optarg; + break; + case 'v': + debuglevel++; + ssh_set_log_level(debuglevel); + break; + case 'r': + break; + case 'c': + cipher = optarg; + break; + case 'i': + identity = optarg; + break; + case 'C': + compress++; + break; + case 'o': + opt_rc = ssh_config_parse_line_cli(session, optarg); + break; + case '2': + break; + case '1': + break; + default: + { + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current] = argv[optind-1]; + current++; + /* We can not use optarg here as getopt does not set it for + * unknown options. We need to manually extract following + * option and skip it manually from further processing */ + if (optind < argc && argv[optind][0] != '-') { + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current++] = argv[optind]; + optind++; + } + } + } /* switch */ + if (opt_rc == SSH_ERROR) { + break; + } + } /* while */ + opterr = saveopterr; + tmp = realloc(save, (current + (argc - optind)) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + while (optind < argc) { + tmp = realloc(save, (current + 1) * sizeof(char*)); + if (tmp == NULL) { + SAFE_FREE(save); + ssh_set_error_oom(session); + return -1; + } + save = tmp; + save[current] = argv[optind]; + current++; + optind++; + } + + optind = saveoptind; + + if (opt_rc == SSH_ERROR) { + SAFE_FREE(save); + return SSH_ERROR; + } + + if(!cont) { + SAFE_FREE(save); + return -1; + } + + /* first recopy the save vector into the original's */ + for (i = 0; i < current; i++) { + /* don't erase argv[0] */ + argv[ i + 1] = save[i]; + } + argv[current + 1] = NULL; + *argcptr = current + 1; + SAFE_FREE(save); + + /* set a new option struct */ + if (compress) { + if (ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "yes") < 0) { + cont = 0; + } + } + + if (cont && cipher) { + if (ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher) < 0) { + cont = 0; + } + if (cont && ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher) < 0) { + cont = 0; + } + } + + if (cont && user) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + cont = 0; + } + } + + if (cont && identity) { + if (ssh_options_set(session, SSH_OPTIONS_IDENTITY, identity) < 0) { + cont = 0; + } + } + + if (port != NULL) { + ssh_options_set(session, SSH_OPTIONS_PORT_STR, port); + } + + if (!cont) { + return SSH_ERROR; + } + + return SSH_OK; +#endif +} + +/** + * @brief Parse the ssh config file. + * + * This should be the last call of all options. Options that were already set + * explicitly via ssh_options_set() take precedence and are not overwritten by + * the configuration file, matching OpenSSH's "first obtained value wins" + * behavior. Accumulative options such as IdentityFile and CertificateFile, as + * well as host-alias resolution via HostName, are still applied from the + * configuration. It requires that the host name is already set with + * ssh_options_set(SSH_OPTIONS_HOST). + * + * @param session SSH session handle + * + * @param filename The options file to use, if NULL the default + * ~/.ssh/config and /etc/ssh/ssh_config will be used. + * If complied with support for hermetic-usr, + * /usr/etc/ssh/ssh_config will be used last. + * + * @return 0 on success, < 0 on error. + * + * @see ssh_options_set() + */ +int ssh_options_parse_config(ssh_session session, const char *filename) +{ + char *expanded_filename = NULL; + int r; + FILE *fp = NULL; + + if (session == NULL) { + return -1; + } + if (session->opts.host == NULL) { + ssh_set_error_invalid(session); + return -1; + } + + if (session->opts.sshdir == NULL) { + r = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + if (r < 0) { + ssh_set_error_oom(session); + return -1; + } + } + + /* set default filename */ + if (filename == NULL) { + expanded_filename = ssh_path_expand_escape(session, "%d/.ssh/config"); + } else { + expanded_filename = ssh_path_expand_escape(session, filename); + } + if (expanded_filename == NULL) { + return -1; + } + + r = ssh_config_parse_file(session, expanded_filename); + if (r < 0) { + goto out; + } + if (filename == NULL) { + fp = ssh_strict_fopen(GLOBAL_CLIENT_CONFIG, SSH_MAX_CONFIG_FILE_SIZE); + if (fp != NULL) { + filename = GLOBAL_CLIENT_CONFIG; +#ifdef USR_GLOBAL_CLIENT_CONFIG + } else { + fp = ssh_strict_fopen(USR_GLOBAL_CLIENT_CONFIG, + SSH_MAX_CONFIG_FILE_SIZE); + if (fp != NULL) { + filename = USR_GLOBAL_CLIENT_CONFIG; + } +#endif + } + + if (fp) { + SSH_LOG(SSH_LOG_PACKET, + "Reading configuration data from %s", + filename); + r = ssh_config_parse(session, fp, true); + fclose(fp); + } + } + + /* Do not process the default configuration as part of connection again */ + session->opts.config_processed = true; +out: + free(expanded_filename); + return r; +} + +int ssh_options_apply(ssh_session session) +{ + char *tmp = NULL; + int rc; + + if (session->opts.sshdir == NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + if (rc < 0) { + return -1; + } + } + + if (session->opts.username == NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL); + if (rc < 0) { + return -1; + } + } + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS) == 0) { + if (session->opts.knownhosts == NULL) { + tmp = ssh_path_expand_escape(session, "%d/.ssh/known_hosts"); + } else { + tmp = ssh_path_expand_escape(session, session->opts.knownhosts); + } + if (tmp == NULL) { + return -1; + } + free(session->opts.knownhosts); + session->opts.knownhosts = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_KNOWNHOSTS; + } + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS) == 0) { + if (session->opts.global_knownhosts == NULL) { + tmp = strdup(GLOBAL_CONF_DIR "/ssh_known_hosts"); + } else { + tmp = ssh_path_expand_escape(session, + session->opts.global_knownhosts); + } + if (tmp == NULL) { + return -1; + } + free(session->opts.global_knownhosts); + session->opts.global_knownhosts = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS; + } + + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND) == 0) { + if (session->opts.ProxyCommand != NULL) { + char *p = NULL; + size_t plen = strlen(session->opts.ProxyCommand) + + 5 /* strlen("exec ") */; + + if (strncmp(session->opts.ProxyCommand, "exec ", 5) != 0) { + p = malloc(plen + 1 /* \0 */); + if (p == NULL) { + return -1; + } + + rc = snprintf(p, plen + 1, "exec %s", session->opts.ProxyCommand); + if ((size_t)rc != plen) { + free(p); + return -1; + } + tmp = ssh_path_expand_escape(session, p); + free(p); + } else { + tmp = ssh_path_expand_escape(session, + session->opts.ProxyCommand); + } + + if (tmp == NULL) { + return -1; + } + free(session->opts.ProxyCommand); + session->opts.ProxyCommand = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_PROXYCOMMAND; + } + } + + if ((session->opts.exp_flags & SSH_OPT_EXP_FLAG_CONTROL_PATH) == 0) { + if (session->opts.control_path != NULL) { + tmp = ssh_path_expand_escape(session, session->opts.control_path); + if (tmp == NULL) { + return -1; + } + free(session->opts.control_path); + session->opts.control_path = tmp; + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_CONTROL_PATH; + } + } + + for (tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp); + tmp != NULL; + tmp = ssh_list_pop_head(char *, session->opts.identity_non_exp)) { + char *id = tmp; + if (strncmp(id, "pkcs11:", 6) != 0) { + /* PKCS#11 URIs are using percent-encoding so we can not mix + * it with ssh expansion of ssh escape characters. + */ + tmp = ssh_path_expand_escape(session, id); + free(id); + if (tmp == NULL) { + return -1; + } + } + + /* use append to keep the order at first call and use prepend + * to put anything that comes on the nth calls to the beginning */ + if (session->opts.exp_flags & SSH_OPT_EXP_FLAG_IDENTITY) { + rc = ssh_list_prepend(session->opts.identity, tmp); + } else { + rc = ssh_list_append(session->opts.identity, tmp); + } + if (rc != SSH_OK) { + free(tmp); + return -1; + } + } + session->opts.exp_flags |= SSH_OPT_EXP_FLAG_IDENTITY; + + for (tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + tmp != NULL; + tmp = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + char *id = tmp; + + tmp = ssh_path_expand_escape(session, id); + free(id); + if (tmp == NULL) { + return -1; + } + + rc = ssh_list_append(session->opts.certificate, tmp); + if (rc != SSH_OK) { + free(tmp); + return -1; + } + } + +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + rc = ssh_gssapi_check_client_config(session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Disabled GSSAPI key exchange"); + session->opts.gssapi_key_exchange = false; + } + } +#endif + + return 0; +} + +/** @} */ + +#ifdef WITH_SERVER +static bool ssh_bind_key_size_allowed(ssh_bind sshbind, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = sshbind->rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + +/** + * @addtogroup libssh_server + * @{ + */ +static int +ssh_bind_set_key(ssh_bind sshbind, char **key_loc, const void *value) +{ + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(*key_loc); + *key_loc = strdup(value); + if (*key_loc == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + return 0; +} + +static int ssh_bind_set_algo(ssh_bind sshbind, + enum ssh_kex_types_e algo, + const char *list, + char **place) +{ + /* sshbind is needed only for ssh_set_error which takes void* + * the typecast is only to satisfy function parameter type */ + return ssh_options_set_algo((ssh_session)sshbind, algo, list, place); +} + +/** + * @brief Set options for an SSH server bind. + * + * @param sshbind The ssh server bind to configure. + * + * @param type The option type to set. This should be one of the + * following: + * + * - SSH_BIND_OPTIONS_HOSTKEY: + * Set the path to an ssh host key, regardless + * of type. Only one key from per key type + * (RSA, ED25519 and ECDSA) is allowed in an ssh_bind + * at a time, and later calls to this function + * with this option for the same key type will + * override prior calls (const char *). + * + * - SSH_BIND_OPTIONS_BINDADDR: + * Set the IP address to bind (const char *). + * + * - SSH_BIND_OPTIONS_BINDPORT: + * Set the port to bind (unsigned int). + * + * - SSH_BIND_OPTIONS_BINDPORT_STR: + * Set the port to bind (const char *). + * + * - SSH_BIND_OPTIONS_LOG_VERBOSITY: + * Set the session logging verbosity (int). + * The logging verbosity should have one of the + * following values, which are listed in order + * of increasing verbosity. Every log message + * with verbosity less than or equal to the + * logging verbosity will be shown. + * - SSH_LOG_NOLOG: No logging + * - SSH_LOG_WARNING: Only warnings + * - SSH_LOG_PROTOCOL: High level protocol information + * - SSH_LOG_PACKET: Lower level protocol information, + * packet level + * - SSH_LOG_FUNCTIONS: Every function path + * The default is SSH_LOG_NOLOG. + * + * - SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: + * Set the session logging verbosity via a + * string that will be converted to a numerical + * value (e.g. "3") and interpreted according + * to the values of + * SSH_BIND_OPTIONS_LOG_VERBOSITY above + * (const char *). + * + * - SSH_BIND_OPTIONS_RSAKEY: + * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY + * (const char *). + * + * - SSH_BIND_OPTIONS_ECDSAKEY: + * Deprecated alias to SSH_BIND_OPTIONS_HOSTKEY + * (const char *). + * + * - SSH_BIND_OPTIONS_BANNER: + * Set the server banner sent to clients (const char *). + * + * - SSH_BIND_OPTIONS_DSAKEY: + * This is DEPRECATED, please do not use. + * + * - SSH_BIND_OPTIONS_IMPORT_KEY: + * Set the Private Key for the server directly + * (ssh_key). It will be free'd by ssh_bind_free(). + * + * - SSH_BIND_OPTIONS_IMPORT_KEY_STR: + * Set the Private key for the server from a + * base64 encoded buffer (const char *). + * + * - SSH_BIND_OPTIONS_CIPHERS_C_S: + * Set the symmetric cipher client to server + * (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_CIPHERS_S_C: + * Set the symmetric cipher server to client + * (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_KEY_EXCHANGE: + * Set the key exchange method to be used + * (const char *, comma-separated list). ex: + * "ecdh-sha2-nistp256,diffie-hellman-group14-sha1" + * + * - SSH_BIND_OPTIONS_HMAC_C_S: + * Set the Message Authentication Code algorithm client + * to server (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_HMAC_S_C: + * Set the Message Authentication Code algorithm server + * to client (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_CONFIG_DIR: + * Set the directory (const char *, format string) + * to be used when the "%d" scape is used when providing + * paths of configuration files to + * ssh_bind_options_parse_config(). + * + * - SSH_BIND_OPTIONS_PROCESS_CONFIG + * Set it to false to disable automatic processing of + * system-wide configuration files. LibSSH automatically + * uses these configuration files otherwise. This + * option will only have effect if set before any call + * to ssh_bind_options_parse_config() (bool). + * + * - SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES: + * Set the public key algorithm accepted by the server + * (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: + * Set the list of allowed hostkey signatures algorithms + * to offer to the client, ordered by preference. This + * list is used as a filter when creating the list of + * algorithms to offer to the client: first the list of + * possible algorithms is created from the list of keys + * set and then filtered against this list. + * (const char *, comma-separated list). + * + * - SSH_BIND_OPTIONS_MODULI + * Set the path to the moduli file. Defaults to + * /etc/ssh/moduli if not specified (const char *). + * + * - SSH_BIND_OPTIONS_RSA_MIN_SIZE + * Set the minimum RSA key size in bits to be accepted by + * the server for both authentication and hostkey + * operations. The values under 1024 bits are not accepted + * even with this configuration option as they are + * considered completely broken. Setting 0 will revert + * the value to defaults. + * Default is 3072 bits or 2048 bits in FIPS mode. + * (int) + * + * - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE + * Set true to enable GSSAPI key exchange, + * false to disable GSSAPI key exchange. (bool) + * + * - SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS + * Set the GSSAPI key exchange method to be used + * (const char *, comma-separated list). + * ex: "gss-group14-sha256-,gss-group16-sha512-" + * + * @param value The value to set. This is a generic pointer and the + * datatype which should be used is described at the + * corresponding value of type above. + * + * @return 0 on success, < 0 on error, invalid option, or + * parameter. + * + * @warning When the option value to set is represented via a + * pointer (e.g const char * in case of strings, ssh_key + * in case of a libssh key), the value parameter should be + * that pointer. Do NOT pass a pointer to a pointer (const + * char **, ssh_key *) + * + * @warning When the option value to set is not a pointer (e.g int, + * unsigned int, bool, long), the value parameter should be + * a pointer to the location storing the value to set (int + * *, unsigned int *, bool *, long *) + * + * @warning If the value parameter has an invalid type (e.g if its + * not a pointer when it should have been a pointer, or if + * its a pointer to a pointer when it should have just been + * a pointer), then the behaviour is undefined. + */ +int +ssh_bind_options_set(ssh_bind sshbind, + enum ssh_bind_options_e type, + const void *value) +{ + bool allowed; + char *p = NULL, *q = NULL; + const char *v = NULL; + int i, rc; + char **wanted_methods = sshbind->wanted_methods; + + if (sshbind == NULL) { + return -1; + } + + switch (type) { + case SSH_BIND_OPTIONS_RSAKEY: + case SSH_BIND_OPTIONS_ECDSAKEY: + /* deprecated */ + case SSH_BIND_OPTIONS_HOSTKEY: + case SSH_BIND_OPTIONS_IMPORT_KEY: + case SSH_BIND_OPTIONS_IMPORT_KEY_STR: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int key_type; + ssh_key *bind_key_loc = NULL; + ssh_key key = NULL; + char **bind_key_path_loc = NULL; + + if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) { + const char *key_str = (const char *)value; + rc = ssh_pki_import_privkey_base64(key_str, + NULL, + NULL, + NULL, + &key); + if (rc == SSH_ERROR) { + ssh_set_error(sshbind, + SSH_FATAL, + "Failed to import key from buffer"); + return -1; + } + } else if (type == SSH_BIND_OPTIONS_IMPORT_KEY) { + key = (ssh_key)value; + } else { + rc = ssh_pki_import_privkey_file(value, NULL, NULL, NULL, &key); + if (rc != SSH_OK) { + return -1; + } + } + allowed = ssh_bind_key_size_allowed(sshbind, key); + if (!allowed) { + ssh_set_error(sshbind, + SSH_FATAL, + "The host key size %d is too small.", + ssh_key_size(key)); + if (type != SSH_BIND_OPTIONS_IMPORT_KEY) { + SSH_KEY_FREE(key); + } + return -1; + } + key_type = ssh_key_type(key); + switch (key_type) { + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_ECC + bind_key_loc = &sshbind->ecdsa; + bind_key_path_loc = &sshbind->ecdsakey; +#else + ssh_set_error(sshbind, + SSH_FATAL, + "ECDSA key used and libssh compiled " + "without ECDSA support"); +#endif + break; + case SSH_KEYTYPE_RSA: + bind_key_loc = &sshbind->rsa; + bind_key_path_loc = &sshbind->rsakey; + break; + case SSH_KEYTYPE_ED25519: + bind_key_loc = &sshbind->ed25519; + bind_key_path_loc = &sshbind->ed25519key; + break; + default: + ssh_set_error(sshbind, + SSH_FATAL, + "Unsupported key type %d", + key_type); + } + if (type == SSH_BIND_OPTIONS_RSAKEY || + type == SSH_BIND_OPTIONS_ECDSAKEY || + type == SSH_BIND_OPTIONS_HOSTKEY) { + if (bind_key_loc == NULL) { + ssh_key_free(key); + return -1; + } + /* Set the location of the key on disk even though we don't + need it in case some other function wants it */ + rc = ssh_bind_set_key(sshbind, bind_key_path_loc, value); + if (rc < 0) { + ssh_key_free(key); + return -1; + } + } else if (type == SSH_BIND_OPTIONS_IMPORT_KEY_STR) { + if (bind_key_loc == NULL) { + ssh_key_free(key); + return -1; + } + } else { + if (bind_key_loc == NULL) { + return -1; + } + } + ssh_key_free(*bind_key_loc); + *bind_key_loc = key; + } + break; + case SSH_BIND_OPTIONS_BINDADDR: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->bindaddr); + sshbind->bindaddr = strdup(value); + if (sshbind->bindaddr == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_BINDPORT: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + sshbind->bindport = *x & 0xffffU; + } + break; + case SSH_BIND_OPTIONS_BINDPORT_STR: + if (value == NULL) { + sshbind->bindport = 22 & 0xffffU; + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No bind port was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); + + sshbind->bindport = i & 0xffffU; + } + break; + case SSH_BIND_OPTIONS_LOG_VERBOSITY: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + ssh_set_log_level(*x & 0xffffU); + } + break; + case SSH_BIND_OPTIONS_LOG_VERBOSITY_STR: + if (value == NULL) { + ssh_set_log_level(0); + } else { + q = strdup(value); + if (q == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + i = strtol(q, &p, 10); + if (q == p) { + SSH_LOG(SSH_LOG_DEBUG, "No log verbositiy was parsed"); + SAFE_FREE(q); + return -1; + } + SAFE_FREE(q); + + ssh_set_log_level(i & 0xffffU); + } + break; + case SSH_BIND_OPTIONS_BANNER: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->banner); + sshbind->banner = strdup(value); + if (sshbind->banner == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_CIPHERS_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_C_S, + v, + &wanted_methods[SSH_CRYPT_C_S]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_CIPHERS_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_CRYPT_S_C, + v, + &wanted_methods[SSH_CRYPT_S_C]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_KEY_EXCHANGE: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_KEX, + v, + &wanted_methods[SSH_KEX]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_HMAC_C_S: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_C_S, + v, + &wanted_methods[SSH_MAC_C_S]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_HMAC_S_C: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_MAC_S_C, + v, + &wanted_methods[SSH_MAC_S_C]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_CONFIG_DIR: + v = value; + SAFE_FREE(sshbind->config_dir); + if (v == NULL) { + break; + } else if (v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + sshbind->config_dir = ssh_path_expand_tilde(v); + if (sshbind->config_dir == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &sshbind->pubkey_accepted_key_types); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS: + v = value; + if (v == NULL || v[0] == '\0') { + ssh_set_error_invalid(sshbind); + return -1; + } else { + rc = ssh_bind_set_algo(sshbind, + SSH_HOSTKEYS, + v, + &wanted_methods[SSH_HOSTKEYS]); + if (rc < 0) { + return -1; + } + } + break; + case SSH_BIND_OPTIONS_PROCESS_CONFIG: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + bool *x = (bool *)value; + sshbind->config_processed = !(*x); + } + break; + case SSH_BIND_OPTIONS_MODULI: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + SAFE_FREE(sshbind->moduli_file); + sshbind->moduli_file = strdup(value); + if (sshbind->moduli_file == NULL) { + ssh_set_error_oom(sshbind); + return -1; + } + } + break; + case SSH_BIND_OPTIONS_RSA_MIN_SIZE: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + int *x = (int *)value; + + if (*x < 0) { + ssh_set_error_invalid(sshbind); + return -1; + } + + /* (*x == 0) is allowed as it is used to revert to default */ + + if (*x > 0 && *x < RSA_MIN_KEY_SIZE) { + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "The provided value (%d) for minimal RSA key " + "size is too small. Use at least %d bits.", + *x, + RSA_MIN_KEY_SIZE); + return -1; + } + sshbind->rsa_min_size = *x; + } + break; +#ifdef WITH_GSSAPI + case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + bool *x = (bool *)value; + sshbind->gssapi_key_exchange = *x; + } + break; + case SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS: + if (value == NULL) { + ssh_set_error_invalid(sshbind); + return -1; + } else { + char *ret = NULL; + SAFE_FREE(sshbind->gssapi_key_exchange_algs); + ret = ssh_find_all_matching(GSSAPI_KEY_EXCHANGE_SUPPORTED, value); + if (ret == NULL) { + ssh_set_error( + sshbind, + SSH_REQUEST_DENIED, + "GSSAPI key exchange algorithms not supported or invalid"); + return -1; + } + sshbind->gssapi_key_exchange_algs = ret; + } + break; +#endif /* WITH_GSSAPI */ + default: + ssh_set_error(sshbind, + SSH_REQUEST_DENIED, + "Unknown ssh option %d", + type); + return -1; + break; + } + + return 0; +} + +static char *ssh_bind_options_expand_escape(ssh_bind sshbind, const char *s) +{ + char *buf = NULL; + char *r = NULL; + char *x = NULL; + const char *p = NULL; + size_t i, l; + + r = ssh_path_expand_tilde(s); + if (r == NULL) { + ssh_set_error_oom(sshbind); + return NULL; + } + + if (strlen(r) > MAX_BUF_SIZE) { + ssh_set_error(sshbind, SSH_FATAL, "string to expand too long"); + free(r); + return NULL; + } + + buf = malloc(MAX_BUF_SIZE); + if (buf == NULL) { + ssh_set_error_oom(sshbind); + free(r); + return NULL; + } + + p = r; + buf[0] = '\0'; + + for (i = 0; *p != '\0'; p++) { + if (*p != '%') { + buf[i] = *p; + i++; + if (i >= MAX_BUF_SIZE) { + free(buf); + free(r); + return NULL; + } + buf[i] = '\0'; + continue; + } + + p++; + if (*p == '\0') { + break; + } + + switch (*p) { + case 'd': + x = strdup(sshbind->config_dir); + break; + default: + ssh_set_error(sshbind, SSH_FATAL, + "Wrong escape sequence detected"); + free(buf); + free(r); + return NULL; + } + + if (x == NULL) { + ssh_set_error_oom(sshbind); + free(buf); + free(r); + return NULL; + } + + i += strlen(x); + if (i >= MAX_BUF_SIZE) { + ssh_set_error(sshbind, SSH_FATAL, + "String too long"); + free(buf); + free(x); + free(r); + return NULL; + } + l = strlen(buf); + strncpy(buf + l, x, MAX_BUF_SIZE - l - 1); + buf[i] = '\0'; + SAFE_FREE(x); + } + + free(r); + + /* strip the unused space by realloc */ + x = realloc(buf, strlen(buf) + 1); + if (x == NULL) { + ssh_set_error_oom(sshbind); + free(buf); + } + return x; +} + +/** + * @brief Parse a ssh bind options configuration file. + * + * This parses the options file and set them to the ssh_bind handle provided. If + * an option was previously set, it is overridden. If the global configuration + * hasn't been processed yet, it is processed prior to the provided file. + * + * @param sshbind SSH bind handle + * + * @param filename The options file to use; if NULL only the global + * configuration is parsed and applied (if it hasn't been + * processed before). + * + * @return 0 on success, < 0 on error. + */ +int ssh_bind_options_parse_config(ssh_bind sshbind, const char *filename) +{ + int rc = 0; + char *expanded_filename = NULL; + + if (sshbind == NULL) { + return -1; + } + + /* If the global default configuration hasn't been processed yet, process it + * before the provided configuration. */ + if (!(sshbind->config_processed)) { + if (ssh_file_readaccess_ok(GLOBAL_BIND_CONFIG)) { + rc = ssh_bind_config_parse_file(sshbind, GLOBAL_BIND_CONFIG); +#ifdef USR_GLOBAL_BIND_CONFIG + } else { + rc = ssh_bind_config_parse_file(sshbind, USR_GLOBAL_BIND_CONFIG); +#endif + } + if (rc != 0) { + return rc; + } + sshbind->config_processed = true; + } + + if (filename != NULL) { + expanded_filename = ssh_bind_options_expand_escape(sshbind, filename); + if (expanded_filename == NULL) { + return -1; + } + + /* Apply the user provided configuration */ + rc = ssh_bind_config_parse_file(sshbind, expanded_filename); + free(expanded_filename); + } + + return rc; +} + +#endif + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/packet.c b/src/libs/libssh-0.12.2/src/packet.c new file mode 100644 index 000000000000..e6f8e579053c --- /dev/null +++ b/src/libs/libssh-0.12.2/src/packet.c @@ -0,0 +1,2285 @@ +/* + * packet.c - packet building functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/packet.h" +#include "libssh/socket.h" +#include "libssh/channels.h" +#include "libssh/misc.h" +#include "libssh/session.h" +#include "libssh/messages.h" +#include "libssh/pcap.h" +#include "libssh/kex.h" +#include "libssh/auth.h" +#include "libssh/gssapi.h" +#include "libssh/bytearray.h" +#include "libssh/dh.h" + +static ssh_packet_callback default_packet_handlers[]= { + ssh_packet_disconnect_callback, // SSH2_MSG_DISCONNECT 1 + ssh_packet_ignore_callback, // SSH2_MSG_IGNORE 2 + ssh_packet_unimplemented, // SSH2_MSG_UNIMPLEMENTED 3 + ssh_packet_debug_callback, // SSH2_MSG_DEBUG 4 +#if WITH_SERVER + ssh_packet_service_request, // SSH2_MSG_SERVICE_REQUEST 5 +#else + NULL, +#endif + ssh_packet_service_accept, // SSH2_MSG_SERVICE_ACCEPT 6 + ssh_packet_ext_info, // SSH2_MSG_EXT_INFO 7 + NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, // 8-19 + ssh_packet_kexinit, // SSH2_MSG_KEXINIT 20 + ssh_packet_newkeys, // SSH2_MSG_NEWKEYS 21 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, // 22-29 +#if WITH_SERVER + ssh_packet_kexdh_init, // SSH2_MSG_KEXDH_INIT 30 + // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD 30 +#else + NULL, +#endif + NULL, // SSH2_MSG_KEXDH_REPLY 31 + // SSH2_MSG_KEX_DH_GEX_GROUP 31 + NULL, // SSH2_MSG_KEX_DH_GEX_INIT 32 + NULL, // SSH2_MSG_KEX_DH_GEX_REPLY 33 + NULL, // SSH2_MSG_KEX_DH_GEX_REQUEST 34 + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, // 35-49 +#if WITH_SERVER + ssh_packet_userauth_request, // SSH2_MSG_USERAUTH_REQUEST 50 +#else + NULL, +#endif + ssh_packet_userauth_failure, // SSH2_MSG_USERAUTH_FAILURE 51 + ssh_packet_userauth_success, // SSH2_MSG_USERAUTH_SUCCESS 52 + ssh_packet_userauth_banner, // SSH2_MSG_USERAUTH_BANNER 53 + NULL,NULL,NULL,NULL,NULL,NULL, // 54-59 + ssh_packet_userauth_pk_ok, // SSH2_MSG_USERAUTH_PK_OK 60 + // SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ 60 + // SSH2_MSG_USERAUTH_INFO_REQUEST 60 + // SSH2_MSG_USERAUTH_GSSAPI_RESPONSE 60 + ssh_packet_userauth_info_response, // SSH2_MSG_USERAUTH_INFO_RESPONSE 61 + // SSH2_MSG_USERAUTH_GSSAPI_TOKEN 61 + NULL, // 62 + NULL, // SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE 63 + NULL, // SSH2_MSG_USERAUTH_GSSAPI_ERROR 64 + NULL, // SSH2_MSG_USERAUTH_GSSAPI_ERRTOK 65 +#if defined(WITH_GSSAPI) && defined(WITH_SERVER) + ssh_packet_userauth_gssapi_mic, // SSH2_MSG_USERAUTH_GSSAPI_MIC 66 +#else /* WITH_GSSAPI && WITH_SERVER */ + NULL, +#endif /* WITH_GSSAPI && WITH_SERVER */ + NULL, NULL, + NULL, NULL, NULL, NULL, NULL, NULL, NULL, + NULL, NULL, NULL, NULL, // 67-79 +#ifdef WITH_SERVER + ssh_packet_global_request, // SSH2_MSG_GLOBAL_REQUEST 80 +#else /* WITH_SERVER */ + NULL, +#endif /* WITH_SERVER */ + ssh_request_success, // SSH2_MSG_REQUEST_SUCCESS 81 + ssh_request_denied, // SSH2_MSG_REQUEST_FAILURE 82 + NULL, NULL, NULL, NULL, NULL, NULL, NULL,// 83-89 + ssh_packet_channel_open, // SSH2_MSG_CHANNEL_OPEN 90 + ssh_packet_channel_open_conf, // SSH2_MSG_CHANNEL_OPEN_CONFIRMATION 91 + ssh_packet_channel_open_fail, // SSH2_MSG_CHANNEL_OPEN_FAILURE 92 + channel_rcv_change_window, // SSH2_MSG_CHANNEL_WINDOW_ADJUST 93 + channel_rcv_data, // SSH2_MSG_CHANNEL_DATA 94 + channel_rcv_data, // SSH2_MSG_CHANNEL_EXTENDED_DATA 95 + channel_rcv_eof, // SSH2_MSG_CHANNEL_EOF 96 + channel_rcv_close, // SSH2_MSG_CHANNEL_CLOSE 97 + channel_rcv_request, // SSH2_MSG_CHANNEL_REQUEST 98 + ssh_packet_channel_success, // SSH2_MSG_CHANNEL_SUCCESS 99 + ssh_packet_channel_failure, // SSH2_MSG_CHANNEL_FAILURE 100 +}; + +/** @internal + * @brief check if the received packet is allowed for the current session state + * @param session current ssh_session + * @returns SSH_PACKET_ALLOWED if the packet is allowed; SSH_PACKET_DENIED + * if the packet arrived in wrong state; SSH_PACKET_UNKNOWN if the packet type + * is unknown + */ +static enum ssh_packet_filter_result_e ssh_packet_incoming_filter(ssh_session session) +{ + enum ssh_packet_filter_result_e rc; + +#ifdef DEBUG_PACKET + SSH_LOG(SSH_LOG_PACKET, "Filtering packet type %d", + session->in_packet.type); +#endif + + switch(session->in_packet.type) { + case SSH2_MSG_DISCONNECT: // 1 + /* + * States required: + * - None + * + * Transitions: + * - session->socket->state = SSH_SOCKET_CLOSED + * - session->session_state = SSH_SESSION_STATE_ERROR + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_IGNORE: // 2 + /* + * States required: + * - None + * + * Transitions: + * - None + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_UNIMPLEMENTED: // 3 + /* + * States required: + * - None + * + * Transitions: + * - None + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_DEBUG: // 4 + /* + * States required: + * - None + * + * Transitions: + * - None + * */ + + /* Always allowed */ + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_SERVICE_REQUEST: // 5 + /* Server only */ + + /* + * States required: + * - session->session_state == SSH_SESSION_STATE_AUTHENTICATING + * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED + * - session->dh_handshake_state == DH_STATE_FINISHED + * + * Transitions: + * - None + * */ + + /* If this is a client, reject the message */ + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && + (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_SERVICE_ACCEPT: // 6 + /* + * States required: + * - session->session_state == SSH_SESSION_STATE_AUTHENTICATING + * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED + * - session->dh_handshake_state == DH_STATE_FINISHED + * - session->auth.service_state == SSH_AUTH_SERVICE_SENT + * + * Transitions: + * - auth.service_state = SSH_AUTH_SERVICE_ACCEPTED + * */ + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && + (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + /* TODO check if only auth service can be requested */ + if (session->auth.service_state != SSH_AUTH_SERVICE_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_EXT_INFO: // 7 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * or session->session_state == SSH_SESSION_STATE_AUTHENTICATED + * (re-exchange) + * - dh_handshake_state == DH_STATE_FINISHED + * + * Transitions: + * - None + * */ + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATING) && + (session->session_state != SSH_SESSION_STATE_AUTHENTICATED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEXINIT: // 20 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * or session_state == SSH_SESSION_STATE_INITIAL_KEX + * - dh_handshake_state == DH_STATE_INIT + * or dh_handshake_state == DH_STATE_INIT_SENT (re-exchange) + * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) + * or dh_handshake_state == DH_STATE_FINISHED (re-exchange) + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT + * - session->session_state = SSH_SESSION_STATE_KEXINIT_RECEIVED + * + * On server: + * - session->session_state = SSH_SESSION_STATE_DH + * */ + + if ((session->session_state != SSH_SESSION_STATE_AUTHENTICATED) && + (session->session_state != SSH_SESSION_STATE_INITIAL_KEX)) + { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->dh_handshake_state != DH_STATE_INIT) && + (session->dh_handshake_state != DH_STATE_INIT_SENT) && + (session->dh_handshake_state != DH_STATE_REQUEST_SENT) && + (session->dh_handshake_state != DH_STATE_FINISHED)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_NEWKEYS: // 21 + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_NEWKEYS_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_FINISHED + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATING + * if session->flags & SSH_SESSION_FLAG_AUTHENTICATED + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED + * */ + + /* If DH has not been started, reject message */ + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in NEWKEYS_SENT state */ + if (session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEXDH_INIT: // 30 + // SSH2_MSG_KEX_ECDH_INIT: // 30 + // SSH2_MSG_KEX_HYBRID_INIT: // 30 + // SSH2_MSG_KEX_DH_GEX_REQUEST_OLD: // 30 + + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT_SENT + * then calls dh_handshake_server which triggers: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in initial state */ + if (session->dh_handshake_state != DH_STATE_INIT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEXDH_REPLY: // 31 + // SSH2_MSG_KEX_ECDH_REPLY: // 31 + // SSH2_MSG_KEX_HYBRID_REPLY: // 31 + // SSH2_MSG_KEX_DH_GEX_GROUP: // 31 + + /* Client only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT_SENT + * or dh_handshake_state == DH_STATE_REQUEST_SENT (dh-gex) + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_INIT_SENT && + session->dh_handshake_state != DH_STATE_REQUEST_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEX_DH_GEX_INIT: // 32 + // SSH2_MSG_KEXGSS_COMPLETE: // 32 + if (ssh_kex_is_gss(session->next_crypto)) { + /* SSH2_MSG_KEXGSS_COMPLETE */ + /* Client only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT_SENT + * then calls ssh_packet_client_gss_kex_reply which triggers: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (!session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_INIT_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + } else { + /* SSH2_MSG_KEX_DH_GEX_INIT */ + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_GROUP_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_GROUP_SENT + * then calls ssh_packet_server_dhgex_init which triggers: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in initial state */ + if (session->dh_handshake_state != DH_STATE_GROUP_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + } + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEX_DH_GEX_REPLY: // 33 + + /* Client only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT_SENT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_NEWKEYS_SENT + * */ + + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_INIT_SENT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_KEX_DH_GEX_REQUEST: // 34 + + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_DH + * - dh_handshake_state == DH_STATE_INIT + * + * Transitions: + * - session->dh_handshake_state = DH_STATE_INIT_SENT + * then calls ssh_packet_server_dhgex_request which triggers: + * - session->dh_handshake_state = DH_STATE_GROUP_SENT + * */ + + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_DH) { + rc = SSH_PACKET_DENIED; + break; + } + + /* Only allowed if dh_handshake_state is in initial state */ + if (session->dh_handshake_state != DH_STATE_INIT) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_REQUEST: // 50 + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - dh_handshake_state == DH_STATE_FINISHED + * + * Transitions: + * - if authentication was successful: + * - session_state = SSH_SESSION_STATE_AUTHENTICATED + * */ + + /* If this is a client, reject the message */ + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_FAILURE: // 51 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - dh_handshake_state == DH_STATE_FINISHED + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * or session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT + * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT + * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_MIC_SENT + * + * Transitions: + * - if unpacking failed: + * - session->auth.state = SSH_AUTH_ERROR + * - if failure was partial: + * - session->auth.state = SSH_AUTH_PARTIAL + * - else: + * - session->auth.state = SSH_AUTH_STATE_FAILED + * */ + + /* If this is a server, reject the message */ + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_SUCCESS: // 52 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - dh_handshake_state == DH_STATE_FINISHED + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * or session->auth.state == SSH_AUTH_STATE_PUBKEY_AUTH_SENT + * or session->auth.state == SSH_AUTH_STATE_PASSWORD_AUTH_SENT + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_MIC_SENT + * or session->auth.state == SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT + * or session->auth.state == SSH_AUTH_STATE_AUTH_NONE_SENT + * + * Transitions: + * - session->auth.state = SSH_AUTH_STATE_SUCCESS + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED + * - session->flags |= SSH_SESSION_FLAG_AUTHENTICATED + * - sessions->auth.current_method = SSH_AUTH_METHOD_UNKNOWN + * */ + + /* If this is a server, reject the message */ + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->auth.state != SSH_AUTH_STATE_KBDINT_SENT) && + (session->auth.state != SSH_AUTH_STATE_PUBKEY_AUTH_SENT) && + (session->auth.state != SSH_AUTH_STATE_PASSWORD_AUTH_SENT) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_MIC_SENT) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_KEYEX_MIC_SENT) && + (session->auth.state != SSH_AUTH_STATE_AUTH_NONE_SENT)) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_BANNER: // 53 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_PK_OK: // 60 + // SSH2_MSG_USERAUTH_PASSWD_CHANGEREQ: // 60 + // SSH2_MSG_USERAUTH_INFO_REQUEST: // 60 + // SSH2_MSG_USERAUTH_GSSAPI_RESPONSE: // 60 + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * or + * session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT + * or + * session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT + * + * Transitions: + * Depending on the current state, the message is treated + * differently: + * - session->auth.state == SSH_AUTH_STATE_KBDINT_SENT + * - session->auth.state = SSH_AUTH_STATE_INFO + * - session->auth.state == SSH_AUTH_STATE_GSSAPI_REQUEST_SENT + * - session->auth.state = SSH_AUTH_STATE_GSSAPI_TOKEN + * - session->auth.state == SSH_AUTH_STATE_PUBKEY_OFFER_SENT + * - session->auth.state = SSH_AUTH_STATE_PK_OK + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->auth.state != SSH_AUTH_STATE_KBDINT_SENT) && + (session->auth.state != SSH_AUTH_STATE_PUBKEY_OFFER_SENT) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_REQUEST_SENT)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_INFO_RESPONSE: // 61 + // SSH2_MSG_USERAUTH_GSSAPI_TOKEN: // 61 + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session_state->auth.state == SSH_SESSION_STATE_GSSAPI_TOKEN + * or + * session_state->auth.state == SSH_SESSION_STATE_INFO + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + if ((session->auth.state != SSH_AUTH_STATE_INFO) && + (session->auth.state != SSH_AUTH_STATE_GSSAPI_TOKEN)) + { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_USERAUTH_GSSAPI_EXCHANGE_COMPLETE: // 63 + /* Server only */ + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session->gssapi->state == SSH_GSSAPI_STATE_RCV_MIC + * + * Transitions: + * - None + */ +#ifdef WITH_GSSAPI + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->gssapi == NULL) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->gssapi->state != SSH_GSSAPI_STATE_RCV_MIC) { + rc = SSH_PACKET_DENIED; + break; + } + rc = SSH_PACKET_ALLOWED; + break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ + case SSH2_MSG_USERAUTH_GSSAPI_ERROR: // 64 + /* Client only */ + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * + * Transitions: + * - None + */ +#ifdef WITH_GSSAPI + if (session->server) { + rc = SSH_PACKET_DENIED; + break; + } + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ + case SSH2_MSG_USERAUTH_GSSAPI_ERRTOK: // 65 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * + * Transitions: + * - None + */ +#ifdef WITH_GSSAPI + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ + case SSH2_MSG_USERAUTH_GSSAPI_MIC: // 66 + /* Server only */ + + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATING + * - session->gssapi->state == SSH_GSSAPI_STATE_RCV_MIC + * + * Transitions: + * Depending on the result of the verification, the states are + * changed: + * - SSH_AUTH_SUCCESS: + * - session->session_state = SSH_SESSION_STATE_AUTHENTICATED + * - session->flags != SSH_SESSION_FLAG_AUTHENTICATED + * - SSH_AUTH_PARTIAL: + * - None + * - any other case: + * - None + * */ +#ifdef WITH_GSSAPI + /* If this is a client, reject the message */ + if (session->client) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->dh_handshake_state != DH_STATE_FINISHED) { + rc = SSH_PACKET_DENIED; + break; + } + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATING) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; +#else + rc = SSH_PACKET_DENIED; + break; +#endif /* WITH_GSSAPI */ + case SSH2_MSG_GLOBAL_REQUEST: // 80 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_REQUEST_SUCCESS: // 81 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_REQUEST_FAILURE: // 82 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_OPEN: // 90 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_OPEN_CONFIRMATION: // 91 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - channel->state = SSH_CHANNEL_STATE_OPEN + * - channel->flags &= ~SSH_CHANNEL_FLAG_NOT_BOUND + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_OPEN_FAILURE: // 92 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - channel->state = SSH_CHANNEL_STATE_OPEN_DENIED + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_WINDOW_ADJUST: // 93 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_DATA: // 94 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_EXTENDED_DATA: // 95 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_EOF: // 96 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - None + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_CLOSE: // 97 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - channel->state = SSH_CHANNEL_STATE_CLOSED + * - channel->flags |= SSH_CHANNEL_FLAG_CLOSED_REMOTE + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_REQUEST: // 98 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - Depends on the request + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_SUCCESS: // 99 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + case SSH2_MSG_CHANNEL_FAILURE: // 100 + /* + * States required: + * - session_state == SSH_SESSION_STATE_AUTHENTICATED + * + * Transitions: + * - From channel->request_state == SSH_CHANNEL_REQ_STATE_PENDING + * - To channel->request_state = SSH_CHANNEL_REQ_STATE_ACCEPTED + * + * If not in a pending state, message is ignored in the callback handler. + * */ + + if (session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = SSH_PACKET_DENIED; + break; + } + + rc = SSH_PACKET_ALLOWED; + break; + default: + /* Unknown message, do not filter */ + rc = SSH_PACKET_UNKNOWN; + goto end; + } + +end: +#ifdef DEBUG_PACKET + if (rc == SSH_PACKET_DENIED) { + SSH_LOG(SSH_LOG_PACKET, "REJECTED packet type %d: ", + session->in_packet.type); + } + + if (rc == SSH_PACKET_UNKNOWN) { + SSH_LOG(SSH_LOG_PACKET, "UNKNOWN packet type %d", + session->in_packet.type); + } +#endif + + return rc; +} + +/* Returns current_crypto structure from the session. + * During key exchange (or rekey), after one of the sides + * sending NEWKEYS packet, this might return next_crypto for one + * of the directions that is ahead to send already queued packets + */ +struct ssh_crypto_struct * +ssh_packet_get_current_crypto(ssh_session session, + enum ssh_crypto_direction_e direction) +{ + struct ssh_crypto_struct *crypto = NULL; + + if (session == NULL) { + return NULL; + } + + if (session->current_crypto != NULL && + session->current_crypto->used & direction) { + crypto = session->current_crypto; + } else if (session->next_crypto != NULL && + session->next_crypto->used & direction) { + crypto = session->next_crypto; + } else { + return NULL; + } + + switch (direction) { + case SSH_DIRECTION_IN: + if (crypto->in_cipher != NULL) { + return crypto; + } + break; + case SSH_DIRECTION_OUT: + if (crypto->out_cipher != NULL) { + return crypto; + } + break; + case SSH_DIRECTION_BOTH: + if (crypto->in_cipher != NULL && + crypto->out_cipher != NULL) { + return crypto; + } + } + + return NULL; +} + +#define MAX_PACKETS (1UL<<31) + +static bool ssh_packet_need_rekey(ssh_session session, + const uint32_t payloadsize) +{ + bool data_rekey_needed = false; + struct ssh_crypto_struct *crypto = NULL; + struct ssh_cipher_struct *out_cipher = NULL, *in_cipher = NULL; + uint32_t next_blocks; + + /* We can safely rekey only in authenticated state */ + if ((session->flags & SSH_SESSION_FLAG_AUTHENTICATED) == 0) { + return false; + } + + /* Do not rekey if the rekey/key-exchange is in progress */ + if (session->dh_handshake_state != DH_STATE_FINISHED) { + return false; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return false; + } + + out_cipher = crypto->out_cipher; + in_cipher = crypto->in_cipher; + + /* Make sure we can send at least something for very small limits */ + if ((out_cipher->packets == 0) && (in_cipher->packets == 0)) { + return false; + } + + /* Time based rekeying */ + if (session->opts.rekey_time != 0 && + ssh_timeout_elapsed(&session->last_rekey_time, + session->opts.rekey_time)) { + return true; + } + + /* RFC4344, Section 3.1 Recommends rekeying after 2^31 packets in either + * direction to avoid possible information leakage through the MAC tag + */ + if (out_cipher->packets > MAX_PACKETS || + in_cipher->packets > MAX_PACKETS) { + return true; + } + + /* Data-based rekeying: + * * For outgoing packets we can still delay them + * * Incoming packets need to be processed anyway, but we can + * signalize our intention to rekey + */ + next_blocks = payloadsize / out_cipher->blocksize; + data_rekey_needed = (out_cipher->max_blocks != 0 && + out_cipher->blocks + next_blocks > out_cipher->max_blocks) || + (in_cipher->max_blocks != 0 && + in_cipher->blocks + next_blocks > in_cipher->max_blocks); + + SSH_LOG(SSH_LOG_PACKET, + "rekey: [data_rekey_needed=%d, out_blocks=%" PRIu64 ", in_blocks=%" PRIu64 "]", + data_rekey_needed, + out_cipher->blocks + next_blocks, + in_cipher->blocks + next_blocks); + + return data_rekey_needed; +} + +/* in nonblocking mode, socket_read will read as much as it can, and return */ +/* SSH_OK if it has read at least len bytes, otherwise, SSH_AGAIN. */ +/* in blocking mode, it will read at least len bytes and will block until it's ok. */ + +/** @internal + * @brief handles a data received event + * + * Processes up to one packet from the given buffer and calls the handlers + * for the different packet types or an exception handler callback. If the + * buffer does not contain a complete packet, nothing is processed and zero + * is returned. So typically this function needs to be called in a loop until + * it returns zero to properly handle multiple packets in the buffer. + * + * @param user pointer to current ssh_session + * @param data pointer to the data received + * @len length of data received. It might not be enough for a complete packet + * @returns number of bytes read and processed. Zero means only partial packet + * received. + */ +size_t +ssh_packet_socket_callback(const void *data, size_t receivedlen, void *user) +{ + ssh_session session = (ssh_session)user; + uint32_t blocksize = 8; + uint32_t lenfield_blocksize = 8; + size_t current_macsize = 0; + uint8_t *ptr = NULL; + ssize_t to_be_read; + int rc; + uint8_t *cleartext_packet = NULL; + uint8_t *packet_second_block = NULL; + uint8_t *mac = NULL; + size_t packet_remaining, packet_offset; + uint32_t packet_len, compsize, payloadsize; + uint8_t padding; + size_t processed = 0; /* number of bytes processed from the callback */ + enum ssh_packet_filter_result_e filter_result; + struct ssh_crypto_struct *crypto = NULL; + bool etm = false; + uint32_t etm_packet_offset = 0; + bool ok; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL) { + current_macsize = hmac_digest_len(crypto->in_hmac); + blocksize = crypto->in_cipher->blocksize; + lenfield_blocksize = crypto->in_cipher->lenfield_blocksize; + etm = crypto->in_hmac_etm; + } + + if (etm) { + /* In EtM mode packet size is unencrypted. This means + * we need to use this offset and set the block size + * that is part of the encrypted part to 0. + */ + etm_packet_offset = sizeof(uint32_t); + lenfield_blocksize = 0; + } else if (lenfield_blocksize == 0) { + lenfield_blocksize = blocksize; + } + if (data == NULL) { + goto error; + } + + if (session->session_state == SSH_SESSION_STATE_ERROR) { + goto error; + } +#ifdef DEBUG_PACKET + SSH_LOG(SSH_LOG_PACKET, + "rcv packet cb (len=%zu, state=%s)", + receivedlen, + session->packet_state == PACKET_STATE_INIT ? + "INIT" : + session->packet_state == PACKET_STATE_SIZEREAD ? + "SIZE_READ" : + session->packet_state == PACKET_STATE_PROCESSING ? + "PROCESSING" : "unknown"); +#endif + switch (session->packet_state) { + case PACKET_STATE_INIT: + if (receivedlen < lenfield_blocksize + etm_packet_offset) { + /* + * We didn't receive enough data to read either at least one + * block size or the unencrypted length in EtM mode. + */ +#ifdef DEBUG_PACKET + SSH_LOG(SSH_LOG_PACKET, + "Waiting for more data (%zu < %u)", + receivedlen, + lenfield_blocksize); +#endif + return 0; + } + + session->in_packet = (struct packet_struct) { + .type = 0, + }; + + if (session->in_buffer) { + rc = ssh_buffer_reinit(session->in_buffer); + if (rc < 0) { + goto error; + } + } else { + session->in_buffer = ssh_buffer_new(); + if (session->in_buffer == NULL) { + goto error; + } + } + + if (!etm) { + ptr = ssh_buffer_allocate(session->in_buffer, + lenfield_blocksize); + if (ptr == NULL) { + goto error; + } + packet_len = ssh_packet_decrypt_len(session, ptr, + (uint8_t *)data); + to_be_read = packet_len - lenfield_blocksize + sizeof(uint32_t); + } else { + /* Length is unencrypted in case of Encrypt-then-MAC */ + packet_len = PULL_BE_U32(data, 0); + to_be_read = packet_len - etm_packet_offset; + } + + processed += lenfield_blocksize + etm_packet_offset; + if (packet_len > MAX_PACKET_LEN) { + ssh_set_error(session, + SSH_FATAL, + "read_packet(): Packet len too high(%" PRIu32 " %.4" PRIx32 ")", + packet_len, packet_len); + goto error; + } + if (to_be_read < 0) { + /* remote sshd sends invalid sizes? */ + ssh_set_error(session, + SSH_FATAL, + "Given numbers of bytes left to be read < 0 (%zd)!", + to_be_read); + goto error; + } + + session->in_packet.len = packet_len; + session->packet_state = PACKET_STATE_SIZEREAD; + FALL_THROUGH; + case PACKET_STATE_SIZEREAD: + packet_len = session->in_packet.len; + packet_offset = processed = lenfield_blocksize + etm_packet_offset; + to_be_read = packet_len + sizeof(uint32_t) + current_macsize; + /* if to_be_read is zero, the whole packet was blocksize bytes. */ + if (to_be_read != 0) { + if (receivedlen < (unsigned long)to_be_read) { + /* give up, not enough data in buffer */ + SSH_LOG(SSH_LOG_PACKET, + "packet: partial packet (read len) " + "[len=%" PRIu32 ", receivedlen=%zu, to_be_read=%zd]", + packet_len, + receivedlen, + to_be_read); + return 0; + } + + packet_second_block = (uint8_t*)data + packet_offset; + processed = to_be_read - current_macsize; + } + + if (packet_offset - sizeof(uint32_t) > (size_t)packet_len) { + ssh_set_error(session, + SSH_FATAL, + "Invalid packet length %" PRIu32 ", required %zu", + packet_len, + packet_offset + sizeof(uint32_t)); + goto error; + } + + /* remaining encrypted bytes from the packet, MAC not included */ + packet_remaining = packet_len - (packet_offset - sizeof(uint32_t)); + cleartext_packet = ssh_buffer_allocate(session->in_buffer, + (uint32_t)packet_remaining); + if (cleartext_packet == NULL) { + goto error; + } + + if (packet_second_block != NULL) { + if (crypto != NULL) { + mac = packet_second_block + packet_remaining; + + if (crypto->in_hmac != SSH_HMAC_NONE && etm) { + rc = ssh_packet_hmac_verify(session, + data, + processed, + mac, + crypto->in_hmac); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "HMAC error"); + goto error; + } + } + /* + * Decrypt the packet. In case of EtM mode, the length is + * already known as it's unencrypted. In the other case, + * lenfield_blocksize bytes already have been decrypted. + */ + if (packet_remaining > 0) { + rc = ssh_packet_decrypt(session, + cleartext_packet, + (uint8_t *)data, + packet_offset, + processed - packet_offset); + if (rc < 0) { + ssh_set_error(session, + SSH_FATAL, + "Decryption error"); + goto error; + } + } + + if (crypto->in_hmac != SSH_HMAC_NONE && !etm) { + ssh_buffer in = session->in_buffer; + rc = ssh_packet_hmac_verify(session, + ssh_buffer_get(in), + ssh_buffer_get_len(in), + mac, + crypto->in_hmac); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "HMAC error"); + goto error; + } + } + processed += current_macsize; + } else { + memcpy(cleartext_packet, + packet_second_block, + packet_remaining); + } + } + +#ifdef WITH_PCAP + if (session->pcap_ctx != NULL) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + ssh_buffer_get(session->in_buffer), + ssh_buffer_get_len(session->in_buffer), + ssh_buffer_get_len(session->in_buffer)); + } +#endif + + if (!etm) { + /* skip the size field which has been processed before */ + ssh_buffer_pass_bytes(session->in_buffer, sizeof(uint32_t)); + } + + rc = ssh_buffer_get_u8(session->in_buffer, &padding); + if (rc == 0) { + ssh_set_error(session, + SSH_FATAL, + "Packet too short to read padding"); + goto error; + } + + if (padding > ssh_buffer_get_len(session->in_buffer)) { + ssh_set_error(session, + SSH_FATAL, + "Invalid padding: %d (%" PRIu32 " left)", + padding, + ssh_buffer_get_len(session->in_buffer)); + goto error; + } + ssh_buffer_pass_bytes_end(session->in_buffer, padding); + compsize = ssh_buffer_get_len(session->in_buffer); + + if (crypto && crypto->do_compress_in && + ssh_buffer_get_len(session->in_buffer) > 0) { + rc = decompress_buffer(session, session->in_buffer, + MAX_PACKET_LEN); + if (rc < 0) { + goto error; + } + } + payloadsize = ssh_buffer_get_len(session->in_buffer); + if (session->recv_seq == UINT32_MAX) { + /* Overflowing sequence numbers is always fishy */ + if (crypto == NULL) { + /* don't allow sequence number overflow when unencrypted */ + ssh_set_error(session, + SSH_FATAL, + "Incoming sequence number overflow"); + goto error; + } else { + SSH_LOG(SSH_LOG_WARNING, + "Incoming sequence number overflow"); + } + } + session->recv_seq++; + if (crypto != NULL) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = crypto->in_cipher; + cipher->packets++; + cipher->blocks += payloadsize / cipher->blocksize; + } + if (session->raw_counter != NULL) { + session->raw_counter->in_bytes += payloadsize; + session->raw_counter->in_packets++; + } + + /* + * We don't want to rewrite a new packet while still executing the + * packet callbacks + */ + session->packet_state = PACKET_STATE_PROCESSING; + ssh_packet_parse_type(session); + SSH_LOG(SSH_LOG_PACKET, + "packet: read type %hhd [len=%" PRIu32 ",padding=%hhd," + "comp=%" PRIu32 ",payload=%" PRIu32 "]", + session->in_packet.type, packet_len, padding, compsize, + payloadsize); + if (crypto == NULL) { + /* In strict kex, only a few packets are allowed. Taint the session + * if we received packets that are normally allowed but to be + * refused if we are in strict kex when KEX is over. + */ + uint8_t type = session->in_packet.type; + + if (type != SSH2_MSG_KEXINIT && type != SSH2_MSG_NEWKEYS && + (type < SSH2_MSG_KEXDH_INIT || + type > SSH2_MSG_KEX_DH_GEX_REQUEST)) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } + /* Check if the packet is expected */ + filter_result = ssh_packet_incoming_filter(session); + + switch (filter_result) { + case SSH_PACKET_ALLOWED: + /* Execute callbacks */ + ssh_packet_process(session, session->in_packet.type); + break; + case SSH_PACKET_DENIED: + ssh_set_error(session, + SSH_FATAL, + "Packet filter: rejected packet (type %d)", + session->in_packet.type); + goto error; + case SSH_PACKET_UNKNOWN: + if (crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + ssh_packet_send_unimplemented(session, session->recv_seq - 1); + break; + } + + session->packet_state = PACKET_STATE_INIT; + if (processed < receivedlen) { + SSH_LOG(SSH_LOG_PACKET, + "packet: %zu bytes still remaining in socket buffer " + "after processing", + receivedlen-processed); + } + + ok = ssh_packet_need_rekey(session, 0); + if (ok) { + SSH_LOG(SSH_LOG_PACKET, "Incoming packet triggered rekey"); + rc = ssh_send_rekex(session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Rekey failed: rc = %d", rc); + goto error; + } + } + + return processed; + case PACKET_STATE_PROCESSING: + SSH_LOG(SSH_LOG_PACKET, "Nested packet processing. Delaying."); + return 0; + } + + ssh_set_error(session, + SSH_FATAL, + "Invalid state into packet_read2(): %d", + session->packet_state); + +error: + session->session_state = SSH_SESSION_STATE_ERROR; + SSH_LOG(SSH_LOG_PACKET, "Packet: processed %zu bytes", processed); + return processed; +} + +static void ssh_packet_socket_controlflow_callback(int code, void *userdata) +{ + ssh_session session = userdata; + struct ssh_iterator *it = NULL; + ssh_channel channel = NULL; + + if (code == SSH_SOCKET_FLOW_WRITEWONTBLOCK) { + SSH_LOG(SSH_LOG_TRACE, "sending channel_write_wontblock callback"); + + /* the out pipe is empty so we can forward this to channels */ + it = ssh_list_get_iterator(session->channels); + while (it != NULL) { + channel = ssh_iterator_value(ssh_channel, it); + ssh_callbacks_execute_list(channel->callbacks, + ssh_channel_callbacks, + channel_write_wontblock_function, + session, + channel, + channel->remote_window); + it = it->next; + } + } +} + +void ssh_packet_register_socket_callback(ssh_session session, ssh_socket s) +{ + struct ssh_socket_callbacks_struct *callbacks = &session->socket_callbacks; + + callbacks->data = ssh_packet_socket_callback; + callbacks->connected = NULL; + callbacks->controlflow = ssh_packet_socket_controlflow_callback; + callbacks->userdata = session; + ssh_socket_set_callbacks(s, callbacks); +} + +/** @internal + * @brief sets the callbacks for the packet layer + */ +void +ssh_packet_set_callbacks(ssh_session session, ssh_packet_callbacks callbacks) +{ + if (session->packet_callbacks == NULL) { + session->packet_callbacks = ssh_list_new(); + if (session->packet_callbacks == NULL) { + ssh_set_error_oom(session); + return; + } + } + ssh_list_append(session->packet_callbacks, callbacks); +} + +/** @internal + * @brief remove the callbacks from the packet layer + */ +void +ssh_packet_remove_callbacks(ssh_session session, ssh_packet_callbacks callbacks) +{ + struct ssh_iterator *it = NULL; + + it = ssh_list_find(session->packet_callbacks, callbacks); + if (it != NULL) { + ssh_list_remove(session->packet_callbacks, it); + } +} + +/** @internal + * @brief sets the default packet handlers + */ +void ssh_packet_set_default_callbacks(ssh_session session) +{ + struct ssh_packet_callbacks_struct *c = &session->default_packet_callbacks; + + c->start = 1; + c->n_callbacks = sizeof(default_packet_handlers) / sizeof(ssh_packet_callback); + c->user = session; + c->callbacks = default_packet_handlers; + ssh_packet_set_callbacks(session, c); +} + +/** @internal + * @brief dispatch the call of packet handlers callbacks for a received packet + * @param type type of packet + */ +void ssh_packet_process(ssh_session session, uint8_t type) +{ + struct ssh_iterator *i = NULL; + int rc = SSH_PACKET_NOT_USED; + ssh_packet_callbacks cb; + + SSH_LOG(SSH_LOG_PACKET, "Dispatching handler for packet type %d", type); + if (session->packet_callbacks == NULL) { + SSH_LOG(SSH_LOG_RARE, "Packet callback is not initialized !"); + return; + } + + i = ssh_list_get_iterator(session->packet_callbacks); + while (i != NULL) { + cb = ssh_iterator_value(ssh_packet_callbacks, i); + i = i->next; + + if (!cb) { + continue; + } + + if (cb->start > type) { + continue; + } + + if (cb->start + cb->n_callbacks <= type) { + continue; + } + + if (cb->callbacks[type - cb->start] == NULL) { + continue; + } + + rc = cb->callbacks[type - cb->start](session, type, session->in_buffer, + cb->user); + if (rc == SSH_PACKET_USED) { + break; + } + } + + if (rc == SSH_PACKET_NOT_USED) { + SSH_LOG(SSH_LOG_RARE, "Couldn't do anything with packet type %d", type); + rc = ssh_packet_send_unimplemented(session, session->recv_seq - 1); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_RARE, "Failed to send unimplemented: %s", + ssh_get_error(session)); + } + if (session->current_crypto == NULL) { + session->flags |= SSH_SESSION_FLAG_KEX_TAINTED; + } + } +} + +/** @internal + * @brief sends a SSH_MSG_NEWKEYS when enabling the new negotiated ciphers + * @param session the SSH session + * @return SSH_ERROR on error, else SSH_OK + */ +int ssh_packet_send_newkeys(ssh_session session) +{ + int rc; + + /* Send the MSG_NEWKEYS */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); + if (rc < 0) { + return rc; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return rc; + } + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + return rc; +} + +/** @internal + * @brief sends a SSH_MSG_UNIMPLEMENTED answer to an unhandled packet + * @param session the SSH session + * @param seqnum the sequence number of the unknown packet + * @return SSH_ERROR on error, else SSH_OK + */ +int ssh_packet_send_unimplemented(ssh_session session, uint32_t seqnum){ + int rc; + + rc = ssh_buffer_pack(session->out_buffer, + "bd", + SSH2_MSG_UNIMPLEMENTED, + seqnum); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_packet_send(session); + + return rc; +} + +/** @internal + * @brief handles a SSH_MSG_UNIMPLEMENTED packet + */ +SSH_PACKET_CALLBACK(ssh_packet_unimplemented){ + uint32_t seq; + int rc; + + (void)session; /* unused */ + (void)type; + (void)user; + + rc = ssh_buffer_unpack(packet, "d", &seq); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Could not unpack SSH_MSG_UNIMPLEMENTED packet"); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_RARE, + "Received SSH_MSG_UNIMPLEMENTED (sequence number %" PRIu32 ")",seq); + + return SSH_PACKET_USED; +} + +/** @internal + * @parse the "Type" header field of a packet and updates the session + */ +int ssh_packet_parse_type(struct ssh_session_struct *session) +{ + session->in_packet = (struct packet_struct) { + .type = 0, + }; + + if (session->in_buffer == NULL) { + return SSH_ERROR; + } + + if (ssh_buffer_get_u8(session->in_buffer, &session->in_packet.type) == 0) { + ssh_set_error(session, SSH_FATAL, "Packet too short to read type"); + return SSH_ERROR; + } + + session->in_packet.valid = 1; + + return SSH_OK; +} + +/* + * This function places the outgoing packet buffer into an outgoing + * socket buffer + */ +static int ssh_packet_write(ssh_session session) { + int rc = SSH_ERROR; + + rc=ssh_socket_write(session->socket, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + + return rc; +} + +static int packet_send2(ssh_session session) +{ + unsigned int blocksize = 8; + unsigned int lenfield_blocksize = 0; + enum ssh_hmac_e hmac_type; + uint32_t currentlen = ssh_buffer_get_len(session->out_buffer); + struct ssh_crypto_struct *crypto = NULL; + unsigned char *hmac = NULL; + uint8_t padding_data[32] = { 0 }; + uint8_t padding_size; + uint32_t finallen, payloadsize, compsize; + uint8_t header[5] = {0}; + uint8_t type, *payload; + int rc = SSH_ERROR; + bool etm = false; + int etm_packet_offset = 0; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto) { + blocksize = crypto->out_cipher->blocksize; + lenfield_blocksize = crypto->out_cipher->lenfield_blocksize; + hmac_type = crypto->out_hmac; + etm = crypto->out_hmac_etm; + } else { + hmac_type = session->next_crypto->out_hmac; + } + + payload = (uint8_t *)ssh_buffer_get(session->out_buffer); + type = payload[0]; /* type is the first byte of the packet now */ + + payloadsize = currentlen; + if (etm) { + etm_packet_offset = sizeof(uint32_t); + lenfield_blocksize = 0; + } + + if (crypto != NULL && crypto->do_compress_out && + ssh_buffer_get_len(session->out_buffer) > 0) { + rc = compress_buffer(session,session->out_buffer); + if (rc < 0) { + goto error; + } + currentlen = ssh_buffer_get_len(session->out_buffer); + } + compsize = currentlen; + /* compressed payload + packet len (4) + padding_size len (1) */ + /* totallen - lenfield_blocksize - etm_packet_offset must be equal to 0 (mod blocksize) */ + padding_size = (blocksize - ((blocksize - lenfield_blocksize - etm_packet_offset + currentlen + 5) % blocksize)); + if (padding_size < 4) { + padding_size += blocksize; + } + + if (crypto != NULL) { + int ok; + + ok = ssh_get_random(padding_data, padding_size, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + goto error; + } + } + + finallen = currentlen - etm_packet_offset + padding_size + 1; + + PUSH_BE_U32(header, 0, finallen); + PUSH_BE_U8(header, 4, padding_size); + + rc = ssh_buffer_prepend_data(session->out_buffer, + header, + sizeof(header)); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_data(session->out_buffer, padding_data, padding_size); + if (rc < 0) { + goto error; + } + +#ifdef WITH_PCAP + if (session->pcap_ctx != NULL) { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_OUT, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + } +#endif + + hmac = ssh_packet_encrypt(session, + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); + /* XXX This returns null before switching on crypto, with none MAC + * and on various errors. + * We should distinguish between these cases to avoid hiding errors. */ + if (hmac != NULL) { + rc = ssh_buffer_add_data(session->out_buffer, + hmac, + (uint32_t)hmac_digest_len(hmac_type)); + if (rc < 0) { + goto error; + } + } + + rc = ssh_packet_write(session); + if (rc == SSH_ERROR) { + goto error; + } + session->send_seq++; + if (crypto != NULL) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = crypto->out_cipher; + cipher->packets++; + cipher->blocks += payloadsize / cipher->blocksize; + } + if (session->raw_counter != NULL) { + session->raw_counter->out_bytes += payloadsize; + session->raw_counter->out_packets++; + } + + SSH_LOG(SSH_LOG_PACKET, + "packet: wrote [type=%u, len=%" PRIu32 ", padding_size=%hhd, comp=%" PRIu32 ", " + "payload=%" PRIu32 "]", + type, + finallen, + padding_size, + compsize, + payloadsize); + + rc = ssh_buffer_reinit(session->out_buffer); + if (rc < 0) { + rc = SSH_ERROR; + goto error; + } + + /* We sent the NEWKEYS so any further packet needs to be encrypted + * with the new keys. We can not switch both directions (need to decrypt + * peer NEWKEYS) and we do not want to wait for the peer NEWKEYS + * too, so we will switch only the OUT direction now. + */ + if (type == SSH2_MSG_NEWKEYS) { + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_OUT); + } +error: + return rc; /* SSH_OK, AGAIN or ERROR */ +} + +static bool +ssh_packet_is_kex(unsigned char type) +{ + return type >= SSH2_MSG_DISCONNECT && + type <= SSH2_MSG_KEX_DH_GEX_REQUEST && + type != SSH2_MSG_SERVICE_REQUEST && + type != SSH2_MSG_SERVICE_ACCEPT && + type != SSH2_MSG_IGNORE && + type != SSH2_MSG_EXT_INFO; +} + +static bool +ssh_packet_in_rekey(ssh_session session) +{ + /* We know we are rekeying if we are authenticated and the DH + * status is not finished, but we only queue packets until we've + * sent our NEWKEYS. + */ + return (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) && + (session->dh_handshake_state != DH_STATE_FINISHED) && + (session->dh_handshake_state != DH_STATE_NEWKEYS_SENT); +} + +int ssh_packet_send(ssh_session session) +{ + uint32_t payloadsize; + uint8_t type, *payload; + bool need_rekey, in_rekey; + int rc; + + payloadsize = ssh_buffer_get_len(session->out_buffer); + if (payloadsize < 1) { + return SSH_ERROR; + } + + payload = (uint8_t *)ssh_buffer_get(session->out_buffer); + type = payload[0]; /* type is the first byte of the packet now */ + need_rekey = ssh_packet_need_rekey(session, payloadsize); + in_rekey = ssh_packet_in_rekey(session); + + /* The rekey is triggered here. After that, only the key exchange + * packets can be sent, until we send our NEWKEYS. + */ + if (need_rekey || (in_rekey && !ssh_packet_is_kex(type))) { + if (need_rekey) { + SSH_LOG(SSH_LOG_PACKET, "Outgoing packet triggered rekey"); + } + /* Queue the current packet -- we will send it after the rekey */ + SSH_LOG(SSH_LOG_PACKET, "Queuing packet type %d", type); + rc = ssh_list_append(session->out_queue, session->out_buffer); + if (rc != SSH_OK) { + return SSH_ERROR; + } + session->out_buffer = ssh_buffer_new(); + if (session->out_buffer == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (need_rekey) { + /* Send the KEXINIT packet instead. + * This recursively calls the packet_send(), but it should + * not get into rekeying again. + * After that we need to handle the key exchange responses + * up to the point where we can send the rest of the queue. + */ + return ssh_send_rekex(session); + } + return SSH_OK; + } + + /* Send the packet normally */ + rc = packet_send2(session); + + /* We finished the key exchange so we can try to send our queue now */ + if (rc == SSH_OK && type == SSH2_MSG_NEWKEYS) { + struct ssh_iterator *it = NULL; + + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->send_seq = 0; + } + for (it = ssh_list_get_iterator(session->out_queue); + it != NULL; + it = ssh_list_get_iterator(session->out_queue)) { + struct ssh_buffer_struct *next_buffer = NULL; + + /* Peek only -- do not remove from queue yet */ + next_buffer = (struct ssh_buffer_struct *)it->data; + payloadsize = ssh_buffer_get_len(next_buffer); + if (ssh_packet_need_rekey(session, payloadsize)) { + /* Sigh ... we still can not send this packet. Repeat. */ + SSH_LOG(SSH_LOG_PACKET, "Queued packet triggered rekey"); + return ssh_send_rekex(session); + } + SSH_BUFFER_FREE(session->out_buffer); + session->out_buffer = ssh_list_pop_head(struct ssh_buffer_struct *, + session->out_queue); + payload = (uint8_t *)ssh_buffer_get(session->out_buffer); + type = payload[0]; + SSH_LOG(SSH_LOG_PACKET, "Dequeue packet type %d", type); + rc = packet_send2(session); + if (rc != SSH_OK) { + return rc; + } + } + } + + return rc; +} + +static void +ssh_init_rekey_state(struct ssh_session_struct *session, + struct ssh_cipher_struct *cipher) +{ + /* Reset the counters: should be NOOP */ + cipher->packets = 0; + cipher->blocks = 0; + + /* Default rekey limits for ciphers as specified in RFC4344, Section 3.2 */ + if (cipher->blocksize >= 16) { + /* For larger block size (L bits) use maximum of 2**(L/4) blocks */ + cipher->max_blocks = (uint64_t)1 << (cipher->blocksize*2); + } else { + /* For smaller blocks use limit of 1 GB as recommended in RFC4253 */ + cipher->max_blocks = ((uint64_t)1 << 30) / cipher->blocksize; + } + /* If we have limit provided by user, use the smaller one */ + if (session->opts.rekey_data != 0) { + cipher->max_blocks = MIN(cipher->max_blocks, + session->opts.rekey_data / cipher->blocksize); + } + + SSH_LOG(SSH_LOG_DEBUG, + "Set rekey after %" PRIu64 " blocks", + cipher->max_blocks); +} + +/* + * Once we got SSH2_MSG_NEWKEYS we can switch next_crypto and + * current_crypto for our desired direction + */ +int +ssh_packet_set_newkeys(ssh_session session, + enum ssh_crypto_direction_e direction) +{ + struct ssh_cipher_struct *in_cipher = NULL, *out_cipher = NULL; + int rc; + + SSH_LOG(SSH_LOG_TRACE, + "called, direction =%s%s", + direction & SSH_DIRECTION_IN ? " IN " : "", + direction & SSH_DIRECTION_OUT ? " OUT " : ""); + + if (session->next_crypto == NULL) { + return SSH_ERROR; + } + + session->next_crypto->used |= direction; + if (session->current_crypto != NULL) { + if (session->current_crypto->used & direction) { + SSH_LOG(SSH_LOG_TRACE, "This direction isn't used anymore."); + } + /* Mark the current requested direction unused */ + session->current_crypto->used &= ~direction; + } + + /* Both sides switched: do the actual switch now */ + if (session->next_crypto->used == SSH_DIRECTION_BOTH) { + size_t session_id_len; + + if (session->current_crypto != NULL) { + crypto_free(session->current_crypto); + session->current_crypto = NULL; + } + + session->current_crypto = session->next_crypto; + session->current_crypto->used = SSH_DIRECTION_BOTH; + + /* Initialize the next_crypto structure */ + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + session_id_len = session->current_crypto->session_id_len; + session->next_crypto->session_id = malloc(session_id_len); + if (session->next_crypto->session_id == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + memcpy(session->next_crypto->session_id, + session->current_crypto->session_id, + session_id_len); + session->next_crypto->session_id_len = session_id_len; + + return SSH_OK; + } + + /* Initialize common structures so the next context can be used in + * either direction */ + if (session->client) { + /* The server has this part already done */ + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + /* + * Set the cryptographic functions for the next crypto + * (it is needed for ssh_generate_session_keys for key lengths) + */ + rc = crypt_set_algorithms_client(session); + if (rc < 0) { + return SSH_ERROR; + } + } + + if (ssh_generate_session_keys(session) < 0) { + return SSH_ERROR; + } + + in_cipher = session->next_crypto->in_cipher; + out_cipher = session->next_crypto->out_cipher; + if (in_cipher == NULL || out_cipher == NULL) { + return SSH_ERROR; + } + + /* Initialize rekeying states */ + ssh_init_rekey_state(session, out_cipher); + ssh_init_rekey_state(session, in_cipher); + if (session->opts.rekey_time != 0) { + ssh_timestamp_init(&session->last_rekey_time); + SSH_LOG(SSH_LOG_DEBUG, "Set rekey after %" PRIu32 " seconds", + session->opts.rekey_time/1000); + } + + if (in_cipher->set_decrypt_key) { + /* Initialize the encryption and decryption keys in next_crypto */ + rc = in_cipher->set_decrypt_key(in_cipher, + session->next_crypto->decryptkey, + session->next_crypto->decryptIV); + if (rc < 0) { + /* On error, make sure it is not used */ + session->next_crypto->used = 0; + return SSH_ERROR; + } + } + + if (out_cipher->set_encrypt_key) { + rc = out_cipher->set_encrypt_key(out_cipher, + session->next_crypto->encryptkey, + session->next_crypto->encryptIV); + if (rc < 0) { + /* On error, make sure it is not used */ + session->next_crypto->used = 0; + return SSH_ERROR; + } + } + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/packet_cb.c b/src/libs/libssh-0.12.2/src/packet_cb.c new file mode 100644 index 000000000000..6228b44a48c6 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/packet_cb.c @@ -0,0 +1,372 @@ +/* + * packet.c - packet building functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#ifdef HAVE_ARPA_INET_H +#include +#endif +#ifdef WITH_GSSAPI +#include "libssh/gssapi.h" +#include +#endif + +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/misc.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/session.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#include "libssh/curve25519.h" + +/** + * @internal + * + * @brief Handle a SSH_DISCONNECT packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_disconnect_callback) +{ + int rc; + uint32_t code = 0; + char *error = NULL; + ssh_string error_s = NULL; + + (void)user; + (void)type; + + rc = ssh_buffer_get_u32(packet, &code); + if (rc != 0) { + code = ntohl(code); + } + + error_s = ssh_buffer_get_ssh_string(packet); + if (error_s != NULL) { + error = ssh_string_to_char(error_s); + SSH_STRING_FREE(error_s); + } + + if (error != NULL) { + session->peer_discon_msg = strdup(error); + } + + SSH_LOG(SSH_LOG_PACKET, + "Received SSH_MSG_DISCONNECT %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + ssh_set_error(session, + SSH_FATAL, + "Received SSH_MSG_DISCONNECT: %" PRIu32 ":%s", + code, + error != NULL ? error : "no error"); + SAFE_FREE(error); + + ssh_session_socket_close(session); + /* correctly handle disconnect during authorization */ + session->auth.state = SSH_AUTH_STATE_FAILED; + + /* TODO: handle a graceful disconnect */ + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_IGNORE packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_ignore_callback) +{ + (void)session; /* unused */ + (void)user; + (void)type; + (void)packet; + + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_IGNORE packet"); + + return SSH_PACKET_USED; +} + +/** + * @internal + * + * @brief Handle a SSH_DEBUG packet. + */ +SSH_PACKET_CALLBACK(ssh_packet_debug_callback) +{ + uint8_t always_display = -1; + char *message = NULL; + int rc; + + (void)session; /* unused */ + (void)type; + (void)user; + + rc = ssh_buffer_unpack(packet, "bs", &always_display, &message); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Error reading debug message"); + return SSH_PACKET_USED; + } + SSH_LOG(SSH_LOG_DEBUG, + "Received SSH_MSG_DEBUG packet with message %s%s", + message, + always_display != 0 ? " (always display)" : ""); + SAFE_FREE(message); + + return SSH_PACKET_USED; +} + +SSH_PACKET_CALLBACK(ssh_packet_newkeys) +{ + ssh_string sig_blob = NULL; + ssh_signature sig = NULL; + int rc; + + (void)packet; + (void)user; + (void)type; + + SSH_LOG(SSH_LOG_DEBUG, "Received SSH_MSG_NEWKEYS"); + + if (session->session_state != SSH_SESSION_STATE_DH || + session->dh_handshake_state != DH_STATE_NEWKEYS_SENT) { + ssh_set_error(session, + SSH_FATAL, + "ssh_packet_newkeys called in wrong state : %d:%d", + session->session_state, + session->dh_handshake_state); + goto error; + } + + if (session->flags & SSH_SESSION_FLAG_KEX_STRICT) { + /* reset packet sequence number when running in strict kex mode */ + session->recv_seq = 0; + /* Check that we aren't tainted */ + if (session->flags & SSH_SESSION_FLAG_KEX_TAINTED) { + ssh_set_error(session, + SSH_FATAL, + "Received unexpected packets in strict KEX mode."); + goto error; + } + } + + if (session->server) { + /* server things are done in server.c */ + session->dh_handshake_state=DH_STATE_FINISHED; + } else { +#ifdef WITH_GSSAPI + if (ssh_kex_is_gss(session->next_crypto)) { + OM_uint32 maj_stat, min_stat; + gss_buffer_desc mic = GSS_C_EMPTY_BUFFER, msg = GSS_C_EMPTY_BUFFER; + + if (session->gssapi == NULL || session->gssapi->ctx == NULL) { + ssh_set_error(session, SSH_FATAL, "GSSAPI context not initialized"); + goto error; + } + + if (session->gssapi_key_exchange_mic == NULL) { + ssh_set_error(session, + SSH_FATAL, + "GSSAPI mic not set"); + goto error; + } + + mic.length = ssh_string_len(session->gssapi_key_exchange_mic); + mic.value = ssh_string_data(session->gssapi_key_exchange_mic); + + msg.length = session->next_crypto->digest_len; + msg.value = session->next_crypto->secret_hash; + + maj_stat = gss_verify_mic(&min_stat, + session->gssapi->ctx, + &msg, + &mic, + NULL); + if (maj_stat != GSS_S_COMPLETE) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify mic after GSSAPI Key Exchange"); + goto error; + } + SSH_STRING_FREE(session->gssapi_key_exchange_mic); + } else +#endif + { + ssh_key server_key = NULL; + + /* client */ + + /* Verify the host's signature. FIXME do it sooner */ + sig_blob = session->next_crypto->dh_server_signature; + session->next_crypto->dh_server_signature = NULL; + + /* get the server public key */ + server_key = ssh_dh_get_next_server_publickey(session); + if (server_key == NULL) { + goto error; + } + + rc = ssh_pki_import_signature_blob(sig_blob, server_key, &sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc != SSH_OK) { + goto error; + } + + /* Check if signature from server matches user preferences */ + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + rc = match_group(session->opts.wanted_methods[SSH_HOSTKEYS], + sig->type_c); + if (rc == 0) { + ssh_set_error( + session, + SSH_FATAL, + "Public key from server (%s) doesn't match user " + "preference (%s)", + sig->type_c, + session->opts.wanted_methods[SSH_HOSTKEYS]); + goto error; + } + } + + rc = ssh_pki_signature_verify(session, + sig, + server_key, + session->next_crypto->secret_hash, + session->next_crypto->digest_len); + SSH_SIGNATURE_FREE(sig); + if (rc == SSH_ERROR) { + ssh_set_error(session, + SSH_FATAL, + "Failed to verify server hostkey signature"); + goto error; + } + } + SSH_LOG(SSH_LOG_DEBUG, "Signature verified and valid"); + + /* When receiving this packet, we switch on the incoming crypto. */ + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); + if (rc != SSH_OK) { + goto error; + } + } + session->dh_handshake_state = DH_STATE_FINISHED; + session->ssh_connection_callback(session); + return SSH_PACKET_USED; + +error: +#ifdef WITH_GSSAPI + SSH_STRING_FREE(session->gssapi_key_exchange_mic); +#endif + SSH_SIGNATURE_FREE(sig); + ssh_string_burn(sig_blob); + SSH_STRING_FREE(sig_blob); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +/** + * @internal + * @brief handles a SSH_SERVICE_ACCEPT packet + * + */ +SSH_PACKET_CALLBACK(ssh_packet_service_accept) +{ + (void)packet; + (void)type; + (void)user; + + session->auth.service_state = SSH_AUTH_SERVICE_ACCEPTED; + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_SERVICE_ACCEPT"); + + return SSH_PACKET_USED; +} + +/** + * @internal + * @brief handles a SSH2_MSG_EXT_INFO packet defined in RFC 8308 + * + */ +SSH_PACKET_CALLBACK(ssh_packet_ext_info) +{ + int rc; + uint32_t nr_extensions = 0; + uint32_t i; + + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_MSG_EXT_INFO"); + + rc = ssh_buffer_get_u32(packet, &nr_extensions); + if (rc == 0) { + SSH_LOG(SSH_LOG_PACKET, "Failed to read number of extensions"); + return SSH_PACKET_USED; + } + + nr_extensions = ntohl(nr_extensions); + if (nr_extensions > 128) { + SSH_LOG(SSH_LOG_PACKET, "Invalid number of extensions"); + return SSH_PACKET_USED; + } + + SSH_LOG(SSH_LOG_PACKET, "Follows %" PRIu32 " extensions", nr_extensions); + + for (i = 0; i < nr_extensions; i++) { + char *name = NULL; + char *value = NULL; + + rc = ssh_buffer_unpack(packet, "ss", &name, &value); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PACKET, "Error reading extension name-value pair"); + return SSH_PACKET_USED; + } + + if (strcmp(name, "server-sig-algs") == 0) { + /* TODO check for NULL bytes */ + SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value); + + rc = match_group(value, "rsa-sha2-512"); + if (rc == 1) { + session->extensions |= SSH_EXT_SIG_RSA_SHA512; + } + + rc = match_group(value, "rsa-sha2-256"); + if (rc == 1) { + session->extensions |= SSH_EXT_SIG_RSA_SHA256; + } + } else if (strcmp(name, "publickey-hostbound@openssh.com") == 0) { + SSH_LOG(SSH_LOG_PACKET, "Extension: %s=<%s>", name, value); + session->extensions |= SSH_EXT_PUBLICKEY_HOSTBOUND; + } else { + SSH_LOG(SSH_LOG_PACKET, "Unknown extension: %s", name); + } + free(name); + free(value); + } + + return SSH_PACKET_USED; +} diff --git a/src/libs/libssh-0.12.2/src/packet_crypt.c b/src/libs/libssh-0.12.2/src/packet_crypt.c new file mode 100644 index 000000000000..12846fe5836d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/packet_crypt.c @@ -0,0 +1,330 @@ +/* + * crypt.c - blowfish-cbc code + * + * This file is part of the SSH Library + * + * Copyright (c) 2003 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#ifdef OPENSSL_CRYPTO +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/wrapper.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/bytearray.h" + +/** @internal + * @brief decrypt the packet length from a raw encrypted packet, and store the first decrypted + * blocksize. + * @returns native byte-ordered decrypted length of the upcoming packet + */ +uint32_t ssh_packet_decrypt_len(ssh_session session, + uint8_t *destination, + uint8_t *source) +{ + struct ssh_crypto_struct *crypto = NULL; + uint32_t decrypted; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL) { + if (crypto->in_cipher->aead_decrypt_length != NULL) { + rc = crypto->in_cipher->aead_decrypt_length( + crypto->in_cipher, source, destination, + crypto->in_cipher->lenfield_blocksize, + session->recv_seq); + } else { + rc = ssh_packet_decrypt( + session, + destination, + source, + 0, + crypto->in_cipher->blocksize); + } + if (rc < 0) { + return 0; + } + } else { + memcpy(destination, source, 8); + } + memcpy(&decrypted,destination,sizeof(decrypted)); + + return ntohl(decrypted); +} + +/** @internal + * @brief decrypts the content of an SSH packet. + * @param[source] source packet, including the encrypted length field + * @param[start] index in the packet that was not decrypted yet. + * @param[encrypted_size] size of the encrypted data to be decrypted after start. + */ +int ssh_packet_decrypt(ssh_session session, + uint8_t *destination, + uint8_t *source, + size_t start, + size_t encrypted_size) +{ + struct ssh_crypto_struct *crypto = NULL; + struct ssh_cipher_struct *cipher = NULL; + + if (encrypted_size <= 0) { + return SSH_ERROR; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto == NULL) { + return SSH_ERROR; + } + cipher = crypto->in_cipher; + + if (encrypted_size % cipher->blocksize != 0) { + ssh_set_error(session, + SSH_FATAL, + "Cryptographic functions must be used on multiple of " + "blocksize (received %zu)", + encrypted_size); + return SSH_ERROR; + } + + if (cipher->aead_decrypt != NULL) { + return cipher->aead_decrypt(cipher, + source, + destination, + encrypted_size, + session->recv_seq); + } else { + cipher->decrypt(cipher, source + start, destination, encrypted_size); + } + + return 0; +} + +unsigned char *ssh_packet_encrypt(ssh_session session, void *data, size_t len) +{ + struct ssh_crypto_struct *crypto = NULL; + struct ssh_cipher_struct *cipher = NULL; + HMACCTX ctx = NULL; + char *out = NULL; + int etm_packet_offset = 0, rc; + unsigned int blocksize; + size_t finallen = DIGEST_MAX_LEN; + uint32_t seq, lenfield_blocksize; + enum ssh_hmac_e type; + bool etm; + + assert(len); + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto == NULL) { + return NULL; /* nothing to do here */ + } + + blocksize = crypto->out_cipher->blocksize; + lenfield_blocksize = crypto->out_cipher->lenfield_blocksize; + + type = crypto->out_hmac; + etm = crypto->out_hmac_etm; + + if (etm) { + etm_packet_offset = sizeof(uint32_t); + } + + if ((len - lenfield_blocksize - etm_packet_offset) % blocksize != 0) { + ssh_set_error(session, SSH_FATAL, "Cryptographic functions must be set" + " on at least one blocksize (received %zu)", len); + return NULL; + } + out = calloc(1, len); + if (out == NULL) { + return NULL; + } + + seq = ntohl(session->send_seq); + cipher = crypto->out_cipher; + + if (cipher->aead_encrypt != NULL) { + cipher->aead_encrypt(cipher, data, out, len, + crypto->hmacbuf, session->send_seq); + memcpy(data, out, len); + } else { + if (type != SSH_HMAC_NONE) { + ctx = hmac_init(crypto->encryptMAC, hmac_digest_len(type), type); + if (ctx == NULL) { + SAFE_FREE(out); + return NULL; + } + + if (!etm) { + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + } + } + + cipher->encrypt(cipher, (uint8_t*)data + etm_packet_offset, out, len - etm_packet_offset); + memcpy((uint8_t*)data + etm_packet_offset, out, len - etm_packet_offset); + + if (type != SSH_HMAC_NONE) { + if (etm) { + PUSH_BE_U32(data, 0, len - etm_packet_offset); + rc = hmac_update(ctx, (unsigned char *)&seq, sizeof(uint32_t)); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_update(ctx, data, len); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + rc = hmac_final(ctx, crypto->hmacbuf, &finallen); + if (rc != 1) { + SAFE_FREE(out); + return NULL; + } + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("mac: ", data, len); + if (finallen != hmac_digest_len(type)) { + printf("Final len is %zu\n", finallen); + } + ssh_log_hexdump("Packet hmac", crypto->hmacbuf, hmac_digest_len(type)); +#endif + } + } + ssh_burn(out, len); + SAFE_FREE(out); + + return crypto->hmacbuf; +} + +/** + * @internal + * + * @brief Verify the hmac of a packet + * + * @param session The session to use. + * @param data The pointer to the data to verify the hmac from. + * @param len The length of the given data. + * @param mac The mac to compare with the hmac. + * + * @return 0 if hmac and mac are equal, < 0 if not or an error + * occurred. + */ +int ssh_packet_hmac_verify(ssh_session session, + const void *data, + size_t len, + uint8_t *mac, + enum ssh_hmac_e type) +{ + struct ssh_crypto_struct *crypto = NULL; + unsigned char hmacbuf[DIGEST_MAX_LEN] = {0}; + HMACCTX ctx = NULL; + size_t hmaclen = DIGEST_MAX_LEN; + uint32_t seq; + int cmp; + int rc; + + /* AEAD types have no mac checking */ + if (type == SSH_HMAC_AEAD_POLY1305 || + type == SSH_HMAC_AEAD_GCM) { + return SSH_OK; + } + + crypto = ssh_packet_get_current_crypto(session, + SSH_DIRECTION_IN); + if (crypto == NULL) { + return SSH_ERROR; + } + + ctx = hmac_init(crypto->decryptMAC, + hmac_digest_len(type), + type); + if (ctx == NULL) { + return SSH_ERROR; + } + + seq = htonl(session->recv_seq); + + rc = hmac_update(ctx, + (unsigned char *) &seq, + sizeof(uint32_t)); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_update(ctx, + data, + len); + if (rc != 1) { + return SSH_ERROR; + } + rc = hmac_final(ctx, + hmacbuf, + &hmaclen); + if (rc != 1) { + return SSH_ERROR; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("received mac", + mac, + hmaclen); + ssh_log_hexdump("Computed mac", + hmacbuf, + hmaclen); + ssh_log_hexdump("seq", + (unsigned char *)&seq, + sizeof(uint32_t)); +#endif + cmp = secure_memcmp(mac, + hmacbuf, + hmaclen); + if (cmp == 0) { + return SSH_OK; + } + + return SSH_ERROR; +} diff --git a/src/libs/libssh-0.12.2/src/pcap.c b/src/libs/libssh-0.12.2/src/pcap.c new file mode 100644 index 000000000000..1a98e1cd1928 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pcap.c @@ -0,0 +1,585 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* pcap.c */ +#include "config.h" +#ifdef WITH_PCAP + +#include +#ifdef _WIN32 +#include +#include +#else +#include +#include +#endif +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include + +#include "libssh/libssh.h" +#include "libssh/pcap.h" +#include "libssh/session.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" + +/** + * @defgroup libssh_pcap The libssh pcap functions + * @ingroup libssh + * + * The pcap file generation + * + * @{ + */ + +/* The header of a pcap file is the following. We are not going to make it + * very complicated. + * Just for information. + */ +struct pcap_hdr_s { + uint32_t magic_number; /* magic number */ + uint16_t version_major; /* major version number */ + uint16_t version_minor; /* minor version number */ + int32_t thiszone; /* GMT to local correction */ + uint32_t sigfigs; /* accuracy of timestamps */ + uint32_t snaplen; /* max length of captured packets, in octets */ + uint32_t network; /* data link type */ +}; + +#define PCAP_MAGIC 0xa1b2c3d4 +#define PCAP_VERSION_MAJOR 2 +#define PCAP_VERSION_MINOR 4 + +#define DLT_RAW 12 /* raw IP */ + +/* TCP flags */ +#define TH_FIN 0x01 +#define TH_SYN 0x02 +#define TH_RST 0x04 +#define TH_PUSH 0x08 +#define TH_ACK 0x10 +#define TH_URG 0x20 + +/* The header of a pcap packet. + * Just for information. + */ +struct pcaprec_hdr_s { + uint32_t ts_sec; /* timestamp seconds */ + uint32_t ts_usec; /* timestamp microseconds */ + uint32_t incl_len; /* number of octets of packet saved in file */ + uint32_t orig_len; /* actual length of packet */ +}; + +/** @private + * @brief a pcap context expresses the state of a pcap dump + * in a SSH session only. Multiple pcap contexts may be used into + * a single pcap file. + */ +struct ssh_pcap_context_struct { + ssh_session session; + ssh_pcap_file file; + int connected; + /* All of this information is useful to generate + * the dummy IP and TCP packets + */ + uint32_t ipsource; + uint32_t ipdest; + uint16_t portsource; + uint16_t portdest; + uint32_t outsequence; + uint32_t insequence; +}; + +/** @private + * @brief a pcap file expresses the state of a pcap file which may + * contain several streams. + */ +struct ssh_pcap_file_struct { + FILE *output; + uint16_t ipsequence; +}; + +/** + * @brief create a new ssh_pcap_file object + */ +ssh_pcap_file ssh_pcap_file_new(void) +{ + struct ssh_pcap_file_struct *pcap = NULL; + + pcap = calloc(1, sizeof(struct ssh_pcap_file_struct)); + if (pcap == NULL) { + return NULL; + } + + return pcap; +} + +/** @internal + * @brief writes a packet on file + */ +static int ssh_pcap_file_write(ssh_pcap_file pcap, ssh_buffer packet) +{ + int err; + uint32_t len; + if (pcap == NULL || pcap->output == NULL) { + return SSH_ERROR; + } + len = ssh_buffer_get_len(packet); + err = fwrite(ssh_buffer_get(packet), len, 1, pcap->output); + if (err < 0) { + return SSH_ERROR; + } else { + return SSH_OK; + } +} + +/** @internal + * @brief prepends a packet with the pcap header and writes packet + * on file + */ +int ssh_pcap_file_write_packet(ssh_pcap_file pcap, ssh_buffer packet, uint32_t original_len) +{ + ssh_buffer header = ssh_buffer_new(); + struct timeval now; + int err; + + if (header == NULL) { + return SSH_ERROR; + } + + gettimeofday(&now, NULL); + err = ssh_buffer_allocate_size(header, + sizeof(uint32_t) * 4 + + ssh_buffer_get_len(packet)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(now.tv_sec)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(now.tv_usec)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(ssh_buffer_get_len(packet))); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(original_len)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_buffer(header, packet); + if (err < 0) { + goto error; + } + err = ssh_pcap_file_write(pcap, header); +error: + SSH_BUFFER_FREE(header); + return err; +} + +/** + * @brief opens a new pcap file and creates header + */ +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename) +{ + ssh_buffer header = NULL; + int err; + + if (pcap == NULL) { + return SSH_ERROR; + } + if (pcap->output) { + fclose(pcap->output); + pcap->output = NULL; + } + pcap->output = fopen(filename, "wb"); + if (pcap->output == NULL) { + return SSH_ERROR; + } + header = ssh_buffer_new(); + if (header == NULL) { + return SSH_ERROR; + } + err = ssh_buffer_allocate_size(header, + sizeof(uint32_t) * 5 + + sizeof(uint16_t) * 2); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u32(header, htonl(PCAP_MAGIC)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MAJOR)); + if (err < 0) { + goto error; + } + err = ssh_buffer_add_u16(header, htons(PCAP_VERSION_MINOR)); + if (err < 0) { + goto error; + } + /* currently hardcode GMT to 0 */ + err = ssh_buffer_add_u32(header, htonl(0)); + if (err < 0) { + goto error; + } + /* accuracy */ + err = ssh_buffer_add_u32(header, htonl(0)); + if (err < 0) { + goto error; + } + /* size of the biggest packet */ + err = ssh_buffer_add_u32(header, htonl(MAX_PACKET_LEN)); + if (err < 0) { + goto error; + } + /* we will write sort-of IP */ + err = ssh_buffer_add_u32(header, htonl(DLT_RAW)); + if (err < 0) { + goto error; + } + err = ssh_pcap_file_write(pcap,header); +error: + SSH_BUFFER_FREE(header); + return err; +} + +int ssh_pcap_file_close(ssh_pcap_file pcap) +{ + int err; + + if (pcap == NULL || pcap->output == NULL) { + return SSH_ERROR; + } + err = fclose(pcap->output); + pcap->output = NULL; + if (err != 0) { + return SSH_ERROR; + } else { + return SSH_OK; + } +} + +void ssh_pcap_file_free(ssh_pcap_file pcap) +{ + ssh_pcap_file_close(pcap); + SAFE_FREE(pcap); +} + + +/** @internal + * @brief allocates a new ssh_pcap_context object + */ +ssh_pcap_context ssh_pcap_context_new(ssh_session session) +{ + ssh_pcap_context ctx = NULL; + + ctx = calloc(1, sizeof(struct ssh_pcap_context_struct)); + if (ctx == NULL) { + ssh_set_error_oom(session); + return NULL; + } + ctx->session = session; + return ctx; +} + +void ssh_pcap_context_free(ssh_pcap_context ctx) +{ + SAFE_FREE(ctx); +} + +void ssh_pcap_context_set_file(ssh_pcap_context ctx, ssh_pcap_file pcap) +{ + ctx->file = pcap; +} + +/** @internal + * @brief sets the IP and port parameters in the connection + */ +static int ssh_pcap_context_connect(ssh_pcap_context ctx) +{ + ssh_session session=ctx->session; + struct sockaddr_in local = { + .sin_family = AF_UNSPEC, + }; + struct sockaddr_in remote = { + .sin_family = AF_UNSPEC, + }; + socket_t fd; + socklen_t len; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + if (session == NULL) { + return SSH_ERROR; + } + + if (session->socket == NULL) { + return SSH_ERROR; + } + + fd = ssh_socket_get_fd(session->socket); + + /* TODO: adapt for windows */ + if (fd < 0) { + return SSH_ERROR; + } + + len = sizeof(local); + rc = getsockname(fd, (struct sockaddr *)&local, &len); + if (rc < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Getting local IP address: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + len = sizeof(remote); + rc = getpeername(fd, (struct sockaddr *)&remote, &len); + if (rc < 0) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Getting remote IP address: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + + if (local.sin_family != AF_INET) { + ssh_set_error(session, + SSH_REQUEST_DENIED, + "Only IPv4 supported for pcap logging"); + return SSH_ERROR; + } + + memcpy(&ctx->ipsource, &local.sin_addr, sizeof(ctx->ipsource)); + memcpy(&ctx->ipdest, &remote.sin_addr, sizeof(ctx->ipdest)); + memcpy(&ctx->portsource, &local.sin_port, sizeof(ctx->portsource)); + memcpy(&ctx->portdest, &remote.sin_port, sizeof(ctx->portdest)); + + ctx->connected = 1; + return SSH_OK; +} + +#define IPHDR_LEN 20 +#define TCPHDR_LEN 20 +#define TCPIPHDR_LEN (IPHDR_LEN + TCPHDR_LEN) +/** @internal + * @brief write a SSH packet as a TCP over IP in a pcap file + * @param ctx open pcap context + * @param direction SSH_PCAP_DIRECTION_IN if the packet has been received + * @param direction SSH_PCAP_DIRECTION_OUT if the packet has been emitted + * @param data pointer to the data to write + * @param len data to write in the pcap file. May be smaller than origlen. + * @param origlen number of bytes of complete data. + * @returns SSH_OK write is successful + * @returns SSH_ERROR an error happened. + */ +int ssh_pcap_context_write(ssh_pcap_context ctx, + enum ssh_pcap_direction direction, + void *data, + uint32_t len, + uint32_t origlen) +{ + ssh_buffer ip; + int rc; + + if (ctx == NULL || ctx->file == NULL) { + return SSH_ERROR; + } + if (ctx->connected == 0) { + if (ssh_pcap_context_connect(ctx) == SSH_ERROR) { + return SSH_ERROR; + } + } + ip = ssh_buffer_new(); + if (ip == NULL) { + ssh_set_error_oom(ctx->session); + return SSH_ERROR; + } + + /* build an IP packet */ + rc = ssh_buffer_pack(ip, + "bbwwwbbw", + 4 << 4 | 5, /* V4, 20 bytes */ + 0, /* tos */ + origlen + TCPIPHDR_LEN, /* total len */ + ctx->file->ipsequence, /* IP id number */ + 0, /* fragment offset */ + 64, /* TTL */ + 6, /* protocol TCP=6 */ + 0); /* checksum */ + + ctx->file->ipsequence++; + if (rc != SSH_OK) { + goto error; + } + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_add_u32(ip, ctx->ipsource); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(ip, ctx->ipdest); + if (rc < 0) { + goto error; + } + } else { + rc = ssh_buffer_add_u32(ip, ctx->ipdest); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u32(ip, ctx->ipsource); + if (rc < 0) { + goto error; + } + } + /* TCP */ + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_add_u16(ip, ctx->portsource); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u16(ip, ctx->portdest); + if (rc < 0) { + goto error; + } + } else { + rc = ssh_buffer_add_u16(ip, ctx->portdest); + if (rc < 0) { + goto error; + } + rc = ssh_buffer_add_u16(ip, ctx->portsource); + if (rc < 0) { + goto error; + } + } + /* sequence number */ + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_pack(ip, "d", ctx->outsequence); + if (rc != SSH_OK) { + goto error; + } + ctx->outsequence += origlen; + } else { + rc = ssh_buffer_pack(ip, "d", ctx->insequence); + if (rc != SSH_OK) { + goto error; + } + ctx->insequence += origlen; + } + /* ack number */ + if (direction == SSH_PCAP_DIR_OUT) { + rc = ssh_buffer_pack(ip, "d", ctx->insequence); + if (rc != SSH_OK) { + goto error; + } + } else { + rc = ssh_buffer_pack(ip, "d", ctx->outsequence); + if (rc != SSH_OK) { + goto error; + } + } + + rc = ssh_buffer_pack(ip, + "bbwwwP", + 5 << 4, /* header len = 20 = 5 * 32 bits, at offset 4*/ + TH_PUSH | TH_ACK, /* flags */ + 65535, /* window */ + 0, /* checksum */ + 0, /* urgent data ptr */ + (size_t)len, data); /* actual data */ + if (rc != SSH_OK) { + goto error; + } + rc = ssh_pcap_file_write_packet(ctx->file, ip, origlen + TCPIPHDR_LEN); + +error: + SSH_BUFFER_FREE(ip); + return rc; +} + +/** @brief sets the pcap file used to trace the session + * @param current session + * @param pcap a handler to a pcap file. A pcap file may be used in several + * sessions. + * @returns SSH_ERROR in case of error, SSH_OK otherwise. + */ +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcap) +{ + ssh_pcap_context ctx = ssh_pcap_context_new(session); + if (ctx == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + ctx->file = pcap; + if (session->pcap_ctx) { + ssh_pcap_context_free(session->pcap_ctx); + } + session->pcap_ctx = ctx; + return SSH_OK; +} +/** @} */ + +#else /* WITH_PCAP */ + +/* Simple stub returning errors when no pcap compiled in */ + +#include "libssh/libssh.h" +#include "libssh/priv.h" + +int ssh_pcap_file_close(ssh_pcap_file pcap) +{ + (void)pcap; + + return SSH_ERROR; +} + +void ssh_pcap_file_free(ssh_pcap_file pcap) +{ + (void)pcap; +} + +ssh_pcap_file ssh_pcap_file_new(void) +{ + return NULL; +} +int ssh_pcap_file_open(ssh_pcap_file pcap, const char *filename) +{ + (void)pcap; + (void)filename; + + return SSH_ERROR; +} + +int ssh_set_pcap_file(ssh_session session, ssh_pcap_file pcapfile) +{ + (void)pcapfile; + + ssh_set_error(session, SSH_REQUEST_DENIED, "Pcap support not compiled in"); + return SSH_ERROR; +} + +#endif diff --git a/src/libs/libssh-0.12.2/src/pki.c b/src/libs/libssh-0.12.2/src/pki.c new file mode 100644 index 000000000000..b5fe7ccc81e1 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki.c @@ -0,0 +1,4011 @@ +/* + * pki.c + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * Copyright (c) 2011-2013 Andreas Schneider + * Copyright (c) 2019 Sahana Prasad + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** + * @defgroup libssh_pki The SSH Public Key Infrastructure + * @ingroup libssh + * + * Functions for the creation, importation and manipulation of public and + * private keys in the context of the SSH protocol + * + * @{ + */ + +#include "config.h" +#include "libssh/wrapper.h" + +#include +#include +#include +#include +#include +#include +#include + +#include "libssh/agent.h" +#include "libssh/buffer.h" +#include "libssh/keys.h" +#include "libssh/libssh.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/pki_context.h" +#include "libssh/pki_priv.h" +#include "libssh/pki_sk.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/sk_common.h" /* For SK_NOT_SUPPORTED_MSG */ + +#ifndef MAX_LINE_SIZE +#define MAX_LINE_SIZE 4096 +#endif /* NOT MAX_LINE_SIZE */ + +#define PKCS11_URI "pkcs11:" + +enum ssh_keytypes_e pki_privatekey_type_from_string(const char *privkey) +{ + const char *start = NULL; + + start = strstr(privkey, RSA_HEADER_BEGIN); + if (start != NULL) { + return SSH_KEYTYPE_RSA; + } + + start = strstr(privkey, ECDSA_HEADER_BEGIN); + if (start != 0) { + /* We don't know what the curve is at this point, so we don't actually + * know the type. We figure out the actual curve and fix things up in + * pki_private_key_from_base64 */ + return SSH_KEYTYPE_ECDSA_P256; + } + + return SSH_KEYTYPE_UNKNOWN; +} + +/** + * @brief returns the ECDSA key name ("ecdsa-sha2-nistp256" for example) + * + * @param[in] key the ssh_key whose ECDSA name to get + * + * @returns the ECDSA key name ("ecdsa-sha2-nistp256" for example) + * + * @returns "unknown" if the ECDSA key name is not known + */ +const char *ssh_pki_key_ecdsa_name(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + +#ifdef HAVE_ECC /* FIXME Better ECC check needed */ + return pki_key_ecdsa_nid_to_name(key->ecdsa_nid); +#else + return NULL; +#endif /* HAVE_ECC */ +} + +/** + * @brief creates a new empty SSH key + * + * @returns an empty ssh_key handle, or NULL on error. + */ +ssh_key ssh_key_new (void) +{ + ssh_key ptr = malloc (sizeof (struct ssh_key_struct)); + if (ptr == NULL) { + return NULL; + } + ZERO_STRUCTP(ptr); + return ptr; +} + +/** + * @internal + * + * @brief Initialize a new SSH key by duplicating common fields from an existing + * key. + * + * This function creates a new SSH key and copies the common fields from the + * source key, including the key type, type string, flags, and security key + * fields if applicable. This is a helper function used by key duplication + * routines. + * + * @param[in] key The source ssh_key to copy common fields from. + * @param[in] demote Whether to demote the new key to public only. If non-zero, + * only the public fields will be copied and the flags will + * be set accordingly. + * + * @return A new ssh_key with common fields initialized, or NULL on + * error. + * + * @note The caller is responsible for freeing the returned key with + * ssh_key_free(). + */ +ssh_key pki_key_dup_common_init(const ssh_key key, int demote) +{ + ssh_key new = NULL; + + if (key == NULL) { + return NULL; + } + + new = ssh_key_new(); + if (new == NULL) { + return NULL; + } + + new->type = key->type; + new->type_c = key->type_c; + if (demote) { + new->flags = SSH_KEY_FLAG_PUBLIC; + } else { + new->flags = key->flags; + } + + /* Copy security key fields if present */ + if (is_sk_key_type(key->type)) { + new->sk_application = ssh_string_copy(key->sk_application); + if (new->sk_application == NULL) { + goto fail; + } + + if (key->sk_user_id != NULL) { + new->sk_user_id = ssh_string_copy(key->sk_user_id); + if (new->sk_user_id == NULL) { + goto fail; + } + } + + if (!demote) { + new->sk_flags = key->sk_flags; + + new->sk_key_handle = ssh_string_copy(key->sk_key_handle); + if (new->sk_key_handle == NULL) { + goto fail; + } + + new->sk_reserved = ssh_string_copy(key->sk_reserved); + if (new->sk_reserved == NULL) { + goto fail; + } + } + } + + return new; + +fail: + SSH_KEY_FREE(new); + return NULL; +} + +/** + * @brief duplicates the key + * + * @param key An ssh_key to duplicate + * + * @return A duplicated ssh_key key + */ +ssh_key ssh_key_dup(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + + return pki_key_dup(key, 0); +} + +/** + * @brief clean up the key and deallocate all existing keys + * @param[in] key ssh_key to clean + */ +void ssh_key_clean (ssh_key key) +{ + if (key == NULL) + return; + + pki_key_clean(key); + +#ifndef HAVE_LIBCRYPTO + if (key->ed25519_privkey != NULL) { + ssh_burn(key->ed25519_privkey, sizeof(ed25519_privkey)); + SAFE_FREE(key->ed25519_privkey); + } + SAFE_FREE(key->ed25519_pubkey); +#endif /* HAVE_LIBCRYPTO */ + if (key->cert != NULL) { + SSH_BUFFER_FREE(key->cert); + } + if (is_sk_key_type(key->type)) { + ssh_string_burn(key->sk_application); + ssh_string_free(key->sk_application); + ssh_string_burn(key->sk_key_handle); + ssh_string_free(key->sk_key_handle); + ssh_string_burn(key->sk_reserved); + ssh_string_free(key->sk_reserved); + ssh_string_burn(key->sk_user_id); + ssh_string_free(key->sk_user_id); + key->sk_flags = 0; + } + key->cert_type = SSH_KEYTYPE_UNKNOWN; + key->flags = SSH_KEY_FLAG_EMPTY; + key->type = SSH_KEYTYPE_UNKNOWN; + key->ecdsa_nid = 0; + key->type_c = NULL; +} + +/** + * @brief deallocate a SSH key + * @param[in] key ssh_key handle to free + */ +void ssh_key_free (ssh_key key) +{ + if (key) { + ssh_key_clean(key); + SAFE_FREE(key); + } +} + +/** + * @brief returns the type of a ssh key + * @param[in] key the ssh_key handle + * @returns one of SSH_KEYTYPE_RSA, + * SSH_KEYTYPE_ECDSA_P256, SSH_KEYTYPE_ECDSA_P384, + * SSH_KEYTYPE_ECDSA_P521, SSH_KEYTYPE_ED25519, + * SSH_KEYTYPE_RSA_CERT01, SSH_KEYTYPE_ECDSA_P256_CERT01, + * SSH_KEYTYPE_ECDSA_P384_CERT01, SSH_KEYTYPE_ECDSA_P521_CERT01, or + * SSH_KEYTYPE_ED25519_CERT01. + * @returns SSH_KEYTYPE_UNKNOWN if the type is unknown + */ +enum ssh_keytypes_e ssh_key_type(const ssh_key key) +{ + if (key == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + return key->type; +} + +/** + * @brief Get security key (FIDO2) flags for a security key backed ssh_key. + * + * The returned value contains a bitmask of SSH_SK_* flags (e.g. + * SSH_SK_USER_PRESENCE_REQD, SSH_SK_USER_VERIFICATION_REQD, etc.). + * If NULL is passed, then 0 is returned. + * + * @param[in] key The ssh_key handle. + * + * @return Bitmask of security key flags, or 0 if not applicable. + */ +uint32_t ssh_key_get_sk_flags(const ssh_key key) +{ + if (key == NULL) { + return 0; + } + return key->sk_flags; +} + +/** + * @brief Get the application (RP ID) associated with a security key. + * + * This function returns a freshly allocated ssh_string containing a copy of the + * application (RP ID). The caller owns the returned ssh_string and must free it + * with SSH_STRING_FREE() when no longer needed. + * + * Returns NULL if the key is NULL, not a security key type or if the field is + * not set. + * + * @param[in] key The ssh_key handle. + * + * @return ssh_string copy of the application (RP ID) or NULL if not available. + */ +ssh_string ssh_key_get_sk_application(const ssh_key key) +{ + if (key == NULL || key->sk_application == NULL) { + return NULL; + } + + return ssh_string_copy(key->sk_application); +} + +/** + * @brief Get a copy of the user ID associated with a resident security key + * credential. + * + * For resident (discoverable) credentials, authenticators may provide a user + * id which can be arbitrary binary data to allow for storing multiple keys for + * the same Relying Party. This function returns a freshly allocated ssh_string + * containing a copy of that user id. The caller owns the returned ssh_string + * and must free it with SSH_STRING_FREE() when no longer needed. + * + * @note This function will only return useful information if the ssh_key + * passed represents a resident key loaded using the ssh_sk_resident_keys_load() + * function. + * + * @param[in] key The ssh_key handle. + * + * @return ssh_string copy of user id or NULL if not available. + */ +ssh_string ssh_key_get_sk_user_id(const ssh_key key) +{ + if (key == NULL) { + return NULL; + } + return ssh_string_copy(key->sk_user_id); +} + +/** + * @brief Convert a signature type to a string. + * + * @param[in] type The algorithm type to convert. + * + * @param[in] hash_type The hash type to convert + * + * @return A string for the keytype or NULL if unknown. + */ +const char * +ssh_key_signature_to_char(enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + switch (type) { + case SSH_KEYTYPE_RSA: + switch (hash_type) { + case SSH_DIGEST_SHA256: + return "rsa-sha2-256"; + case SSH_DIGEST_SHA512: + return "rsa-sha2-512"; + case SSH_DIGEST_SHA1: + case SSH_DIGEST_AUTO: + return "ssh-rsa"; + default: + return NULL; + } + break; + case SSH_KEYTYPE_RSA_CERT01: + switch (hash_type) { + case SSH_DIGEST_SHA256: + return "rsa-sha2-256-cert-v01@openssh.com"; + case SSH_DIGEST_SHA512: + return "rsa-sha2-512-cert-v01@openssh.com"; + case SSH_DIGEST_SHA1: + case SSH_DIGEST_AUTO: + return "ssh-rsa-cert-v01@openssh.com"; + default: + return NULL; + } + break; + default: + return ssh_key_type_to_char(type); + } + + /* We should never reach this */ + return NULL; +} + +/** + * @brief Convert a key type to a string. + * + * @param[in] type The type to convert. + * + * @return A string for the keytype or NULL if unknown. + */ +const char *ssh_key_type_to_char(enum ssh_keytypes_e type) { + switch (type) { + case SSH_KEYTYPE_RSA: + return "ssh-rsa"; + case SSH_KEYTYPE_ECDSA: + return "ssh-ecdsa"; /* deprecated. invalid value */ + case SSH_KEYTYPE_ECDSA_P256: + return "ecdsa-sha2-nistp256"; + case SSH_KEYTYPE_ECDSA_P384: + return "ecdsa-sha2-nistp384"; + case SSH_KEYTYPE_ECDSA_P521: + return "ecdsa-sha2-nistp521"; + case SSH_KEYTYPE_ED25519: + return "ssh-ed25519"; + case SSH_KEYTYPE_RSA_CERT01: + return "ssh-rsa-cert-v01@openssh.com"; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + return "ecdsa-sha2-nistp256-cert-v01@openssh.com"; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return "ecdsa-sha2-nistp384-cert-v01@openssh.com"; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return "ecdsa-sha2-nistp521-cert-v01@openssh.com"; + case SSH_KEYTYPE_ED25519_CERT01: + return "ssh-ed25519-cert-v01@openssh.com"; + case SSH_KEYTYPE_SK_ECDSA: + return "sk-ecdsa-sha2-nistp256@openssh.com"; + case SSH_KEYTYPE_SK_ED25519: + return "sk-ssh-ed25519@openssh.com"; + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com"; + case SSH_KEYTYPE_SK_ED25519_CERT01: + return "sk-ssh-ed25519-cert-v01@openssh.com"; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + /* We should never reach this */ + return NULL; +} + +enum ssh_digest_e ssh_key_hash_from_name(const char *name) +{ + if (name == NULL) { + /* TODO we should rather fail */ + return SSH_DIGEST_AUTO; + } + + if (strcmp(name, "ssh-rsa") == 0) { + return SSH_DIGEST_SHA1; + } else if (strcmp(name, "rsa-sha2-256") == 0) { + return SSH_DIGEST_SHA256; + } else if (strcmp(name, "rsa-sha2-512") == 0) { + return SSH_DIGEST_SHA512; + } else if (strcmp(name, "ecdsa-sha2-nistp256") == 0) { + return SSH_DIGEST_SHA256; + } else if (strcmp(name, "ecdsa-sha2-nistp384") == 0) { + return SSH_DIGEST_SHA384; + } else if (strcmp(name, "ecdsa-sha2-nistp521") == 0) { + return SSH_DIGEST_SHA512; + } else if (strcmp(name, "ssh-ed25519") == 0) { + return SSH_DIGEST_AUTO; + } else if (strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) { + return SSH_DIGEST_SHA256; + } else if (strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) { + return SSH_DIGEST_AUTO; + } + + SSH_LOG(SSH_LOG_TRACE, "Unknown signature name %s", name); + + /* TODO we should rather fail */ + return SSH_DIGEST_AUTO; +} + +/** + * @brief Checks the given key against the configured allowed + * public key algorithm types + * + * @param[in] session The SSH session + * @param[in] type The key algorithm to check + * @returns 1 if the key algorithm is allowed, 0 otherwise + */ +int ssh_key_algorithm_allowed(ssh_session session, const char *type) +{ + const char *allowed_list = NULL; + + if (session->client) { + allowed_list = session->opts.pubkey_accepted_types; + if (allowed_list == NULL) { + if (ssh_fips_mode()) { + allowed_list = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + allowed_list = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + } +#ifdef WITH_SERVER + else if (session->server) { + allowed_list = session->opts.wanted_methods[SSH_HOSTKEYS]; + if (allowed_list == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Session invalid: no host key available"); + return 0; + } + } +#endif /* WITH_SERVER */ + else { + SSH_LOG(SSH_LOG_TRACE, "Session invalid: not set as client nor server"); + return 0; + } + + SSH_LOG(SSH_LOG_DEBUG, "Checking %s with list <%s>", type, allowed_list); + return match_group(allowed_list, type); +} + +bool ssh_key_size_allowed_rsa(int min_size, ssh_key key) +{ + int key_size = ssh_key_size(key); + + if (min_size < RSA_MIN_KEY_SIZE) { + if (ssh_fips_mode()) { + min_size = RSA_MIN_FIPS_KEY_SIZE; + } else { + min_size = RSA_MIN_KEY_SIZE; + } + } + return (key_size >= min_size); +} + +/** + * @brief Check the given key is acceptable in regards to the key size policy + * specified by the configuration + * + * @param[in] session The SSH session + * @param[in] key The SSH key + * @returns true if the key is allowed, false otherwise + */ +bool ssh_key_size_allowed(ssh_session session, ssh_key key) +{ + int min_size = 0; + + switch (ssh_key_type(key)) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + min_size = session->opts.rsa_min_size; + return ssh_key_size_allowed_rsa(min_size, key); + default: + return true; + } +} + +/** + * @brief Helper function to convert a key type to a hash type. + * + * @param[in] type The type to convert. + * + * @return A hash type to be used. + * + * @warning This helper function is available for use without session (for + * example for signing commits) and might cause interoperability issues + * when used within session! It is recommended to use + * ssh_key_type_to_hash() instead of this helper directly when a + * session is available. + * + * @note In order to follow current security best practises for RSA, defaults + * to SHA-2 with SHA-512 digest (RFC8332) instead of the default for + * the SSH protocol (SHA1 with RSA ; RFC 4253). + * + * @see ssh_key_type_to_hash() + */ +static enum ssh_digest_e key_type_to_hash(enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA: + return SSH_DIGEST_SHA512; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_SK_ECDSA: + return SSH_DIGEST_SHA256; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + return SSH_DIGEST_SHA384; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + return SSH_DIGEST_SHA512; + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + return SSH_DIGEST_AUTO; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_WARN, + "Digest algorithm to be used with key type %u " + "is not defined", + type); + } + + /* We should never reach this */ + return SSH_DIGEST_AUTO; +} + +/** + * @brief Convert a key type to a hash type. This is usually unambiguous + * for all the key types, unless the SHA2 extension (RFC 8332) is + * negotiated during key exchange. + * + * @param[in] session SSH Session. + * + * @param[in] type The type to convert. + * + * @return A hash type to be used. + */ +enum ssh_digest_e ssh_key_type_to_hash(ssh_session session, + enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA_CERT01: + /* If we are talking to an old OpenSSH version which does not support + * SHA2 in certificates */ + if ((session->openssh > 0) && + (session->openssh < SSH_VERSION_INT(7, 2, 0))) + { + SSH_LOG(SSH_LOG_DEBUG, + "We are talking to an old OpenSSH (%x); " + "returning SSH_DIGEST_SHA1", + session->openssh); + + return SSH_DIGEST_SHA1; + } + FALL_THROUGH; + case SSH_KEYTYPE_RSA: + if (ssh_key_algorithm_allowed(session, "rsa-sha2-512") && + (session->extensions & SSH_EXT_SIG_RSA_SHA512)) { + return SSH_DIGEST_SHA512; + } + + if (ssh_key_algorithm_allowed(session, "rsa-sha2-256") && + (session->extensions & SSH_EXT_SIG_RSA_SHA256)) { + return SSH_DIGEST_SHA256; + } + + /* Default algorithm for RSA is SHA1 */ + return SSH_DIGEST_SHA1; + + default: + return key_type_to_hash(type); + } + + /* We should never reach this */ + return SSH_DIGEST_AUTO; +} + +/** + * @brief Gets signature algorithm name to be used with the given + * key type. + * + * @param[in] session SSH session. + * @param[in] type The algorithm type to convert. + * + * @return A string for the keytype or NULL if unknown. + */ +const char * +ssh_key_get_signature_algorithm(ssh_session session, + enum ssh_keytypes_e type) +{ + enum ssh_digest_e hash_type; + + if (type == SSH_KEYTYPE_RSA_CERT01) { + /* If we are talking to an old OpenSSH version which does not support + * rsa-sha2-{256,512}-cert-v01@openssh.com */ + if ((session->openssh > 0) && + (session->openssh < SSH_VERSION_INT(7, 8, 0))) + { + SSH_LOG(SSH_LOG_DEBUG, + "We are talking to an old OpenSSH (%x); " + "using old cert format", + session->openssh); + + return "ssh-rsa-cert-v01@openssh.com"; + } + } + + hash_type = ssh_key_type_to_hash(session, type); + + return ssh_key_signature_to_char(type, hash_type); +} + +/** + * @brief Convert a ssh key algorithm name to a ssh key algorithm type. + * + * @param[in] name The name to convert. + * + * @return The enum ssh key algorithm type. + */ +enum ssh_keytypes_e ssh_key_type_from_signature_name(const char *name) { + if (name == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + + if ((strcmp(name, "rsa-sha2-256") == 0) || + (strcmp(name, "rsa-sha2-512") == 0)) { + return SSH_KEYTYPE_RSA; + } + + /* Otherwise the key type matches the signature type */ + return ssh_key_type_from_name(name); +} + +/** + * @brief Convert a ssh key name to a ssh key type. + * + * @param[in] name The name to convert. + * + * @return The enum ssh key type. + */ +enum ssh_keytypes_e ssh_key_type_from_name(const char *name) +{ + if (name == NULL) { + return SSH_KEYTYPE_UNKNOWN; + } + + if (strcmp(name, "rsa") == 0) { + return SSH_KEYTYPE_RSA; + } else if (strcmp(name, "ssh-rsa") == 0) { + return SSH_KEYTYPE_RSA; + } else if (strcmp(name, "ssh-ecdsa") == 0 + || strcmp(name, "ecdsa") == 0 + || strcmp(name, "ecdsa-sha2-nistp256") == 0) { + return SSH_KEYTYPE_ECDSA_P256; + } else if (strcmp(name, "ecdsa-sha2-nistp384") == 0) { + return SSH_KEYTYPE_ECDSA_P384; + } else if (strcmp(name, "ecdsa-sha2-nistp521") == 0) { + return SSH_KEYTYPE_ECDSA_P521; + } else if (strcmp(name, "ssh-ed25519") == 0){ + return SSH_KEYTYPE_ED25519; + } else if (strcmp(name, "ssh-rsa-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_RSA_CERT01; + } else if (strcmp(name, "ecdsa-sha2-nistp256-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ECDSA_P256_CERT01; + } else if (strcmp(name, "ecdsa-sha2-nistp384-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ECDSA_P384_CERT01; + } else if (strcmp(name, "ecdsa-sha2-nistp521-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ECDSA_P521_CERT01; + } else if (strcmp(name, "ssh-ed25519-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_ED25519_CERT01; + } else if(strcmp(name, "sk-ecdsa-sha2-nistp256@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ECDSA; + } else if(strcmp(name, "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ECDSA_CERT01; + } else if(strcmp(name, "sk-ssh-ed25519@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ED25519; + } else if(strcmp(name, "sk-ssh-ed25519-cert-v01@openssh.com") == 0) { + return SSH_KEYTYPE_SK_ED25519_CERT01; + } + + return SSH_KEYTYPE_UNKNOWN; +} + +/** + * @brief Get the public key type corresponding to a certificate type. + * + * @param[in] type The certificate or public key type. + * + * @return The matching public key type. + */ +enum ssh_keytypes_e ssh_key_type_plain(enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA_CERT01: + return SSH_KEYTYPE_RSA; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + return SSH_KEYTYPE_ECDSA_P256; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return SSH_KEYTYPE_ECDSA_P384; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return SSH_KEYTYPE_ECDSA_P521; + case SSH_KEYTYPE_ED25519_CERT01: + return SSH_KEYTYPE_ED25519; + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return SSH_KEYTYPE_SK_ECDSA; + case SSH_KEYTYPE_SK_ED25519_CERT01: + return SSH_KEYTYPE_SK_ED25519; + default: + return type; + } +} + +/** + * @brief Check if the key has/is a public key. + * + * @param[in] k The key to check. + * + * @return 1 if it is a public key, 0 if not. + */ +int ssh_key_is_public(const ssh_key k) +{ + if (k == NULL) { + return 0; + } + + return (k->flags & SSH_KEY_FLAG_PUBLIC) == SSH_KEY_FLAG_PUBLIC; +} + +/** + * @brief Check if the key is a private key. + * + * @param[in] k The key to check. + * + * @return 1 if it is a private key, 0 if not. + */ +int ssh_key_is_private(const ssh_key k) { + if (k == NULL) { + return 0; + } + + return (k->flags & SSH_KEY_FLAG_PRIVATE) == SSH_KEY_FLAG_PRIVATE; +} + +/** + * @brief Compare keys if they are equal. + * + * Note that comparing private keys is almost never needed. The private key + * is cryptographically bound to the public key and comparing public keys should + * always be preferred. + * + * @param[in] k1 The first key to compare. + * + * @param[in] k2 The second key to compare. + * + * @param[in] what What part or type of the key do you want to compare. + * + * @return 0 if equal, 1 if not. + */ +int ssh_key_cmp(const ssh_key k1, + const ssh_key k2, + enum ssh_keycmp_e what) +{ + if (k1 == NULL || k2 == NULL) { + return 1; + } + + if (ssh_key_type_plain(k1->type) != ssh_key_type_plain(k2->type)) { + SSH_LOG(SSH_LOG_DEBUG, "key types don't match!"); + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (!ssh_key_is_private(k1) || + !ssh_key_is_private(k2)) { + return 1; + } + } + + if (is_sk_key_type(k1->type)) { + if (ssh_string_cmp(k1->sk_application, k2->sk_application) != 0) { + return 1; + } + + if (ssh_string_cmp(k1->sk_user_id, k2->sk_user_id) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (k1->sk_flags != k2->sk_flags) { + return 1; + } + + if (ssh_string_cmp(k1->sk_key_handle, k2->sk_key_handle) != 0) { + return 1; + } + + if (ssh_string_cmp(k1->sk_reserved, k2->sk_reserved) != 0) { + return 1; + } + } + } + + if (what == SSH_KEY_CMP_CERTIFICATE) { + if (!is_cert_type(k1->type) || + !is_cert_type(k2->type)) { + return 1; + } + if (k1->cert == NULL || k2->cert == NULL) { + return 1; + } + if (ssh_buffer_get_len(k1->cert) != ssh_buffer_get_len(k2->cert)) { + return 1; + } + return memcmp(ssh_buffer_get(k1->cert), + ssh_buffer_get(k2->cert), + ssh_buffer_get_len(k1->cert)); + } + +#ifndef HAVE_LIBCRYPTO + if (ssh_key_type_plain(k1->type) == SSH_KEYTYPE_ED25519) { + return pki_ed25519_key_cmp(k1, k2, what); + } else if (ssh_key_type_plain(k1->type) == SSH_KEYTYPE_SK_ED25519) { + return pki_ed25519_key_cmp(k1, k2, SSH_KEY_CMP_PUBLIC); + } +#endif + + return pki_key_compare(k1, k2, what); +} + +ssh_signature ssh_signature_new(void) +{ + struct ssh_signature_struct *sig = NULL; + + sig = calloc(1, sizeof(struct ssh_signature_struct)); + if (sig == NULL) { + return NULL; + } + + return sig; +} + +void ssh_signature_free(ssh_signature sig) +{ + if (sig == NULL) { + return; + } + + switch(sig->type) { + case SSH_KEYTYPE_RSA: +#ifdef HAVE_LIBGCRYPT + gcry_sexp_release(sig->rsa_sig); +#elif defined HAVE_LIBMBEDCRYPTO + SAFE_FREE(sig->rsa_sig); +#endif /* HAVE_LIBGCRYPT */ + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + gcry_sexp_release(sig->ecdsa_sig); +#elif defined HAVE_LIBMBEDCRYPTO + bignum_safe_free(sig->ecdsa_sig.r); + bignum_safe_free(sig->ecdsa_sig.s); +#endif /* HAVE_GCRYPT_ECC */ + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: +#ifndef HAVE_LIBCRYPTO + /* When using OpenSSL, the signature is stored in sig->raw_sig */ + SAFE_FREE(sig->ed25519_sig); +#endif /* HAVE_LIBCRYPTO */ + break; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + break; + } + + /* Explicitly zero the signature content before free */ + ssh_string_burn(sig->raw_sig); + SSH_STRING_FREE(sig->raw_sig); + SAFE_FREE(sig); +} + +/** + * @brief import a base64 formatted key from a memory c-string + * + * @param[in] b64_key The c-string holding the base64 encoded key + * + * @param[in] passphrase The passphrase to decrypt the key, or NULL + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free() + * + * @return SSH_ERROR in case of error, SSH_OK otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_privkey_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey) +{ + ssh_key key = NULL; + const char *openssh_header = NULL; + + if (b64_key == NULL || pkey == NULL) { + return SSH_ERROR; + } + + if (b64_key == NULL || !*b64_key) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Trying to decode privkey passphrase=%s", + passphrase ? "true" : "false"); + + /* Test for OpenSSH key format first */ + openssh_header = strstr(b64_key, OPENSSH_HEADER_BEGIN); + if (openssh_header != NULL) { + key = ssh_pki_openssh_privkey_import(openssh_header, + passphrase, + auth_fn, + auth_data); + } else { + /* fallback on PEM decoder */ + key = pki_private_key_from_base64(b64_key, + passphrase, + auth_fn, + auth_data); + } + if (key == NULL) { + return SSH_ERROR; + } + + *pkey = key; + + return SSH_OK; +} + + + /** + * @brief Convert a private key to a base64 encoded key in given format + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] b64_key A pointer to store the allocated base64 encoded key. You + * need to free the buffer using ssh_string_from_char(). + * + * @param[in] format The file format (OpenSSH, PEM, or default) + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int +ssh_pki_export_privkey_base64_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key, + enum ssh_file_format_e format) +{ + ssh_string blob = NULL; + char *b64 = NULL; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + * + * The FIDO2/U2F security keys are an extension to the SSH protocol + * proposed by OpenSSH, and do not have any representation in PEM format. + * So, they are always exported in the OpenSSH file format. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519 && + !is_sk_key_type(privkey->type)) { + format = SSH_FILE_FORMAT_PEM; + } +#endif /* HAVE_LIBCRYPTO */ + + switch (format) { + case SSH_FILE_FORMAT_PEM: + blob = pki_private_key_to_pem(privkey, + passphrase, + auth_fn, + auth_data); + break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; + } + if (blob == NULL) { + return SSH_ERROR; + } + + b64 = strndup(ssh_string_data(blob), ssh_string_len(blob)); + SSH_STRING_FREE(blob); + if (b64 == NULL) { + return SSH_ERROR; + } + + *b64_key = b64; + + return SSH_OK; +} + + /** + * @brief Convert a private key to a pem base64 encoded key, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] b64_key A pointer to store the allocated base64 encoded key. You + * need to free the buffer using ssh_string_from_char(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int ssh_pki_export_privkey_base64(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + char **b64_key) +{ + return ssh_pki_export_privkey_base64_format(privkey, + passphrase, + auth_fn, + auth_data, + b64_key, + SSH_FILE_FORMAT_DEFAULT); +} + + + +/** + * @brief Import a private key from a file or a PKCS #11 device. + * + * @param[in] filename The filename of the private key or the + * PKCS #11 URI corresponding to the private key. + * + * @param[in] passphrase The passphrase to decrypt the private key. Set to NULL + * if none is needed or it is unknown. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[out] pkey A pointer to store the allocated ssh_key. You need to + * free the key using ssh_key_free(). + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + **/ +int ssh_pki_import_privkey_file(const char *filename, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + ssh_key *pkey) { + struct stat sb; + char *key_buf = NULL; + FILE *file = NULL; + off_t size; + int rc; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + if (pkey == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + +#ifdef WITH_PKCS11_URI + if (ssh_pki_is_uri(filename)) { + rc = pki_uri_import(filename, pkey, SSH_KEY_PRIVATE); + return rc; + } +#endif /* WITH_PKCS11_URI */ + + file = fopen(filename, "rb"); + if (file == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error opening %s: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_EOF; + } + + rc = fstat(fileno(file), &sb); + if (rc < 0) { + fclose(file); + SSH_LOG(SSH_LOG_TRACE, + "Error getting stat of %s: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + switch (errno) { + case ENOENT: + case EACCES: + return SSH_EOF; + } + + return SSH_ERROR; + } + + if (sb.st_size > MAX_PRIVKEY_SIZE) { + SSH_LOG(SSH_LOG_TRACE, + "Private key is bigger than 4M."); + fclose(file); + return SSH_ERROR; + } + + key_buf = malloc(sb.st_size + 1); + if (key_buf == NULL) { + fclose(file); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + return SSH_ERROR; + } + + size = fread(key_buf, 1, sb.st_size, file); + fclose(file); + + if (size != sb.st_size) { + SAFE_FREE(key_buf); + SSH_LOG(SSH_LOG_TRACE, + "Error reading %s: %s", + filename, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + key_buf[size] = 0; + + rc = ssh_pki_import_privkey_base64(key_buf, + passphrase, + auth_fn, + auth_data, + pkey); + + SAFE_FREE(key_buf); + return rc; +} + +/** + * @brief Export a private key to a file in format specified in the argument + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[in] filename The path where to store the pem file. + * + * @param[in] format The file format (OpenSSH, PEM, or default) + * + * @return SSH_OK on success, SSH_ERROR on error. + */ + +int +ssh_pki_export_privkey_file_format(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename, + enum ssh_file_format_e format) +{ + ssh_string blob = NULL; + FILE *fp = NULL; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + fp = fopen(filename, "wb"); + if (fp == NULL) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_FUNCTIONS, "Error opening %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_EOF; + } + + /* + * For historic reasons, the Ed25519 keys are exported in OpenSSH file + * format by default also when built with OpenSSL. + * + * The FIDO2/U2F security keys are an extension to the SSH protocol + * proposed by OpenSSH, and do not have any representation in PEM format. + * So, they are always exported in the OpenSSH file format. + */ +#ifdef HAVE_LIBCRYPTO + if (format == SSH_FILE_FORMAT_DEFAULT && + privkey->type != SSH_KEYTYPE_ED25519 && + !is_sk_key_type(privkey->type)) { + + format = SSH_FILE_FORMAT_PEM; + } +#endif /* HAVE_LIBCRYPTO */ + + switch (format) { + case SSH_FILE_FORMAT_PEM: + blob = pki_private_key_to_pem(privkey, + passphrase, + auth_fn, + auth_data); + break; + case SSH_FILE_FORMAT_DEFAULT: + /* default except (OpenSSL && !ED25519) handled above */ + case SSH_FILE_FORMAT_OPENSSH: + blob = ssh_pki_openssh_privkey_export(privkey, + passphrase, + auth_fn, + auth_data); + break; + } + if (blob == NULL) { + fclose(fp); + return -1; + } + + rc = fwrite(ssh_string_data(blob), ssh_string_len(blob), 1, fp); + SSH_STRING_FREE(blob); + if (rc != 1 || ferror(fp)) { + fclose(fp); + unlink(filename); + return SSH_ERROR; + } + fclose(fp); + + return SSH_OK; +} + +/** + * @brief Export a private key to a pem file on disk, or OpenSSH format for + * keytype ssh-ed25519 + * + * @param[in] privkey The private key to export. + * + * @param[in] passphrase The passphrase to use to encrypt the key with or + * NULL. An empty string means no passphrase. + * + * @param[in] auth_fn An auth function you may want to use or NULL. + * + * @param[in] auth_data Private data passed to the auth function. + * + * @param[in] filename The path where to store the pem file. + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +int +ssh_pki_export_privkey_file(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + const char *filename) +{ + return ssh_pki_export_privkey_file_format(privkey, + passphrase, + auth_fn, + auth_data, + filename, + SSH_FILE_FORMAT_DEFAULT); +} + +/* temporary function to migrate seamlessly to ssh_key */ +ssh_public_key ssh_pki_convert_key_to_publickey(const ssh_key key) +{ + ssh_public_key pub = NULL; + ssh_key tmp = NULL; + + if (key == NULL) { + return NULL; + } + + tmp = ssh_key_dup(key); + if (tmp == NULL) { + return NULL; + } + + pub = calloc(1, sizeof(struct ssh_public_key_struct)); + if (pub == NULL) { + ssh_key_free(tmp); + return NULL; + } + + pub->type = tmp->type; + pub->type_c = tmp->type_c; + +#if defined(HAVE_LIBMBEDCRYPTO) + pub->rsa_pub = tmp->pk; + tmp->pk = NULL; +#elif defined(HAVE_LIBCRYPTO) + pub->key_pub = tmp->key; + tmp->key = NULL; +#else + pub->rsa_pub = tmp->rsa; + tmp->rsa = NULL; +#endif /* HAVE_LIBCRYPTO */ + + ssh_key_free(tmp); + + return pub; +} + +ssh_private_key ssh_pki_convert_key_to_privatekey(const ssh_key key) +{ + ssh_private_key privkey = NULL; + + privkey = calloc(1, sizeof(struct ssh_private_key_struct)); + if (privkey == NULL) { + ssh_key_free(key); + return NULL; + } + + privkey->type = key->type; +#if defined(HAVE_LIBMBEDCRYPTO) + privkey->rsa_priv = key->pk; +#elif defined(HAVE_LIBCRYPTO) + privkey->key_priv = key->key; +#else + privkey->rsa_priv = key->rsa; +#endif /* HAVE_LIBCRYPTO */ + + return privkey; +} + +int pki_import_privkey_buffer(enum ssh_keytypes_e type, + ssh_buffer buffer, + ssh_key *pkey) +{ + ssh_key key = NULL; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + switch (type) { + case SSH_KEYTYPE_RSA: { + ssh_string n = NULL; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + + rc = ssh_buffer_unpack(buffer, "SSSSSS", &n, &e, &d, &iqmp, &p, &q); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + rc = pki_privkey_build_rsa(key, n, e, d, iqmp, p, q); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); + ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); + ssh_log_hexdump("d", ssh_string_data(d), ssh_string_len(d)); + ssh_log_hexdump("iqmp", ssh_string_data(iqmp), ssh_string_len(iqmp)); + ssh_log_hexdump("p", ssh_string_data(p), ssh_string_len(p)); + ssh_log_hexdump("q", ssh_string_data(q), ssh_string_len(q)); +#endif /* DEBUG_CRYPTO */ + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA private key"); + goto fail; + } + break; + } +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: { + ssh_string e = NULL; + ssh_string exp = NULL; + ssh_string i = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SSS", &i, &e, &exp); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); + SSH_STRING_FREE(i); + if (nid == -1) { + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(exp); + SSH_STRING_FREE(exp); + goto fail; + } + + rc = pki_privkey_build_ecdsa(key, nid, e, exp); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(exp); + SSH_STRING_FREE(exp); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA private key"); + goto fail; + } + break; + } + case SSH_KEYTYPE_SK_ECDSA: { + ssh_string type_str = NULL; + ssh_string pubkey = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SS", &type_str, &pubkey); + if (rc != SSH_OK) { + goto fail; + } + + rc = pki_buffer_unpack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + SSH_STRING_FREE(type_str); + SSH_STRING_FREE(pubkey); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(type_str)); + SSH_STRING_FREE(type_str); + + if (nid == -1) { + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ecdsa(key, nid, pubkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + goto fail; + } + break; + } +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_ED25519: { + ssh_string pubkey = NULL, privkey = NULL; + + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "Ed25519 keys not supported in FIPS mode"); + goto fail; + } + + rc = ssh_buffer_unpack(buffer, "SS", &pubkey, &privkey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + rc = pki_privkey_build_ed25519(key, pubkey, privkey); + ssh_string_burn(privkey); + SSH_STRING_FREE(privkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ed25519 key"); + goto fail; + } + break; + } + case SSH_KEYTYPE_SK_ED25519: { + ssh_string pubkey = NULL; + + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "Ed25519 keys not supported in FIPS mode"); + goto fail; + } + + rc = ssh_buffer_unpack(buffer, "S", &pubkey); + if (rc != SSH_OK) { + goto fail; + } + + rc = pki_buffer_unpack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ed25519(key, pubkey); + SSH_STRING_FREE(pubkey); + if (rc != SSH_OK) { + goto fail; + } + break; + } + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown private key type (%d)", type); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +static int pki_import_pubkey_buffer(ssh_buffer buffer, + enum ssh_keytypes_e type, + ssh_key *pkey) +{ + ssh_key key = NULL; + int rc; + + key = ssh_key_new(); + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PUBLIC; + + switch (type) { + case SSH_KEYTYPE_RSA: + { + ssh_string e = NULL; + ssh_string n = NULL; + + rc = ssh_buffer_unpack(buffer, "SS", &e, &n); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + rc = pki_pubkey_build_rsa(key, e, n); +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("e", ssh_string_data(e), ssh_string_len(e)); + ssh_log_hexdump("n", ssh_string_data(n), ssh_string_len(n)); +#endif /* DEBUG_CRYPTO */ + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build RSA public key"); + goto fail; + } + } + break; +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA: /* deprecated */ + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: + { + ssh_string e = NULL; + ssh_string i = NULL; + int nid; + + rc = ssh_buffer_unpack(buffer, "SS", &i, &e); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + goto fail; + } + + nid = pki_key_ecdsa_nid_from_name(ssh_string_get_char(i)); + SSH_STRING_FREE(i); + if (nid == -1) { + ssh_string_burn(e); + SSH_STRING_FREE(e); + goto fail; + } + + rc = pki_pubkey_build_ecdsa(key, nid, e); + ssh_string_burn(e); + SSH_STRING_FREE(e); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ECDSA public key"); + goto fail; + } + + /* Unpack SK specific parameters */ + if (type == SSH_KEYTYPE_SK_ECDSA) { + ssh_string application = ssh_buffer_get_ssh_string(buffer); + if (application == NULL) { + SSH_LOG(SSH_LOG_TRACE, "SK Unpack error"); + goto fail; + } + key->sk_application = application; + key->type_c = ssh_key_type_to_char(key->type); + } + } + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + { + ssh_string pubkey = ssh_buffer_get_ssh_string(buffer); + + if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid public key length"); + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + goto fail; + } + + rc = pki_pubkey_build_ed25519(key, pubkey); + ssh_string_burn(pubkey); + SSH_STRING_FREE(pubkey); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to build ED25519 public key"); + goto fail; + } + + if (type == SSH_KEYTYPE_SK_ED25519) { + ssh_string application = ssh_buffer_get_ssh_string(buffer); + if (application == NULL) { + SSH_LOG(SSH_LOG_TRACE, "SK Unpack error"); + goto fail; + } + key->sk_application = application; + } + } + break; + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type %d", type); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +static int pki_import_cert_buffer(ssh_buffer buffer, + enum ssh_keytypes_e type, + ssh_key *pkey) +{ + ssh_buffer cert = NULL; + ssh_string tmp_s = NULL; + const char *type_c = NULL; + ssh_key key = NULL; + int rc; + + /* + * The cert blob starts with the key type as an ssh_string, but this + * string has been read out of the buffer to identify the key type. + * Simply add it again as first element before copying the rest. + */ + cert = ssh_buffer_new(); + if (cert == NULL) { + goto fail; + } + type_c = ssh_key_type_to_char(type); + tmp_s = ssh_string_from_char(type_c); + if (tmp_s == NULL) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(cert, tmp_s); + SSH_STRING_FREE(tmp_s); + if (rc != 0) { + goto fail; + } + rc = ssh_buffer_add_buffer(cert, buffer); + if (rc != 0) { + goto fail; + } + + /* + * After the key type, comes an ssh_string nonce. Just after this comes the + * cert public key, which can be parsed out of the buffer. + */ + tmp_s = ssh_buffer_get_ssh_string(buffer); + if (tmp_s == NULL) { + goto fail; + } + SSH_STRING_FREE(tmp_s); + + switch (type) { + case SSH_KEYTYPE_RSA_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_RSA, &key); + break; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ECDSA_P256, &key); + break; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ECDSA_P384, &key); + break; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ECDSA_P521, &key); + break; + case SSH_KEYTYPE_ED25519_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_ED25519, &key); + break; + case SSH_KEYTYPE_SK_ECDSA_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_SK_ECDSA, &key); + break; + case SSH_KEYTYPE_SK_ED25519_CERT01: + rc = pki_import_pubkey_buffer(buffer, SSH_KEYTYPE_SK_ED25519, &key); + break; + default: + key = ssh_key_new(); + } + if (rc != 0 || key == NULL) { + goto fail; + } + + key->type = type; + key->type_c = type_c; + key->cert = cert; + + *pkey = key; + return SSH_OK; + +fail: + ssh_key_free(key); + SSH_BUFFER_FREE(cert); + return SSH_ERROR; +} + +/** + * @brief Import a base64 formatted public key from a memory c-string. + * + * Note that the public key is just the base64 part (without the key + * type prefix and comment suffix you can find in the OpenSSH public + * key file or known_hosts file). + * + * @param[in] b64_key The base64 key to import. + * @param[in] type The type of the key to import. + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free(). + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_base64(const char *b64_key, + enum ssh_keytypes_e type, + ssh_key *pkey) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + int rc; + + if (b64_key == NULL || pkey == NULL) { + return SSH_ERROR; + } + + buffer = base64_to_bin(b64_key); + if (buffer == NULL) { + return SSH_ERROR; + } + + type_s = ssh_buffer_get_ssh_string(buffer); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + SSH_STRING_FREE(type_s); + + if (is_cert_type(type)) { + rc = pki_import_cert_buffer(buffer, type, pkey); + } else { + rc = pki_import_pubkey_buffer(buffer, type, pkey); + } + SSH_BUFFER_FREE(buffer); + + return rc; +} + +/** + * @internal + * + * @brief Import a public key from a ssh string. + * + * @param[in] key_blob The key blob to import as specified in RFC 4253 section + * 6.6 "Public Key Algorithms". + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_blob(const ssh_string key_blob, + ssh_key *pkey) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + enum ssh_keytypes_e type; + int rc; + + if (key_blob == NULL || pkey == NULL) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + return SSH_ERROR; + } + + rc = ssh_buffer_add_data(buffer, + ssh_string_data(key_blob), + (uint32_t)ssh_string_len(key_blob)); + if (rc < 0) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + goto fail; + } + + type_s = ssh_buffer_get_ssh_string(buffer); + if (type_s == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + goto fail; + } + + type = ssh_key_type_from_name(ssh_string_get_char(type_s)); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Unknown key type found!"); + goto fail; + } + SSH_STRING_FREE(type_s); + + if (is_cert_type(type)) { + rc = pki_import_cert_buffer(buffer, type, pkey); + } else { + rc = pki_import_pubkey_buffer(buffer, type, pkey); + } + + SSH_BUFFER_FREE(buffer); + + return rc; +fail: + SSH_BUFFER_FREE(buffer); + SSH_STRING_FREE(type_s); + + return SSH_ERROR; +} + +#ifdef WITH_PKCS11_URI +/** + *@brief Detect if the pathname in cmp is a PKCS #11 URI. + * + * @param[in] cmp The path to the public/private key + * or a private/public PKCS #11 URI. + * + * @returns true if filename is a URI starting with "pkcs11:" + * false otherwise. + */ +bool ssh_pki_is_uri(const char *cmp) +{ + int rc; + + rc = strncmp(cmp, PKCS11_URI, strlen(PKCS11_URI)); + if (rc == 0) { + return true; + } + + return false; +} + +/** + *@brief export a Public PKCS #11 URI from a Private PKCS #11 URI + * by replacing "type=private" to "type=public". + * TODO: Improve the parser + * + * @param[in] priv_uri Private PKCS #11 URI. + * + * @returns pointer to the public PKCS #11 URI. You need to free + * the memory using ssh_string_free_char(). + * + * @see ssh_string_free_char(). + */ +char *ssh_pki_export_pub_uri_from_priv_uri(const char *priv_uri) +{ + char *pub_uri_temp = NULL; + + pub_uri_temp = ssh_strreplace(priv_uri, + "type=private", + "type=public"); + + return pub_uri_temp; +} +#endif /* WITH_PKCS11_URI */ + +/** + * @brief Import a public key from a file or a PKCS #11 device. + * + * @param[in] filename The filename of the public key or the + * PKCS #11 URI corresponding to the public key. + * + * @param[out] pkey A pointer to store the allocated public key. You need to + * free the memory using ssh_key_free(). + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_pubkey_file(const char *filename, ssh_key *pkey) +{ + enum ssh_keytypes_e type; + struct stat sb; + char *key_buf = NULL, *p = NULL; + size_t buflen, i; + const char *q = NULL; + FILE *file = NULL; + off_t size; + int rc, cmp; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_key priv_key = NULL; + + if (pkey == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + +#ifdef WITH_PKCS11_URI + if (ssh_pki_is_uri(filename)) { + rc = pki_uri_import(filename, pkey, SSH_KEY_PUBLIC); + return rc; + } +#endif /* WITH_PKCS11_URI */ + + file = fopen(filename, "rb"); + if (file == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Error opening %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_EOF; + } + + rc = fstat(fileno(file), &sb); + if (rc < 0) { + fclose(file); + SSH_LOG(SSH_LOG_TRACE, "Error gettint stat of %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + switch (errno) { + case ENOENT: + case EACCES: + return SSH_EOF; + } + return SSH_ERROR; + } + + if (sb.st_size > MAX_PUBKEY_SIZE) { + fclose(file); + return SSH_ERROR; + } + + key_buf = malloc(sb.st_size + 1); + if (key_buf == NULL) { + fclose(file); + SSH_LOG(SSH_LOG_TRACE, "Out of memory!"); + return SSH_ERROR; + } + + size = fread(key_buf, 1, sb.st_size, file); + fclose(file); + + if (size != sb.st_size) { + SAFE_FREE(key_buf); + SSH_LOG(SSH_LOG_TRACE, "Error reading %s: %s", + filename, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + key_buf[size] = '\0'; + buflen = strlen(key_buf); + + /* Test for new OpenSSH key format first */ + cmp = strncmp(key_buf, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); + if (cmp == 0) { + *pkey = ssh_pki_openssh_pubkey_import(key_buf); + SAFE_FREE(key_buf); + if (*pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key from OpenSSH" + " private key file"); + return SSH_ERROR; + } + return SSH_OK; + } + + /* + * Try to parse key as PEM. Set empty passphrase, so user won't be prompted + * for passphrase. Don't try to decrypt encrypted private key. + */ + priv_key = pki_private_key_from_base64(key_buf, "", NULL, NULL); + if (priv_key) { + rc = ssh_pki_export_privkey_to_pubkey(priv_key, pkey); + ssh_key_free(priv_key); + SAFE_FREE(key_buf); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to import public key from PEM" + " private key file"); + return SSH_ERROR; + } + return SSH_OK; + } + + /* This the old one-line public key format */ + q = p = key_buf; + for (i = 0; i < buflen; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + type = ssh_key_type_from_name(q); + if (type == SSH_KEYTYPE_UNKNOWN) { + SAFE_FREE(key_buf); + return SSH_ERROR; + } + + if (i >= buflen) { + SAFE_FREE(key_buf); + return SSH_ERROR; + } + q = &p[i + 1]; + for (; i < buflen; i++) { + if (isspace((int)p[i])) { + p[i] = '\0'; + break; + } + } + + rc = ssh_pki_import_pubkey_base64(q, type, pkey); + SAFE_FREE(key_buf); + + return rc; +} + +/** + * @brief Import a base64 formatted certificate from a memory c-string. + * + * Note that the certificate is just the base64 part (without the key + * type prefix and comment suffix you can find in the OpenSSH certificate + * file). + * + * @param[in] b64_cert The base64 cert to import. + * @param[in] type The type of the cert to import. + * @param[out] pkey A pointer where the allocated certificate can be stored. + * You need to free the memory using ssh_key_free(). + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_cert_base64(const char *b64_cert, + enum ssh_keytypes_e type, + ssh_key *pkey) +{ + return ssh_pki_import_pubkey_base64(b64_cert, type, pkey); +} + +/** + * @internal + * + * @brief Import a certificate from a ssh string. + * + * @param[in] cert_blob The cert blob to import as specified in RFC 4253 section + * 6.6 "Public Key Algorithms". + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_import_cert_blob(const ssh_string cert_blob, + ssh_key *pkey) +{ + return ssh_pki_import_pubkey_blob(cert_blob, pkey); +} + +/** + * @brief Import a certificate from the given filename. + * + * @param[in] filename The path to the certificate. + * + * @param[out] pkey A pointer to store the allocated certificate. You need to + * free the memory using ssh_key_free(). + * + * @returns SSH_OK on success, SSH_EOF if the file doesn't exist or permission + * denied, SSH_ERROR otherwise. + * + * @see ssh_key_free() + */ +int ssh_pki_import_cert_file(const char *filename, ssh_key *pkey) +{ + int rc; + + rc = ssh_pki_import_pubkey_file(filename, pkey); + if (rc == SSH_OK) { + /* check the key is a cert type. */ + if (!is_cert_type((*pkey)->type)) { + SSH_KEY_FREE(*pkey); + return SSH_ERROR; + } + } + + return rc; +} + +/** + * @internal + * + * @brief Internal function to generate a key pair. + * + * @param[in] type Type of key to create + * + * @param[in] parameter Parameter to the creation of key: + * rsa : length of the key in bits (e.g. 1024, 2048, 4096) + * If parameter is 0, then the default size will be used. + * @param[out] pkey A pointer to store the allocated private key. You need + * to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + */ +static int pki_generate_key_internal(enum ssh_keytypes_e type, + int parameter, + ssh_key *pkey) +{ + int rc; + ssh_key key = NULL; + + if (pkey == NULL) { + return SSH_ERROR; + } + + key = ssh_key_new(); + if (key == NULL) { + return SSH_ERROR; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + switch(type){ + case SSH_KEYTYPE_RSA: + if (parameter != 0 && parameter < RSA_MIN_KEY_SIZE) { + SSH_LOG( + SSH_LOG_WARN, + "RSA key size parameter (%d) is below minimum allowed (%d)", + parameter, + RSA_MIN_KEY_SIZE); + goto error; + } + + rc = pki_key_generate_rsa(key, parameter); + if(rc == SSH_ERROR) + goto error; + break; +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA: /* deprecated */ + rc = pki_key_generate_ecdsa(key, parameter); + if (rc == SSH_ERROR) { + goto error; + } + + /* Update key type */ + key->type_c = ssh_pki_key_ecdsa_name(key); + break; + case SSH_KEYTYPE_ECDSA_P256: + rc = pki_key_generate_ecdsa(key, 256); + if (rc == SSH_ERROR) { + goto error; + } + break; + case SSH_KEYTYPE_ECDSA_P384: + rc = pki_key_generate_ecdsa(key, 384); + if (rc == SSH_ERROR) { + goto error; + } + break; + case SSH_KEYTYPE_ECDSA_P521: + rc = pki_key_generate_ecdsa(key, 521); + if (rc == SSH_ERROR) { + goto error; + } + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_ED25519: + rc = pki_key_generate_ed25519(key); + if (rc == SSH_ERROR) { + goto error; + } + break; + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + goto error; + } + + *pkey = key; + return SSH_OK; +error: + ssh_key_free(key); + return SSH_ERROR; +} + +/** + * @brief Generates a key pair. + * + * @param[in] type Type of key to create + * + * @param[in] parameter Parameter to the creation of key: + * rsa : length of the key in bits (e.g. 1024, 2048, 4096) + * If parameter is 0, then the default size will be used. + * @param[out] pkey A pointer to store the allocated private key. You need + * to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @warning Generating a key pair may take some time. + * + * @see ssh_key_free() + */ +int ssh_pki_generate(enum ssh_keytypes_e type, int parameter, ssh_key *pkey) +{ + return pki_generate_key_internal(type, parameter, pkey); +} + +/** + * @brief Generates a key pair. + * + * @param[in] type Type of key to create + * + * @param[in] pki_context PKI context containing various configuration + * parameters and sub-contexts. Can be NULL for + * standard SSH key types (RSA, ECDSA, ED25519) where + * defaults will be used. Can also be NULL for security + * key types (SK_*), in which case default callbacks and + * settings will be used automatically. + * + * @param[out] pkey A pointer to store the allocated private key. You need + * to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_pki_ctx_new() + * @see ssh_key_free() + */ +int ssh_pki_generate_key(enum ssh_keytypes_e type, + ssh_pki_ctx pki_context, + ssh_key *pkey) +{ + + /* Handle Security Key types with the specialized function */ + if (is_sk_key_type(type)) { +#ifdef WITH_FIDO2 + ssh_pki_ctx temp_ctx = NULL; + ssh_pki_ctx ctx_to_use = pki_context; + int rc; + + /* If no context provided, create a temporary default one */ + if (pki_context == NULL) { + SSH_LOG(SSH_LOG_INFO, + "No PKI context provided, using the default one"); + + temp_ctx = ssh_pki_ctx_new(); + if (temp_ctx == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context"); + return SSH_ERROR; + } + ctx_to_use = temp_ctx; + } + + /* Verify that we have valid SK callbacks */ + if (ctx_to_use->sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Missing SK callbacks in PKI context"); + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + return SSH_ERROR; + } + + rc = pki_sk_enroll_key(ctx_to_use, type, pkey); + + /* Clean up temporary context if we created one */ + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + + return rc; +#else /* WITH_FIDO2 */ + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ + } else { + int parameter = 0; + + if (type == SSH_KEYTYPE_RSA && pki_context != NULL) { + parameter = pki_context->rsa_key_size; + } + + return pki_generate_key_internal(type, parameter, pkey); + } +} + +/** + * @brief Create a public key from a private key. + * + * @param[in] privkey The private key to get the public key from. + * + * @param[out] pkey A pointer to store the newly allocated public key. You + * NEED to free the key using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +int ssh_pki_export_privkey_to_pubkey(const ssh_key privkey, + ssh_key *pkey) +{ + ssh_key pubkey = NULL; + + if (privkey == NULL || !ssh_key_is_private(privkey)) { + return SSH_ERROR; + } + + pubkey = pki_key_dup(privkey, 1); + if (pubkey == NULL) { + return SSH_ERROR; + } + + *pkey = pubkey; + return SSH_OK; +} + +/** + * @internal + * + * @brief Pack security key private data into a buffer. + * + * This function packs the common security key fields (application, flags, + * key handle, and reserved data) into a buffer. + * This is used for both ECDSA and Ed25519 security keys when exporting + * private key data. + * + * @param[in] buffer The buffer to pack the security key data into. + * + * @param[in] key The security key containing the data to pack. + * Must be a security key type (SK_ECDSA or SK_ED25519). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_buffer_pack() + */ +int pki_buffer_pack_sk_priv_data(ssh_buffer buffer, ssh_key key) +{ + return ssh_buffer_pack(buffer, + "SbSS", + key->sk_application, + key->sk_flags, + key->sk_key_handle, + key->sk_reserved); +} + +/** + * @internal + * + * @brief Unpack security key private data from a buffer. + * + * This function unpacks the common security key fields (application, flags, + * key handle, and reserved data) from a buffer. + * This is used for both ECDSA and Ed25519 security keys when importing + * private key data. + * + * @param[in] buffer The buffer to unpack the security key data from. + * + * @param[in] key The security key to store the unpacked data into. + * Must be a security key type (SK_ECDSA or SK_ED25519). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_buffer_unpack() + */ +int pki_buffer_unpack_sk_priv_data(ssh_buffer buffer, ssh_key key) +{ + return ssh_buffer_unpack(buffer, + "SbSS", + &key->sk_application, + &key->sk_flags, + &key->sk_key_handle, + &key->sk_reserved); +} + +/** + * @internal + * + * @brief Create a key_blob from a public key. + * + * The "key_blob" is encoded as per RFC 4253 section 6.6 "Public Key + * Algorithms" for any of the supported protocol 2 key types. + * Encoding of EC keys is described in RFC 5656 section 3.1 "Key + * Format". + * + * @param[in] key A public or private key to create the public ssh_string + * from. + * + * @param[out] pblob A pointer to store the newly allocated key blob. You + * need to free it using ssh_string_free(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + * + * @see ssh_string_free() + */ +int ssh_pki_export_pubkey_blob(const ssh_key key, + ssh_string *pblob) +{ + ssh_string blob = NULL; + + if (key == NULL) { + return SSH_OK; + } + + blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); + if (blob == NULL) { + return SSH_ERROR; + } + + *pblob = blob; + return SSH_OK; +} + +/** + * @internal + * + * @brief Create a key_blob from a private key. + * + * The "key_blob" is encoded as per draft-miller-ssh-agent-08 section 4.2 + * "Adding keys to the agent" for any of the supported key types. + * + * @param[in] key A private key to create the private ssh_string from. + * + * @param[out] pblob A pointer to store the newly allocated key blob. You + * need to free it using ssh_string_free(). + * + * @return SSH_OK on success, SSH_ERROR otherwise. + * + * @see ssh_string_free() + */ +int ssh_pki_export_privkey_blob(const ssh_key key, + ssh_string *pblob) +{ + ssh_string blob = NULL; + + if (key == NULL) { + return SSH_OK; + } + + blob = pki_key_to_blob(key, SSH_KEY_PRIVATE); + if (blob == NULL) { + return SSH_ERROR; + } + + *pblob = blob; + return SSH_OK; +} + +/** + * @brief Convert a public key to a base64 encoded key. + * + * @param[in] key The key to hash + * + * @param[out] b64_key A pointer to store the allocated base64 encoded key. You + * need to free the buffer using ssh_string_free_char() + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_string_free_char() + */ +int ssh_pki_export_pubkey_base64(const ssh_key key, + char **b64_key) +{ + ssh_string key_blob = NULL; + unsigned char *b64 = NULL; + + if (key == NULL || b64_key == NULL) { + return SSH_ERROR; + } + + key_blob = pki_key_to_blob(key, SSH_KEY_PUBLIC); + if (key_blob == NULL) { + return SSH_ERROR; + } + + b64 = bin_to_base64(ssh_string_data(key_blob), ssh_string_len(key_blob)); + SSH_STRING_FREE(key_blob); + if (b64 == NULL) { + return SSH_ERROR; + } + + *b64_key = (char *)b64; + + return SSH_OK; +} + +/** + * @brief Export public key to file + * + * Exports the public key in AuthorizedKeysFile acceptable format. + * For more information see `man sshd` + * + * @param key A key to export + * + * @param filename The name of the output file + * + * @returns SSH_OK on success, SSH_ERROR otherwise. + */ +int ssh_pki_export_pubkey_file(const ssh_key key, + const char *filename) +{ + char key_buf[MAX_LINE_SIZE]; + char *host = NULL; + char *b64_key = NULL; + char *user = NULL; + FILE *fp = NULL; + int rc; + + if (key == NULL || filename == NULL || *filename == '\0') { + return SSH_ERROR; + } + + user = ssh_get_local_username(); + if (user == NULL) { + return SSH_ERROR; + } + + host = ssh_get_local_hostname(); + if (host == NULL) { + free(user); + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + if (rc < 0) { + free(user); + free(host); + return SSH_ERROR; + } + + rc = snprintf(key_buf, sizeof(key_buf), + "%s %s %s@%s\n", + key->type_c, + b64_key, + user, + host); + free(user); + free(host); + free(b64_key); + if (rc < 0) { + return SSH_ERROR; + } + + fp = fopen(filename, "wb+"); + if (fp == NULL) { + return SSH_ERROR; + } + rc = fwrite(key_buf, strlen(key_buf), 1, fp); + if (rc != 1 || ferror(fp)) { + fclose(fp); + unlink(filename); + return SSH_ERROR; + } + fclose(fp); + + return SSH_OK; +} + +/** + * @brief Copy the certificate part of a public key into a private key. + * + * @param[in] certkey The certificate key. + * + * @param[in] privkey The target private key to copy the certificate to. + * + * @returns SSH_OK on success, SSH_ERROR otherwise. + **/ +int ssh_pki_copy_cert_to_privkey(const ssh_key certkey, ssh_key privkey) +{ + ssh_buffer cert_buffer = NULL; + int rc, cmp; + + if (certkey == NULL || privkey == NULL) { + return SSH_ERROR; + } + + if (privkey->cert != NULL) { + return SSH_ERROR; + } + + if (certkey->cert == NULL) { + return SSH_ERROR; + } + + /* make sure the public keys match */ + cmp = ssh_key_cmp(certkey, privkey, SSH_KEY_CMP_PUBLIC); + if (cmp != 0) { + return SSH_ERROR; + } + + cert_buffer = ssh_buffer_new(); + if (cert_buffer == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_buffer(cert_buffer, certkey->cert); + if (rc != 0) { + SSH_BUFFER_FREE(cert_buffer); + return SSH_ERROR; + } + + privkey->cert = cert_buffer; + privkey->cert_type = certkey->type; + return SSH_OK; +} + +int ssh_pki_export_signature_blob(const ssh_signature sig, + ssh_string *sig_blob) +{ + ssh_buffer buf = NULL; + ssh_string str = NULL; + int rc; + + if (sig == NULL || sig_blob == NULL) { + return SSH_ERROR; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + str = ssh_string_from_char(sig->type_c); + if (str == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(buf, str); + SSH_STRING_FREE(str); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + str = pki_signature_to_blob(sig); + if (str == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + rc = ssh_buffer_add_ssh_string(buf, str); + SSH_STRING_FREE(str); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + if (is_sk_key_type(sig->type)) { + /* Add flags and counter for SK keys */ + rc = ssh_buffer_pack(buf, "bd", sig->sk_flags, sig->sk_counter); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + } + + str = ssh_string_new(ssh_buffer_get_len(buf)); + if (str == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + rc = ssh_string_fill(str, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); + SSH_BUFFER_FREE(buf); + if (rc < 0) { + SSH_STRING_FREE(str); + return SSH_ERROR; + } + + *sig_blob = str; + + return SSH_OK; +} + +int ssh_pki_import_signature_blob(const ssh_string sig_blob, + const ssh_key pubkey, + ssh_signature *psig) +{ + ssh_signature sig = NULL; + enum ssh_keytypes_e type; + enum ssh_digest_e hash_type; + ssh_string algorithm = NULL, blob = NULL; + ssh_buffer buf = NULL; + const char *alg = NULL; + uint8_t flags = 0; + uint32_t counter = 0; + int rc; + + if (sig_blob == NULL || psig == NULL) { + return SSH_ERROR; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_add_data(buf, + ssh_string_data(sig_blob), + (uint32_t)ssh_string_len(sig_blob)); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + algorithm = ssh_buffer_get_ssh_string(buf); + if (algorithm == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + alg = ssh_string_get_char(algorithm); + type = ssh_key_type_from_signature_name(alg); + hash_type = ssh_key_hash_from_name(alg); + SSH_STRING_FREE(algorithm); + + blob = ssh_buffer_get_ssh_string(buf); + if (blob == NULL) { + SSH_BUFFER_FREE(buf); + return SSH_ERROR; + } + + if (type == SSH_KEYTYPE_SK_ECDSA || + type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_unpack(buf, "bd", &flags, &counter); + if (rc < 0) { + SSH_BUFFER_FREE(buf); + SSH_STRING_FREE(blob); + return SSH_ERROR; + } + } + SSH_BUFFER_FREE(buf); + + sig = pki_signature_from_blob(pubkey, blob, type, hash_type); + SSH_STRING_FREE(blob); + if (sig == NULL) { + return SSH_ERROR; + } + + /* Set SK specific values */ + sig->sk_flags = flags; + sig->sk_counter = counter; + + *psig = sig; + return SSH_OK; +} + +/** + * @internal + * + * @brief Check if the provided key can be used with the provided hash type for + * data signing or signature verification. + * + * @param[in] key The key to be checked. + * @param[in] hash_type The digest algorithm to be checked. + * + * @return SSH_OK if compatible; SSH_ERROR otherwise + */ +int pki_key_check_hash_compatible(ssh_key key, + enum ssh_digest_e hash_type) +{ + if (key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Null pointer provided as key to " + "pki_key_check_hash_compatible()"); + return SSH_ERROR; + } + + switch(key->type) { + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA: + if (hash_type == SSH_DIGEST_SHA1) { + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, "SHA1 is not allowed in FIPS mode"); + return SSH_ERROR; + } else { + return SSH_OK; + } + } + + if (hash_type == SSH_DIGEST_SHA256 || + hash_type == SSH_DIGEST_SHA512) + { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + if (hash_type == SSH_DIGEST_SHA256) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + if (hash_type == SSH_DIGEST_SHA384) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + if (hash_type == SSH_DIGEST_SHA512) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + if (hash_type == SSH_DIGEST_AUTO) { + return SSH_OK; + } + break; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_UNKNOWN: + SSH_LOG(SSH_LOG_TRACE, "Unknown key type %d", key->type); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_TRACE, "Key type %d incompatible with hash type %d", + key->type, hash_type); + + return SSH_ERROR; +} + +/** + * @brief Prepare buffer for FIDO2/U2F security key signature verification + * + * This function creates a buffer containing the application hash, flags, + * counter, and input hash for FIDO/U2F key signature verification. + * + * @param key The SSH key containing sk_application + * @param sig The signature containing sk_flags and sk_counter + * @param input The input data to hash + * @param input_len Length of the input data + * @param sk_buffer_out Pointer to store the created buffer + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int pki_sk_signature_buffer_prepare(const ssh_key key, + const ssh_signature sig, + const unsigned char *input, + size_t input_len, + ssh_buffer *sk_buffer_out) +{ + ssh_buffer sk_buffer = NULL; + SHA256CTX ctx = NULL; + unsigned char application_hash[SHA256_DIGEST_LEN] = {0}; + unsigned char input_hash[SHA256_DIGEST_LEN] = {0}; + int rc, ret = SSH_ERROR; + + if (key == NULL || sig == NULL || input == NULL || sk_buffer_out == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter(s) provided to %s()", __func__); + return SSH_ERROR; + } + + *sk_buffer_out = NULL; + + /* Calculate application hash */ + ctx = sha256_init(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for application hash"); + return SSH_ERROR; + } + sha256_update(ctx, + ssh_string_data(key->sk_application), + ssh_string_len(key->sk_application)); + sha256_final(application_hash, ctx); + + /* Calculate input hash */ + ctx = sha256_init(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Can not create SHA256CTX for input hash"); + goto out; + } + sha256_update(ctx, input, input_len); + sha256_final(input_hash, ctx); + + /* Create and pack the sk_buffer */ + sk_buffer = ssh_buffer_new(); + if (sk_buffer == NULL) { + goto out; + } + + rc = ssh_buffer_pack(sk_buffer, + "PbdP", + (size_t)SHA256_DIGEST_LEN, + application_hash, + sig->sk_flags, + sig->sk_counter, + (size_t)SHA256_DIGEST_LEN, + input_hash); + if (rc != SSH_OK) { + goto out; + } + + *sk_buffer_out = sk_buffer; + sk_buffer = NULL; + ret = SSH_OK; + +out: + SSH_BUFFER_FREE(sk_buffer); + ssh_burn(application_hash, SHA256_DIGEST_LEN); + ssh_burn(input_hash, SHA256_DIGEST_LEN); + + return ret; +} + +int ssh_pki_signature_verify(ssh_session session, + ssh_signature sig, + const ssh_key key, + const unsigned char *input, + size_t input_len) +{ + int rc; + bool allowed; + enum ssh_keytypes_e key_type; + + if (session == NULL || sig == NULL || key == NULL || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter(s) provided to %s()", __func__); + return SSH_ERROR; + } + key_type = ssh_key_type_plain(key->type); + + SSH_LOG(SSH_LOG_FUNCTIONS, + "Going to verify a %s type signature", + sig->type_c); + + if (key_type != sig->type) { + SSH_LOG(SSH_LOG_TRACE, + "Can not verify %s signature with %s key", + sig->type_c, key->type_c); + return SSH_ERROR; + } + + allowed = ssh_key_size_allowed(session, key); + if (!allowed) { + ssh_set_error(session, + SSH_FATAL, + "The '%s' key of size %d is not allowed by RSA_MIN_SIZE", + key->type_c, + ssh_key_size(key)); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(key, sig->hash_type); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + if (is_sk_key_type(key->type)) { + ssh_buffer sk_buffer = NULL; + + rc = pki_sk_signature_buffer_prepare(key, + sig, + input, + input_len, + &sk_buffer); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + rc = pki_verify_data_signature(sig, + key, + ssh_buffer_get(sk_buffer), + ssh_buffer_get_len(sk_buffer)); + SSH_BUFFER_FREE(sk_buffer); + return rc; + } + + return pki_verify_data_signature(sig, key, input, input_len); +} + +ssh_signature pki_do_sign(const ssh_key privkey, + const unsigned char *input, + size_t input_len, + enum ssh_digest_e hash_type) +{ + int rc; + + if (privkey == NULL || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_do_sign()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + return pki_sign_data(privkey, hash_type, input, input_len); +} + +/** + * @brief Encodes a binary signature blob as an sshsig armored signature + * + * @param blob The binary signature blob to encode + * @param out_str Pointer to store the allocated base64 encoded string + * Must be freed with ssh_string_free_char() + * + * @return SSH_OK on success, SSH_ERROR on error + */ +static int sshsig_armor(ssh_buffer blob, char **out_str) +{ + char *b64_data = NULL; + char *armored = NULL; + const unsigned char *data = NULL; + size_t len, b64_len, armored_len, num_lines; + size_t i, j; + + if (blob == NULL || out_str == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid input parameters"); + return SSH_ERROR; + } + + *out_str = NULL; + + data = ssh_buffer_get(blob); + len = ssh_buffer_get_len(blob); + + b64_data = (char *)bin_to_base64(data, len); + if (b64_data == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to base64 encode signature blob"); + return SSH_ERROR; + } + + b64_len = strlen(b64_data); + + /* Calculate space needed: header + data with line breaks + footer */ + num_lines = (b64_len + SSHSIG_LINE_LENGTH - 1) / + SSHSIG_LINE_LENGTH; /* Round up division */ + armored_len = strlen(SSHSIG_BEGIN_SIGNATURE) + 1 + /* header + \n */ + b64_len + num_lines + /* data + line breaks */ + strlen(SSHSIG_END_SIGNATURE) + 1; /* footer + \0 */ + + armored = calloc(armored_len, 1); + if (armored == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate %zu bytes for armored signature", + armored_len); + SAFE_FREE(b64_data); + return SSH_ERROR; + } + + j = snprintf(armored, armored_len, SSHSIG_BEGIN_SIGNATURE "\n"); + for (i = 0; i < b64_len; i++) { + if (i > 0 && i % SSHSIG_LINE_LENGTH == 0) { + armored[j++] = '\n'; + } + armored[j++] = b64_data[i]; + } + armored[j++] = '\n'; + snprintf(armored + j, armored_len - j, SSHSIG_END_SIGNATURE); + + SAFE_FREE(b64_data); + + *out_str = armored; + return SSH_OK; +} + +/** + * @brief Dearmor an sshsig signature from ASCII armored format to binary + * + * @param[in] signature The armored sshsig signature string + * @param[out] out Pointer to store the allocated binary buffer + * + * @return SSH_OK on success, SSH_ERROR on error + */ +static int sshsig_dearmor(const char *signature, ssh_buffer *out) +{ + const char *begin = NULL; + const char *end = NULL; + char *clean_b64 = NULL; + ssh_buffer decoded_buffer = NULL; + int i, j; + int rc = SSH_ERROR; + + if (signature == NULL || out == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid input parameters"); + return SSH_ERROR; + } + + *out = NULL; + + rc = strncmp(signature, + SSHSIG_BEGIN_SIGNATURE, + strlen(SSHSIG_BEGIN_SIGNATURE)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Signature does not start with expected header"); + return SSH_ERROR; + } + + begin = signature + strlen(SSHSIG_BEGIN_SIGNATURE); + while (isspace(*begin)) { + begin++; + } + + end = strstr(begin, SSHSIG_END_SIGNATURE); + if (end == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Signature end marker not found"); + return SSH_ERROR; + } + + /* Backtrack to find the real end of data */ + while (end > begin && (isspace(*(end - 1)))) { + end--; + } + + clean_b64 = calloc(end - begin + 1, 1); + if (clean_b64 == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate %td bytes for clean base64 data", + end - begin + 1); + return SSH_ERROR; + } + + for (i = 0, j = 0; begin + i < end; i++) { + if (!isspace(begin[i])) { + clean_b64[j++] = begin[i]; + } + } + clean_b64[j] = '\0'; + + decoded_buffer = base64_to_bin(clean_b64); + SAFE_FREE(clean_b64); + + if (decoded_buffer == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to decode base64 signature data"); + return SSH_ERROR; + } + + *out = decoded_buffer; + return SSH_OK; +} + +/** + * @internal + * @brief Common helper function to prepare the data in sshsig format + * + * This function handles the common logic to prepare the sshsig format: + * 1. Hash the input data using the specified algorithm + * 2. Build the data buffer to sign + * + * @param data The raw data to process + * @param data_length The length of the data + * @param hash_alg The hash algorithm to use (sha256 or sha512) + * @param sig_namespace The signature namespace + * @param tosign_buf Pointer to store the allocated to-sign buffer + * + * @return SSH_OK on success, SSH_ERROR on error + */ +static int sshsig_prepare_data(const void *data, + size_t data_length, + const char *hash_alg, + const char *sig_namespace, + ssh_buffer *tosign_buf) +{ + ssh_buffer tosign = NULL; + ssh_string hash_string = NULL; + char hash[SHA512_DIGEST_LEN]; + size_t hash_len; + int rc = SSH_ERROR; + + if (data == NULL || hash_alg == NULL || sig_namespace == NULL || + tosign_buf == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid input parameters"); + return SSH_ERROR; + } + + *tosign_buf = NULL; + + if (strcmp(hash_alg, "sha256") == 0) { + hash_len = SHA256_DIGEST_LEN; + rc = sha256(data, data_length, (unsigned char *)hash); + } else if (strcmp(hash_alg, "sha512") == 0) { + hash_len = SHA512_DIGEST_LEN; + rc = sha512(data, data_length, (unsigned char *)hash); + } else { + SSH_LOG(SSH_LOG_TRACE, "Unsupported hash algorithm: %s", hash_alg); + goto cleanup; + } + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to compute %s hash of data", hash_alg); + goto cleanup; + } + + hash_string = ssh_string_new(hash_len); + if (hash_string == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to allocate ssh_string for hash"); + goto cleanup; + } + + rc = ssh_string_fill(hash_string, hash, hash_len); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to fill ssh_string with hash data"); + goto cleanup; + } + + tosign = ssh_buffer_new(); + if (tosign == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to allocate buffer for signing data"); + goto cleanup; + } + + rc = ssh_buffer_pack(tosign, + "tsssS", + SSHSIG_MAGIC_PREAMBLE, + sig_namespace, + "", + hash_alg, + hash_string); + + if (rc == SSH_OK) { + *tosign_buf = tosign; + tosign = NULL; + } else { + SSH_LOG(SSH_LOG_TRACE, "Failed to pack signing data into buffer"); + } + +cleanup: + SSH_BUFFER_FREE(tosign); + SSH_STRING_FREE(hash_string); + + return rc; +} + +/** + * @brief Signs data in sshsig compatible format + * + * @param data The data to sign + * @param data_length The length of the data + * @param privkey The private key to sign with + * @param pki_context The PKI context. For non-SK keys, this parameter is + * ignored and can be NULL. For SK keys, can be NULL in + * which case a default context with default callbacks + * will be used. If provided, the context must have + * sk_callbacks set with a valid sign callback + * implementation. See ssh_pki_ctx_set_sk_callbacks(). + * @param sig_namespace The signature namespace (e.g. "file", "email", etc.) + * @param hash_alg The hash algorithm to use (SSHSIG_DIGEST_SHA2_256 or + * SSHSIG_DIGEST_SHA2_512) + * @param signature Pointer to store the allocated signature string in the + * armored format. Must be freed with + * ssh_string_free_char() + * + * @return SSH_OK on success, SSH_ERROR on error + */ +int sshsig_sign(const void *data, + size_t data_length, + ssh_key privkey, + ssh_pki_ctx pki_context, + const char *sig_namespace, + enum sshsig_digest_e hash_alg, + char **signature) +{ + ssh_buffer tosign = NULL; + ssh_buffer signature_blob = NULL; + ssh_signature sig = NULL; + ssh_string sig_string = NULL; + ssh_string pub_blob = NULL; + ssh_pki_ctx temp_ctx = NULL; + ssh_pki_ctx ctx_to_use = NULL; + enum ssh_digest_e digest_type; + const char *hash_alg_str = NULL; + int rc = SSH_ERROR; + + if (privkey == NULL || data == NULL || sig_namespace == NULL || + signature == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid parameters provided to sshsig_sign"); + return SSH_ERROR; + } + + if (strlen(sig_namespace) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid parameters provided to sshsig_sign: empty namespace " + "string"); + return SSH_ERROR; + } + + /* Check if this is an SK key that requires a PKI context */ + if (is_sk_key_type(privkey->type)) { + /* If no context provided, create a temporary default one */ + if (pki_context == NULL) { + SSH_LOG(SSH_LOG_INFO, + "No PKI context provided, using the default one"); + + temp_ctx = ssh_pki_ctx_new(); + if (temp_ctx == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context"); + return SSH_ERROR; + } + ctx_to_use = temp_ctx; + } else { + ctx_to_use = pki_context; + } + + /* Verify that we have valid SK callbacks */ + if (ctx_to_use->sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Security Key callbacks not configured in PKI context"); + goto cleanup; + } + } + + *signature = NULL; + + if (hash_alg == SSHSIG_DIGEST_SHA2_256) { + hash_alg_str = "sha256"; + } else if (hash_alg == SSHSIG_DIGEST_SHA2_512) { + hash_alg_str = "sha512"; + } else { + SSH_LOG(SSH_LOG_TRACE, "Invalid hash algorithm %d", hash_alg); + return SSH_ERROR; + } + + rc = sshsig_prepare_data(data, + data_length, + hash_alg_str, + sig_namespace, + &tosign); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to prepare data for sshsig signing"); + goto cleanup; + } + + /* Use appropriate signing method based on key type */ + if (is_sk_key_type(privkey->type)) { +#ifdef WITH_FIDO2 + sig = pki_sk_do_sign(ctx_to_use, + privkey, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign)); +#else + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + goto cleanup; +#endif + } else { + digest_type = key_type_to_hash(ssh_key_type_plain(privkey->type)); + sig = pki_sign_data(privkey, + digest_type, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign)); + } + if (sig == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to sign data with private key"); + goto cleanup; + } + + rc = ssh_pki_export_pubkey_blob(privkey, &pub_blob); + if (rc != SSH_OK || pub_blob == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to export public key blob from private key"); + goto cleanup; + } + + rc = ssh_pki_export_signature_blob(sig, &sig_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to export signature blob"); + goto cleanup; + } + + signature_blob = ssh_buffer_new(); + if (signature_blob == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to allocate signature buffer"); + goto cleanup; + } + + rc = ssh_buffer_pack(signature_blob, + "tdSsssS", + SSHSIG_MAGIC_PREAMBLE, + SSHSIG_VERSION, + pub_blob, + sig_namespace, + "", + hash_alg_str, + sig_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to pack signature blob"); + goto cleanup; + } + + rc = sshsig_armor(signature_blob, signature); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to armor signature blob"); + goto cleanup; + } + +cleanup: + SSH_BUFFER_FREE(tosign); + SSH_BUFFER_FREE(signature_blob); + SSH_SIGNATURE_FREE(sig); + SSH_STRING_FREE(sig_string); + SSH_STRING_FREE(pub_blob); + + /* Clean up temporary context if we created one */ + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + + return rc; +} + +/** + * @brief Verifies an sshsig formatted signature against data + * + * @param data The data to verify + * @param data_length The length of the data + * @param signature The armored sshsig signature + * @param sig_namespace The expected signature namespace + * @param sign_key If not NULL, returns the allocated public key that was + * used for signing this data. Must be freed with + * ssh_key_free(). Note that this is an output parameter + * and is not checked against "allowed signers". The + * caller needs to compare it with expected signer key + * using ssh_key_cmp(). + * + * @return SSH_OK on success, SSH_ERROR on verification failure + */ +int sshsig_verify(const void *data, + size_t data_length, + const char *signature, + const char *sig_namespace, + ssh_key *sign_key) +{ + ssh_buffer sig_buf = NULL; + ssh_buffer tosign = NULL; + ssh_key key = NULL; + char *hash_alg_str = NULL; + ssh_string sig_data = NULL; + ssh_string sig_namespace_str = NULL; + ssh_string reserved_str = NULL; + ssh_string pubkey_blob = NULL; + int rc = SSH_ERROR; + ssh_signature signature_obj = NULL; + uint32_t sig_version; + + if (sign_key != NULL) { + *sign_key = NULL; + } + + if (signature == NULL || data == NULL || sig_namespace == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Invalid parameters provided to sshsig_verify"); + return SSH_ERROR; + } + + if (strlen(sig_namespace) == 0) { + SSH_LOG(SSH_LOG_TRACE, + "Invalid parameters provided to sshsig_verify: empty namespace " + "string"); + return SSH_ERROR; + } + + rc = sshsig_dearmor(signature, &sig_buf); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to dearmor signature"); + return SSH_ERROR; + } + + if (ssh_buffer_get_len(sig_buf) < SSHSIG_MAGIC_PREAMBLE_LEN || + memcmp(ssh_buffer_get(sig_buf), + SSHSIG_MAGIC_PREAMBLE, + SSHSIG_MAGIC_PREAMBLE_LEN) != 0) { + SSH_LOG(SSH_LOG_TRACE, "Invalid signature magic preamble"); + SSH_BUFFER_FREE(sig_buf); + return SSH_ERROR; + } + + ssh_buffer_pass_bytes(sig_buf, SSHSIG_MAGIC_PREAMBLE_LEN); + rc = ssh_buffer_unpack(sig_buf, + "dSSSsS", + &sig_version, + &pubkey_blob, + &sig_namespace_str, + &reserved_str, + &hash_alg_str, + &sig_data); + + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to unpack signature buffer"); + SSH_BUFFER_FREE(sig_buf); + return SSH_ERROR; + } + + if (sig_version != SSHSIG_VERSION) { + SSH_LOG(SSH_LOG_TRACE, + "Unsupported signature version %u, expected %u", + sig_version, + SSHSIG_VERSION); + rc = SSH_ERROR; + goto cleanup; + } + + rc = ssh_pki_import_pubkey_blob(pubkey_blob, &key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key from signature"); + goto cleanup; + } + + if (ssh_string_len(sig_namespace_str) != strlen(sig_namespace) || + memcmp(ssh_string_data(sig_namespace_str), + sig_namespace, + strlen(sig_namespace)) != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Signature namespace mismatch: expected '%s', got '%s'", + sig_namespace, + ssh_string_get_char(sig_namespace_str)); + rc = SSH_ERROR; + goto cleanup; + } + + if (strcmp(hash_alg_str, "sha256") != 0 && + strcmp(hash_alg_str, "sha512") != 0) { + SSH_LOG(SSH_LOG_TRACE, "Unsupported hash algorithm '%s'", hash_alg_str); + rc = SSH_ERROR; + goto cleanup; + } + + rc = sshsig_prepare_data(data, + data_length, + hash_alg_str, + sig_namespace, + &tosign); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to prepare data for sshsig verification"); + goto cleanup; + } + + rc = ssh_pki_import_signature_blob(sig_data, key, &signature_obj); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import signature blob"); + goto cleanup; + } + + if (is_sk_key_type(key->type)) { + ssh_buffer sk_buffer = NULL; + rc = pki_sk_signature_buffer_prepare(key, + signature_obj, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign), + &sk_buffer); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to prepare sk signature buffer"); + goto cleanup; + } + + rc = pki_verify_data_signature(signature_obj, + key, + ssh_buffer_get(sk_buffer), + ssh_buffer_get_len(sk_buffer)); + SSH_BUFFER_FREE(sk_buffer); + } else { + rc = pki_verify_data_signature(signature_obj, + key, + ssh_buffer_get(tosign), + ssh_buffer_get_len(tosign)); + } + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Signature verification failed"); + goto cleanup; + } + + if (sign_key != NULL) { + *sign_key = key; + key = NULL; /* Transferred ownership */ + } + +cleanup: + SSH_STRING_FREE(pubkey_blob); + SSH_STRING_FREE(sig_namespace_str); + SSH_STRING_FREE(reserved_str); + SSH_STRING_FREE(sig_data); + SSH_BUFFER_FREE(tosign); + SSH_BUFFER_FREE(sig_buf); + SSH_KEY_FREE(key); + SAFE_FREE(hash_alg_str); + SSH_SIGNATURE_FREE(signature_obj); + + return rc; +} + +/* + * This function signs the session id as a string then + * the content of sigbuf */ +ssh_string ssh_pki_do_sign(ssh_session session, + ssh_buffer sigbuf, + const ssh_key privkey, + enum ssh_digest_e hash_type) +{ + struct ssh_crypto_struct *crypto = NULL; + + ssh_signature sig = NULL; + ssh_string sig_blob = NULL; + + ssh_string session_id = NULL; + ssh_buffer sign_input = NULL; + + int rc; + + if (session == NULL || sigbuf == NULL || privkey == NULL || + !ssh_key_is_private(privkey)) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "ssh_pki_do_sign()"); + return NULL; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return NULL; + } + + /* Get the session ID */ + session_id = ssh_string_new(crypto->session_id_len); + if (session_id == NULL) { + return NULL; + } + rc = ssh_string_fill(session_id, crypto->session_id, crypto->session_id_len); + if (rc < 0) { + goto end; + } + + /* Fill the input */ + sign_input = ssh_buffer_new(); + if (sign_input == NULL) { + goto end; + } + ssh_buffer_set_secure(sign_input); + + rc = ssh_buffer_pack(sign_input, + "SP", + session_id, + (size_t)ssh_buffer_get_len(sigbuf), + ssh_buffer_get(sigbuf)); + if (rc != SSH_OK) { + goto end; + } + + /* Generate the signature */ + if (is_sk_key_type(privkey->type)) { +#ifdef WITH_FIDO2 + if (session->pki_context == NULL || + session->pki_context->sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Missing PKI context or SK callbacks"); + goto end; + } + + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Incompatible hash type %d for sk key type %d", + hash_type, + privkey->type); + goto end; + } + + sig = pki_sk_do_sign(session->pki_context, + privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input)); +#else + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + goto end; +#endif /* WITH_FIDO2 */ + } else { + sig = pki_do_sign(privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + hash_type); + } + + if (sig == NULL) { + goto end; + } + + /* Convert the signature to blob */ + rc = ssh_pki_export_signature_blob(sig, &sig_blob); + if (rc < 0) { + sig_blob = NULL; + } + +end: + ssh_signature_free(sig); + SSH_BUFFER_FREE(sign_input); + SSH_STRING_FREE(session_id); + + return sig_blob; +} + +ssh_string ssh_pki_do_sign_agent(ssh_session session, + struct ssh_buffer_struct *buf, + const ssh_key pubkey) +{ + struct ssh_crypto_struct *crypto = NULL; + ssh_string session_id = NULL; + ssh_string sig_blob = NULL; + ssh_buffer sig_buf = NULL; + int rc; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_BOTH); + if (crypto == NULL) { + return NULL; + } + + /* prepend session identifier */ + session_id = ssh_string_new(crypto->session_id_len); + if (session_id == NULL) { + return NULL; + } + rc = ssh_string_fill(session_id, crypto->session_id, crypto->session_id_len); + if (rc < 0) { + SSH_STRING_FREE(session_id); + return NULL; + } + + sig_buf = ssh_buffer_new(); + if (sig_buf == NULL) { + SSH_STRING_FREE(session_id); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(sig_buf, session_id); + if (rc < 0) { + SSH_STRING_FREE(session_id); + SSH_BUFFER_FREE(sig_buf); + return NULL; + } + SSH_STRING_FREE(session_id); + + /* append out buffer */ + if (ssh_buffer_add_buffer(sig_buf, buf) < 0) { + SSH_BUFFER_FREE(sig_buf); + return NULL; + } + + /* create signature */ + sig_blob = ssh_agent_sign_data(session, pubkey, sig_buf); + + SSH_BUFFER_FREE(sig_buf); + + return sig_blob; +} + +#ifdef WITH_SERVER +ssh_string ssh_srv_pki_do_sign_sessionid(ssh_session session, + const ssh_key privkey, + const enum ssh_digest_e digest) +{ + struct ssh_crypto_struct *crypto = NULL; + bool allowed; + ssh_signature sig = NULL; + ssh_string sig_blob = NULL; + + ssh_buffer sign_input = NULL; + + int rc; + + if (session == NULL || privkey == NULL || !ssh_key_is_private(privkey)) { + return NULL; + } + + allowed = ssh_key_size_allowed(session, privkey); + if (!allowed) { + ssh_set_error(session, SSH_FATAL, "The hostkey size too small"); + return NULL; + } + + crypto = session->next_crypto ? session->next_crypto : + session->current_crypto; + + if (crypto->secret_hash == NULL){ + ssh_set_error(session, SSH_FATAL, "Missing secret_hash"); + return NULL; + } + + /* Fill the input */ + sign_input = ssh_buffer_new(); + if (sign_input == NULL) { + goto end; + } + ssh_buffer_set_secure(sign_input); + + rc = ssh_buffer_pack(sign_input, + "P", + crypto->digest_len, + crypto->secret_hash); + if (rc != SSH_OK) { + goto end; + } + + /* Generate the signature */ + sig = pki_do_sign(privkey, + ssh_buffer_get(sign_input), + ssh_buffer_get_len(sign_input), + digest); + if (sig == NULL) { + goto end; + } + + /* Convert the signature to blob */ + rc = ssh_pki_export_signature_blob(sig, &sig_blob); + if (rc < 0) { + sig_blob = NULL; + } + +end: + ssh_signature_free(sig); + SSH_BUFFER_FREE(sign_input); + + return sig_blob; +} +#endif /* WITH_SERVER */ + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/pki_container_openssh.c b/src/libs/libssh-0.12.2/src/pki_container_openssh.c new file mode 100644 index 000000000000..3d782b93a0ed --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_container_openssh.c @@ -0,0 +1,696 @@ +/* + * pki_container_openssh.c + * This file is part of the SSH Library + * + * Copyright (c) 2013,2014 Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** + * @ingroup libssh_pki + * * + * @{ + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/buffer.h" + + +/** + * @internal + * + * @brief Import a private key from a ssh buffer. + * + * @param[in] key_blob_buffer The key blob to import as specified in + * key.c:key_private_serialize in OpenSSH source + * code. + * + * @param[out] pkey A pointer where the allocated key can be stored. You + * need to free the memory using ssh_key_free(). + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @see ssh_key_free() + */ +static int pki_openssh_import_privkey_blob(ssh_buffer key_blob_buffer, + ssh_key *pkey) +{ + enum ssh_keytypes_e type; + char *type_s = NULL; + ssh_key key = NULL; + int rc; + + if (pkey == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_unpack(key_blob_buffer, "s", &type_s); + if (rc == SSH_ERROR){ + SSH_LOG(SSH_LOG_TRACE, "Unpack error"); + return SSH_ERROR; + } + + type = ssh_key_type_from_name(type_s); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Unknown key type '%s' found!", type_s); + return SSH_ERROR; + } + SAFE_FREE(type_s); + + rc = pki_import_privkey_buffer(type, key_blob_buffer, &key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to read key in OpenSSH format"); + goto fail; + } + + *pkey = key; + return SSH_OK; +fail: + ssh_key_free(key); + + return SSH_ERROR; +} + +/** + * @brief decrypts an encrypted private key blob in OpenSSH format. + * + */ +static int pki_private_key_decrypt(ssh_string blob, + const char* passphrase, + const char *ciphername, + const char *kdfname, + ssh_string kdfoptions, + ssh_auth_callback auth_fn, + void *auth_data) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + struct ssh_cipher_struct cipher; + uint8_t key_material[128] = {0}; + char passphrase_buffer[128] = {0}; + size_t key_material_len; + ssh_buffer buffer = NULL; + ssh_string salt = NULL; + uint32_t rounds; + int cmp; + int rc; + int i; + + cmp = strcmp(ciphername, "none"); + if (cmp == 0){ + /* no decryption required */ + return SSH_OK; + } + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0){ + memcpy(&cipher, &ciphers[i], sizeof(cipher)); + break; + } + } + + if (ciphers[i].name == NULL){ + SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); + return SSH_ERROR; + } + + cmp = strcmp(kdfname, "bcrypt"); + if (cmp != 0) { + SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); + return SSH_ERROR; + } + if (ssh_string_len(blob) % cipher.blocksize != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Encrypted string not multiple of blocksize: %zu", + ssh_string_len(blob)); + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL){ + return SSH_ERROR; + } + rc = ssh_buffer_add_data(buffer, + ssh_string_data(kdfoptions), + (uint32_t)ssh_string_len(kdfoptions)); + if (rc != SSH_ERROR){ + rc = ssh_buffer_unpack(buffer, "Sd", &salt, &rounds); + } + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR){ + return SSH_ERROR; + } + + /* We need material for key (keysize bits / 8) and IV (blocksize) */ + key_material_len = cipher.keysize/8 + cipher.blocksize; + if (key_material_len > sizeof(key_material)) { + SSH_LOG(SSH_LOG_TRACE, "Key material too big"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Decryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", + cipher.keysize/8, + cipher.blocksize, + rounds, + ssh_string_len(salt)); + + if (passphrase == NULL) { + if (auth_fn == NULL) { + SAFE_FREE(salt); + SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); + return SSH_ERROR; + } + rc = auth_fn("Passphrase", + passphrase_buffer, + sizeof(passphrase_buffer), + 0, + 0, + auth_data); + if (rc != SSH_OK) { + SAFE_FREE(salt); + return SSH_ERROR; + } + passphrase = passphrase_buffer; + } + + rc = bcrypt_pbkdf(passphrase, + strlen(passphrase), + ssh_string_data(salt), + ssh_string_len(salt), + key_material, + key_material_len, + rounds); + SAFE_FREE(salt); + if (rc < 0){ + return SSH_ERROR; + } + ssh_burn(passphrase_buffer, sizeof(passphrase_buffer)); + + cipher.set_decrypt_key(&cipher, + key_material, + key_material + cipher.keysize/8); + cipher.decrypt(&cipher, + ssh_string_data(blob), + ssh_string_data(blob), + ssh_string_len(blob)); + ssh_cipher_clear(&cipher); + return SSH_OK; +} + + +/** @internal + * @brief Import a private key in OpenSSH (new) format. This format is + * typically used with ed25519 keys but can be used for others. + */ +static ssh_key +ssh_pki_openssh_import(const char *text_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data, + bool private) +{ + const char *ptr = text_key; + const char *end = NULL; + char *base64 = NULL; + int cmp; + int rc; + int i; + ssh_buffer buffer = NULL, privkey_buffer = NULL; + char *magic = NULL, *ciphername = NULL, *kdfname = NULL; + uint32_t nkeys = 0, checkint1 = 0, checkint2 = 0xFFFF; + ssh_string kdfoptions = NULL; + ssh_string pubkey0 = NULL; + ssh_string privkeys = NULL; + ssh_string comment = NULL; + ssh_key key = NULL; + uint8_t padding; + + cmp = strncmp(ptr, OPENSSH_HEADER_BEGIN, strlen(OPENSSH_HEADER_BEGIN)); + if (cmp != 0) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no header)"); + goto out; + } + ptr += strlen(OPENSSH_HEADER_BEGIN); + while(ptr[0] != '\0' && !isspace((int)ptr[0])) { + ptr++; + } + end = strstr(ptr, OPENSSH_HEADER_END); + if (end == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (no footer)"); + goto out; + } + base64 = malloc(end - ptr + 1); + if (base64 == NULL) { + goto out; + } + for (i = 0; ptr < end; ptr++) { + if (!isspace((int)ptr[0])) { + base64[i] = ptr[0]; + i++; + } + } + base64[i] = '\0'; + buffer = base64_to_bin(base64); + SAFE_FREE(base64); + if (buffer == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (base64 error)"); + goto out; + } + rc = ssh_buffer_unpack(buffer, "PssSdSS", + strlen(OPENSSH_AUTH_MAGIC) + 1, + &magic, + &ciphername, + &kdfname, + &kdfoptions, + &nkeys, + &pubkey0, + &privkeys); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (unpack error)"); + goto out; + } + cmp = strncmp(magic, OPENSSH_AUTH_MAGIC, strlen(OPENSSH_AUTH_MAGIC)); + if (cmp != 0) { + SSH_LOG(SSH_LOG_TRACE, "Not an OpenSSH private key (bad magic)"); + goto out; + } + SSH_LOG(SSH_LOG_DEBUG, + "Opening OpenSSH private key: ciphername: %s, kdf: %s, nkeys: %" PRIu32, + ciphername, + kdfname, + nkeys); + if (nkeys != 1) { + SSH_LOG(SSH_LOG_TRACE, "Opening OpenSSH private key: only 1 key supported (%" PRIu32 " available)", nkeys); + goto out; + } + + /* If we are interested only in public key do not progress + * to the key decryption later + */ + if (!private) { + rc = ssh_pki_import_pubkey_blob(pubkey0, &key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import public key blob"); + } + /* in either case we clean up here */ + goto out; + } + + rc = pki_private_key_decrypt(privkeys, + passphrase, + ciphername, + kdfname, + kdfoptions, + auth_fn, + auth_data); + if (rc == SSH_ERROR) { + goto out; + } + + privkey_buffer = ssh_buffer_new(); + if (privkey_buffer == NULL) { + goto out; + } + + ssh_buffer_set_secure(privkey_buffer); + ssh_buffer_add_data(privkey_buffer, + ssh_string_data(privkeys), + (uint32_t)ssh_string_len(privkeys)); + + rc = ssh_buffer_unpack(privkey_buffer, "dd", &checkint1, &checkint2); + if (rc == SSH_ERROR || checkint1 != checkint2) { + SSH_LOG(SSH_LOG_TRACE, "OpenSSH private key unpack error (correct password?)"); + goto out; + } + rc = pki_openssh_import_privkey_blob(privkey_buffer, &key); + if (rc == SSH_ERROR) { + goto out; + } + comment = ssh_buffer_get_ssh_string(privkey_buffer); + SAFE_FREE(comment); + /* verify that the remaining data is correct padding */ + for (i = 1; ssh_buffer_get_len(privkey_buffer) > 0; ++i) { + ssh_buffer_get_u8(privkey_buffer, &padding); + if (padding != i) { + ssh_key_free(key); + key = NULL; + SSH_LOG(SSH_LOG_TRACE, "Invalid padding"); + goto out; + } + } +out: + if (buffer != NULL) { + SSH_BUFFER_FREE(buffer); + buffer = NULL; + } + if (privkey_buffer != NULL) { + SSH_BUFFER_FREE(privkey_buffer); + privkey_buffer = NULL; + } + SAFE_FREE(magic); + SAFE_FREE(ciphername); + SAFE_FREE(kdfname); + SAFE_FREE(kdfoptions); + SAFE_FREE(pubkey0); + SAFE_FREE(privkeys); + return key; +} + +ssh_key ssh_pki_openssh_privkey_import(const char *text_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + return ssh_pki_openssh_import(text_key, passphrase, auth_fn, auth_data, true); +} + +ssh_key ssh_pki_openssh_pubkey_import(const char *text_key) +{ + return ssh_pki_openssh_import(text_key, NULL, NULL, NULL, false); +} + + +/** @internal + * @brief encrypts an ed25519 private key blob + * + */ +static int pki_private_key_encrypt(ssh_buffer privkey_buffer, + const char* passphrase, + const char *ciphername, + const char *kdfname, + ssh_auth_callback auth_fn, + void *auth_data, + uint32_t rounds, + ssh_string salt) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + struct ssh_cipher_struct cipher; + uint8_t key_material[128] = {0}; + size_t key_material_len; + char passphrase_buffer[128] = {0}; + int rc; + int i; + int cmp; + + cmp = strcmp(ciphername, "none"); + if (cmp == 0){ + /* no encryption required */ + return SSH_OK; + } + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0){ + memcpy(&cipher, &ciphers[i], sizeof(cipher)); + break; + } + } + + if (ciphers[i].name == NULL){ + SSH_LOG(SSH_LOG_TRACE, "Unsupported cipher %s", ciphername); + return SSH_ERROR; + } + + cmp = strcmp(kdfname, "bcrypt"); + if (cmp != 0){ + SSH_LOG(SSH_LOG_TRACE, "Unsupported KDF %s", kdfname); + return SSH_ERROR; + } + /* We need material for key (keysize bits / 8) and IV (blocksize) */ + key_material_len = cipher.keysize/8 + cipher.blocksize; + if (key_material_len > sizeof(key_material)){ + SSH_LOG(SSH_LOG_TRACE, "Key material too big"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, "Encryption: %d key, %d IV, %" PRIu32 " rounds, %zu bytes salt", + cipher.keysize/8, + cipher.blocksize, rounds, ssh_string_len(salt)); + + if (passphrase == NULL){ + if (auth_fn == NULL){ + SSH_LOG(SSH_LOG_TRACE, "No passphrase provided"); + return SSH_ERROR; + } + rc = auth_fn("Passphrase", + passphrase_buffer, + sizeof(passphrase_buffer), + 0, + 0, + auth_data); + if (rc != SSH_OK){ + return SSH_ERROR; + } + passphrase = passphrase_buffer; + } + + rc = bcrypt_pbkdf(passphrase, + strlen(passphrase), + ssh_string_data(salt), + ssh_string_len(salt), + key_material, + key_material_len, + rounds); + if (rc < 0){ + return SSH_ERROR; + } + + cipher.set_encrypt_key(&cipher, + key_material, + key_material + cipher.keysize/8); + cipher.encrypt(&cipher, + ssh_buffer_get(privkey_buffer), + ssh_buffer_get(privkey_buffer), + ssh_buffer_get_len(privkey_buffer)); + ssh_cipher_clear(&cipher); + ssh_burn(passphrase_buffer, sizeof(passphrase_buffer)); + + return SSH_OK; +} + + +/** @internal + * generate an OpenSSH private key (defined in PROTOCOL.key) and output it in text format. + * @param privkey[in] private key to export + * @returns an SSH string containing the text representation of the exported key. + * @warning currently only supports ED25519 key types. + */ + +ssh_string ssh_pki_openssh_privkey_export(const ssh_key privkey, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + ssh_buffer buffer = NULL; + ssh_string str = NULL, blob = NULL; + ssh_string pubkey_s = NULL; + ssh_buffer privkey_buffer = NULL; + uint32_t rnd; + uint32_t rounds = 16; + ssh_string salt = NULL; + ssh_string kdf_options = NULL; + int to_encrypt=0; + unsigned char *b64 = NULL; + uint32_t str_len, len; + uint8_t padding = 1; + int ok; + int rc; + + if (privkey == NULL) { + return NULL; + } + if (passphrase != NULL || auth_fn != NULL){ + SSH_LOG(SSH_LOG_DEBUG, "Enabling encryption for private key export"); + to_encrypt = 1; + } + buffer = ssh_buffer_new(); + rc = ssh_pki_export_pubkey_blob(privkey, &pubkey_s); + if (buffer == NULL || rc != SSH_OK) { + goto error; + } + + ok = ssh_get_random(&rnd, sizeof(rnd), 0); + if (!ok) { + goto error; + } + + privkey_buffer = ssh_buffer_new(); + if (privkey_buffer == NULL) { + goto error; + } + + rc = ssh_pki_export_privkey_blob(privkey, &blob); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_buffer_pack(privkey_buffer, + "ddPs", + rnd, /* checkint 1 & 2 */ + rnd, + ssh_string_len(blob), + ssh_string_data(blob), + "" /* comment */); + if (rc == SSH_ERROR){ + goto error; + } + + /* Add padding regardless encryption because it is expected + * by OpenSSH tools. + * XXX Using 16 B as we use only AES cipher below anyway. + */ + while (ssh_buffer_get_len(privkey_buffer) % 16 != 0) { + rc = ssh_buffer_add_u8(privkey_buffer, padding); + if (rc < 0) { + goto error; + } + padding++; + } + + if (to_encrypt){ + ssh_buffer kdf_buf; + + kdf_buf = ssh_buffer_new(); + if (kdf_buf == NULL) { + goto error; + } + + salt = ssh_string_new(16); + if (salt == NULL){ + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + + ok = ssh_get_random(ssh_string_data(salt), 16, 0); + if (!ok) { + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + + rc = ssh_buffer_pack(kdf_buf, "Sd", salt, rounds); + if (rc != SSH_OK) { + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + kdf_options = ssh_string_new(ssh_buffer_get_len(kdf_buf)); + if (kdf_options == NULL){ + SSH_BUFFER_FREE(kdf_buf); + goto error; + } + memcpy(ssh_string_data(kdf_options), + ssh_buffer_get(kdf_buf), + ssh_buffer_get_len(kdf_buf)); + SSH_BUFFER_FREE(kdf_buf); + rc = pki_private_key_encrypt(privkey_buffer, + passphrase, + "aes128-cbc", + "bcrypt", + auth_fn, + auth_data, + rounds, + salt); + if (rc != SSH_OK){ + goto error; + } + } else { + kdf_options = ssh_string_new(0); + } + + rc = ssh_buffer_pack(buffer, + "PssSdSdP", + strlen(OPENSSH_AUTH_MAGIC) + 1, + OPENSSH_AUTH_MAGIC, + to_encrypt ? "aes128-cbc" : "none", /* ciphername */ + to_encrypt ? "bcrypt" : "none", /* kdfname */ + kdf_options, /* kdfoptions */ + (uint32_t)1, /* nkeys */ + pubkey_s, + ssh_buffer_get_len(privkey_buffer), + /* rest of buffer is a string */ + (size_t)ssh_buffer_get_len(privkey_buffer), + ssh_buffer_get(privkey_buffer)); + if (rc != SSH_OK) { + goto error; + } + + b64 = bin_to_base64(ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (b64 == NULL){ + goto error; + } + + /* we can reuse the buffer */ + ssh_buffer_reinit(buffer); + rc = ssh_buffer_pack(buffer, + "tttttt", + OPENSSH_HEADER_BEGIN, + "\n", + b64, + "\n", + OPENSSH_HEADER_END, + "\n"); + ssh_burn(b64, strlen((char *)b64)); + SAFE_FREE(b64); + + if (rc != SSH_OK){ + goto error; + } + + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL){ + goto error; + } + + str_len = ssh_buffer_get_len(buffer); + len = ssh_buffer_get_data(buffer, ssh_string_data(str), str_len); + if (str_len != len) { + SSH_STRING_FREE(str); + str = NULL; + } + +error: + ssh_string_burn(blob); + ssh_string_free(blob); + if (privkey_buffer != NULL) { + void *bufptr = ssh_buffer_get(privkey_buffer); + ssh_burn(bufptr, ssh_buffer_get_len(privkey_buffer)); + SSH_BUFFER_FREE(privkey_buffer); + } + SAFE_FREE(pubkey_s); + SAFE_FREE(kdf_options); + SAFE_FREE(salt); + if (buffer != NULL) { + SSH_BUFFER_FREE(buffer); + } + + return str; +} + + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/pki_context.c b/src/libs/libssh-0.12.2/src/pki_context.c new file mode 100644 index 000000000000..6cd78fbfbeba --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_context.c @@ -0,0 +1,581 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/libssh.h" +#include "libssh/pki.h" +#include "libssh/pki_context.h" +#include "libssh/priv.h" +#include "libssh/sk_common.h" + +#ifdef WITH_FIDO2 +#include "libssh/buffer.h" +#include "libssh/callbacks.h" +#include "libssh/sk_api.h" +#endif /* WITH_FIDO2 */ + +/** + * @addtogroup libssh_pki + * @{ + */ + +/** + * @brief Allocate a new generic PKI context container. + * + * Allocates and default-initializes a new ssh_pki_ctx instance. + * + * @return Newly allocated context on success, or NULL on allocation failure. + * @see ssh_pki_ctx_free() + */ +ssh_pki_ctx ssh_pki_ctx_new(void) +{ + struct ssh_pki_ctx_struct *ctx = NULL; + + ctx = calloc(1, sizeof(struct ssh_pki_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + +#ifdef WITH_FIDO2 + /* Initialize SK fields with default, if available. */ + ctx->sk_callbacks = ssh_sk_get_default_callbacks(); + + /* + * Both OpenSSH security key enrollment and server authentication require + * user presence by default, so we replicate that for consistency. + */ + ctx->sk_flags = SSH_SK_USER_PRESENCE_REQD; + + ctx->sk_application = strdup("ssh:"); + if (ctx->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for default application"); + SAFE_FREE(ctx); + return NULL; + } +#endif /* WITH_FIDO2 */ + + return ctx; +} + +/** + * @brief Free a generic PKI context container. + * + * @param[in] context The PKI context to free (may be NULL). + * @see ssh_pki_ctx_new() + */ +void ssh_pki_ctx_free(ssh_pki_ctx context) +{ + if (context == NULL) { + return; + } + +#ifdef WITH_FIDO2 + SAFE_FREE(context->sk_application); + SSH_BUFFER_FREE(context->sk_challenge_buffer); + SSH_BUFFER_FREE(context->sk_attestation_buffer); + SK_OPTIONS_FREE(context->sk_callbacks_options); +#endif /* WITH_FIDO2 */ + + SAFE_FREE(context); +} + +/** + * @brief Set various options for a PKI context. + * + * This function can set all possible PKI context options. + * + * @param[in] context Target PKI context. + * @param option The option type to set. This could be one of the following: + * + * - SSH_PKI_OPTION_RSA_KEY_SIZE (int): + * Set the RSA key size in bits for key generation. + * Typically 2048, 3072, or 4096 bits. Must be greater + * than or equal to 1024, as anything below is considered + * insecure. + * + * - SSH_PKI_OPTION_SK_APPLICATION (const char *): + * The Relying Party identifier (application string) that + * determines which service/domain this security key + * credential will be associated with. This is a required + * field for all security key generation operations. + * The application string typically starts with "ssh:" for + * SSH keys. It is copied internally and can be freed + * after setting. + * + * - SSH_PKI_SK_OPTION_FLAGS (uint8_t): + * Set FIDO2/U2F operation flags that control how the FIDO2/U2F + * authenticator behaves during generation operations. Multiple + * flags can be combined using bitwise OR operations. The + * pointer must not be NULL. + * + * Available flags: + * + * SSH_SK_USER_PRESENCE_REQD: Requires user presence + * + * SSH_SK_USER_VERIFICATION_REQD: Requires user verification + * + * SSH_SK_FORCE_OPERATION: Forces generation even if a + * resident key already exists. + * + * SSH_SK_RESIDENT_KEY: Creates a resident + * key stored on the authenticator. + * + * - SSH_PKI_OPTION_SK_USER_ID (const char *): + * Sets the user identifier to associate with a resident + * credential during enrollment. When a resident key is + * requested (SSH_SK_RESIDENT_KEY), this ID is stored on the + * authenticator and later used to look up or prevent duplicate + * credentials. Maximum length is SK_MAX_USER_ID_LEN bytes; + * longer values will cause the operation to fail. + * + * - SSH_PKI_OPTION_SK_CHALLENGE (ssh_buffer): + * Set custom cryptographic challenge data to be included in + * the generation operation. The challenge is signed by the + * authenticator during key generation. If not provided, + * a random 32-byte challenge will be automatically generated. + * The challenge data is copied internally and the caller + * retains ownership of the provided buffer. + * + * - SSH_PKI_OPTION_SK_CALLBACKS (ssh_sk_callbacks): + * Set the security key callback structure to use custom + * callback functions for FIDO2/U2F operations like enrollment, + * signing, and loading resident keys. The structure is not + * copied so it needs to be valid for the whole context + * lifetime or until replaced. + * + * @param value The value to set. This is a generic pointer and the + * datatype which is used should be set according to the + * option type. + * + * @return SSH_OK on success, SSH_ERROR on error. + * + * @warning When the option value to set is represented via a pointer + * (e.g const char *, ssh_buffer), the value parameter + * should be that pointer. Do NOT pass a pointer to a + * pointer. + * + * @warning When the option value to set is not a pointer (e.g int, + * uint8_t), the value parameter should be a pointer to the + * location storing the value to set (int *, uint8_t *). + */ +int ssh_pki_ctx_options_set(ssh_pki_ctx context, + enum ssh_pki_options_e option, + const void *value) +{ + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid PKI context passed"); + return SSH_ERROR; + } + + switch (option) { + case SSH_PKI_OPTION_RSA_KEY_SIZE: + if (value == NULL) { + SSH_LOG(SSH_LOG_WARN, "RSA key size pointer must not be NULL"); + return SSH_ERROR; + } else if (*(int *)value != 0 && *(int *)value <= RSA_MIN_KEY_SIZE) { + SSH_LOG( + SSH_LOG_WARN, + "RSA key size must be greater than %d bits or 0 for default", + RSA_MIN_KEY_SIZE); + return SSH_ERROR; + } + context->rsa_key_size = *(int *)value; + break; + +#ifdef WITH_FIDO2 + case SSH_PKI_OPTION_SK_APPLICATION: + SAFE_FREE(context->sk_application); + if (value != NULL) { + context->sk_application = strdup((char *)value); + if (context->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for application"); + return SSH_ERROR; + } + } + break; + + case SSH_PKI_OPTION_SK_FLAGS: + if (value == NULL) { + return SSH_ERROR; + } else { + context->sk_flags = *(uint8_t *)value; + } + break; + + case SSH_PKI_OPTION_SK_USER_ID: { + int rc; + + /* + * Set required to false, because only the enrollment callback supports + * the user ID option, and if this context is used for any other + * operation, it would fail unnecessarily. + */ + rc = ssh_pki_ctx_sk_callbacks_option_set(context, + SSH_SK_OPTION_NAME_USER_ID, + value, + false); + if (rc != SSH_OK) { + return SSH_ERROR; + } + break; + } + + case SSH_PKI_OPTION_SK_CHALLENGE: { + SSH_BUFFER_FREE(context->sk_challenge_buffer); + if (value == NULL) { + break; + } + + context->sk_challenge_buffer = ssh_buffer_dup((ssh_buffer)value); + if (context->sk_challenge_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to duplicate challenge buffer"); + return SSH_ERROR; + } + ssh_buffer_set_secure(context->sk_challenge_buffer); + break; + } + + case SSH_PKI_OPTION_SK_CALLBACKS: { + bool is_compatible = sk_callbacks_check_compatibility(value); + if (!is_compatible) { + return SSH_ERROR; + } + context->sk_callbacks = value; + break; + } +#else /* WITH_FIDO2 */ + case SSH_PKI_OPTION_SK_APPLICATION: + case SSH_PKI_OPTION_SK_FLAGS: + case SSH_PKI_OPTION_SK_USER_ID: + case SSH_PKI_OPTION_SK_CHALLENGE: + case SSH_PKI_OPTION_SK_CALLBACKS: + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ + + default: + SSH_LOG(SSH_LOG_WARN, "Unknown PKI context option: %d", option); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Set the PIN callback function to get the PIN for security + * key authenticator access. + * + * @param context The PKI context to modify. + * @param pin_callback The callback used when the authenticator requires PIN + * entry for verification. + * @param userdata A generic pointer that is passed as the userdata + * argument to the callback function. Can be NULL. + * + * @return SSH_OK on success, SSH_ERROR if context is NULL. + * + * @note The callback and userdata are stored internally in the context + * structure and must remain valid until the context is freed or + * replaced. + * + * @see ssh_auth_callback + */ +int ssh_pki_ctx_set_sk_pin_callback(ssh_pki_ctx context, + ssh_auth_callback pin_callback, + void *userdata) +{ +#ifdef WITH_FIDO2 + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context should not be NULL"); + return SSH_ERROR; + } + + context->sk_pin_callback = pin_callback; + context->sk_userdata = userdata; + + return SSH_OK; + +#else + (void)context; + (void)pin_callback; + (void)userdata; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Set a security key (FIDO2/U2F) callback option in the + * context. These options are passed to the sk_callbacks during + * enroll/sign/load_resident_keys operations. + * + * Both the name and value strings are duplicated internally so the caller + * retains ownership of the original pointers. + * + * @param[in] context The PKI context. Must not be NULL. + * @param[in] name option name string. Must not be NULL. + * @param[in] value option value string. Must not be NULL. + * @param[in] required Set to true if the option is mandatory. If set and the + * ssh_sk_callbacks do not recognize the option, + * the operation should fail. + * + * @return SSH_OK on success, SSH_ERROR on allocation failure or invalid args. + * + * @note The option objects are freed automatically when the context is freed + * via ssh_pki_sk_ctx_free(). + * + * @see ssh_sk_callbacks_struct + */ +int ssh_pki_ctx_sk_callbacks_option_set(ssh_pki_ctx context, + const char *name, + const char *value, + bool required) +{ +#ifdef WITH_FIDO2 + struct sk_option *new_option = NULL; + struct sk_option **temp = NULL; + size_t count = 0; + + if (context == NULL || name == NULL || value == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid parameters passed"); + return SSH_ERROR; + } + + /* Count existing options */ + if (context->sk_callbacks_options != NULL) { + while (context->sk_callbacks_options[count] != NULL) { + count++; + } + } + + /* Allocate new option */ + new_option = calloc(1, sizeof(struct sk_option)); + if (new_option == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for new option"); + return SSH_ERROR; + } + + new_option->name = strdup(name); + if (new_option->name == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for option name"); + SAFE_FREE(new_option); + return SSH_ERROR; + } + + new_option->value = strdup(value); + if (new_option->value == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for option value"); + SAFE_FREE(new_option->name); + SAFE_FREE(new_option); + return SSH_ERROR; + } + + new_option->required = required; + + /* Reallocate array to accommodate new option */ + temp = realloc(context->sk_callbacks_options, + (count + 2) * sizeof(struct sk_option *)); + if (temp == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to reallocate options array"); + SAFE_FREE(new_option->name); + SAFE_FREE(new_option->value); + SAFE_FREE(new_option); + return SSH_ERROR; + } + + context->sk_callbacks_options = temp; + context->sk_callbacks_options[count] = new_option; + context->sk_callbacks_options[count + 1] = NULL; + + return SSH_OK; +#else + (void)context; + (void)name; + (void)value; + (void)required; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Clear all sk_callbacks options. + * + * Removes and frees all previously set sk_callbacks options from the context. + * + * @param[in] context The PKI context to modify. + * + * @return SSH_OK on success, SSH_ERROR if context is NULL. + */ +int ssh_pki_ctx_sk_callbacks_options_clear(ssh_pki_ctx context) +{ +#ifdef WITH_FIDO2 + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context should not be NULL"); + return SSH_ERROR; + } + + SK_OPTIONS_FREE(context->sk_callbacks_options); + return SSH_OK; +#else + (void)context; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Get a copy of the attestation buffer from a PKI context. + * + * Retrieves a copy of the attestation buffer stored in the context after a key + * enrollment operation. The attestation buffer contains serialized attestation + * information in the "ssh-sk-attest-v01" format. + * + * @param[in] context The PKI context. Must not be NULL. + * @param[out] attestation_buffer Pointer to store a copy of the attestation + * buffer. Will be set to NULL if no attestation + * data is available (e.g., authenticator doesn't + * support attestation, or attestation data + * was invalid/incomplete). + * + * @return SSH_OK on success, SSH_ERROR if context or attestation_buffer is + * NULL, or if buffer duplication fails. + * + * @note The caller is responsible for freeing the returned buffer using + * SSH_BUFFER_FREE(). + */ +int ssh_pki_ctx_get_sk_attestation_buffer( + const struct ssh_pki_ctx_struct *context, + ssh_buffer *attestation_buffer) +{ +#ifdef WITH_FIDO2 + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context should not be NULL"); + return SSH_ERROR; + } + + if (attestation_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "attestation_buffer pointer should not be NULL"); + return SSH_ERROR; + } + + *attestation_buffer = ssh_buffer_dup(context->sk_attestation_buffer); + if (*attestation_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to duplicate attestation buffer"); + return SSH_ERROR; + } + + return SSH_OK; +#else + (void)context; + (void)attestation_buffer; + + SSH_LOG(SSH_LOG_WARN, SK_NOT_SUPPORTED_MSG); + return SSH_ERROR; +#endif /* WITH_FIDO2 */ +} + +/** + * @brief Duplicate an existing PKI context + * + * Creates a new PKI context and copies all fields from the source context. + * This function performs deep copying for all dynamically allocated fields + * to ensure independent ownership between source and destination contexts. + * + * @param[in] context The PKI context to copy from + * + * @return New PKI context with copied data on success, + * NULL on failure or if src_context is NULL + */ +ssh_pki_ctx ssh_pki_ctx_dup(const ssh_pki_ctx context) +{ + ssh_pki_ctx new_context = NULL; + + if (context == NULL) { + return NULL; + } + + new_context = ssh_pki_ctx_new(); + if (new_context == NULL) { + goto error; + } + + new_context->rsa_key_size = context->rsa_key_size; + +#ifdef WITH_FIDO2 + new_context->sk_callbacks = context->sk_callbacks; + + // Free the default application string before copying + SAFE_FREE(new_context->sk_application); + + if (context->sk_application != NULL) { + new_context->sk_application = strdup(context->sk_application); + if (new_context->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK application string"); + goto error; + } + } + + new_context->sk_flags = context->sk_flags; + + new_context->sk_pin_callback = context->sk_pin_callback; + new_context->sk_userdata = context->sk_userdata; + + if (context->sk_challenge_buffer != NULL) { + new_context->sk_challenge_buffer = + ssh_buffer_dup(context->sk_challenge_buffer); + if (new_context->sk_challenge_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK challenge buffer"); + goto error; + } + } + + if (context->sk_callbacks_options != NULL) { + new_context->sk_callbacks_options = sk_options_dup( + (const struct sk_option **)context->sk_callbacks_options); + if (new_context->sk_callbacks_options == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK callbacks options"); + goto error; + } + } + + if (context->sk_attestation_buffer != NULL) { + new_context->sk_attestation_buffer = + ssh_buffer_dup(context->sk_attestation_buffer); + if (new_context->sk_attestation_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy SK attestation buffer"); + goto error; + } + } +#endif /* WITH_FIDO2 */ + + return new_context; + +error: + SSH_PKI_CTX_FREE(new_context); + return NULL; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/pki_crypto.c b/src/libs/libssh-0.12.2/src/pki_crypto.c new file mode 100644 index 000000000000..7bb5b6447336 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_crypto.c @@ -0,0 +1,3038 @@ +/* + * pki_crypto.c - PKI infrastructure using OpenSSL + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 by Aris Adamantiadis + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2019 by Sahana Prasad + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _PKI_CRYPTO_H +#define _PKI_CRYPTO_H + +#include "config.h" + +#include "libssh/priv.h" +#include "libcrypto-compat.h" + +#include +#include +#if defined(WITH_PKCS11_URI) && !defined(WITH_PKCS11_PROVIDER) +#include +#endif +#include +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#include +#include +#else +#include +#include +#include +#if defined(WITH_PKCS11_URI) && defined(WITH_PKCS11_PROVIDER) +#include +#endif +#endif /* OPENSSL_VERSION_NUMBER */ + +#ifdef HAVE_OPENSSL_EC_H +#include +#endif +#ifdef HAVE_OPENSSL_ECDSA_H +#include +#endif + +#include "libssh/libssh.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/bignum.h" + +struct pem_get_password_struct { + ssh_auth_callback fn; + void *data; +}; + +static int pem_get_password(char *buf, int size, int rwflag, void *userdata) { + struct pem_get_password_struct *pgp = userdata; + + (void) rwflag; /* unused */ + + if (buf == NULL) { + return 0; + } + + memset(buf, '\0', size); + if (pgp) { + int rc; + + rc = pgp->fn("Passphrase for private key:", + buf, size, 0, 0, + pgp->data); + if (rc == 0) { + return (int)strlen(buf); + } + } + + return 0; +} + +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + EVP_PKEY_free(key->key); + key->key = NULL; +} + +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L +static int pki_key_ecdsa_to_nid(EC_KEY *k) +{ + const EC_GROUP *g = EC_KEY_get0_group(k); + int nid; + + if (g == NULL) { + return -1; + } + nid = EC_GROUP_get_curve_name(g); + if (nid) { + return nid; + } + + return -1; +} +#else +static int pki_key_ecdsa_to_nid(EVP_PKEY *k) +{ + char gname[25] = { 0 }; + int rc; + + rc = EVP_PKEY_get_utf8_string_param(k, + OSSL_PKEY_PARAM_GROUP_NAME, + gname, + 25, + NULL); + if (rc != 1) { + return -1; + } + + return pki_key_ecgroup_name_to_nid(gname); +} +#endif /* OPENSSL_VERSION_NUMBER */ + +#if OPENSSL_VERSION_NUMBER < 0x30000000L +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EC_KEY *k) +#else +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(EVP_PKEY *k) +#endif /* OPENSSL_VERSION_NUMBER */ +{ + int nid; + + nid = pki_key_ecdsa_to_nid(k); + + switch (nid) { + case NID_X9_62_prime256v1: + return SSH_KEYTYPE_ECDSA_P256; + case NID_secp384r1: + return SSH_KEYTYPE_ECDSA_P384; + case NID_secp521r1: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; + } +} + +const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return "ecdsa-sha2-nistp256"; + case NID_secp384r1: + return "ecdsa-sha2-nistp384"; + case NID_secp521r1: + return "ecdsa-sha2-nistp521"; + default: + break; + } + + return "unknown"; +} + +static const char *pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_X9_62_prime256v1: + return "nistp256"; + case NID_secp384r1: + return "nistp384"; + case NID_secp521r1: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + if (strcmp(name, "nistp256") == 0) { + return NID_X9_62_prime256v1; + } else if (strcmp(name, "nistp384") == 0) { + return NID_secp384r1; + } else if (strcmp(name, "nistp521") == 0) { + return NID_secp521r1; + } + + return -1; +} + +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) +{ + int rc = 0; + BIGNUM *bexp = NULL; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_POINT *p = NULL; + const EC_GROUP *g = NULL; + EC_KEY *ecdsa = NULL; +#else + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld = NULL; + + if (group_name == NULL) { + return -1; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + bexp = ssh_make_string_bn(exp); + if (bexp == NULL) { + return -1; + } + + key->ecdsa_nid = nid; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + rc = -1; + goto cleanup; + } + + g = EC_KEY_get0_group(ecdsa); + + p = EC_POINT_new(g); + if (p == NULL) { + rc = -1; + goto cleanup; + } + + rc = EC_POINT_oct2point(g, + p, + ssh_string_data(e), + ssh_string_len(e), + NULL); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + /* EC_KEY_set_public_key duplicates p */ + rc = EC_KEY_set_public_key(ecdsa, p); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + /* EC_KEY_set_private_key duplicates exp */ + rc = EC_KEY_set_private_key(ecdsa, bexp); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + rc = -1; + goto cleanup; + } + + /* ecdsa will be freed when the EVP_PKEY key->key is freed */ + rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (rc != 1) { + rc = -1; + goto cleanup; + } + /* ssh_key is now the owner of this memory */ + ecdsa = NULL; + + /* set rc to 0 if everything went well */ + rc = 0; + +cleanup: + EC_KEY_free(ecdsa); + EC_POINT_free(p); + BN_free(bexp); + return rc; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL){ + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_PRIV_KEY, bexp); + if (rc != 1) { + rc = -1; + goto cleanup; + } + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + +cleanup: + OSSL_PARAM_BLD_free(param_bld); + BN_free(bexp); + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_POINT *p = NULL; + const EC_GROUP *g = NULL; + EC_KEY *ecdsa = NULL; + int ok; +#else + const char *group_name = OSSL_EC_curve_nid2name(nid); + OSSL_PARAM_BLD *param_bld = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + key->ecdsa_nid = nid; + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + return -1; + } + + g = EC_KEY_get0_group(ecdsa); + + p = EC_POINT_new(g); + if (p == NULL) { + EC_KEY_free(ecdsa); + return -1; + } + + ok = EC_POINT_oct2point(g, + p, + ssh_string_data(e), + ssh_string_len(e), + NULL); + if (!ok) { + EC_KEY_free(ecdsa); + EC_POINT_free(p); + return -1; + } + + /* EC_KEY_set_public_key duplicates p */ + ok = EC_KEY_set_public_key(ecdsa, p); + EC_POINT_free(p); + if (!ok) { + EC_KEY_free(ecdsa); + return -1; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + EC_KEY_free(ecdsa); + return -1; + } + + rc = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (rc != 1) { + EC_KEY_free(ecdsa); + return -1; + } + + return 0; +#else + param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) + goto err; + + rc = OSSL_PARAM_BLD_push_utf8_string(param_bld, OSSL_PKEY_PARAM_GROUP_NAME, + group_name, strlen(group_name)); + if (rc != 1) + goto err; + rc = OSSL_PARAM_BLD_push_octet_string(param_bld, OSSL_PKEY_PARAM_PUB_KEY, + ssh_string_data(e), ssh_string_len(e)); + if (rc != 1) + goto err; + + rc = evp_build_pkey("EC", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); + OSSL_PARAM_BLD_free(param_bld); + + return rc; +err: + OSSL_PARAM_BLD_free(param_bld); + return -1; +#endif /* OPENSSL_VERSION_NUMBER */ +} +#endif /* HAVE_OPENSSL_ECC */ + +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey) +{ + EVP_PKEY *pkey = NULL; + + if (ssh_string_len(pubkey) != ED25519_KEY_LEN || + ssh_string_len(privkey) != (2 * ED25519_KEY_LEN)) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, + NULL, + (const uint8_t *)ssh_string_data(privkey), + ED25519_KEY_LEN); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + key->key = pkey; + + return SSH_OK; +} + +int pki_pubkey_build_ed25519(ssh_key key, ssh_string pubkey) +{ + EVP_PKEY *pkey = NULL; + + if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + if (ssh_fips_mode()) { + /* We do not want to fail here as we know the algorithm, but we can not + * use it. Just store the public key here. We won't be able to use it + * for anything though. */ + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to allocate memory for the Ed25519 public key"); + return SSH_ERROR; + } + + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + return SSH_OK; + } + + pkey = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, + NULL, + (const uint8_t *)ssh_string_data(pubkey), + ED25519_KEY_LEN); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY: %s", + ERR_error_string(ERR_get_error(), NULL)); + return SSH_ERROR; + } + + key->key = pkey; + + return SSH_OK; +} + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new = NULL; + int rc; + + new = pki_key_dup_common_init(key, demote); + if (new == NULL) { + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *n = NULL, *e = NULL, *d = NULL; + BIGNUM *nn, *ne, *nd; + RSA *new_rsa = NULL; + const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } +#endif /* WITH_PKCS11_URI */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + new_rsa = RSA_new(); + if (new_rsa == NULL) { + goto fail; + } + + /* + * n = public modulus + * e = public exponent + * d = private exponent + * p = secret prime factor + * q = secret prime factor + * dmp1 = d mod (p-1) + * dmq1 = d mod (q-1) + * iqmp = q^-1 mod p + */ + RSA_get0_key(key_rsa, &n, &e, &d); + nn = BN_dup(n); + ne = BN_dup(e); + if (nn == NULL || ne == NULL) { + RSA_free(new_rsa); + BN_free(nn); + BN_free(ne); + goto fail; + } + + /* Memory management of nn and ne is transferred to RSA object */ + rc = RSA_set0_key(new_rsa, nn, ne, NULL); + if (rc == 0) { + RSA_free(new_rsa); + BN_free(nn); + BN_free(ne); + goto fail; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + const BIGNUM *p = NULL, *q = NULL, *dmp1 = NULL, + *dmq1 = NULL, *iqmp = NULL; + BIGNUM *np, *nq, *ndmp1, *ndmq1, *niqmp; + + nd = BN_dup(d); + if (nd == NULL) { + RSA_free(new_rsa); + goto fail; + } + + /* Memory management of nd is transferred to RSA object */ + rc = RSA_set0_key(new_rsa, NULL, NULL, nd); + if (rc == 0) { + RSA_free(new_rsa); + goto fail; + } + + /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the + * RSA operations are much faster when these values are available. + */ + RSA_get0_factors(key_rsa, &p, &q); + if (p != NULL && q != NULL) { /* need to set both of them */ + np = BN_dup(p); + nq = BN_dup(q); + if (np == NULL || nq == NULL) { + RSA_free(new_rsa); + BN_free(np); + BN_free(nq); + goto fail; + } + + /* Memory management of np and nq is transferred to RSA object */ + rc = RSA_set0_factors(new_rsa, np, nq); + if (rc == 0) { + RSA_free(new_rsa); + BN_free(np); + BN_free(nq); + goto fail; + } + } + + RSA_get0_crt_params(key_rsa, &dmp1, &dmq1, &iqmp); + if (dmp1 != NULL || dmq1 != NULL || iqmp != NULL) { + ndmp1 = BN_dup(dmp1); + ndmq1 = BN_dup(dmq1); + niqmp = BN_dup(iqmp); + if (ndmp1 == NULL || ndmq1 == NULL || niqmp == NULL) { + RSA_free(new_rsa); + BN_free(ndmp1); + BN_free(ndmq1); + BN_free(niqmp); + goto fail; + } + + /* Memory management of ndmp1, ndmq1 and niqmp is transferred + * to RSA object */ + rc = RSA_set0_crt_params(new_rsa, ndmp1, ndmq1, niqmp); + if (rc == 0) { + RSA_free(new_rsa); + BN_free(ndmp1); + BN_free(ndmq1); + BN_free(niqmp); + goto fail; + } + } + } + + new->key = EVP_PKEY_new(); + if (new->key == NULL) { + RSA_free(new_rsa); + goto fail; + } + + rc = EVP_PKEY_assign_RSA(new->key, new_rsa); + if (rc != 1) { + EVP_PKEY_free(new->key); + RSA_free(new_rsa); + goto fail; + } + + new_rsa = NULL; +#else + rc = evp_dup_rsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_OPENSSL_ECC + new->ecdsa_nid = key->ecdsa_nid; +#ifdef WITH_PKCS11_URI + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } +#endif /* WITH_PKCS11_URI */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* privkey -> pubkey */ + if (demote && ssh_key_is_private(key)) { + const EC_POINT *p = NULL; + EC_KEY *new_ecdsa = NULL, *old_ecdsa = NULL; + int ok; + + new_ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (new_ecdsa == NULL) { + goto fail; + } + + old_ecdsa = EVP_PKEY_get0_EC_KEY(key->key); + if (old_ecdsa == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + p = EC_KEY_get0_public_key(old_ecdsa); + if (p == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + ok = EC_KEY_set_public_key(new_ecdsa, p); + if (ok != 1) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + new->key = EVP_PKEY_new(); + if (new->key == NULL) { + EC_KEY_free(new_ecdsa); + goto fail; + } + + ok = EVP_PKEY_assign_EC_KEY(new->key, new_ecdsa); + if (ok != 1) { + EC_KEY_free(new_ecdsa); + goto fail; + } + } else { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + } +#else + rc = evp_dup_ecdsa_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + break; +#endif /* HAVE_OPENSSL_ECC */ + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* Take the PKCS#11 keys as they are */ + if (key->flags & SSH_KEY_FLAG_PKCS11_URI && !demote) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + return new; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE) && + key->type == SSH_KEYTYPE_ED25519) { + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto fail; + } + new->key = key->key; + } else { + unsigned char *ed25519_pubkey = NULL; + size_t key_len = 0; + + rc = EVP_PKEY_get_raw_public_key(key->key, NULL, &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, + "Unexpected length of public key %zu. Expected %d.", + key_len, + ED25519_KEY_LEN); + goto fail; + } + + ed25519_pubkey = malloc(key_len); + if (ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + rc = EVP_PKEY_get_raw_public_key(key->key, + ed25519_pubkey, + &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + free(ed25519_pubkey); + goto fail; + } + + new->key = EVP_PKEY_new_raw_public_key(EVP_PKEY_ED25519, + NULL, + ed25519_pubkey, + key_len); + free(ed25519_pubkey); + } + +#else + rc = evp_dup_ed25519_pkey(key, new, demote); + if (rc != SSH_OK) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER < 0x30000000L */ + break; + } + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_key_free(new); + return NULL; + } + + return new; +fail: + ssh_key_free(new); + return NULL; +} + +int pki_key_generate_rsa(ssh_key key, int parameter){ + int rc; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + BIGNUM *e = NULL; + RSA *key_rsa = NULL; +#else + OSSL_PARAM params[3]; + EVP_PKEY_CTX *pctx = EVP_PKEY_CTX_new_from_name(NULL, "RSA", NULL); + unsigned e = 65537; +#endif /* OPENSSL_VERSION_NUMBER */ + + if (parameter == 0) { + parameter = RSA_DEFAULT_KEY_SIZE; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + e = BN_new(); + key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } + + BN_set_word(e, 65537); + rc = RSA_generate_key_ex(key_rsa, parameter, e, NULL); + + BN_free(e); + + if (rc <= 0 || key_rsa == NULL) { + return SSH_ERROR; + } + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + RSA_free(key_rsa); + return SSH_ERROR; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + RSA_free(key_rsa); + EVP_PKEY_free(key->key); + return SSH_ERROR; + } + + key_rsa = NULL; +#else + key->key = NULL; + + rc = EVP_PKEY_keygen_init(pctx); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + + params[0] = OSSL_PARAM_construct_int("bits", ¶meter); + params[1] = OSSL_PARAM_construct_uint("e", &e); + params[2] = OSSL_PARAM_construct_end(); + rc = EVP_PKEY_CTX_set_params(pctx, params); + if (rc != 1) { + EVP_PKEY_CTX_free(pctx); + return SSH_ERROR; + } + + rc = EVP_PKEY_generate(pctx, &(key->key)); + + EVP_PKEY_CTX_free(pctx); + + if (rc != 1 || key->key == NULL) + return SSH_ERROR; +#endif /* OPENSSL_VERSION_NUMBER */ + return SSH_OK; +} + +#ifdef HAVE_OPENSSL_ECC +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdsa = NULL; + int ok; +#else + const char *group_name = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + switch (parameter) { + case 256: + key->ecdsa_nid = NID_X9_62_prime256v1; + key->type = SSH_KEYTYPE_ECDSA_P256; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP256; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + case 384: + key->ecdsa_nid = NID_secp384r1; + key->type = SSH_KEYTYPE_ECDSA_P384; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP384; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + case 521: + key->ecdsa_nid = NID_secp521r1; + key->type = SSH_KEYTYPE_ECDSA_P521; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + group_name = NISTP521; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Invalid parameter %d for ECDSA key " + "generation", parameter); + return SSH_ERROR; + } +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EC_KEY_new_by_curve_name(key->ecdsa_nid); + if (ecdsa == NULL) { + return SSH_ERROR; + } + ok = EC_KEY_generate_key(ecdsa); + if (!ok) { + EC_KEY_free(ecdsa); + return SSH_ERROR; + } + + EC_KEY_set_asn1_flag(ecdsa, OPENSSL_EC_NAMED_CURVE); + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + EC_KEY_free(ecdsa); + return SSH_ERROR; + } + + ok = EVP_PKEY_assign_EC_KEY(key->key, ecdsa); + if (ok != 1) { + return SSH_ERROR; + } + +#else + key->key = EVP_EC_gen(group_name); + if (key->key == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + return SSH_OK; +} +#endif /* HAVE_OPENSSL_ECC */ + +/* With OpenSSL 3.0 and higher the parameter 'what' + * is ignored and the comparison is done by OpenSSL + */ +int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + int rc, cmp; + + (void)what; + + /* We got here only if the types match */ + switch (ssh_key_type_plain(k1->type)) { + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#if OPENSSL_VERSION_NUMBER < 0x30000000L +#ifdef HAVE_OPENSSL_ECC + { + const EC_KEY *ec1 = EVP_PKEY_get0_EC_KEY(k1->key); + const EC_KEY *ec2 = EVP_PKEY_get0_EC_KEY(k2->key); + const EC_POINT *p1 = NULL; + const EC_POINT *p2 = NULL; + const EC_GROUP *g1 = NULL; + const EC_GROUP *g2 = NULL; + + if (ec1 == NULL || ec2 == NULL) { + return 1; + } + + p1 = EC_KEY_get0_public_key(ec1); + p2 = EC_KEY_get0_public_key(ec2); + g1 = EC_KEY_get0_group(ec1); + g2 = EC_KEY_get0_group(ec2); + + if (p1 == NULL || p2 == NULL || g1 == NULL || g2 == NULL) { + return 1; + } + + if (EC_GROUP_cmp(g1, g2, NULL) != 0) { + return 1; + } + + if (EC_POINT_cmp(g1, p1, p2, NULL) != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE && !is_sk_key_type(k1->type)) { + if (bignum_cmp(EC_KEY_get0_private_key(ec1), + EC_KEY_get0_private_key(ec2))) { + return 1; + } + } + break; + } +#endif /* HAVE_OPENSSL_ECC */ +#endif /* OPENSSL_VERSION_NUMBER */ + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + /* In FIPS mode, we can not use OpenSSL to compare Ed25519 keys. + * The OpenSSL < 3.0 also crashes in EVP_PKEY_eq() when either of + * keys keys is NULL so catch it here. */ + if (ssh_fips_mode() && k1->key == NULL && k2->key == NULL) { + if (what == SSH_KEY_CMP_PRIVATE) { + /* we should never have Ed25519 private key in FIPS mode */ + return 1; + } + cmp = memcmp(k1->ed25519_pubkey, + k2->ed25519_pubkey, + ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + /* they match */ + return 0; + } + FALL_THROUGH; + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rc = EVP_PKEY_eq(k1->key, k2->key); + if (rc != 1) { + return 1; + } + break; + case SSH_KEYTYPE_UNKNOWN: + default: + return 1; + } + return 0; +} + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + ssh_string blob = NULL; + BUF_MEM *buf = NULL; + BIO *mem = NULL; + EVP_PKEY *pkey = NULL; + int rc; + + mem = BIO_new(BIO_s_mem()); + if (mem == NULL) { + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ED25519: + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + goto err; + } + pkey = key->key; + + /* Mark the operation as successful as for the other key types */ + rc = 1; + + break; + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, + "Unknown or invalid private key type %d", + key->type); + goto err; + } + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to initialize EVP_PKEY structure"); + goto err; + } + + if (passphrase == NULL) { + struct pem_get_password_struct pgp = { auth_fn, auth_data }; + + rc = PEM_write_bio_PrivateKey(mem, + pkey, + NULL, /* cipher */ + NULL, /* kstr */ + 0, /* klen */ + pem_get_password, + &pgp); + } else { + rc = PEM_write_bio_PrivateKey(mem, + pkey, + EVP_aes_128_cbc(), + NULL, /* kstr */ + 0, /* klen */ + NULL, /* auth_fn */ + (void*) passphrase); + } + EVP_PKEY_free(pkey); + pkey = NULL; + + if (rc != 1) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to write private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + goto err; + } + + BIO_get_mem_ptr(mem, &buf); + + blob = ssh_string_new(buf->length); + if (blob == NULL) { + goto err; + } + + rc = ssh_string_fill(blob, buf->data, buf->length); + if (rc < 0) { + ssh_string_free(blob); + goto err; + } + + BIO_free(mem); + + return blob; + +err: + EVP_PKEY_free(pkey); + BIO_free(mem); + return NULL; +} + +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + BIO *mem = NULL; +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY *ecdsa = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + ssh_key key = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + EVP_PKEY *pkey = NULL; + + mem = BIO_new_mem_buf((void*)b64_key, -1); + + if (passphrase == NULL) { + if (auth_fn) { + struct pem_get_password_struct pgp = { auth_fn, auth_data }; + + pkey = PEM_read_bio_PrivateKey(mem, NULL, pem_get_password, &pgp); + } else { + /* openssl uses its own callback to get the passphrase here */ + pkey = PEM_read_bio_PrivateKey(mem, NULL, NULL, NULL); + } + } else { + pkey = PEM_read_bio_PrivateKey(mem, NULL, NULL, (void *) passphrase); + } + + BIO_free(mem); + + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error parsing private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + return NULL; + } + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + type = SSH_KEYTYPE_RSA; + break; + case EVP_PKEY_EC: +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EVP_PKEY_get0_EC_KEY(pkey); + if (ecdsa == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Error parsing private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ +#if OPENSSL_VERSION_NUMBER < 0x30000000L + type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); + goto fail; + } + + break; +#endif /* HAVE_OPENSSL_ECC */ + case EVP_PKEY_ED25519: + type = SSH_KEYTYPE_ED25519; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", + EVP_PKEY_base_id(pkey)); + EVP_PKEY_free(pkey); + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + key->key = pkey; +#ifdef HAVE_OPENSSL_ECC + if (is_ecdsa_key_type(key->type)) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ + } +#endif /* HAVE_OPENSSL_ECC */ + + return key; +fail: + EVP_PKEY_free(pkey); + ssh_key_free(key); + return NULL; +} + +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + ssh_string iqmp, + ssh_string p, + ssh_string q) +{ + int rc; + BIGNUM *be = NULL, *bn = NULL, *bd = NULL; + BIGNUM *biqmp = NULL, *bp = NULL, *bq = NULL; + BIGNUM *aux = NULL, *d_consttime = NULL; + BIGNUM *bdmq1 = NULL, *bdmp1 = NULL; + BN_CTX *ctx = NULL; + +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + return SSH_ERROR; + } +#else + RSA *key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + bn = ssh_make_string_bn(n); + be = ssh_make_string_bn(e); + bd = ssh_make_string_bn(d); + biqmp = ssh_make_string_bn(iqmp); + bp = ssh_make_string_bn(p); + bq = ssh_make_string_bn(q); + if (be == NULL || bn == NULL || bd == NULL || + /*biqmp == NULL ||*/ bp == NULL || bq == NULL) { + rc = SSH_ERROR; + goto fail; + } + + /* Calculate remaining CRT parameters for OpenSSL to be happy + * taken from OpenSSH */ + if ((ctx = BN_CTX_new()) == NULL) { + rc = SSH_ERROR; + goto fail; + } + if ((aux = BN_new()) == NULL || + (bdmq1 = BN_new()) == NULL || + (bdmp1 = BN_new()) == NULL) { + rc = SSH_ERROR; + goto fail; + } + if ((d_consttime = BN_dup(bd)) == NULL) { + rc = SSH_ERROR; + goto fail; + } + BN_set_flags(aux, BN_FLG_CONSTTIME); + BN_set_flags(d_consttime, BN_FLG_CONSTTIME); + + if ((BN_sub(aux, bq, BN_value_one()) == 0) || + (BN_mod(bdmq1, d_consttime, aux, ctx) == 0) || + (BN_sub(aux, bp, BN_value_one()) == 0) || + (BN_mod(bdmp1, d_consttime, aux, ctx) == 0)) { + rc = SSH_ERROR; + goto fail; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + /* Memory management of be, bn and bd is transferred to RSA object */ + rc = RSA_set0_key(key_rsa, bn, be, bd); + if (rc == 0) { + goto fail; + } + + /* Memory management of bp and bq is transferred to RSA object */ + rc = RSA_set0_factors(key_rsa, bp, bq); + if (rc == 0) { + goto fail; + } + + /* p, q, dmp1, dmq1 and iqmp may be NULL in private keys, but the RSA + * operations are much faster when these values are available. + * https://www.openssl.org/docs/man1.0.2/crypto/rsa.html + * And OpenSSL fails to export these keys to PEM if these are missing: + * https://github.com/openssl/openssl/issues/21826 + */ + rc = RSA_set0_crt_params(key_rsa, bdmp1, bdmq1, biqmp); + if (rc == 0) { + goto fail; + } + bignum_safe_free(aux); + bignum_safe_free(d_consttime); + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + goto fail; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + goto fail; + } + + return SSH_OK; +fail: + RSA_free(key_rsa); + EVP_PKEY_free(key->key); + return SSH_ERROR; +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_D, bd); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR1, bp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_FACTOR2, bq); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT1, bdmp1); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_EXPONENT2, bdmq1); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_COEFFICIENT1, biqmp); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_KEYPAIR); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARNING, + "Failed to import private key: %s\n", + ERR_error_string(ERR_get_error(), NULL)); + rc = SSH_ERROR; + goto fail; + } + +fail: + OSSL_PARAM_BLD_free(param_bld); + bignum_safe_free(bn); + bignum_safe_free(be); + bignum_safe_free(bd); + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(biqmp); + + bignum_safe_free(aux); + bignum_safe_free(d_consttime); + bignum_safe_free(bdmp1); + bignum_safe_free(bdmq1); + BN_CTX_free(ctx); + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ +} + +int pki_pubkey_build_rsa(ssh_key key, + ssh_string e, + ssh_string n) { + int rc; + BIGNUM *be = NULL, *bn = NULL; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_BLD *param_bld = OSSL_PARAM_BLD_new(); + if (param_bld == NULL) { + return SSH_ERROR; + } +#else + RSA *key_rsa = RSA_new(); + if (key_rsa == NULL) { + return SSH_ERROR; + } +#endif /* OPENSSL_VERSION_NUMBER */ + + be = ssh_make_string_bn(e); + if (be == NULL) { + rc = SSH_ERROR; + goto fail; + } + bn = ssh_make_string_bn(n); + if (bn == NULL) { + rc = SSH_ERROR; + goto fail; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + rc = RSA_set0_key(key_rsa, bn, be, NULL); + if (rc == 0) { + goto fail; + } + /* Memory management of bn and be is transferred to RSA object */ + bn = NULL; + be = NULL; + + key->key = EVP_PKEY_new(); + if (key->key == NULL) { + goto fail; + } + + rc = EVP_PKEY_assign_RSA(key->key, key_rsa); + if (rc != 1) { + goto fail; + } + + return SSH_OK; +#else + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_N, bn); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + rc = OSSL_PARAM_BLD_push_BN(param_bld, OSSL_PKEY_PARAM_RSA_E, be); + if (rc != 1) { + rc = SSH_ERROR; + goto fail; + } + + rc = evp_build_pkey("RSA", param_bld, &(key->key), EVP_PKEY_PUBLIC_KEY); +#endif /* OPENSSL_VERSION_NUMBER */ + +fail: + bignum_safe_free(bn); + bignum_safe_free(be); +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EVP_PKEY_free(key->key); + RSA_free(key_rsa); + + return SSH_ERROR; +#else + OSSL_PARAM_BLD_free(param_bld); + + return rc; +#endif /* OPENSSL_VERSION_NUMBER */ +} + +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + ssh_string str = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string p = NULL; + ssh_string g = NULL; + ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + int rc; +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + BIGNUM *bp = NULL, *bq = NULL, *bg = NULL, *bpub_key = NULL, + *bn = NULL, *be = NULL, + *bd = NULL, *biqmp = NULL; + OSSL_PARAM *params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + uint8_t *ed25519_pubkey = NULL; + uint8_t *ed25519_privkey = NULL; + size_t key_len = 0; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); + + if (key->cert != NULL) { + rc = ssh_buffer_add_buffer(buffer, key->cert); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + goto makestring; + } + + type_s = ssh_string_from_char(key->type_c); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *be = NULL, *bn = NULL; + const RSA *key_rsa = EVP_PKEY_get0_RSA(key->key); + RSA_get0_key(key_rsa, &bn, &be, NULL); +#else + const OSSL_PARAM *out_param = NULL; + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc != 1) { + goto fail; + } + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_E); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param E has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &be); + if (rc != 1) { + goto fail; + } + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_N); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param N has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bn); + if (rc != 1) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + e = ssh_make_bignum_string((BIGNUM *)be); + if (e == NULL) { + goto fail; + } + + n = ssh_make_bignum_string((BIGNUM *)bn); + if (n == NULL) { + goto fail; + } + + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const BIGNUM *bd, *biqmp, *bp, *bq; + RSA_get0_key(key_rsa, NULL, NULL, &bd); + RSA_get0_factors(key_rsa, &bp, &bq); + RSA_get0_crt_params(key_rsa, NULL, NULL, &biqmp); +#else + OSSL_PARAM_free(params); + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_D); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param D has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bd); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param P has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bp); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_FACTOR2); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param Q has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &bq); + if (rc != 1) { + goto fail; + } + + out_param = OSSL_PARAM_locate_const(params, OSSL_PKEY_PARAM_RSA_COEFFICIENT1); + if (out_param == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA: No param IQMP has been found"); + goto fail; + } + rc = OSSL_PARAM_get_BN(out_param, &biqmp); + if (rc != 1) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + + iqmp = ssh_make_bignum_string((BIGNUM *)biqmp); + if (iqmp == NULL) { + goto fail; + } + + p = ssh_make_bignum_string((BIGNUM *)bp); + if (p == NULL) { + goto fail; + } + + q = ssh_make_bignum_string((BIGNUM *)bq); + if (q == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bd); + bignum_safe_free(biqmp); + bignum_safe_free(bp); + bignum_safe_free(bq); +#endif /* OPENSSL_VERSION_NUMBER */ + } + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bn); + bignum_safe_free(be); + OSSL_PARAM_free(params); + params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = EVP_PKEY_get_raw_public_key(key->key, NULL, &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, + "Unexpected length of private key %zu. Expected %d.", + key_len, + ED25519_KEY_LEN); + goto fail; + } + + ed25519_pubkey = malloc(key_len); + if (ed25519_pubkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + rc = EVP_PKEY_get_raw_public_key(key->key, + (uint8_t *)ed25519_pubkey, + &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw public key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + rc = ssh_buffer_pack(buffer, + "dP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, + ed25519_pubkey); + if (rc == SSH_ERROR) { + goto fail; + } + + if (type == SSH_KEY_PRIVATE && key->type == SSH_KEYTYPE_ED25519) { + key_len = 0; + rc = EVP_PKEY_get_raw_private_key(key->key, NULL, &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key length: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + if (key_len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, + "Unexpected length of private key %zu. Expected %d.", + key_len, + ED25519_KEY_LEN); + goto fail; + } + + ed25519_privkey = malloc(key_len); + if (ed25519_privkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto fail; + } + + rc = EVP_PKEY_get_raw_private_key(key->key, + ed25519_privkey, + &key_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to get ed25519 raw private key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + rc = ssh_buffer_pack(buffer, + "dPP", + (uint32_t)(2 * ED25519_KEY_LEN), + (size_t)ED25519_KEY_LEN, + ed25519_privkey, + (size_t)ED25519_KEY_LEN, + ed25519_pubkey); + if (rc == SSH_ERROR) { + goto fail; + } + ssh_burn(ed25519_privkey, ED25519_KEY_LEN); + SAFE_FREE(ed25519_privkey); + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ED25519) { + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ED25519) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc != SSH_OK) { + goto fail; + } + } + + SAFE_FREE(ed25519_pubkey); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_OPENSSL_ECC + { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + EC_GROUP *group = NULL; + EC_POINT *point = NULL; + const void *pubkey = NULL; + size_t pubkey_len; + OSSL_PARAM *locate_param = NULL; +#else + const EC_GROUP *group = NULL; + const EC_POINT *point = NULL; + const BIGNUM *exp = NULL; + EC_KEY *ec = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + + type_s = ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ec = EVP_PKEY_get0_EC_KEY(key->key); + if (ec == NULL) { + goto fail; + } +#ifdef WITH_PKCS11_URI + if (ssh_key_is_private(key) && !EC_KEY_get0_public_key(ec)) { + SSH_LOG(SSH_LOG_TRACE, + "It is mandatory to have separate" + " public ECDSA key objects in the PKCS #11 device." + " Unlike RSA, ECDSA public keys cannot be derived" + " from their private keys."); + goto fail; + } +#endif /* WITH_PKCS11_URI */ + group = EC_KEY_get0_group(ec); + point = EC_KEY_get0_public_key(ec); + if (group == NULL || point == NULL) { + goto fail; + } + e = pki_key_make_ecpoint_string(group, point); +#else + rc = EVP_PKEY_todata(key->key, EVP_PKEY_PUBLIC_KEY, ¶ms); + if (rc < 0) { + goto fail; + } + + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PUB_KEY); +#ifdef WITH_PKCS11_URI + if (ssh_key_is_private(key) && !locate_param) { + SSH_LOG(SSH_LOG_TRACE, + "It is mandatory to have separate" + " public ECDSA key objects in the PKCS #11 device." + " Unlike RSA, ECDSA public keys cannot be derived" + " from their private keys."); + goto fail; + } +#endif /* WITH_PKCS11_URI */ + + rc = OSSL_PARAM_get_octet_string_ptr(locate_param, &pubkey, &pubkey_len); + if (rc != 1) { + goto fail; + } + /* Convert the data to low-level representation */ + group = EC_GROUP_new_by_curve_name_ex(NULL, NULL, key->ecdsa_nid); + point = EC_POINT_new(group); + rc = EC_POINT_oct2point(group, point, pubkey, pubkey_len, NULL); + if (group == NULL || point == NULL || rc != 1) { + EC_GROUP_free(group); + EC_POINT_free(point); + goto fail; + } + + e = pki_key_make_ecpoint_string(group, point); + EC_GROUP_free(group); + EC_POINT_free(point); +#endif /* OPENSSL_VERSION_NUMBER */ + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + + if (type == SSH_KEY_PRIVATE && key->type != SSH_KEYTYPE_SK_ECDSA) { +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + OSSL_PARAM_free(params); + rc = EVP_PKEY_todata(key->key, EVP_PKEY_KEYPAIR, ¶ms); + if (rc < 0) { + goto fail; + } + + locate_param = OSSL_PARAM_locate(params, OSSL_PKEY_PARAM_PRIV_KEY); + rc = OSSL_PARAM_get_BN(locate_param, &bd); + if (rc != 1) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)bd); + if (d == NULL) { + goto fail; + } + if (ssh_buffer_add_ssh_string(buffer, d) < 0) { + goto fail; + } +#else + exp = EC_KEY_get0_private_key(ec); + if (exp == NULL) { + goto fail; + } + d = ssh_make_bignum_string((BIGNUM *)exp); + if (d == NULL) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } +#endif /* OPENSSL_VERSION_NUMBER */ + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ECDSA) { + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc != SSH_OK) { + goto fail; + } + } +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bd); + OSSL_PARAM_free(params); + params = NULL; +#endif /* OPENSSL_VERSION_NUMBER */ + break; + } +#endif /* HAVE_OPENSSL_ECC */ + case SSH_KEYTYPE_UNKNOWN: + default: + goto fail; + } + +makestring: + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL) { + goto fail; + } + + rc = ssh_string_fill(str, ssh_buffer_get(buffer), ssh_buffer_get_len(buffer)); + if (rc < 0) { + goto fail; + } + SSH_BUFFER_FREE(buffer); + + return str; +fail: + SSH_BUFFER_FREE(buffer); + ssh_string_burn(str); + SSH_STRING_FREE(str); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(g); + SSH_STRING_FREE(g); + ssh_string_burn(q); + SSH_STRING_FREE(q); + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); +#if OPENSSL_VERSION_NUMBER >= 0x30000000L + bignum_safe_free(bp); + bignum_safe_free(bq); + bignum_safe_free(bg); + bignum_safe_free(bpub_key); + bignum_safe_free(bn); + bignum_safe_free(be); + bignum_safe_free(bd); + bignum_safe_free(biqmp); + OSSL_PARAM_free(params); +#endif /* OPENSSL_VERSION_NUMBER */ + free(ed25519_pubkey); + if (ed25519_privkey) { + ssh_burn(ed25519_privkey, ED25519_KEY_LEN); + free(ed25519_privkey); + } + + return NULL; +} + +static ssh_string pki_ecdsa_signature_to_blob(const ssh_signature sig) +{ + ssh_string r = NULL; + ssh_string s = NULL; + + ssh_buffer buf = NULL; + ssh_string sig_blob = NULL; + + const BIGNUM *pr = NULL, *ps = NULL; + + const unsigned char *raw_sig_data = NULL; + long raw_sig_len; + + ECDSA_SIG *ecdsa_sig = NULL; + + int rc; + + if (sig == NULL || sig->raw_sig == NULL) { + return NULL; + } + raw_sig_data = ssh_string_data(sig->raw_sig); + if (raw_sig_data == NULL) { + return NULL; + } + raw_sig_len = (long)ssh_string_len(sig->raw_sig); + + ecdsa_sig = d2i_ECDSA_SIG(NULL, &raw_sig_data, raw_sig_len); + if (ecdsa_sig == NULL) { + return NULL; + } + + ECDSA_SIG_get0(ecdsa_sig, &pr, &ps); + if (pr == NULL || ps == NULL) { + goto error; + } + + r = ssh_make_bignum_string((BIGNUM *)pr); + if (r == NULL) { + goto error; + } + + s = ssh_make_bignum_string((BIGNUM *)ps); + if (s == NULL) { + goto error; + } + + buf = ssh_buffer_new(); + if (buf == NULL) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(buf, r); + if (rc < 0) { + goto error; + } + + rc = ssh_buffer_add_ssh_string(buf, s); + if (rc < 0) { + goto error; + } + + sig_blob = ssh_string_new(ssh_buffer_get_len(buf)); + if (sig_blob == NULL) { + goto error; + } + + rc = ssh_string_fill(sig_blob, ssh_buffer_get(buf), ssh_buffer_get_len(buf)); + if (rc < 0) { + goto error; + } + + SSH_STRING_FREE(r); + SSH_STRING_FREE(s); + ECDSA_SIG_free(ecdsa_sig); + SSH_BUFFER_FREE(buf); + + return sig_blob; + +error: + SSH_STRING_FREE(sig_blob); + SSH_STRING_FREE(r); + SSH_STRING_FREE(s); + ECDSA_SIG_free(ecdsa_sig); + SSH_BUFFER_FREE(buf); + return NULL; +} + +ssh_string pki_signature_to_blob(const ssh_signature sig) +{ + ssh_string sig_blob = NULL; + + switch(sig->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + sig_blob = ssh_string_copy(sig->raw_sig); + break; + case SSH_KEYTYPE_ED25519: + sig_blob = pki_ed25519_signature_to_blob(sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_OPENSSL_ECC + sig_blob = pki_ecdsa_signature_to_blob(sig); + break; +#endif /* HAVE_OPENSSL_ECC */ + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ED25519: + /* For SK keys, signature data is already in raw_sig */ + sig_blob = ssh_string_copy(sig->raw_sig); + break; + default: + case SSH_KEYTYPE_UNKNOWN: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", sig->type_c); + return NULL; + } + + return sig_blob; +} + +static int pki_signature_from_rsa_blob(const ssh_key pubkey, + const ssh_string sig_blob, + ssh_signature sig) +{ + size_t pad_len = 0; + char *blob_orig = NULL; + char *blob_padded_data = NULL; + ssh_string sig_blob_padded = NULL; + + size_t rsalen = 0; + size_t len = ssh_string_len(sig_blob); + +#if OPENSSL_VERSION_NUMBER < 0x30000000L + const RSA *rsa = EVP_PKEY_get0_RSA(pubkey->key); + + if (rsa == NULL) { + SSH_LOG(SSH_LOG_TRACE, "RSA field NULL"); + goto errout; + } + + rsalen = RSA_size(rsa); +#else + if (EVP_PKEY_get_base_id(pubkey->key) != EVP_PKEY_RSA) { + SSH_LOG(SSH_LOG_TRACE, "Key has no RSA pubkey"); + goto errout; + } + + rsalen = EVP_PKEY_size(pubkey->key); +#endif /* OPENSSL_VERSION_NUMBER */ + if (len > rsalen) { + SSH_LOG(SSH_LOG_TRACE, + "Signature is too big: %lu > %lu", + (unsigned long)len, + (unsigned long)rsalen); + goto errout; + } + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); + ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); +#endif /* DEBUG_CRYPTO */ + + if (len == rsalen) { + sig->raw_sig = ssh_string_copy(sig_blob); + } else { + /* pad the blob to the expected rsalen size */ + SSH_LOG(SSH_LOG_DEBUG, + "RSA signature len %lu < %lu", + (unsigned long)len, + (unsigned long)rsalen); + + pad_len = rsalen - len; + + sig_blob_padded = ssh_string_new(rsalen); + if (sig_blob_padded == NULL) { + goto errout; + } + + blob_padded_data = (char *) ssh_string_data(sig_blob_padded); + blob_orig = (char *) ssh_string_data(sig_blob); + + if (blob_padded_data == NULL || blob_orig == NULL) { + goto errout; + } + + /* front-pad the buffer with zeroes */ + ssh_burn(blob_padded_data, pad_len); + /* fill the rest with the actual signature blob */ + memcpy(blob_padded_data + pad_len, blob_orig, len); + + sig->raw_sig = sig_blob_padded; + } + + return SSH_OK; + +errout: + SSH_STRING_FREE(sig_blob_padded); + return SSH_ERROR; +} + +static int pki_signature_from_ecdsa_blob(UNUSED_PARAM(const ssh_key pubkey), + const ssh_string sig_blob, + ssh_signature sig) +{ + ECDSA_SIG *ecdsa_sig = NULL; + BIGNUM *pr = NULL, *ps = NULL; + + ssh_string r = NULL; + ssh_string s = NULL; + + ssh_buffer buf = NULL; + uint32_t rlen; + + unsigned char *raw_sig_data = NULL; + unsigned char *temp_raw_sig = NULL; + size_t raw_sig_len = 0; + + int rc; + + /* build ecdsa signature */ + buf = ssh_buffer_new(); + if (buf == NULL) { + return SSH_ERROR; + } + + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buf); + + rc = ssh_buffer_add_data(buf, + ssh_string_data(sig_blob), + (uint32_t)ssh_string_len(sig_blob)); + if (rc < 0) { + goto error; + } + + r = ssh_buffer_get_ssh_string(buf); + if (r == NULL) { + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); +#endif + + pr = ssh_make_string_bn(r); + ssh_string_burn(r); + SSH_STRING_FREE(r); + if (pr == NULL) { + goto error; + } + + s = ssh_buffer_get_ssh_string(buf); + rlen = ssh_buffer_get_len(buf); + SSH_BUFFER_FREE(buf); + if (s == NULL) { + goto error; + } + + if (rlen != 0) { + ssh_string_burn(s); + SSH_STRING_FREE(s); + SSH_LOG(SSH_LOG_TRACE, + "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); +#endif + + ps = ssh_make_string_bn(s); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (ps == NULL) { + goto error; + } + + ecdsa_sig = ECDSA_SIG_new(); + if (ecdsa_sig == NULL) { + goto error; + } + + /* Memory management of pr and ps is transferred to + * ECDSA signature object */ + rc = ECDSA_SIG_set0(ecdsa_sig, pr, ps); + if (rc == 0) { + goto error; + } + pr = NULL; + ps = NULL; + + /* Get the expected size of the buffer */ + rc = i2d_ECDSA_SIG(ecdsa_sig, NULL); + if (rc <= 0) { + goto error; + } + raw_sig_len = rc; + + raw_sig_data = (unsigned char *)calloc(1, raw_sig_len); + if (raw_sig_data == NULL) { + goto error; + } + temp_raw_sig = raw_sig_data; + + /* It is necessary to use a temporary pointer as i2d_* "advances" the + * pointer */ + rc = i2d_ECDSA_SIG(ecdsa_sig, &temp_raw_sig); + if (rc <= 0) { + goto error; + } + + sig->raw_sig = ssh_string_new(raw_sig_len); + if (sig->raw_sig == NULL) { + ssh_burn(raw_sig_data, raw_sig_len); + goto error; + } + + rc = ssh_string_fill(sig->raw_sig, raw_sig_data, raw_sig_len); + if (rc < 0) { + ssh_burn(raw_sig_data, raw_sig_len); + goto error; + } + + ssh_burn(raw_sig_data, raw_sig_len); + SAFE_FREE(raw_sig_data); + ECDSA_SIG_free(ecdsa_sig); + return SSH_OK; + +error: + SSH_BUFFER_FREE(buf); + bignum_safe_free(ps); + bignum_safe_free(pr); + SAFE_FREE(raw_sig_data); + if (ecdsa_sig != NULL) { + ECDSA_SIG_free(ecdsa_sig); + } + return SSH_ERROR; +} + +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + ssh_signature sig; + int rc; + + if (ssh_key_type_plain(pubkey->type) != type) { + SSH_LOG(SSH_LOG_TRACE, + "Incompatible public key provided (%d) expecting (%d)", + type, + pubkey->type); + return NULL; + } + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + sig->type_c = ssh_key_signature_to_char(type, hash_type); + sig->hash_type = hash_type; + + switch(type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + rc = pki_signature_from_rsa_blob(pubkey, sig_blob, sig); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc != SSH_OK){ + goto error; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: +#ifdef HAVE_OPENSSL_ECC + rc = pki_signature_from_ecdsa_blob(pubkey, sig_blob, sig); + if (rc != SSH_OK) { + goto error; + } + break; +#endif + default: + case SSH_KEYTYPE_UNKNOWN: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); + goto error; + } + + return sig; + +error: + ssh_signature_free(sig); + return NULL; +} + +static const EVP_MD *pki_digest_to_md(enum ssh_digest_e hash_type) +{ + const EVP_MD *md = NULL; + + switch (hash_type) { + case SSH_DIGEST_SHA256: + md = EVP_sha256(); + break; + case SSH_DIGEST_SHA384: + md = EVP_sha384(); + break; + case SSH_DIGEST_SHA512: + md = EVP_sha512(); + break; + case SSH_DIGEST_SHA1: + md = EVP_sha1(); + break; + case SSH_DIGEST_AUTO: + md = NULL; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d", + hash_type); + return NULL; + } + + return md; +} + +static EVP_PKEY *pki_key_to_pkey(ssh_key key) +{ + EVP_PKEY *pkey = NULL; + int rc = 0; + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + if (key->key == NULL) { + SSH_LOG(SSH_LOG_TRACE, "NULL key->key"); + goto error; + } + rc = EVP_PKEY_up_ref(key->key); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to reference EVP_PKEY"); + return NULL; + } + pkey = key->key; + break; + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown private key algorithm for type: %d", + key->type); + goto error; + } + + return pkey; + +error: + EVP_PKEY_free(pkey); + return NULL; +} + +/** + * @internal + * + * @brief Sign the given input data. The digest to be signed is calculated + * internally as necessary. + * + * @param[in] privkey The private key to be used for signing. + * @param[in] hash_type The digest algorithm to be used. + * @param[in] input The data to be signed. + * @param[in] input_len The length of the data to be signed. + * + * @return a newly allocated ssh_signature or NULL on error. + */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + + unsigned char *raw_sig_data = NULL; + size_t raw_sig_len; + + ssh_signature sig = NULL; + + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_sign_data()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + /* Set hash algorithm to be used */ + md = pki_digest_to_md(hash_type); + if (md == NULL) { + if (hash_type != SSH_DIGEST_AUTO) { + return NULL; + } + } + + /* Setup private key EVP_PKEY */ + pkey = pki_key_to_pkey(privkey); + if (pkey == NULL) { + return NULL; + } + + /* Allocate buffer for signature */ + raw_sig_len = (size_t)EVP_PKEY_size(pkey); + raw_sig_data = (unsigned char *)malloc(raw_sig_len); + if (raw_sig_data == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto out; + } + + /* Create the context */ + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Out of memory"); + goto out; + } + + /* Sign the data */ + rc = EVP_DigestSignInit(ctx, NULL, md, NULL, pkey); + if (rc != 1){ + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestSignInit() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + rc = EVP_DigestSign(ctx, raw_sig_data, &raw_sig_len, input, input_len); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestSign() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Generated signature", raw_sig_data, raw_sig_len); +#endif + + /* Allocate and fill output signature */ + sig = ssh_signature_new(); + if (sig == NULL) { + goto out; + } + + sig->raw_sig = ssh_string_new(raw_sig_len); + if (sig->raw_sig == NULL) { + ssh_signature_free(sig); + sig = NULL; + goto out; + } + + rc = ssh_string_fill(sig->raw_sig, raw_sig_data, raw_sig_len); + if (rc < 0) { + ssh_signature_free(sig); + sig = NULL; + goto out; + } + + sig->type = privkey->type; + sig->hash_type = hash_type; + sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); + +out: + if (ctx != NULL) { + EVP_MD_CTX_free(ctx); + } + if (raw_sig_data != NULL) { + ssh_burn(raw_sig_data, raw_sig_len); + } + SAFE_FREE(raw_sig_data); + EVP_PKEY_free(pkey); + return sig; +} + +/** + * @internal + * + * @brief Verify the signature of a given input. The digest of the input is + * calculated internally as necessary. + * + * @param[in] signature The signature to be verified. + * @param[in] pubkey The public key used to verify the signature. + * @param[in] input The signed data. + * @param[in] input_len The length of the signed data. + * + * @return SSH_OK if the signature is valid; SSH_ERROR otherwise. + */ +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len) +{ + const EVP_MD *md = NULL; + EVP_MD_CTX *ctx = NULL; + EVP_PKEY *pkey = NULL; + + unsigned char *raw_sig_data = NULL; + size_t raw_sig_len; + + /* Function return code + * Do not change this variable throughout the function until the signature + * is successfully verified! + */ + int rc = SSH_ERROR; + int ok; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL || signature->raw_sig == NULL) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_verify_data_signature()"); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + ok = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (ok != SSH_OK) { + return SSH_ERROR; + } + + /* Get the signature to be verified */ + raw_sig_data = ssh_string_data(signature->raw_sig); + raw_sig_len = ssh_string_len(signature->raw_sig); + if (raw_sig_data == NULL) { + return SSH_ERROR; + } + + /* Set hash algorithm to be used */ + md = pki_digest_to_md(signature->hash_type); + if (md == NULL) { + if (signature->hash_type != SSH_DIGEST_AUTO) { + return SSH_ERROR; + } + } + + /* Setup public key EVP_PKEY */ + pkey = pki_key_to_pkey(pubkey); + if (pkey == NULL) { + return SSH_ERROR; + } + + /* Create the context */ + ctx = EVP_MD_CTX_new(); + if (ctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create EVP_MD_CTX: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + /* Verify the signature */ + ok = EVP_DigestVerifyInit(ctx, NULL, md, NULL, pkey); + if (ok != 1){ + SSH_LOG(SSH_LOG_TRACE, + "EVP_DigestVerifyInit() failed: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + ok = EVP_DigestVerify(ctx, raw_sig_data, raw_sig_len, input, input_len); + if (ok != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Signature invalid: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto out; + } + + SSH_LOG(SSH_LOG_TRACE, "Signature valid"); + rc = SSH_OK; + +out: + EVP_MD_CTX_free(ctx); + EVP_PKEY_free(pkey); + return rc; +} + +int ssh_key_size(ssh_key key) +{ + int bits = 0; + EVP_PKEY *pkey = NULL; + + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + pkey = pki_key_to_pkey(key); + if (pkey == NULL) { + return SSH_ERROR; + } + bits = EVP_PKEY_bits(pkey); + EVP_PKEY_free(pkey); + return bits; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +int pki_key_generate_ed25519(ssh_key key) +{ + int evp_rc; + EVP_PKEY_CTX *pctx = NULL; + EVP_PKEY *pkey = NULL; + + if (key == NULL) { + return SSH_ERROR; + } + + pctx = EVP_PKEY_CTX_new_id(EVP_PKEY_ED25519, NULL); + if (pctx == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to create ed25519 EVP_PKEY_CTX: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + evp_rc = EVP_PKEY_keygen_init(pctx); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to initialize ed25519 key generation: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + + evp_rc = EVP_PKEY_keygen(pctx, &pkey); + if (evp_rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate ed25519 key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto error; + } + key->key = pkey; + + EVP_PKEY_CTX_free(pctx); + return SSH_OK; + +error: + if (pctx != NULL) { + EVP_PKEY_CTX_free(pctx); + } + if (pkey != NULL) { + EVP_PKEY_free(pkey); + } + + return SSH_ERROR; +} + +#ifdef WITH_PKCS11_URI + +/** + * @internal + * + * @brief Populate the public/private ssh_key from the engine/provider with + * PKCS#11 URIs as the look up. + * + * @param[in] uri_name The PKCS#11 URI + * @param[in] nkey The ssh-key context for + * the key loaded from the engine/provider. + * @param[in] key_type The type of the key used. Public/Private. + * + * @return SSH_OK if ssh-key is valid; SSH_ERROR otherwise. + */ +int pki_uri_import(const char *uri_name, + ssh_key *nkey, + enum ssh_key_e key_type) +{ + EVP_PKEY *pkey = NULL; + ssh_key key = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; +#if OPENSSL_VERSION_NUMBER < 0x30000000L && HAVE_OPENSSL_ECC + EC_KEY *ecdsa = NULL; +#endif +#ifndef WITH_PKCS11_PROVIDER + ENGINE *engine = NULL; + + /* Do the init only once */ + engine = pki_get_engine(); + if (engine == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Failed to initialize engine"); + goto fail; + } + + switch (key_type) { + case SSH_KEY_PRIVATE: + pkey = ENGINE_load_private_key(engine, uri_name, NULL, NULL); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Could not load key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + break; + case SSH_KEY_PUBLIC: + pkey = ENGINE_load_public_key(engine, uri_name, NULL, NULL); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Could not load key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + break; + default: + SSH_LOG(SSH_LOG_TRACE, + "Invalid key type: %d", key_type); + goto fail; + } +#else /* WITH_PKCS11_PROVIDER */ + OSSL_STORE_CTX *store = NULL; + OSSL_STORE_INFO *info = NULL; + int rv, expect_type = OSSL_STORE_INFO_PKEY; + + /* The provider can be either configured in openssl.cnf or dynamically + * loaded, assuming it does not need any special configuration */ + rv = pki_load_pkcs11_provider(); + if (rv != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "Failed to load or initialize pkcs11 provider"); + goto fail; + } + + store = OSSL_STORE_open(uri_name, NULL, NULL, NULL, NULL); + if (store == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to open OpenSSL store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + if (key_type == SSH_KEY_PUBLIC) { + expect_type = OSSL_STORE_INFO_PUBKEY; + } + rv = OSSL_STORE_expect(store, expect_type); + if (rv != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to set the store preference. Ignoring the error: %s", + ERR_error_string(ERR_get_error(), NULL)); + } + + for (info = OSSL_STORE_load(store); + info != NULL; + info = OSSL_STORE_load(store)) { + int ossl_type = OSSL_STORE_INFO_get_type(info); + + if (ossl_type == OSSL_STORE_INFO_PUBKEY && key_type == SSH_KEY_PUBLIC) { + pkey = OSSL_STORE_INFO_get1_PUBKEY(info); + } else if (ossl_type == OSSL_STORE_INFO_PKEY && + key_type == SSH_KEY_PRIVATE) { + pkey = OSSL_STORE_INFO_get1_PKEY(info); + } else { + SSH_LOG(SSH_LOG_TRACE, + "Ignoring object not matching our type: %d", + ossl_type); + OSSL_STORE_INFO_free(info); + continue; + } + OSSL_STORE_INFO_free(info); + break; + } + OSSL_STORE_close(store); + if (pkey == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "No key found in the pkcs11 store: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + +#endif /* WITH_PKCS11_PROVIDER */ + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + switch (EVP_PKEY_base_id(pkey)) { + case EVP_PKEY_RSA: + type = SSH_KEYTYPE_RSA; + break; + case EVP_PKEY_EC: +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L + ecdsa = EVP_PKEY_get0_EC_KEY(pkey); + if (ecdsa == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Parsing pub key: %s", + ERR_error_string(ERR_get_error(), NULL)); + goto fail; + } + + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ + type = pki_key_ecdsa_to_key_type(ecdsa); +#else + type = pki_key_ecdsa_to_key_type(pkey); +#endif /* OPENSSL_VERSION_NUMBER */ + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid pub key."); + goto fail; + } + + break; +#endif + case EVP_PKEY_ED25519: + type = SSH_KEYTYPE_ED25519; + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid public key type %d", + EVP_PKEY_base_id(pkey)); + goto fail; + } + + key->key = pkey; + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PUBLIC | SSH_KEY_FLAG_PKCS11_URI; + if (key_type == SSH_KEY_PRIVATE) { + key->flags |= SSH_KEY_FLAG_PRIVATE; + } +#ifdef HAVE_OPENSSL_ECC + if (is_ecdsa_key_type(key->type)) { +#if OPENSSL_VERSION_NUMBER < 0x30000000L + key->ecdsa_nid = pki_key_ecdsa_to_nid(ecdsa); +#else + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->key); +#endif /* OPENSSL_VERSION_NUMBER */ + } +#endif + + *nkey = key; + + return SSH_OK; + +fail: + EVP_PKEY_free(pkey); + ssh_key_free(key); + + return SSH_ERROR; +} +#endif /* WITH_PKCS11_URI */ + +#endif /* _PKI_CRYPTO_H */ diff --git a/src/libs/libssh-0.12.2/src/pki_ed25519.c b/src/libs/libssh-0.12.2/src/pki_ed25519.c new file mode 100644 index 000000000000..f39540645158 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_ed25519.c @@ -0,0 +1,349 @@ +/* + * pki_ed25519.c - PKI infrastructure using ed25519 + * + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/ed25519.h" +#include "libssh/buffer.h" + +int pki_pubkey_build_ed25519(ssh_key key, ssh_string pubkey) +{ + if (ssh_string_len(pubkey) != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + + return SSH_OK; +} + +int pki_privkey_build_ed25519(ssh_key key, + ssh_string pubkey, + ssh_string privkey) +{ + if (ssh_string_len(pubkey) != ED25519_KEY_LEN || + ssh_string_len(privkey) != (2 * ED25519_KEY_LEN)) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ed25519 key len"); + return SSH_ERROR; + } + + /* In the internal implementation, the private key is the concatenation of + * the private seed with the public key. */ + key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); + if (key->ed25519_privkey == NULL) { + goto error; + } + + key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (key->ed25519_pubkey == NULL) { + goto error; + } + + memcpy(key->ed25519_privkey, ssh_string_data(privkey), 2 * ED25519_KEY_LEN); + memcpy(key->ed25519_pubkey, ssh_string_data(pubkey), ED25519_KEY_LEN); + + return SSH_OK; + +error: + SAFE_FREE(key->ed25519_privkey); + SAFE_FREE(key->ed25519_pubkey); + + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Compare ed25519 keys if they are equal. + * + * @param[in] k1 The first key to compare. + * + * @param[in] k2 The second key to compare. + * + * @param[in] what What part or type of the key do you want to compare. + * + * @return 0 if equal, 1 if not. + */ +int +pki_ed25519_key_cmp(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + int cmp; + + switch (what) { + case SSH_KEY_CMP_PRIVATE: + if (k1->ed25519_privkey == NULL || k2->ed25519_privkey == NULL) { + return 1; + } + /* In the internal implementation, the private key is the concatenation + * of the private seed with the public key. */ + cmp = secure_memcmp(k1->ed25519_privkey, + k2->ed25519_privkey, + 2 * ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + FALL_THROUGH; + case SSH_KEY_CMP_PUBLIC: + if (k1->ed25519_pubkey == NULL || k2->ed25519_pubkey == NULL) { + return 1; + } + cmp = memcmp(k1->ed25519_pubkey, k2->ed25519_pubkey, ED25519_KEY_LEN); + if (cmp != 0) { + return 1; + } + break; + case SSH_KEY_CMP_CERTIFICATE: + /* handled globally */ + return 1; + } + + return 0; +} + +/** + * @internal + * + * @brief Duplicate an Ed25519 key + * + * @param[out] new Pre-initialized ssh_key structure + * + * @param[in] key Key to copy + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_ed25519_key_dup(ssh_key new_key, const ssh_key key) +{ + if (key->ed25519_privkey == NULL && key->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + + if (key->ed25519_privkey != NULL) { + /* In the internal implementation, the private key is the concatenation + * of the private seed with the public key. */ + new_key->ed25519_privkey = malloc(2 * ED25519_KEY_LEN); + if (new_key->ed25519_privkey == NULL) { + return SSH_ERROR; + } + memcpy(new_key->ed25519_privkey, + key->ed25519_privkey, + 2 * ED25519_KEY_LEN); + } + + if (key->ed25519_pubkey != NULL) { + new_key->ed25519_pubkey = malloc(ED25519_KEY_LEN); + if (new_key->ed25519_pubkey == NULL) { + SAFE_FREE(new_key->ed25519_privkey); + return SSH_ERROR; + } + memcpy(new_key->ed25519_pubkey, key->ed25519_pubkey, ED25519_KEY_LEN); + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief Outputs an Ed25519 public key in a blob buffer. + * + * @param[out] buffer Output buffer + * + * @param[in] key Key to output + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_ed25519_public_key_to_blob(ssh_buffer buffer, ssh_key key) +{ + int rc; + + if (key->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_pack(buffer, + "dP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, + key->ed25519_pubkey); + + return rc; +} + +/** @internal + * @brief exports a ed25519 private key to a string blob. + * @param[in] privkey private key to convert + * @param[out] buffer buffer to write the blob in. + * @returns SSH_OK on success + */ +int pki_ed25519_private_key_to_blob(ssh_buffer buffer, const ssh_key privkey) +{ + int rc; + + if (privkey->type != SSH_KEYTYPE_ED25519) { + SSH_LOG(SSH_LOG_TRACE, "Type %s not supported", privkey->type_c); + return SSH_ERROR; + } + if (privkey->ed25519_privkey == NULL || privkey->ed25519_pubkey == NULL) { + return SSH_ERROR; + } + rc = ssh_buffer_pack(buffer, + "dPdPP", + (uint32_t)ED25519_KEY_LEN, + (size_t)ED25519_KEY_LEN, + privkey->ed25519_pubkey, + (uint32_t)(2 * ED25519_KEY_LEN), + (size_t)ED25519_KEY_LEN, + privkey->ed25519_privkey, + (size_t)ED25519_KEY_LEN, + privkey->ed25519_pubkey); + return rc; +} + +int pki_key_generate_ed25519(ssh_key key) +{ + int rc; + + key->ed25519_privkey = malloc(sizeof (ed25519_privkey)); + if (key->ed25519_privkey == NULL) { + goto error; + } + + key->ed25519_pubkey = malloc(sizeof (ed25519_pubkey)); + if (key->ed25519_pubkey == NULL) { + goto error; + } + + rc = crypto_sign_ed25519_keypair(*key->ed25519_pubkey, + *key->ed25519_privkey); + if (rc != 0) { + goto error; + } + + return SSH_OK; +error: + SAFE_FREE(key->ed25519_privkey); + SAFE_FREE(key->ed25519_pubkey); + + return SSH_ERROR; +} + +int pki_ed25519_sign(const ssh_key privkey, + ssh_signature sig, + const unsigned char *hash, + size_t hlen) +{ + int rc; + uint8_t *buffer = NULL; + uint64_t dlen = 0; + + buffer = malloc(hlen + ED25519_SIG_LEN); + if (buffer == NULL) { + return SSH_ERROR; + } + + rc = crypto_sign_ed25519(buffer, + &dlen, + hash, + hlen, + *privkey->ed25519_privkey); + if (rc != 0) { + goto error; + } + + /* This shouldn't happen */ + if (dlen - hlen != ED25519_SIG_LEN) { + goto error; + } + + sig->ed25519_sig = malloc(ED25519_SIG_LEN); + if (sig->ed25519_sig == NULL) { + goto error; + } + + memcpy(sig->ed25519_sig, buffer, ED25519_SIG_LEN); + SAFE_FREE(buffer); + + return SSH_OK; +error: + SAFE_FREE(buffer); + return SSH_ERROR; +} + +int pki_ed25519_verify(const ssh_key pubkey, + ssh_signature sig, + const unsigned char *hash, + size_t hlen) +{ + uint64_t mlen = 0; + uint8_t *buffer = NULL; + uint8_t *buffer2 = NULL; + int rc; + + if (pubkey == NULL || sig == NULL || + hash == NULL || sig->ed25519_sig == NULL) { + return SSH_ERROR; + } + + buffer = malloc(hlen + ED25519_SIG_LEN); + if (buffer == NULL) { + return SSH_ERROR; + } + + buffer2 = malloc(hlen + ED25519_SIG_LEN); + if (buffer2 == NULL) { + goto error; + } + + memcpy(buffer, sig->ed25519_sig, ED25519_SIG_LEN); + memcpy(buffer + ED25519_SIG_LEN, hash, hlen); + + rc = crypto_sign_ed25519_open(buffer2, + &mlen, + buffer, + hlen + ED25519_SIG_LEN, + *pubkey->ed25519_pubkey); + + ssh_burn(buffer, hlen + ED25519_SIG_LEN); + ssh_burn(buffer2, hlen); + SAFE_FREE(buffer); + SAFE_FREE(buffer2); + if (rc == 0) { + return SSH_OK; + } else { + return SSH_ERROR; + } +error: + SAFE_FREE(buffer); + SAFE_FREE(buffer2); + + return SSH_ERROR; +} + diff --git a/src/libs/libssh-0.12.2/src/pki_ed25519_common.c b/src/libs/libssh-0.12.2/src/pki_ed25519_common.c new file mode 100644 index 000000000000..26e40efb7afa --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_ed25519_common.c @@ -0,0 +1,109 @@ +/* + * pki_ed25519_common.c - Common ed25519 functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2014 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/buffer.h" + +/** + * @internal + * + * @brief output a signature blob from an ed25519 signature + * + * @param[in] sig signature to convert + * + * @return Signature blob in SSH string, or NULL on error + */ +ssh_string pki_ed25519_signature_to_blob(ssh_signature sig) +{ + ssh_string sig_blob = NULL; + int rc; + +#ifdef HAVE_LIBCRYPTO + /* When using the OpenSSL implementation, the signature is stored in raw_sig + * which is shared by all algorithms.*/ + if (sig->raw_sig == NULL) { + return NULL; + } +#else + /* When using the internal implementation, the signature is stored in an + * algorithm specific field. */ + if (sig->ed25519_sig == NULL) { + return NULL; + } +#endif + + sig_blob = ssh_string_new(ED25519_SIG_LEN); + if (sig_blob == NULL) { + return NULL; + } + +#ifdef HAVE_LIBCRYPTO + rc = ssh_string_fill(sig_blob, ssh_string_data(sig->raw_sig), + ssh_string_len(sig->raw_sig)); +#else + rc = ssh_string_fill(sig_blob, sig->ed25519_sig, ED25519_SIG_LEN); +#endif + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + + return sig_blob; +} + +/** + * @internal + * + * @brief Convert a signature blob in an ed25519 signature. + * + * @param[out] sig a preinitialized signature + * + * @param[in] sig_blob a signature blob + * + * @return SSH_ERROR on error, SSH_OK on success + */ +int pki_signature_from_ed25519_blob(ssh_signature sig, ssh_string sig_blob) +{ + size_t len; + + len = ssh_string_len(sig_blob); + if (len != ED25519_SIG_LEN){ + SSH_LOG(SSH_LOG_TRACE, "Invalid ssh-ed25519 signature len: %zu", len); + return SSH_ERROR; + } + +#ifdef HAVE_LIBCRYPTO + sig->raw_sig = ssh_string_copy(sig_blob); +#else + sig->ed25519_sig = malloc(ED25519_SIG_LEN); + if (sig->ed25519_sig == NULL){ + return SSH_ERROR; + } + memcpy(sig->ed25519_sig, ssh_string_data(sig_blob), ED25519_SIG_LEN); +#endif + + return SSH_OK; +} diff --git a/src/libs/libssh-0.12.2/src/pki_gcrypt.c b/src/libs/libssh-0.12.2/src/pki_gcrypt.c new file mode 100644 index 000000000000..802fa7e2bc15 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_gcrypt.c @@ -0,0 +1,2381 @@ +/* + * pki_gcrypt.c private and public key handling using gcrypt. + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2009 Aris Adamantiadis + * Copyright (c) 2009-2011 Andreas Schneider + * Copyright (C) 2016 g10 Code GmbH + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#ifdef HAVE_LIBGCRYPT + +#include +#include +#include +#include +#include +#include + +#include "libssh/buffer.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/wrapper.h" + +#define MAXLINESIZE 80 +#define RSA_HEADER_BEGIN "-----BEGIN RSA PRIVATE KEY-----" +#define RSA_HEADER_END "-----END RSA PRIVATE KEY-----" +#define ECDSA_HEADER_BEGIN "-----BEGIN EC PRIVATE KEY-----" +#define ECDSA_HEADER_END "-----END EC PRIVATE KEY-----" + +#define MAX_KEY_SIZE 32 +#define MAX_PASSPHRASE_SIZE 1024 +#define ASN1_INTEGER 2 +#define ASN1_BIT_STRING 3 +#define ASN1_OCTET_STRING 4 +#define ASN1_OBJECT_IDENTIFIER 6 +#define ASN1_SEQUENCE 48 +#define PKCS5_SALT_LEN 8 + +static int load_iv(const char *header, unsigned char *iv, int iv_len) +{ + int i; + int j; + int k; + + memset(iv, 0, iv_len); + for (i = 0; i < iv_len; i++) { + if ((header[2 * i] >= '0') && (header[2 * i] <= '9')) + j = header[2 * i] - '0'; + else if ((header[2 * i] >= 'A') && (header[2 * i] <= 'F')) + j = header[2 * i] - 'A' + 10; + else if ((header[2 * i] >= 'a') && (header[2 * i] <= 'f')) + j = header[2 * i] - 'a' + 10; + else + return -1; + if ((header[2 * i + 1] >= '0') && (header[2 * i + 1] <= '9')) + k = header[2 * i + 1] - '0'; + else if ((header[2 * i + 1] >= 'A') && (header[2 * i + 1] <= 'F')) + k = header[2 * i + 1] - 'A' + 10; + else if ((header[2 * i + 1] >= 'a') && (header[2 * i + 1] <= 'f')) + k = header[2 * i + 1] - 'a' + 10; + else + return -1; + iv[i] = (j << 4) + k; + } + return 0; +} + +static uint32_t char_to_u32(unsigned char *data, uint32_t size) +{ + uint32_t ret; + uint32_t i; + + for (i = 0, ret = 0; i < size; ret = ret << 8, ret += data[i++]) + ; + return ret; +} + +static uint32_t asn1_get_len(ssh_buffer buffer) +{ + uint32_t len; + unsigned char tmp[4]; + + if (ssh_buffer_get_data(buffer, tmp, 1) == 0) { + return 0; + } + + if (tmp[0] > 127) { + len = tmp[0] & 127; + if (len > 4) { + return 0; /* Length doesn't fit in u32. Can this really happen? */ + } + if (ssh_buffer_get_data(buffer, tmp, len) == 0) { + return 0; + } + len = char_to_u32(tmp, len); + } else { + len = char_to_u32(tmp, 1); + } + + return len; +} + +static ssh_string asn1_get(ssh_buffer buffer, unsigned char want) +{ + ssh_string str = NULL; + unsigned char type; + uint32_t size; + + if (ssh_buffer_get_data(buffer, &type, 1) == 0 || type != want) { + return NULL; + } + size = asn1_get_len(buffer); + if (size == 0) { + return NULL; + } + + str = ssh_string_new(size); + if (str == NULL) { + return NULL; + } + + if (ssh_buffer_get_data(buffer, ssh_string_data(str), size) == 0) { + SSH_STRING_FREE(str); + return NULL; + } + + return str; +} + +static ssh_string asn1_get_int(ssh_buffer buffer) +{ + return asn1_get(buffer, ASN1_INTEGER); +} + +static ssh_string asn1_get_bit_string(ssh_buffer buffer) +{ + ssh_string str = NULL; + unsigned char type; + uint32_t size; + unsigned char unused, last, *p = NULL; + uint32_t len; + + len = ssh_buffer_get_data(buffer, &type, 1); + if (len == 0 || type != ASN1_BIT_STRING) { + return NULL; + } + size = asn1_get_len(buffer); + if (size == 0) { + return NULL; + } + + /* The first octet encodes the number of unused bits. */ + size -= 1; + + str = ssh_string_new(size); + if (str == NULL) { + return NULL; + } + + len = ssh_buffer_get_data(buffer, &unused, 1); + if (len == 0) { + SSH_STRING_FREE(str); + return NULL; + } + + if (unused == 0) { + len = ssh_buffer_get_data(buffer, ssh_string_data(str), size); + if (len == 0) { + SSH_STRING_FREE(str); + return NULL; + } + return str; + } + + /* The bit string is padded at the end, we must shift the whole + string by UNUSED bits. */ + for (p = ssh_string_data(str), last = 0; size; size--, p++) { + unsigned char c; + + len = ssh_buffer_get_data(buffer, &c, 1); + if (len == 0) { + SSH_STRING_FREE(str); + return NULL; + } + *p = last | (c >> unused); + last = c << (8 - unused); + } + + return str; +} + +static int asn1_check_sequence(ssh_buffer buffer) +{ + unsigned char *j = NULL; + unsigned char tmp; + int i; + uint32_t size; + uint32_t padding; + + if (ssh_buffer_get_data(buffer, &tmp, 1) == 0 || tmp != ASN1_SEQUENCE) { + return 0; + } + + size = asn1_get_len(buffer); + if ((padding = ssh_buffer_get_len(buffer) - size) > 0) { + for (i = ssh_buffer_get_len(buffer) - size, + j = (unsigned char *)ssh_buffer_get(buffer) + size; + i; + i--, j++) { + if (*j != padding) { /* padding is allowed */ + return 0; /* but nothing else */ + } + } + } + + return 1; +} + +static int asn1_check_tag(ssh_buffer buffer, unsigned char tag) +{ + unsigned char tmp; + uint32_t len; + + len = ssh_buffer_get_data(buffer, &tmp, 1); + if (len == 0 || tmp != tag) { + return 0; + } + + (void)asn1_get_len(buffer); + return 1; +} + +static int passphrase_to_key(char *data, + unsigned int datalen, + unsigned char *salt, + unsigned char *key, + unsigned int keylen) +{ + MD5CTX md; + unsigned char digest[MD5_DIGEST_LEN] = {0}; + unsigned int i; + unsigned int j; + unsigned int md_not_empty; + + for (j = 0, md_not_empty = 0; j < keylen;) { + md = md5_init(); + if (md == NULL) { + return -1; + } + + if (md_not_empty) { + md5_update(md, digest, MD5_DIGEST_LEN); + } else { + md_not_empty = 1; + } + + md5_update(md, data, datalen); + if (salt) { + md5_update(md, salt, PKCS5_SALT_LEN); + } + md5_final(digest, md); + + for (i = 0; j < keylen && i < MD5_DIGEST_LEN; j++, i++) { + if (key) { + key[j] = digest[i]; + } + } + } + + return 0; +} + +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->rsa) + gcry_sexp_release(key->rsa); + if (key->ecdsa) + gcry_sexp_release(key->ecdsa); + + key->rsa = NULL; + key->ecdsa = NULL; +} + +static int privatekey_decrypt(int algo, + int mode, + unsigned int key_len, + unsigned char *iv, + unsigned int iv_len, + ssh_buffer data, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + char passphrase[MAX_PASSPHRASE_SIZE] = {0}; + unsigned char key[MAX_KEY_SIZE] = {0}; + unsigned char *tmp = NULL; + gcry_cipher_hd_t cipher; + int rc = -1; + + if (!algo) { + return -1; + } + + if (cb) { + rc = (*cb)(desc, passphrase, MAX_PASSPHRASE_SIZE, 0, 0, userdata); + if (rc < 0) { + return -1; + } + } else if (cb == NULL && userdata != NULL) { + snprintf(passphrase, MAX_PASSPHRASE_SIZE, "%s", (char *)userdata); + } + + if (passphrase_to_key(passphrase, strlen(passphrase), iv, key, key_len) < + 0) { + return -1; + } + + if (gcry_cipher_open(&cipher, algo, mode, 0) || + gcry_cipher_setkey(cipher, key, key_len) || + gcry_cipher_setiv(cipher, iv, iv_len) || + (tmp = calloc(ssh_buffer_get_len(data), sizeof(unsigned char))) == + NULL || + gcry_cipher_decrypt(cipher, + tmp, + ssh_buffer_get_len(data), + ssh_buffer_get(data), + ssh_buffer_get_len(data))) { + gcry_cipher_close(cipher); + return -1; + } + + memcpy(ssh_buffer_get(data), tmp, ssh_buffer_get_len(data)); + + SAFE_FREE(tmp); + gcry_cipher_close(cipher); + + return 0; +} + +static int privatekey_dek_header(const char *header, + unsigned int header_len, + int *algo, + int *mode, + unsigned int *key_len, + unsigned char **iv, + unsigned int *iv_len) +{ + unsigned int iv_pos; + + if (header_len > 13 && !strncmp("DES-EDE3-CBC", header, 12)) { + *algo = GCRY_CIPHER_3DES; + iv_pos = 13; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 8; + } else if (header_len > 8 && !strncmp("DES-CBC", header, 7)) { + *algo = GCRY_CIPHER_DES; + iv_pos = 8; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 8; + *iv_len = 8; + } else if (header_len > 12 && !strncmp("AES-128-CBC", header, 11)) { + *algo = GCRY_CIPHER_AES128; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 16; + *iv_len = 16; + } else if (header_len > 12 && !strncmp("AES-192-CBC", header, 11)) { + *algo = GCRY_CIPHER_AES192; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 24; + *iv_len = 16; + } else if (header_len > 12 && !strncmp("AES-256-CBC", header, 11)) { + *algo = GCRY_CIPHER_AES256; + iv_pos = 12; + *mode = GCRY_CIPHER_MODE_CBC; + *key_len = 32; + *iv_len = 16; + } else { + return -1; + } + + *iv = malloc(*iv_len); + if (*iv == NULL) { + return -1; + } + + return load_iv(header + iv_pos, *iv, *iv_len); +} + +#define get_next_line(p, len) \ + { \ + while (p[len] == '\n' || p[len] == '\r') /* skip empty lines */ \ + len++; \ + if (p[len] == '\0') /* EOL */ \ + eol = true; \ + else /* calculate length */ \ + for (p += len, len = 0; \ + p[len] && p[len] != '\n' && p[len] != '\r'; \ + len++) \ + ; \ + } + +static ssh_buffer privatekey_string_to_buffer(const char *pkey, + int type, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + ssh_buffer buffer = NULL; + ssh_buffer out = NULL; + const char *p = NULL; + unsigned char *iv = NULL; + const char *header_begin = NULL; + const char *header_end = NULL; + unsigned int header_begin_size; + unsigned int header_end_size; + unsigned int key_len = 0; + unsigned int iv_len = 0; + int algo = 0; + int mode = 0; + bool eol = false; + size_t len; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + + switch (type) { + case SSH_KEYTYPE_RSA: + header_begin = RSA_HEADER_BEGIN; + header_end = RSA_HEADER_END; + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + header_begin = ECDSA_HEADER_BEGIN; + header_end = ECDSA_HEADER_END; + break; + default: + SSH_BUFFER_FREE(buffer); + return NULL; + } + + header_begin_size = strlen(header_begin); + header_end_size = strlen(header_end); + + p = pkey; + len = 0; + get_next_line(p, len); + + while (!eol && strncmp(p, header_begin, header_begin_size)) { + /* skip line */ + get_next_line(p, len); + } + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + /* skip header line */ + get_next_line(p, len); + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + if (len > 11 && strncmp("Proc-Type: 4,ENCRYPTED", p, 11) == 0) { + /* skip line */ + get_next_line(p, len); + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + if (len > 10 && strncmp("DEK-Info: ", p, 10) == 0) { + p += 10; + len = 0; + get_next_line(p, len); + if (eol) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + if (privatekey_dek_header(p, + len, + &algo, + &mode, + &key_len, + &iv, + &iv_len) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + } else { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + } else { + if (len > 0) { + if (ssh_buffer_add_data(buffer, p, len) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + } + } + + get_next_line(p, len); + while (!eol && strncmp(p, header_end, header_end_size) != 0) { + if (ssh_buffer_add_data(buffer, p, len) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + get_next_line(p, len); + } + + if (eol || strncmp(p, header_end, header_end_size) != 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + + if (ssh_buffer_add_data(buffer, "\0", 1) < 0) { + SSH_BUFFER_FREE(buffer); + SAFE_FREE(iv); + return NULL; + } + + out = base64_to_bin(ssh_buffer_get(buffer)); + SSH_BUFFER_FREE(buffer); + if (out == NULL) { + SAFE_FREE(iv); + return NULL; + } + + if (algo) { + if (privatekey_decrypt(algo, + mode, + key_len, + iv, + iv_len, + out, + cb, + userdata, + desc) < 0) { + SSH_BUFFER_FREE(out); + SAFE_FREE(iv); + return NULL; + } + } + SAFE_FREE(iv); + + return out; +} + +static int b64decode_rsa_privatekey(const char *pkey, + gcry_sexp_t *r, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + const unsigned char *data = NULL; + ssh_string n = NULL; + ssh_string e = NULL; + ssh_string d = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string unused1 = NULL; + ssh_string unused2 = NULL; + ssh_string u = NULL; + ssh_string v = NULL; + ssh_buffer buffer = NULL; + int rc = 1; + gcry_error_t rv = 0; + + buffer = + privatekey_string_to_buffer(pkey, SSH_KEYTYPE_RSA, cb, userdata, desc); + if (buffer == NULL) { + return 0; + } + + if (!asn1_check_sequence(buffer)) { + SSH_BUFFER_FREE(buffer); + return 0; + } + + v = asn1_get_int(buffer); + if (v == NULL) { + SSH_BUFFER_FREE(buffer); + return 0; + } + + data = ssh_string_data(v); + if (ssh_string_len(v) != 1 || data[0] != 0) { + SSH_STRING_FREE(v); + SSH_BUFFER_FREE(buffer); + return 0; + } + + n = asn1_get_int(buffer); + e = asn1_get_int(buffer); + d = asn1_get_int(buffer); + q = asn1_get_int(buffer); + p = asn1_get_int(buffer); + unused1 = asn1_get_int(buffer); + unused2 = asn1_get_int(buffer); + u = asn1_get_int(buffer); + + SSH_BUFFER_FREE(buffer); + + if (n == NULL || e == NULL || d == NULL || p == NULL || q == NULL || + unused1 == NULL || unused2 == NULL || u == NULL) { + rc = 0; + goto error; + } + + rv = gcry_sexp_build( + r, + NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(p), + ssh_string_data(p), + ssh_string_len(q), + ssh_string_data(q), + ssh_string_len(u), + ssh_string_data(u)); + if (rv) { + rc = 0; + } + +error: + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + SSH_STRING_FREE(unused1); + SSH_STRING_FREE(unused2); + ssh_string_burn(u); + SSH_STRING_FREE(u); + SSH_STRING_FREE(v); + + return rc; +} + +#ifdef HAVE_GCRYPT_ECC +static int pki_key_ecdsa_to_nid(gcry_sexp_t k) +{ + gcry_sexp_t sexp = NULL; + const char *tmp = NULL; + size_t size; + + sexp = gcry_sexp_find_token(k, "curve", 0); + if (sexp == NULL) { + return -1; + } + + tmp = gcry_sexp_nth_data(sexp, 1, &size); + + if (size == 10) { + int cmp; + + cmp = memcmp("NIST P-256", tmp, size); + if (cmp == 0) { + gcry_sexp_release(sexp); + return NID_gcrypt_nistp256; + } + + cmp = memcmp("NIST P-384", tmp, size); + if (cmp == 0) { + gcry_sexp_release(sexp); + return NID_gcrypt_nistp384; + } + + cmp = memcmp("NIST P-521", tmp, size); + if (cmp == 0) { + gcry_sexp_release(sexp); + return NID_gcrypt_nistp521; + } + } + + gcry_sexp_release(sexp); + return -1; +} + +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(gcry_sexp_t k) +{ + int nid; + + nid = pki_key_ecdsa_to_nid(k); + + switch (nid) { + case NID_gcrypt_nistp256: + return SSH_KEYTYPE_ECDSA_P256; + case NID_gcrypt_nistp384: + return SSH_KEYTYPE_ECDSA_P384; + case NID_gcrypt_nistp521: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; + } +} + +static const char *pki_key_ecdsa_nid_to_gcrypt_name(int nid) +{ + switch (nid) { + case NID_gcrypt_nistp256: + return "NIST P-256"; + case NID_gcrypt_nistp384: + return "NIST P-384"; + case NID_gcrypt_nistp521: + return "NIST P-521"; + } + + return "unknown"; +} + +const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_gcrypt_nistp256: + return "ecdsa-sha2-nistp256"; + case NID_gcrypt_nistp384: + return "ecdsa-sha2-nistp384"; + case NID_gcrypt_nistp521: + return "ecdsa-sha2-nistp521"; + } + + return "unknown"; +} + +static const char *pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_gcrypt_nistp256: + return "nistp256"; + case NID_gcrypt_nistp384: + return "nistp384"; + case NID_gcrypt_nistp521: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + int cmp; + + cmp = strcmp(name, "nistp256"); + if (cmp == 0) { + return NID_gcrypt_nistp256; + } + + cmp = strcmp(name, "nistp384"); + if (cmp == 0) { + return NID_gcrypt_nistp384; + } + + cmp = strcmp(name, "nistp521"); + if (cmp == 0) { + return NID_gcrypt_nistp521; + } + + return -1; +} + +static int asn1_oi_to_nid(const ssh_string oi) +{ + static const struct { + int nid; + size_t length; + const char *identifier; + } *e, mapping[] = { + {NID_gcrypt_nistp256, 8, "\x2a\x86\x48\xce\x3d\x03\x01\x07"}, + {NID_gcrypt_nistp384, 5, "\x2b\x81\x04\x00\x22"}, + {NID_gcrypt_nistp521, 5, "\x2b\x81\x04\x00\x23"}, + {0}, + }; + size_t len = ssh_string_len(oi); + for (e = mapping; e->length; e++) { + if (len == e->length && + memcmp(ssh_string_data(oi), e->identifier, len) == 0) { + return e->nid; + } + } + return -1; +} + +static int b64decode_ecdsa_privatekey(const char *pkey, + gcry_sexp_t *r, + ssh_auth_callback cb, + void *userdata, + const char *desc) +{ + const unsigned char *data = NULL; + ssh_buffer buffer = NULL; + gcry_error_t err = 0; + ssh_string v = NULL; + ssh_string d = NULL; + ssh_string oi = NULL; + int nid; + ssh_string q = NULL; + int valid = 0; + int ok; + + buffer = privatekey_string_to_buffer(pkey, + SSH_KEYTYPE_ECDSA_P256, + cb, + userdata, + desc); + if (buffer == NULL) { + goto error; + } + + ok = asn1_check_sequence(buffer); + if (!ok) { + goto error; + } + + /* RFC5915 specifies version 1. */ + v = asn1_get_int(buffer); + if (v == NULL) { + goto error; + } + + data = ssh_string_data(v); + if (ssh_string_len(v) != 1 || data[0] != 1) { + goto error; + } + + d = asn1_get(buffer, ASN1_OCTET_STRING); + if (!asn1_check_tag(buffer, 0xa0)) { + goto error; + } + oi = asn1_get(buffer, ASN1_OBJECT_IDENTIFIER); + nid = asn1_oi_to_nid(oi); + ok = asn1_check_tag(buffer, 0xa1); + if (!ok) { + goto error; + } + q = asn1_get_bit_string(buffer); + + if (d == NULL || oi == NULL || nid == -1 || q == NULL) { + goto error; + } + + err = gcry_sexp_build(r, + NULL, + "(private-key(ecdsa(curve %s)(d %b)(q %b)))", + pki_key_ecdsa_nid_to_gcrypt_name(nid), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(q), + ssh_string_data(q)); + if (err == 0) { + valid = 1; + } + +error: + SSH_BUFFER_FREE(buffer); + SSH_STRING_FREE(v); + ssh_string_burn(d); + SSH_STRING_FREE(d); + SSH_STRING_FREE(oi); + ssh_string_burn(q); + SSH_STRING_FREE(q); + + return valid; +} +#endif + +ssh_string pki_private_key_to_pem(const ssh_key key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + (void)key; + (void)passphrase; + (void)auth_fn; + (void)auth_data; + + SSH_LOG(SSH_LOG_TRACE, "PEM export not supported by gcrypt backend!"); + + return NULL; +} + +ssh_key pki_private_key_from_base64(const char *b64_key, + const char *passphrase, + ssh_auth_callback auth_fn, + void *auth_data) +{ + gcry_sexp_t rsa = NULL; + gcry_sexp_t ecdsa = NULL; + ssh_key key = NULL; + enum ssh_keytypes_e type; + int valid; + + type = pki_privatekey_type_from_string(b64_key); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key."); + return NULL; + } + + switch (type) { + case SSH_KEYTYPE_RSA: + if (passphrase == NULL) { + if (auth_fn) { + valid = b64decode_rsa_privatekey(b64_key, + &rsa, + auth_fn, + auth_data, + "Passphrase for private key:"); + } else { + valid = + b64decode_rsa_privatekey(b64_key, &rsa, NULL, NULL, NULL); + } + } else { + valid = b64decode_rsa_privatekey(b64_key, + &rsa, + NULL, + (void *)passphrase, + NULL); + } + + if (!valid) { + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); + goto fail; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#if HAVE_GCRYPT_ECC + if (passphrase == NULL) { + if (auth_fn != NULL) { + valid = + b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + auth_fn, + auth_data, + "Passphrase for private key:"); + } else { + valid = b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + NULL, + NULL, + NULL); + } + } else { + valid = b64decode_ecdsa_privatekey(b64_key, + &ecdsa, + NULL, + (void *)passphrase, + NULL); + } + + if (!valid) { + SSH_LOG(SSH_LOG_TRACE, "Error parsing private key"); + goto fail; + } + + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ + type = pki_key_ecdsa_to_key_type(ecdsa); + if (type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); + goto fail; + } + break; +#endif + case SSH_KEYTYPE_ED25519: + /* Cannot open ed25519 keys with libgcrypt */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown or invalid private key type %d", type); + return NULL; + } + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + key->type = type; + key->type_c = ssh_key_type_to_char(type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + key->rsa = rsa; + key->ecdsa = ecdsa; +#ifdef HAVE_GCRYPT_ECC + if (is_ecdsa_key_type(key->type)) { + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); + } +#endif + + return key; +fail: + ssh_key_free(key); + gcry_sexp_release(rsa); + gcry_sexp_release(ecdsa); + + return NULL; +} + +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + ssh_string iqmp, + ssh_string p, + ssh_string q) +{ + /* in gcrypt, there is no iqmp (inverse of q mod p) argument, + * but it is ipmq (inverse of p mod q) so we need to swap + * the p and q arguments */ + gcry_sexp_build(&key->rsa, + NULL, + "(private-key(rsa(n %b)(e %b)(d %b)(p %b)(q %b)(u %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e), + ssh_string_len(d), + ssh_string_data(d), + ssh_string_len(q), + ssh_string_data(q), + ssh_string_len(p), + ssh_string_data(p), + ssh_string_len(iqmp), + ssh_string_data(iqmp)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) +{ + gcry_sexp_build(&key->rsa, + NULL, + "(public-key(rsa(n %b)(e %b)))", + ssh_string_len(n), + ssh_string_data(n), + ssh_string_len(e), + ssh_string_data(e)); + if (key->rsa == NULL) { + return SSH_ERROR; + } + + return SSH_OK; +} + +#ifdef HAVE_GCRYPT_ECC +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) +{ + gpg_error_t err; + + key->ecdsa_nid = nid; + + err = gcry_sexp_build(&key->ecdsa, + NULL, + "(private-key(ecdsa(curve %s)(d %b)(q %b)))", + pki_key_ecdsa_nid_to_gcrypt_name(nid), + ssh_string_len(exp), + ssh_string_data(exp), + ssh_string_len(e), + ssh_string_data(e)); + if (err) { + return SSH_ERROR; + } + + return SSH_OK; +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + gpg_error_t err; + + key->ecdsa_nid = nid; + + err = gcry_sexp_build(&key->ecdsa, + NULL, + "(public-key(ecdsa(curve %s)(q %b)))", + pki_key_ecdsa_nid_to_gcrypt_name(nid), + ssh_string_len(e), + ssh_string_data(e)); + if (err) { + return SSH_ERROR; + } + + return SSH_OK; +} +#endif + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new = NULL; + gcry_error_t err = 0; + int rc; + + gcry_mpi_t p = NULL; + gcry_mpi_t q = NULL; + gcry_mpi_t g = NULL; + gcry_mpi_t y = NULL; + gcry_mpi_t x = NULL; + + gcry_mpi_t e = NULL; + gcry_mpi_t n = NULL; + gcry_mpi_t d = NULL; + gcry_mpi_t u = NULL; + + gcry_sexp_t curve = NULL; + + new = pki_key_dup_common_init(key, demote); + if (new == NULL) { + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + err = gcry_sexp_extract_param(key->rsa, + NULL, + "ned?p?q?u?", + &n, + &e, + &d, + &p, + &q, + &u, + NULL); + if (err != 0) { + break; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { + err = gcry_sexp_build( + &new->rsa, + NULL, + "(private-key(rsa(n %m)(e %m)(d %m)(p %m)(q %m)(u %m)))", + n, + e, + d, + p, + q, + u); + } else { + err = gcry_sexp_build(&new->rsa, + NULL, + "(public-key(rsa(n %m)(e %m)))", + n, + e); + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_ed25519_key_dup(new, key); + if (rc != SSH_OK) { + ssh_key_free(new); + return NULL; + } + break; + + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + new->ecdsa_nid = key->ecdsa_nid; + + err = gcry_sexp_extract_param(key->ecdsa, NULL, "qd?", &q, &d, NULL); + if (err) { + break; + } + + curve = gcry_sexp_find_token(key->ecdsa, "curve", 0); + if (curve == NULL) { + break; + } + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE) && + !is_sk_key_type(key->type)) { + err = gcry_sexp_build(&new->ecdsa, + NULL, + "(private-key(ecdsa %S (d %m)(q %m)))", + curve, + d, + q); + } else { + err = gcry_sexp_build(&new->ecdsa, + NULL, + "(private-key(ecdsa %S (q %m)))", + curve, + q); + } + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_key_free(new); + return NULL; + } + + if (err) { + ssh_key_free(new); + new = NULL; + } + + gcry_mpi_release(p); + gcry_mpi_release(q); + gcry_mpi_release(g); + gcry_mpi_release(y); + gcry_mpi_release(x); + + gcry_mpi_release(e); + gcry_mpi_release(n); + gcry_mpi_release(d); + gcry_mpi_release(u); + + gcry_sexp_release(curve); + + return new; +} + +static int +pki_key_generate(ssh_key key, int parameter, const char *type_s, int type) +{ + gcry_sexp_t params = NULL; + int rc; + rc = gcry_sexp_build(¶ms, + NULL, + "(genkey(%s(nbits %d)(transient-key)))", + type_s, + parameter); + if (rc != 0) { + return SSH_ERROR; + } + + switch (type) { + case SSH_KEYTYPE_RSA: + rc = gcry_pk_genkey(&key->rsa, params); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + rc = gcry_pk_genkey(&key->ecdsa, params); + break; + default: + assert(!"reached"); + } + gcry_sexp_release(params); + if (rc != 0) + return SSH_ERROR; + return SSH_OK; +} + +int pki_key_generate_rsa(ssh_key key, int parameter) +{ + if (parameter == 0) { + parameter = RSA_DEFAULT_KEY_SIZE; + } + + return pki_key_generate(key, parameter, "rsa", SSH_KEYTYPE_RSA); +} + +#ifdef HAVE_GCRYPT_ECC +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ + switch (parameter) { + case 384: + key->ecdsa_nid = NID_gcrypt_nistp384; + key->type = SSH_KEYTYPE_ECDSA_P384; + return pki_key_generate(key, + parameter, + "ecdsa", + SSH_KEYTYPE_ECDSA_P384); + case 521: + key->ecdsa_nid = NID_gcrypt_nistp521; + key->type = SSH_KEYTYPE_ECDSA_P521; + return pki_key_generate(key, + parameter, + "ecdsa", + SSH_KEYTYPE_ECDSA_P521); + case 256: + default: + key->ecdsa_nid = NID_gcrypt_nistp256; + key->type = SSH_KEYTYPE_ECDSA_P256; + return pki_key_generate(key, + parameter, + "ecdsa", + SSH_KEYTYPE_ECDSA_P256); + } +} +#endif + +static int +_bignum_cmp(const gcry_sexp_t s1, const gcry_sexp_t s2, const char *what) +{ + gcry_sexp_t sexp = NULL; + bignum b1 = NULL; + bignum b2 = NULL; + int result; + + sexp = gcry_sexp_find_token(s1, what, 0); + if (sexp == NULL) { + return 1; + } + b1 = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b1 == NULL) { + return 1; + } + + sexp = gcry_sexp_find_token(s2, what, 0); + if (sexp == NULL) { + bignum_safe_free(b1); + return 1; + } + b2 = gcry_sexp_nth_mpi(sexp, 1, GCRYMPI_FMT_USG); + gcry_sexp_release(sexp); + if (b2 == NULL) { + bignum_safe_free(b1); + return 1; + } + + result = !!bignum_cmp(b1, b2); + bignum_safe_free(b1); + bignum_safe_free(b2); + return result; +} + +int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + switch (k1->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + if (_bignum_cmp(k1->rsa, k2->rsa, "e") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "n") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE) { + if (_bignum_cmp(k1->rsa, k2->rsa, "d") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "p") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "q") != 0) { + return 1; + } + + if (_bignum_cmp(k1->rsa, k2->rsa, "u") != 0) { + return 1; + } + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys handled globally */ + return 1; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: +#ifdef HAVE_GCRYPT_ECC + if (k1->ecdsa_nid != k2->ecdsa_nid) { + return 1; + } + + if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "q") != 0) { + return 1; + } + + if (what == SSH_KEY_CMP_PRIVATE && !is_sk_key_type(k1->type)) { + if (_bignum_cmp(k1->ecdsa, k2->ecdsa, "d") != 0) { + return 1; + } + } + break; +#endif + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_ECDSA: /* deprecated */ + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + return 1; + } + + return 0; +} + +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + ssh_string str = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string d = NULL; + ssh_string p = NULL; + ssh_string g = NULL; + ssh_string q = NULL; + ssh_string u = NULL; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); + + if (key->cert != NULL) { + rc = ssh_buffer_add_buffer(buffer, key->cert); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + goto makestring; + } + + type_s = ssh_string_from_char(key->type_c); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: + e = ssh_sexp_extract_mpi(key->rsa, + "e", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (e == NULL) { + goto fail; + } + + n = ssh_sexp_extract_mpi(key->rsa, + "n", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (n == NULL) { + goto fail; + } + + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + } else if (type == SSH_KEY_PRIVATE) { + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + d = ssh_sexp_extract_mpi(key->rsa, + "d", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + p = ssh_sexp_extract_mpi(key->rsa, + "p", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (p == NULL) { + goto fail; + } + + q = ssh_sexp_extract_mpi(key->rsa, + "q", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (q == NULL) { + goto fail; + } + + u = ssh_sexp_extract_mpi(key->rsa, + "u", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (u == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, u); + if (rc < 0) { + goto fail; + } + /* Swap the P and Q as the iqmp in gcrypt is ipmq ... */ + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto fail; + } + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto fail; + } + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); + ssh_string_burn(u); + SSH_STRING_FREE(u); + } + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + } else { + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + type_s = + ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + goto fail; + } + + e = ssh_sexp_extract_mpi(key->ecdsa, + "q", + GCRYMPI_FMT_STD, + GCRYMPI_FMT_STD); + if (e == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(e); + SSH_STRING_FREE(e); + e = NULL; + + if (type == SSH_KEY_PRIVATE && !is_sk_key_type(key->type)) { + d = ssh_sexp_extract_mpi(key->ecdsa, + "d", + GCRYMPI_FMT_STD, + GCRYMPI_FMT_STD); + if (d == NULL) { + goto fail; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto fail; + } + + ssh_string_burn(d); + SSH_STRING_FREE(d); + d = NULL; + } else if (type == SSH_KEY_PRIVATE && is_sk_key_type(key->type)) { + /* Add security key private data for SK_ECDSA */ + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto fail; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto fail; + } + } + + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + goto fail; + } + +makestring: + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL) { + goto fail; + } + + rc = ssh_string_fill(str, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc < 0) { + goto fail; + } + SSH_BUFFER_FREE(buffer); + + return str; +fail: + SSH_BUFFER_FREE(buffer); + ssh_string_burn(str); + SSH_STRING_FREE(str); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(g); + SSH_STRING_FREE(g); + ssh_string_burn(q); + SSH_STRING_FREE(q); + ssh_string_burn(n); + SSH_STRING_FREE(n); + + return NULL; +} + +ssh_string pki_signature_to_blob(const ssh_signature sig) +{ + const char *s = NULL; /* used in RSA */ + + gcry_sexp_t sexp = NULL; + size_t size = 0; + ssh_string sig_blob = NULL; + int rc; + + switch (sig->type) { + case SSH_KEYTYPE_RSA: + sexp = gcry_sexp_find_token(sig->rsa_sig, "s", 0); + if (sexp == NULL) { + return NULL; + } + s = gcry_sexp_nth_data(sexp, 1, &size); + + /* + * Remove leading zeroes, but only the ones that do not make the MPI + * representation look like a negative value (first bit is one), + * which might confuse some implementations. + */ + while (size > 1 && s[0] == 0 && (s[1] & 0x80) == 0) { + size--; + s++; + } + + sig_blob = ssh_string_new(size); + if (sig_blob == NULL) { + return NULL; + } + rc = ssh_string_fill(sig_blob, discard_const_p(char, s), size); + gcry_sexp_release(sexp); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + sig_blob = pki_ed25519_signature_to_blob(sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + { + ssh_string R = NULL; + ssh_string S = NULL; + ssh_buffer b = NULL; + + b = ssh_buffer_new(); + if (b == NULL) { + return NULL; + } + + R = ssh_sexp_extract_mpi(sig->ecdsa_sig, + "r", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (R == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, R); + SSH_STRING_FREE(R); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + S = ssh_sexp_extract_mpi(sig->ecdsa_sig, + "s", + GCRYMPI_FMT_USG, + GCRYMPI_FMT_STD); + if (S == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, S); + SSH_STRING_FREE(S); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + sig_blob = ssh_string_new(ssh_buffer_get_len(b)); + if (sig_blob == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = + ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b)); + SSH_BUFFER_FREE(b); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + break; + } +#endif + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ED25519: + /* For SK keys, signature data is already in raw_sig */ + sig_blob = ssh_string_copy(sig->raw_sig); + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %d", sig->type); + return NULL; + break; + } + + return sig_blob; +} + +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + ssh_signature sig = NULL; + gcry_error_t err; + size_t len; + size_t rsalen; + int rc; + + if (ssh_key_type_plain(pubkey->type) != type) { + SSH_LOG(SSH_LOG_TRACE, + "Incompatible public key provided (%d) expecting (%d)", + type, + pubkey->type); + return NULL; + } + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + sig->type_c = ssh_key_signature_to_char(type, hash_type); + sig->hash_type = hash_type; + + len = ssh_string_len(sig_blob); + + switch (type) { + case SSH_KEYTYPE_RSA: + rsalen = (gcry_pk_get_nbits(pubkey->rsa) + 7) / 8; + + if (len > rsalen) { + SSH_LOG(SSH_LOG_TRACE, + "Signature is too big: %lu > %lu", + (unsigned long)len, + (unsigned long)rsalen); + ssh_signature_free(sig); + return NULL; + } + + if (len < rsalen) { + SSH_LOG(SSH_LOG_DEBUG, + "RSA signature len %lu < %lu", + (unsigned long)len, + (unsigned long)rsalen); + } + +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len: %lu", (unsigned long)len); + ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); +#endif + + err = gcry_sexp_build(&sig->rsa_sig, + NULL, + "(sig-val(rsa(s %b)))", + ssh_string_len(sig_blob), + ssh_string_data(sig_blob)); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc != SSH_OK) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: +#ifdef HAVE_GCRYPT_ECC + { /* build ecdsa siganature */ + ssh_buffer b = NULL; + ssh_string r = NULL, s = NULL; + uint32_t rlen; + + b = ssh_buffer_new(); + if (b == NULL) { + ssh_signature_free(sig); + return NULL; + } + /* The buffer will contain sensitive information. */ + ssh_buffer_set_secure(b); + + rc = ssh_buffer_add_data(b, + ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + if (rc < 0) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + r = ssh_buffer_get_ssh_string(b); + if (r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + s = ssh_buffer_get_ssh_string(b); + rlen = ssh_buffer_get_len(b); + SSH_BUFFER_FREE(b); + if (s == NULL) { + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_signature_free(sig); + return NULL; + } + + if (rlen != 0) { + SSH_LOG(SSH_LOG_TRACE, + "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_string_burn(s); + SSH_STRING_FREE(s); + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); +#endif + + err = gcry_sexp_build(&sig->ecdsa_sig, + NULL, + "(sig-val(ecdsa(r %b)(s %b)))", + ssh_string_len(r), + ssh_string_data(r), + ssh_string_len(s), + ssh_string_data(s)); + ssh_string_burn(r); + SSH_STRING_FREE(r); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (err) { + ssh_signature_free(sig); + return NULL; + } + } break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); + return NULL; + } + + return sig; +} + +ssh_signature pki_do_sign_hash(const ssh_key privkey, + const unsigned char *hash, + size_t hlen, + enum ssh_digest_e hash_type) +{ + const char *hash_c = NULL; + ssh_signature sig = NULL; + gcry_sexp_t sexp = NULL; + gcry_error_t err; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + sig->type = privkey->type; + sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); + sig->hash_type = hash_type; + switch (privkey->type) { + case SSH_KEYTYPE_RSA: + switch (hash_type) { + case SSH_DIGEST_SHA1: + hash_c = "sha1"; + break; + case SSH_DIGEST_SHA256: + hash_c = "sha256"; + break; + case SSH_DIGEST_SHA512: + hash_c = "sha512"; + break; + case SSH_DIGEST_AUTO: + default: + SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); + return NULL; + } + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash %s %b))", + hash_c, + hlen, + hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + + err = gcry_pk_sign(&sig->rsa_sig, sexp, privkey->rsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + err = pki_ed25519_sign(privkey, sig, hash, hlen); + if (err != SSH_OK) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: +#ifdef HAVE_GCRYPT_ECC + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags raw)(value %b))", + hlen, + hash); + if (err) { + ssh_signature_free(sig); + return NULL; + } + + err = gcry_pk_sign(&sig->ecdsa_sig, sexp, privkey->ecdsa); + gcry_sexp_release(sexp); + if (err) { + ssh_signature_free(sig); + return NULL; + } + break; +#endif + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + ssh_signature_free(sig); + return NULL; + } + + return sig; +} + +/** + * @internal + * + * @brief Sign the given input data. The digest of to be signed is calculated + * internally as necessary. + * + * @param[in] privkey The private key to be used for signing. + * @param[in] hash_type The digest algorithm to be used. + * @param[in] input The data to be signed. + * @param[in] input_len The length of the data to be signed. + * + * @return a newly allocated ssh_signature or NULL on error. + */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + unsigned char hash[SHA512_DIGEST_LEN] = {0}; + const unsigned char *sign_input = NULL; + uint32_t hlen = 0; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Bad parameter provided to " + "pki_sign_data()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + switch (hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_AUTO: + if (privkey->type == SSH_KEYTYPE_ED25519) { + /* SSH_DIGEST_AUTO should only be used with ed25519 */ + sign_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, + "Unknown hash algorithm for type: %d", + hash_type); + return NULL; + } + + return pki_do_sign_hash(privkey, sign_input, hlen, hash_type); +} + +/** + * @internal + * + * @brief Verify the signature of a given input. The digest of the input is + * calculated internally as necessary. + * + * @param[in] signature The signature to be verified. + * @param[in] pubkey The public key used to verify the signature. + * @param[in] input The signed data. + * @param[in] input_len The length of the signed data. + * + * @return SSH_OK if the signature is valid; SSH_ERROR otherwise. + */ +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len) +{ + const char *hash_type = NULL; + gcry_sexp_t sexp = NULL; + gcry_error_t err; + + unsigned char ghash[SHA512_DIGEST_LEN + 1] = {0}; + unsigned char *hash = ghash + 1; + uint32_t hlen = 0; + + const unsigned char *verify_input = NULL; + + int rc; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL) { + SSH_LOG(SSH_LOG_TRACE, + "Bad parameter provided to " + "pki_verify_data_signature()"); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + switch (signature->hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + hash_type = "sha256"; + verify_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + hash_type = "sha384"; + verify_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + hash_type = "sha512"; + verify_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + hash_type = "sha1"; + verify_input = hash; + break; + case SSH_DIGEST_AUTO: + if (pubkey->type == SSH_KEYTYPE_ED25519 || + pubkey->type == SSH_KEYTYPE_ED25519_CERT01 || + pubkey->type == SSH_KEYTYPE_SK_ED25519 || + pubkey->type == SSH_KEYTYPE_SK_ED25519_CERT01) { + verify_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, + "Unknown sig->hash_type: %d", + signature->hash_type); + return SSH_ERROR; + } + + switch (pubkey->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags pkcs1)(hash %s %b))", + hash_type, + hlen, + hash); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "RSA hash error: %s", gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(signature->rsa_sig, sexp, pubkey->rsa); + gcry_sexp_release(sexp); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "Invalid RSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { + SSH_LOG(SSH_LOG_TRACE, + "RSA verify error: %s", + gcry_strerror(err)); + } + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: +#ifdef HAVE_GCRYPT_ECC + err = gcry_sexp_build(&sexp, + NULL, + "(data(flags raw)(value %b))", + hlen, + hash); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "ECDSA hash error: %s", gcry_strerror(err)); + return SSH_ERROR; + } + err = gcry_pk_verify(signature->ecdsa_sig, sexp, pubkey->ecdsa); + gcry_sexp_release(sexp); + if (err) { + SSH_LOG(SSH_LOG_TRACE, "Invalid ECDSA signature"); + if (gcry_err_code(err) != GPG_ERR_BAD_SIGNATURE) { + SSH_LOG(SSH_LOG_TRACE, + "ECDSA verify error: %s", + gcry_strerror(err)); + } + return SSH_ERROR; + } + break; +#endif + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + rc = pki_ed25519_verify(pubkey, signature, verify_input, hlen); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "ED25519 error: Signature invalid"); + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type"); + return SSH_ERROR; + } + + return SSH_OK; +} + +int ssh_key_size(ssh_key key) +{ + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return gcry_pk_get_nbits(key->rsa); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return gcry_pk_get_nbits(key->ecdsa); + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +#ifdef WITH_PKCS11_URI +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) +{ + (void)uri_name; + (void)key; + (void)key_type; + SSH_LOG(SSH_LOG_TRACE, "gcrypt does not support PKCS #11"); + return SSH_ERROR; +} +#endif /* WITH_PKCS11_URI */ +#endif /* HAVE_LIBGCRYPT */ diff --git a/src/libs/libssh-0.12.2/src/pki_mbedcrypto.c b/src/libs/libssh-0.12.2/src/pki_mbedcrypto.c new file mode 100644 index 000000000000..01a9ca80fc03 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_mbedcrypto.c @@ -0,0 +1,2032 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2017 Sartura d.o.o. + * + * Author: Juraj Vijtiuk + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#ifdef HAVE_LIBMBEDCRYPTO +#include +#include +#include "mbedcrypto-compat.h" + +#include "libssh/priv.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/buffer.h" +#include "libssh/bignum.h" +#include "libssh/misc.h" + +#define MAX_PASSPHRASE_SIZE 1024 +#define MAX_KEY_SIZE 32 + +void pki_key_clean(ssh_key key) +{ + if (key == NULL) + return; + + if (key->pk != NULL) { + mbedtls_pk_free(key->pk); + SAFE_FREE(key->pk); + } + + if (key->ecdsa != NULL) { + mbedtls_ecdsa_free(key->ecdsa); + SAFE_FREE(key->ecdsa); + } +} + +ssh_string pki_private_key_to_pem(const ssh_key key, const char *passphrase, + ssh_auth_callback auth_fn, void *auth_data) +{ + (void) key; + (void) passphrase; + (void) auth_fn; + (void) auth_data; return NULL; +} + +static int pki_key_ecdsa_to_nid(mbedtls_ecdsa_context *ecdsa) +{ + mbedtls_ecp_group_id id; + + id = ecdsa->MBEDTLS_PRIVATE(grp.id); + if (id == MBEDTLS_ECP_DP_SECP256R1) { + return NID_mbedtls_nistp256; + } else if (id == MBEDTLS_ECP_DP_SECP384R1) { + return NID_mbedtls_nistp384; + } else if (id == MBEDTLS_ECP_DP_SECP521R1) { + return NID_mbedtls_nistp521; + } + + return -1; +} + +static enum ssh_keytypes_e pki_key_ecdsa_to_key_type(mbedtls_ecdsa_context *ecdsa) +{ + int nid; + + nid = pki_key_ecdsa_to_nid(ecdsa); + + switch (nid) { + case NID_mbedtls_nistp256: + return SSH_KEYTYPE_ECDSA_P256; + case NID_mbedtls_nistp384: + return SSH_KEYTYPE_ECDSA_P384; + case NID_mbedtls_nistp521: + return SSH_KEYTYPE_ECDSA_P521; + default: + return SSH_KEYTYPE_UNKNOWN; + } +} + +ssh_key pki_private_key_from_base64(const char *b64_key, const char *passphrase, + ssh_auth_callback auth_fn, void *auth_data) +{ + ssh_key key = NULL; + mbedtls_pk_context *pk = NULL; + mbedtls_pk_type_t mbed_type; + int valid; + /* mbedtls pk_parse_key expects strlen to count the 0 byte */ + size_t b64len = strlen(b64_key) + 1; + unsigned char tmp[MAX_PASSPHRASE_SIZE] = {0}; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_ctr_drbg_context *ctr_drbg = ssh_get_mbedtls_ctr_drbg_context(); +#endif + + pk = malloc(sizeof(mbedtls_pk_context)); + if (pk == NULL) { + goto fail; + } + mbedtls_pk_init(pk); + + if (passphrase == NULL) { + if (auth_fn) { + valid = auth_fn("Passphrase for private key:", + (char *)tmp, + MAX_PASSPHRASE_SIZE, + 0, + 0, + auth_data); + if (valid < 0) { + goto fail; + } + valid = mbedtls_pk_parse_key( + pk, + (const unsigned char *)b64_key, + b64len, + tmp, + strnlen((const char *)tmp, MAX_PASSPHRASE_SIZE) +#if MBEDTLS_VERSION_MAJOR > 2 + , + mbedtls_ctr_drbg_random, + ctr_drbg +#endif + ); + } else { + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + NULL, + 0 +#if MBEDTLS_VERSION_MAJOR > 2 + , + mbedtls_ctr_drbg_random, + ctr_drbg +#endif + ); + } + } else { + valid = mbedtls_pk_parse_key(pk, + (const unsigned char *)b64_key, + b64len, + (const unsigned char *)passphrase, + strnlen(passphrase, MAX_PASSPHRASE_SIZE) +#if MBEDTLS_VERSION_MAJOR > 2 + , + mbedtls_ctr_drbg_random, + ctr_drbg +#endif + ); + } + if (valid != 0) { + char error_buf[100]; + mbedtls_strerror(valid, error_buf, 100); + SSH_LOG(SSH_LOG_WARN, "Parsing private key %s", error_buf); + goto fail; + } + + mbed_type = mbedtls_pk_get_type(pk); + + key = ssh_key_new(); + if (key == NULL) { + goto fail; + } + + switch (mbed_type) { + case MBEDTLS_PK_RSA: + case MBEDTLS_PK_RSA_ALT: + key->pk = pk; + pk = NULL; + key->type = SSH_KEYTYPE_RSA; + break; + case MBEDTLS_PK_ECKEY: + case MBEDTLS_PK_ECDSA: { + /* type will be set later */ + mbedtls_ecp_keypair *keypair = mbedtls_pk_ec(*pk); + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + goto fail; + } + + mbedtls_ecdsa_init(key->ecdsa); + mbedtls_ecdsa_from_keypair(key->ecdsa, keypair); + key->pk = pk; + + key->ecdsa_nid = pki_key_ecdsa_to_nid(key->ecdsa); + + /* pki_privatekey_type_from_string always returns P256 for ECDSA + * keys, so we need to figure out the correct type here */ + key->type = pki_key_ecdsa_to_key_type(key->ecdsa); + if (key->type == SSH_KEYTYPE_UNKNOWN) { + SSH_LOG(SSH_LOG_TRACE, "Invalid private key."); + goto fail; + } + break; + } + default: + SSH_LOG(SSH_LOG_WARN, + "Unknown or invalid private key type %d", + mbed_type); + return NULL; + } + + key->type_c = ssh_key_type_to_char(key->type); + key->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + return key; +fail: + ssh_key_free(key); + if (pk != NULL) { + mbedtls_pk_free(pk); + SAFE_FREE(pk); + } + return NULL; +} + +int pki_privkey_build_rsa(ssh_key key, + ssh_string n, + ssh_string e, + ssh_string d, + UNUSED_PARAM(ssh_string iqmp), + ssh_string p, + ssh_string q) +{ + mbedtls_rsa_context *rsa = NULL; + const mbedtls_pk_info_t *pk_info = NULL; + int rc; + + key->pk = malloc(sizeof(mbedtls_pk_context)); + if (key->pk == NULL) { + return SSH_ERROR; + } + + mbedtls_pk_init(key->pk); + pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + mbedtls_pk_setup(key->pk, pk_info); + + rc = mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA); + if (rc == 0) { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->pk); + rc = mbedtls_rsa_import_raw(rsa, + ssh_string_data(n), ssh_string_len(n), + ssh_string_data(p), ssh_string_len(p), + ssh_string_data(q), ssh_string_len(q), + ssh_string_data(d), ssh_string_len(d), + ssh_string_data(e), ssh_string_len(e)); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to import private RSA key"); + goto fail; + } + + rc = mbedtls_rsa_complete(rsa); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "Failed to complete private RSA key"); + goto fail; + } + + rc = mbedtls_rsa_check_privkey(rsa); + if (rc != 0) { + SSH_LOG(SSH_LOG_TRACE, "Inconsistent private RSA key"); + goto fail; + } + + return SSH_OK; + +fail: + mbedtls_pk_free(key->pk); + SAFE_FREE(key->pk); + return SSH_ERROR; +} + +int pki_pubkey_build_rsa(ssh_key key, ssh_string e, ssh_string n) +{ + mbedtls_rsa_context *rsa = NULL; + const mbedtls_pk_info_t *pk_info = NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; +#endif + int rc; + + key->pk = malloc(sizeof(mbedtls_pk_context)); + if (key->pk == NULL) { + return SSH_ERROR; + } + + mbedtls_pk_init(key->pk); + pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + mbedtls_pk_setup(key->pk, pk_info); + + rc = mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA); + if (rc == 0) { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->pk); +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); + + rc = mbedtls_mpi_read_binary(&N, ssh_string_data(n), + ssh_string_len(n)); +#else + rc = mbedtls_mpi_read_binary(&rsa->N, ssh_string_data(n), + ssh_string_len(n)); +#endif + if (rc != 0) { + goto fail; + } +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_mpi_read_binary(&E, ssh_string_data(e), + ssh_string_len(e)); +#else + rc = mbedtls_mpi_read_binary(&rsa->E, ssh_string_data(e), + ssh_string_len(e)); +#endif + if (rc != 0) { + goto fail; + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_import(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_rsa_complete(rsa); + if (rc != 0) { + goto fail; + } + +#else + rsa->len = (mbedtls_mpi_bitlen(&rsa->N) + 7) >> 3; +#endif + rc = SSH_OK; + goto exit; +fail: + rc = SSH_ERROR; + mbedtls_pk_free(key->pk); + SAFE_FREE(key->pk); +exit: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); +#endif + return rc; +} + +ssh_key pki_key_dup(const ssh_key key, int demote) +{ + ssh_key new = NULL; + int rc; + const mbedtls_pk_info_t *pk_info = NULL; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N; + mbedtls_mpi E; + mbedtls_mpi D; + mbedtls_mpi P; + mbedtls_mpi Q; +#endif + + new = pki_key_dup_common_init(key, demote); + if (new == NULL) { + return NULL; + } + +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); +#endif + + switch(key->type) { + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa, *new_rsa; + + new->pk = malloc(sizeof(mbedtls_pk_context)); + if (new->pk == NULL) { + goto fail; + } + + mbedtls_pk_init(new->pk); + pk_info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + mbedtls_pk_setup(new->pk, pk_info); + + if (!mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(new->pk, MBEDTLS_PK_RSA)) { + goto fail; + } + + rsa = mbedtls_pk_rsa(*key->pk); + new_rsa = mbedtls_pk_rsa(*new->pk); + + if (!demote && (key->flags & SSH_KEY_FLAG_PRIVATE)) { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, &P, &Q, &D, &E); + if (rc != 0) { + goto fail; + } +#else + rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->E, &rsa->E); + if (rc != 0) { + goto fail; + } + + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; + + rc = mbedtls_mpi_copy(&new_rsa->D, &rsa->D); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->P, &rsa->P); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->Q, &rsa->Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->DP, &rsa->DP); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->DQ, &rsa->DQ); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->QP, &rsa->QP); + if (rc != 0) { + goto fail; + } +#endif + } else { +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } + rc = mbedtls_rsa_import(new_rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto fail; + } +#else + rc = mbedtls_mpi_copy(&new_rsa->N, &rsa->N); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_copy(&new_rsa->E, &rsa->E); + if (rc != 0) { + goto fail; + } + + new_rsa->len = (mbedtls_mpi_bitlen(&new_rsa->N) + 7) >> 3; +#endif + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_complete(new_rsa); + if (rc != 0) { + goto fail; + } +#endif + + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: + new->ecdsa_nid = key->ecdsa_nid; + + new->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + + if (new->ecdsa == NULL) { + goto fail; + } + + mbedtls_ecdsa_init(new->ecdsa); + + if ((demote && ssh_key_is_private(key)) || + is_sk_key_type(key->type)) { + rc = mbedtls_ecp_copy(&new->ecdsa->MBEDTLS_PRIVATE(Q), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_group_copy(&new->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(grp)); + if (rc != 0) { + goto fail; + } + } else { + mbedtls_ecdsa_from_keypair(new->ecdsa, key->ecdsa); + } + + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_ed25519_key_dup(new, key); + if (rc != SSH_OK) { + goto fail; + } + break; + default: + goto fail; + } + + goto cleanup; + +fail: + SSH_KEY_FREE(new); +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); +#endif + return new; +} + +int pki_key_generate_rsa(ssh_key key, int parameter) +{ + int rc; + const mbedtls_pk_info_t *info = NULL; + + if (parameter == 0) { + parameter = RSA_DEFAULT_KEY_SIZE; + } + + key->pk = malloc(sizeof(mbedtls_pk_context)); + if (key->pk == NULL) { + return SSH_ERROR; + } + + mbedtls_pk_init(key->pk); + + info = mbedtls_pk_info_from_type(MBEDTLS_PK_RSA); + rc = mbedtls_pk_setup(key->pk, info); + if (rc != 0) { + return SSH_ERROR; + } + + if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA)) { + rc = mbedtls_rsa_gen_key(mbedtls_pk_rsa(*key->pk), + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context(), + parameter, + 65537); + if (rc != 0) { + mbedtls_pk_free(key->pk); + return SSH_ERROR; + } + } + + return SSH_OK; +} + +int pki_key_compare(const ssh_key k1, const ssh_key k2, enum ssh_keycmp_e what) +{ + int rc = 0; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi N1; + mbedtls_mpi N2; + mbedtls_mpi P1; + mbedtls_mpi P2; + mbedtls_mpi Q1; + mbedtls_mpi Q2; + mbedtls_mpi E1; + mbedtls_mpi E2; + + mbedtls_mpi_init(&N1); + mbedtls_mpi_init(&N2); + mbedtls_mpi_init(&P1); + mbedtls_mpi_init(&P2); + mbedtls_mpi_init(&Q1); + mbedtls_mpi_init(&Q2); + mbedtls_mpi_init(&E1); + mbedtls_mpi_init(&E2); +#endif + + switch (ssh_key_type_plain(k1->type)) { + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa1, *rsa2; + if (!mbedtls_pk_can_do(k1->pk, MBEDTLS_PK_RSA) || + !mbedtls_pk_can_do(k2->pk, MBEDTLS_PK_RSA)) { + break; + } + + if (mbedtls_pk_get_type(k1->pk) != mbedtls_pk_get_type(k2->pk) || + mbedtls_pk_get_bitlen(k1->pk) != + mbedtls_pk_get_bitlen(k2->pk)) { + rc = 1; + goto cleanup; + } + + if (what == SSH_KEY_CMP_PUBLIC) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->pk); + rc = mbedtls_rsa_export(rsa1, &N1, NULL, NULL, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + rsa2 = mbedtls_pk_rsa(*k2->pk); + rc = mbedtls_rsa_export(rsa2, &N2, NULL, NULL, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } +#else + rsa1 = mbedtls_pk_rsa(*k1->pk); + rsa2 = mbedtls_pk_rsa(*k2->pk); + if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { + rc = 1; + goto cleanup; + } +#endif + } else if (what == SSH_KEY_CMP_PRIVATE) { +#if MBEDTLS_VERSION_MAJOR > 2 + rsa1 = mbedtls_pk_rsa(*k1->pk); + rc = mbedtls_rsa_export(rsa1, &N1, &P1, &Q1, NULL, &E1); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + rsa2 = mbedtls_pk_rsa(*k2->pk); + rc = mbedtls_rsa_export(rsa2, &N2, &P2, &Q2, NULL, &E2); + if (rc != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&N1, &N2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&E1, &E2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&P1, &P2) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&Q1, &Q2) != 0) { + rc = 1; + goto cleanup; + } +#else + rsa1 = mbedtls_pk_rsa(*k1->pk); + rsa2 = mbedtls_pk_rsa(*k2->pk); + if (mbedtls_mpi_cmp_mpi(&rsa1->N, &rsa2->N) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->E, &rsa2->E) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->P, &rsa2->P) != 0) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&rsa1->Q, &rsa2->Q) != 0) { + rc = 1; + goto cleanup; + } +#endif + } + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: { + mbedtls_ecp_keypair *ecdsa1 = k1->ecdsa; + mbedtls_ecp_keypair *ecdsa2 = k2->ecdsa; + + if (ecdsa1->MBEDTLS_PRIVATE(grp).id != + ecdsa2->MBEDTLS_PRIVATE(grp).id) { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(X))) + { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Y))) + { + rc = 1; + goto cleanup; + } + + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z), + &ecdsa2->MBEDTLS_PRIVATE(Q).MBEDTLS_PRIVATE(Z))) + { + rc = 1; + goto cleanup; + } + + if (what == SSH_KEY_CMP_PRIVATE && + k1->type != SSH_KEYTYPE_SK_ECDSA) { + if (mbedtls_mpi_cmp_mpi(&ecdsa1->MBEDTLS_PRIVATE(d), + &ecdsa2->MBEDTLS_PRIVATE(d))) + { + rc = 1; + goto cleanup; + } + } + + break; + } + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + /* ed25519 keys handled globally */ + rc = 1; + break; + default: + rc = 1; + break; + } + +cleanup: +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N1); + mbedtls_mpi_free(&N2); + mbedtls_mpi_free(&P1); + mbedtls_mpi_free(&P2); + mbedtls_mpi_free(&Q1); + mbedtls_mpi_free(&Q2); + mbedtls_mpi_free(&E1); + mbedtls_mpi_free(&E2); +#endif + return rc; +} + +ssh_string make_ecpoint_string(const mbedtls_ecp_group *g, const + mbedtls_ecp_point *p) +{ + ssh_string s = NULL; + size_t len = 1; + int rc; + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + rc = mbedtls_ecp_point_write_binary(g, p, MBEDTLS_ECP_PF_UNCOMPRESSED, + &len, ssh_string_data(s), ssh_string_len(s)); + if (rc == MBEDTLS_ERR_ECP_BUFFER_TOO_SMALL) { + SSH_STRING_FREE(s); + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + rc = mbedtls_ecp_point_write_binary(g, p, MBEDTLS_ECP_PF_UNCOMPRESSED, + &len, ssh_string_data(s), ssh_string_len(s)); + } + + if (rc != 0) { + SSH_STRING_FREE(s); + return NULL; + } + + if (len != ssh_string_len(s)) { + SSH_STRING_FREE(s); + return NULL; + } + + return s; +} + +static const char* pki_key_ecdsa_nid_to_char(int nid) +{ + switch (nid) { + case NID_mbedtls_nistp256: + return "nistp256"; + case NID_mbedtls_nistp384: + return "nistp384"; + case NID_mbedtls_nistp521: + return "nistp521"; + default: + break; + } + + return "unknown"; +} + +ssh_string pki_key_to_blob(const ssh_key key, enum ssh_key_e type) +{ + ssh_buffer buffer = NULL; + ssh_string type_s = NULL; + ssh_string e = NULL; + ssh_string n = NULL; + ssh_string p = NULL; + ssh_string q = NULL; + ssh_string d = NULL; + ssh_string iqmp = NULL; + ssh_string str = NULL; + int rc; +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi E = {0}; + mbedtls_mpi N = {0}; + mbedtls_mpi D = {0}; + mbedtls_mpi IQMP = {0}; + mbedtls_mpi P = {0}; + mbedtls_mpi Q = {0}; + + mbedtls_mpi_init(&E); + mbedtls_mpi_init(&N); + mbedtls_mpi_init(&D); + mbedtls_mpi_init(&IQMP); + mbedtls_mpi_init(&P); + mbedtls_mpi_init(&Q); +#endif + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return NULL; + } + /* The buffer will contain sensitive information. Make sure it is erased */ + ssh_buffer_set_secure(buffer); + + if (key->cert != NULL) { + rc = ssh_buffer_add_buffer(buffer, key->cert); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + goto makestring; + } + + type_s = ssh_string_from_char(key->type_c); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + switch (key->type) { + case SSH_KEYTYPE_RSA: { + mbedtls_rsa_context *rsa = NULL; + mbedtls_mpi *E_ptr = NULL, *N_ptr = NULL; + + if (mbedtls_pk_can_do(key->pk, MBEDTLS_PK_RSA) == 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rsa = mbedtls_pk_rsa(*key->pk); +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, &N, NULL, NULL, NULL, &E); + if (rc != 0) { + goto out; + } + + E_ptr = &E; + N_ptr = &N; +#else + E_ptr = &rsa->E; + N_ptr = &rsa->N; +#endif + + e = ssh_make_bignum_string(E_ptr); + if (e == NULL) { + goto out; + } + + n = ssh_make_bignum_string(N_ptr); + if (n == NULL) { + goto out; + } + + if (type == SSH_KEY_PUBLIC) { + /* The N and E parts are swapped in the public key export ! */ + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto out; + } + } else if (type == SSH_KEY_PRIVATE) { + mbedtls_mpi *P_ptr = NULL, *Q_ptr = NULL, *D_ptr = NULL; + mbedtls_mpi *IQMP_ptr = NULL; + + rc = ssh_buffer_add_ssh_string(buffer, n); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } + +#if MBEDTLS_VERSION_MAJOR > 2 + rc = mbedtls_rsa_export(rsa, NULL, &P, &Q, &D, NULL); + if (rc != 0) { + goto out; + } + + rc = mbedtls_rsa_export_crt(rsa, NULL, NULL, &IQMP); + if (rc != 0) { + goto out; + } + + P_ptr = &P; + Q_ptr = &Q; + D_ptr = &D; + IQMP_ptr = &IQMP; +#else + P_ptr = &rsa->P; + Q_ptr = &rsa->Q; + D_ptr = &rsa->D; + IQMP_ptr = &rsa->QP; +#endif + + p = ssh_make_bignum_string(P_ptr); + if (p == NULL) { + goto out; + } + + q = ssh_make_bignum_string(Q_ptr); + if (q == NULL) { + goto out; + } + + d = ssh_make_bignum_string(D_ptr); + if (d == NULL) { + goto out; + } + + iqmp = ssh_make_bignum_string(IQMP_ptr); + if (iqmp == NULL) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, iqmp); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, p); + if (rc < 0) { + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, q); + if (rc < 0) { + goto out; + } + } + break; + } + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: + type_s = + ssh_string_from_char(pki_key_ecdsa_nid_to_char(key->ecdsa_nid)); + if (type_s == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, type_s); + SSH_STRING_FREE(type_s); + if (rc < 0) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + e = make_ecpoint_string(&key->ecdsa->MBEDTLS_PRIVATE(grp), + &key->ecdsa->MBEDTLS_PRIVATE(Q)); + + if (e == NULL) { + SSH_BUFFER_FREE(buffer); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(buffer, e); + if (rc < 0) { + goto out; + } + + if (type == SSH_KEY_PRIVATE && key->type != SSH_KEYTYPE_SK_ECDSA) { + d = ssh_make_bignum_string(&key->ecdsa->MBEDTLS_PRIVATE(d)); + + if (d == NULL) { + SSH_BUFFER_FREE(buffer); + goto out; + } + + rc = ssh_buffer_add_ssh_string(buffer, d); + if (rc < 0) { + goto out; + } + } else if (type == SSH_KEY_PRIVATE && + key->type == SSH_KEYTYPE_SK_ECDSA) { + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc != SSH_OK) { + goto out; + } + } else if (type == SSH_KEY_PUBLIC && + key->type == SSH_KEYTYPE_SK_ECDSA) { + /* public key can contain certificate sk information */ + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto out; + } + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + if (type == SSH_KEY_PUBLIC) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + /* public key can contain certificate sk information */ + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = ssh_buffer_add_ssh_string(buffer, key->sk_application); + if (rc < 0) { + goto out; + } + } + } else { + if (key->type == SSH_KEYTYPE_SK_ED25519) { + rc = pki_ed25519_public_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + + rc = pki_buffer_pack_sk_priv_data(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + } else { + rc = pki_ed25519_private_key_to_blob(buffer, key); + if (rc == SSH_ERROR) { + goto out; + } + } + } + break; + default: + goto out; + } +makestring: + str = ssh_string_new(ssh_buffer_get_len(buffer)); + if (str == NULL) { + goto out; + } + + rc = ssh_string_fill(str, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + if (rc < 0) { + ssh_string_burn(str); + SSH_STRING_FREE(str); + } + +out: + SSH_BUFFER_FREE(buffer); + ssh_string_burn(e); + SSH_STRING_FREE(e); + ssh_string_burn(n); + SSH_STRING_FREE(n); + ssh_string_burn(d); + SSH_STRING_FREE(d); + ssh_string_burn(iqmp); + SSH_STRING_FREE(iqmp); + ssh_string_burn(p); + SSH_STRING_FREE(p); + ssh_string_burn(q); + SSH_STRING_FREE(q); +#if MBEDTLS_VERSION_MAJOR > 2 + mbedtls_mpi_free(&N); + mbedtls_mpi_free(&E); + mbedtls_mpi_free(&D); + mbedtls_mpi_free(&IQMP); + mbedtls_mpi_free(&P); + mbedtls_mpi_free(&Q); +#endif + + return str; +} + +ssh_string pki_signature_to_blob(const ssh_signature sig) +{ + ssh_string sig_blob = NULL; + + switch(sig->type) { + case SSH_KEYTYPE_RSA: + sig_blob = ssh_string_copy(sig->rsa_sig); + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: { + ssh_string r = NULL; + ssh_string s = NULL; + ssh_buffer b = NULL; + int rc; + + b = ssh_buffer_new(); + if (b == NULL) { + return NULL; + } + + r = ssh_make_bignum_string(sig->ecdsa_sig.r); + if (r == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, r); + SSH_STRING_FREE(r); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + s = ssh_make_bignum_string(sig->ecdsa_sig.s); + if (s == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_buffer_add_ssh_string(b, s); + SSH_STRING_FREE(s); + if (rc < 0) { + SSH_BUFFER_FREE(b); + return NULL; + } + + sig_blob = ssh_string_new(ssh_buffer_get_len(b)); + if (sig_blob == NULL) { + SSH_BUFFER_FREE(b); + return NULL; + } + + rc = ssh_string_fill(sig_blob, ssh_buffer_get(b), ssh_buffer_get_len(b)); + SSH_BUFFER_FREE(b); + if (rc < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + + break; + } + case SSH_KEYTYPE_ED25519: + sig_blob = pki_ed25519_signature_to_blob(sig); + break; + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ED25519: + /* For SK keys, signature data is already in raw_sig */ + sig_blob = ssh_string_copy(sig->raw_sig); + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature key type: %s", + sig->type_c); + return NULL; + } + + return sig_blob; +} + +static ssh_signature pki_signature_from_rsa_blob(const ssh_key pubkey, const + ssh_string sig_blob, ssh_signature sig) +{ + size_t pad_len = 0; + char *blob_orig = NULL; + char *blob_padded_data = NULL; + ssh_string sig_blob_padded = NULL; + + size_t rsalen = 0; + size_t len = ssh_string_len(sig_blob); + + if (pubkey->pk == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Pubkey RSA field NULL"); + goto errout; + } + + rsalen = mbedtls_pk_get_bitlen(pubkey->pk) / 8; + if (len > rsalen) { + SSH_LOG(SSH_LOG_TRACE, + "Signature is too big: %lu > %lu", + (unsigned long) len, + (unsigned long) rsalen); + goto errout; + } +#ifdef DEBUG_CRYPTO + SSH_LOG(SSH_LOG_TRACE, "RSA signature len: %lu", (unsigned long)len); + ssh_log_hexdump("RSA signature", ssh_string_data(sig_blob), len); +#endif + + if (len == rsalen) { + sig->rsa_sig = ssh_string_copy(sig_blob); + } else { + SSH_LOG(SSH_LOG_DEBUG, "RSA signature len %lu < %lu", + (unsigned long) len, + (unsigned long) rsalen); + pad_len = rsalen - len; + + sig_blob_padded = ssh_string_new(rsalen); + if (sig_blob_padded == NULL) { + goto errout; + } + + blob_padded_data = (char *) ssh_string_data(sig_blob_padded); + blob_orig = (char *) ssh_string_data(sig_blob); + + ssh_burn(blob_padded_data, pad_len); + memcpy(blob_padded_data + pad_len, blob_orig, len); + + sig->rsa_sig = sig_blob_padded; + } + + return sig; + +errout: + ssh_signature_free(sig); + return NULL; +} +ssh_signature pki_signature_from_blob(const ssh_key pubkey, + const ssh_string sig_blob, + enum ssh_keytypes_e type, + enum ssh_digest_e hash_type) +{ + ssh_signature sig = NULL; + int rc; + + if (ssh_key_type_plain(pubkey->type) != type) { + SSH_LOG(SSH_LOG_TRACE, + "Incompatible public key provided (%d) expecting (%d)", + type, + pubkey->type); + return NULL; + } + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = type; + sig->type_c = ssh_key_signature_to_char(type, hash_type); + sig->hash_type = hash_type; + + switch(type) { + case SSH_KEYTYPE_RSA: + sig = pki_signature_from_rsa_blob(pubkey, sig_blob, sig); + if (sig == NULL) { + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_SK_ECDSA: { + ssh_buffer b = NULL; + ssh_string r = NULL; + ssh_string s = NULL; + size_t rlen; + + b = ssh_buffer_new(); + if (b == NULL) { + ssh_signature_free(sig); + return NULL; + } + + rc = ssh_buffer_add_data(b, ssh_string_data(sig_blob), + ssh_string_len(sig_blob)); + + if (rc < 0) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + r = ssh_buffer_get_ssh_string(b); + if (r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("r", ssh_string_data(r), ssh_string_len(r)); +#endif + sig->ecdsa_sig.r = ssh_make_string_bn(r); + ssh_string_burn(r); + SSH_STRING_FREE(r); + if (sig->ecdsa_sig.r == NULL) { + SSH_BUFFER_FREE(b); + ssh_signature_free(sig); + return NULL; + } + + s = ssh_buffer_get_ssh_string(b); + rlen = ssh_buffer_get_len(b); + SSH_BUFFER_FREE(b); + if (s == NULL) { + ssh_signature_free(sig); + return NULL; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("s", ssh_string_data(s), ssh_string_len(s)); +#endif + sig->ecdsa_sig.s = ssh_make_string_bn(s); + ssh_string_burn(s); + SSH_STRING_FREE(s); + if (sig->ecdsa_sig.s == NULL) { + ssh_signature_free(sig); + return NULL; + } + + if (rlen != 0) { + SSH_LOG(SSH_LOG_TRACE, "Signature has remaining bytes in inner " + "sigblob: %lu", + (unsigned long)rlen); + ssh_signature_free(sig); + return NULL; + } + + break; + } + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_SK_ED25519: + rc = pki_signature_from_ed25519_blob(sig, sig_blob); + if (rc == SSH_ERROR) { + ssh_signature_free(sig); + return NULL; + } + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown signature type"); + return NULL; + } + + return sig; +} + +static ssh_string rsa_do_sign_hash(const unsigned char *digest, + int dlen, + mbedtls_pk_context *privkey, + enum ssh_digest_e hash_type) +{ + ssh_string sig_blob = NULL; + mbedtls_md_type_t md = 0; + unsigned char *sig = NULL; + size_t slen; + size_t sig_size; + int ok; + + switch (hash_type) { + case SSH_DIGEST_SHA1: + md = MBEDTLS_MD_SHA1; + break; + case SSH_DIGEST_SHA256: + md = MBEDTLS_MD_SHA256; + break; + case SSH_DIGEST_SHA512: + md = MBEDTLS_MD_SHA512; + break; + case SSH_DIGEST_AUTO: + default: + SSH_LOG(SSH_LOG_TRACE, "Incompatible key algorithm"); + return NULL; + } + + sig_size = mbedtls_pk_get_bitlen(privkey) / 8; + sig = malloc(sig_size); + if (sig == NULL) { + return NULL; + } + + ok = mbedtls_pk_sign(privkey, + md, + digest, + dlen, + sig, +#if MBEDTLS_VERSION_MAJOR > 2 + sig_size, +#endif + &slen, + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + + if (ok != 0) { + SAFE_FREE(sig); + return NULL; + } + + sig_blob = ssh_string_new(slen); + if (sig_blob == NULL) { + SAFE_FREE(sig); + return NULL; + } + + ok = ssh_string_fill(sig_blob, sig, slen); + ssh_burn(sig, slen); + SAFE_FREE(sig); + if (ok < 0) { + SSH_STRING_FREE(sig_blob); + return NULL; + } + + return sig_blob; +} + + +ssh_signature pki_do_sign_hash(const ssh_key privkey, + const unsigned char *hash, + size_t hlen, + enum ssh_digest_e hash_type) +{ + ssh_signature sig = NULL; + int rc; + + sig = ssh_signature_new(); + if (sig == NULL) { + return NULL; + } + + sig->type = privkey->type; + sig->type_c = ssh_key_signature_to_char(privkey->type, hash_type); + sig->hash_type = hash_type; + + switch(privkey->type) { + case SSH_KEYTYPE_RSA: + sig->rsa_sig = rsa_do_sign_hash(hash, hlen, privkey->pk, hash_type); + if (sig->rsa_sig == NULL) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + sig->ecdsa_sig.r = bignum_new(); + if (sig->ecdsa_sig.r == NULL) { + return NULL; + } + + sig->ecdsa_sig.s = bignum_new(); + if (sig->ecdsa_sig.s == NULL) { + bignum_safe_free(sig->ecdsa_sig.r); + return NULL; + } + + rc = mbedtls_ecdsa_sign(&privkey->ecdsa->MBEDTLS_PRIVATE(grp), + sig->ecdsa_sig.r, + sig->ecdsa_sig.s, + &privkey->ecdsa->MBEDTLS_PRIVATE(d), + hash, + hlen, + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + if (rc != 0) { + ssh_signature_free(sig); + return NULL; + } + break; + case SSH_KEYTYPE_ED25519: + rc = pki_ed25519_sign(privkey, sig, hash, hlen); + if (rc != SSH_OK) { + ssh_signature_free(sig); + return NULL; + } + break; + default: + ssh_signature_free(sig); + return NULL; + + } + + return sig; +} + +/** + * @internal + * + * @brief Sign the given input data. The digest of to be signed is calculated + * internally as necessary. + * + * @param[in] privkey The private key to be used for signing. + * @param[in] hash_type The digest algorithm to be used. + * @param[in] input The data to be signed. + * @param[in] input_len The length of the data to be signed. + * + * @return a newly allocated ssh_signature or NULL on error. + */ +ssh_signature pki_sign_data(const ssh_key privkey, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + unsigned char hash[SHA512_DIGEST_LEN] = {0}; + const unsigned char *sign_input = NULL; + uint32_t hlen = 0; + int rc; + + if (privkey == NULL || !ssh_key_is_private(privkey) || input == NULL) { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_sign_data()"); + return NULL; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(privkey, hash_type); + if (rc != SSH_OK) { + return NULL; + } + + switch (hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + sign_input = hash; + break; + case SSH_DIGEST_AUTO: + if (privkey->type == SSH_KEYTYPE_ED25519) { + /* SSH_DIGEST_AUTO should only be used with ed25519 */ + sign_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown hash algorithm for type: %d", + hash_type); + return NULL; + } + + return pki_do_sign_hash(privkey, sign_input, hlen, hash_type); +} + +/** + * @internal + * + * @brief Verify the signature of a given input. The digest of the input is + * calculated internally as necessary. + * + * @param[in] signature The signature to be verified. + * @param[in] pubkey The public key used to verify the signature. + * @param[in] input The signed data. + * @param[in] input_len The length of the signed data. + * + * @return SSH_OK if the signature is valid; SSH_ERROR otherwise. + */ +int pki_verify_data_signature(ssh_signature signature, + const ssh_key pubkey, + const unsigned char *input, + size_t input_len) +{ + + unsigned char hash[SHA512_DIGEST_LEN] = {0}; + const unsigned char *verify_input = NULL; + uint32_t hlen = 0; + + mbedtls_md_type_t md = 0; + + int rc; + + if (pubkey == NULL || ssh_key_is_private(pubkey) || input == NULL || + signature == NULL) + { + SSH_LOG(SSH_LOG_TRACE, "Bad parameter provided to " + "pki_verify_data_signature()"); + return SSH_ERROR; + } + + /* Check if public key and hash type are compatible */ + rc = pki_key_check_hash_compatible(pubkey, signature->hash_type); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + switch (signature->hash_type) { + case SSH_DIGEST_SHA256: + sha256(input, input_len, hash); + hlen = SHA256_DIGEST_LEN; + md = MBEDTLS_MD_SHA256; + verify_input = hash; + break; + case SSH_DIGEST_SHA384: + sha384(input, input_len, hash); + hlen = SHA384_DIGEST_LEN; + md = MBEDTLS_MD_SHA384; + verify_input = hash; + break; + case SSH_DIGEST_SHA512: + sha512(input, input_len, hash); + hlen = SHA512_DIGEST_LEN; + md = MBEDTLS_MD_SHA512; + verify_input = hash; + break; + case SSH_DIGEST_SHA1: + sha1(input, input_len, hash); + hlen = SHA_DIGEST_LEN; + md = MBEDTLS_MD_SHA1; + verify_input = hash; + break; + case SSH_DIGEST_AUTO: + if (pubkey->type == SSH_KEYTYPE_ED25519 || + pubkey->type == SSH_KEYTYPE_ED25519_CERT01 || + pubkey->type == SSH_KEYTYPE_SK_ED25519 || + pubkey->type == SSH_KEYTYPE_ED25519_CERT01) + { + verify_input = input; + hlen = input_len; + break; + } + FALL_THROUGH; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown sig->hash_type: %d", + signature->hash_type); + return SSH_ERROR; + } + + switch (pubkey->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + rc = mbedtls_pk_verify(pubkey->pk, + md, + hash, + hlen, + ssh_string_data(signature->rsa_sig), + ssh_string_len(signature->rsa_sig)); + if (rc != 0) { + char error_buf[100]; + mbedtls_strerror(rc, error_buf, 100); + SSH_LOG(SSH_LOG_TRACE, "RSA error: %s", error_buf); + return SSH_ERROR; + } + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + rc = mbedtls_ecdsa_verify(&pubkey->ecdsa->MBEDTLS_PRIVATE(grp), hash, + hlen, &pubkey->ecdsa->MBEDTLS_PRIVATE(Q), + signature->ecdsa_sig.r, + signature->ecdsa_sig.s); + if (rc != 0) { + char error_buf[100]; + mbedtls_strerror(rc, error_buf, 100); + SSH_LOG(SSH_LOG_TRACE, "ECDSA error: %s", error_buf); + return SSH_ERROR; + + } + break; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + rc = pki_ed25519_verify(pubkey, signature, verify_input, hlen); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_TRACE, "ED25519 error: Signature invalid"); + return SSH_ERROR; + } + break; + default: + SSH_LOG(SSH_LOG_TRACE, "Unknown public key type"); + return SSH_ERROR; + } + + return SSH_OK; +} + +const char *pki_key_ecdsa_nid_to_name(int nid) +{ + switch (nid) { + case NID_mbedtls_nistp256: + return "ecdsa-sha2-nistp256"; + case NID_mbedtls_nistp384: + return "ecdsa-sha2-nistp384"; + case NID_mbedtls_nistp521: + return "ecdsa-sha2-nistp521"; + default: + break; + } + + return "unknown"; +} + +int pki_key_ecdsa_nid_from_name(const char *name) +{ + if (strcmp(name, "nistp256") == 0) { + return NID_mbedtls_nistp256; + } else if (strcmp(name, "nistp384") == 0) { + return NID_mbedtls_nistp384; + } else if (strcmp(name, "nistp521") == 0) { + return NID_mbedtls_nistp521; + } + + return -1; +} + +static mbedtls_ecp_group_id pki_key_ecdsa_nid_to_mbed_gid(int nid) +{ + switch (nid) { + case NID_mbedtls_nistp256: + return MBEDTLS_ECP_DP_SECP256R1; + case NID_mbedtls_nistp384: + return MBEDTLS_ECP_DP_SECP384R1; + case NID_mbedtls_nistp521: + return MBEDTLS_ECP_DP_SECP521R1; + } + + return MBEDTLS_ECP_DP_NONE; +} + +int pki_privkey_build_ecdsa(ssh_key key, int nid, ssh_string e, ssh_string exp) +{ + int rc; + mbedtls_ecp_keypair keypair; + mbedtls_ecp_group group; + mbedtls_ecp_point Q; + + key->ecdsa_nid = nid; + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + mbedtls_ecdsa_init(key->ecdsa); + mbedtls_ecp_keypair_init(&keypair); + mbedtls_ecp_group_init(&group); + mbedtls_ecp_point_init(&Q); + + rc = mbedtls_ecp_group_load(&group, + pki_key_ecdsa_nid_to_mbed_gid(nid)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_point_read_binary(&group, &Q, ssh_string_data(e), + ssh_string_len(e)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_mpi_read_binary(&keypair.MBEDTLS_PRIVATE(d), + ssh_string_data(exp), + ssh_string_len(exp)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair); + if (rc != 0) { + goto fail; + } + + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + return SSH_OK; + +fail: + mbedtls_ecdsa_free(key->ecdsa); + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + SAFE_FREE(key->ecdsa); + return SSH_ERROR; +} + +int pki_pubkey_build_ecdsa(ssh_key key, int nid, ssh_string e) +{ + int rc; + mbedtls_ecp_keypair keypair; + mbedtls_ecp_group group; + mbedtls_ecp_point Q; + + key->ecdsa_nid = nid; + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + mbedtls_ecdsa_init(key->ecdsa); + mbedtls_ecp_keypair_init(&keypair); + mbedtls_ecp_group_init(&group); + mbedtls_ecp_point_init(&Q); + + rc = mbedtls_ecp_group_load(&group, + pki_key_ecdsa_nid_to_mbed_gid(nid)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_point_read_binary(&group, &Q, ssh_string_data(e), + ssh_string_len(e)); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_copy(&keypair.MBEDTLS_PRIVATE(Q), &Q); + if (rc != 0) { + goto fail; + } + + rc = mbedtls_ecp_group_copy(&keypair.MBEDTLS_PRIVATE(grp), &group); + if (rc != 0) { + goto fail; + } + + mbedtls_mpi_init(&keypair.MBEDTLS_PRIVATE(d)); + + rc = mbedtls_ecdsa_from_keypair(key->ecdsa, &keypair); + if (rc != 0) { + goto fail; + } + + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + return SSH_OK; +fail: + mbedtls_ecdsa_free(key->ecdsa); + mbedtls_ecp_point_free(&Q); + mbedtls_ecp_group_free(&group); + mbedtls_ecp_keypair_free(&keypair); + SAFE_FREE(key->ecdsa); + return SSH_ERROR; +} + +int pki_key_generate_ecdsa(ssh_key key, int parameter) +{ + int ok; + + switch (parameter) { + case 384: + key->ecdsa_nid = NID_mbedtls_nistp384; + key->type = SSH_KEYTYPE_ECDSA_P384; + break; + case 521: + key->ecdsa_nid = NID_mbedtls_nistp521; + key->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 256: + default: + key->ecdsa_nid = NID_mbedtls_nistp256; + key->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + key->ecdsa = malloc(sizeof(mbedtls_ecdsa_context)); + if (key->ecdsa == NULL) { + return SSH_ERROR; + } + + mbedtls_ecdsa_init(key->ecdsa); + + ok = mbedtls_ecdsa_genkey(key->ecdsa, + pki_key_ecdsa_nid_to_mbed_gid(key->ecdsa_nid), + mbedtls_ctr_drbg_random, + ssh_get_mbedtls_ctr_drbg_context()); + + if (ok != 0) { + mbedtls_ecdsa_free(key->ecdsa); + SAFE_FREE(key->ecdsa); + } + + return SSH_OK; +} + +int ssh_key_size(ssh_key key) +{ + switch (key->type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_RSA1: + return mbedtls_pk_get_bitlen(key->pk); + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_SK_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + return 256; + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return 384; + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return 521; + case SSH_KEYTYPE_ED25519: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_SK_ED25519: + case SSH_KEYTYPE_SK_ED25519_CERT01: + /* ed25519 keys have fixed size */ + return 255; + case SSH_KEYTYPE_DSS: /* deprecated */ + case SSH_KEYTYPE_DSS_CERT01: /* deprecated */ + case SSH_KEYTYPE_UNKNOWN: + default: + return SSH_ERROR; + } +} + +#ifdef WITH_PKCS11_URI +int pki_uri_import(const char *uri_name, ssh_key *key, enum ssh_key_e key_type) +{ + (void) uri_name; + (void) key; + (void) key_type; + SSH_LOG(SSH_LOG_WARN, + "mbedcrypto does not support PKCS #11"); + return SSH_ERROR; +} +#endif /* WITH_PKCS11_URI */ +#endif /* HAVE_LIBMBEDCRYPTO */ diff --git a/src/libs/libssh-0.12.2/src/pki_sk.c b/src/libs/libssh-0.12.2/src/pki_sk.c new file mode 100644 index 000000000000..5a25251c2795 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/pki_sk.c @@ -0,0 +1,971 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/buffer.h" +#include "libssh/pki_context.h" +#include "libssh/pki_priv.h" +#include "libssh/pki_sk.h" +#include "libssh/sk_common.h" + +#include +#include + +#define DEFAULT_PIN_PROMPT "Enter SK PIN: " +#define PIN_BUF_SIZE 64 + +/** + * @addtogroup libssh_pki + * @{ + */ + +/** + * @brief Serialize FIDO2 attestation data into an SSH buffer + * + * Serializes the attestation certificate, signature, and authenticator data + * from a FIDO2 enrollment response into an SSH buffer in the + * "ssh-sk-attest-v01" format. + * + * @param[in] enroll_response The sk_enroll_response struct containing + * attestation data from FIDO2 enrollment + * @param[in,out] attestation_buffer SSH buffer to store the serialized + * attestation data + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int pki_sk_serialise_attestation_cert( + const struct sk_enroll_response *enroll_response, + ssh_buffer attestation_buffer) +{ + int rc; + + if (attestation_buffer == NULL || enroll_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Parameters cannot be NULL"); + return SSH_ERROR; + } + + /* Check if attestation data is available */ + if (enroll_response->attestation_cert == NULL || + enroll_response->attestation_cert_len == 0) { + SSH_LOG(SSH_LOG_INFO, "No attestation certificate available"); + return SSH_ERROR; + } + + if (enroll_response->signature == NULL || + enroll_response->signature_len == 0) { + SSH_LOG(SSH_LOG_INFO, "No attestation signature available"); + return SSH_ERROR; + } + + if (enroll_response->authdata == NULL || + enroll_response->authdata_len == 0) { + SSH_LOG(SSH_LOG_INFO, "No authenticator data available"); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(attestation_buffer, + "sdPdPdPds", + "ssh-sk-attest-v01", + (uint32_t)enroll_response->attestation_cert_len, + enroll_response->attestation_cert_len, + enroll_response->attestation_cert, + (uint32_t)enroll_response->signature_len, + enroll_response->signature_len, + enroll_response->signature, + (uint32_t)enroll_response->authdata_len, + enroll_response->authdata_len, + enroll_response->authdata, + (uint32_t)0, /* reserved flags */ + ""); /* reserved */ + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack attestation data into buffer"); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Create an ssh_key from an sk_enroll_response struct + * + * Constructs an ssh_key structure from an sk_enroll_response + * struct for both ECDSA and Ed25519 algorithms. + * + * @param[in] algorithm The algorithm type (SSH_SK_ECDSA or + * SSH_SK_ED25519) + * @param[in] application The application string (relying party ID) + * @param[in] enroll_response The sk_enroll_response struct containing key data + * @param[out] ssh_key_result Pointer to store the newly created ssh_key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int pki_sk_enroll_response_to_ssh_key( + int algorithm, + const char *application, + const struct sk_enroll_response *enroll_response, + ssh_key *ssh_key_result) +{ + ssh_key key_to_build = NULL; + ssh_string public_key_string = NULL; + int rc, ret = SSH_ERROR; + + /* Validate input parameters */ + if (ssh_key_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "ssh_key pointer cannot be NULL"); + return SSH_ERROR; + } + + *ssh_key_result = NULL; + + if (enroll_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Enrollment response cannot be NULL"); + return SSH_ERROR; + } + + /* Validate response data */ + if (enroll_response->public_key == NULL || + enroll_response->key_handle == NULL) { + SSH_LOG( + SSH_LOG_WARN, + "Invalid enrollment response: missing public key or key handle"); + return SSH_ERROR; + } + + key_to_build = ssh_key_new(); + if (key_to_build == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate new ssh_key"); + return SSH_ERROR; + } + + /* Set key type based on algorithm */ + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + key_to_build->type = SSH_KEYTYPE_SK_ECDSA; + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + key_to_build->type = SSH_KEYTYPE_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + goto out; + } + key_to_build->type_c = ssh_key_type_to_char(key_to_build->type); + + public_key_string = ssh_string_from_data(enroll_response->public_key, + enroll_response->public_key_len); + if (public_key_string == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create public key string"); + goto out; + } + + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + rc = pki_pubkey_build_ecdsa(key_to_build, + pki_key_ecdsa_nid_from_name("nistp256"), + public_key_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to build ECDSA public key"); + goto out; + } + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + rc = pki_pubkey_build_ed25519(key_to_build, public_key_string); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to build ED25519 public key"); + goto out; + } + break; + } + + /* Set security key specific fields */ + key_to_build->sk_application = ssh_string_from_char(application); + if (key_to_build->sk_application == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create sk_application string"); + goto out; + } + + /* Set key handle */ + key_to_build->sk_key_handle = + ssh_string_from_data(enroll_response->key_handle, + enroll_response->key_handle_len); + if (key_to_build->sk_key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create sk_key_handle string"); + goto out; + } + + key_to_build->sk_reserved = ssh_string_from_data(NULL, 0); + if (key_to_build->sk_reserved == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create sk_reserved string"); + goto out; + } + + key_to_build->sk_flags = enroll_response->flags; + key_to_build->flags = SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC; + + *ssh_key_result = key_to_build; + key_to_build = NULL; + ret = SSH_OK; + +out: + ssh_string_burn(public_key_string); + SSH_STRING_FREE(public_key_string); + SSH_KEY_FREE(key_to_build); + + return ret; +} + +int pki_sk_enroll_key(ssh_pki_ctx context, + enum ssh_keytypes_e key_type, + ssh_key *enrolled_key_result) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + + struct sk_enroll_response *enroll_response = NULL; + ssh_key enrolled_key = NULL; + + char pin_buf[PIN_BUF_SIZE] = {0}; + const char *pin_to_use = NULL; + + unsigned char random_challenge[32]; + const unsigned char *challenge = NULL; + size_t challenge_length = 0; + + ssh_buffer challenge_buffer = NULL; + ssh_buffer attestation = NULL; + + int rc, ret = SSH_ERROR; + int algorithm; + + /* Validate input parameters */ + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "SK context cannot be NULL"); + return SSH_ERROR; + } + + if (enrolled_key_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "Enrolled key result pointer cannot be NULL"); + return SSH_ERROR; + } + + /* Initialize output parameter */ + *enrolled_key_result = NULL; + + /* Clear any existing attestation data */ + SSH_BUFFER_FREE(context->sk_attestation_buffer); + + /* Get security key callbacks from context */ + sk_callbacks = context->sk_callbacks; + if (sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL"); + return SSH_ERROR; + } + + if (!ssh_callbacks_exists(sk_callbacks, enroll)) { + SSH_LOG(SSH_LOG_WARN, + "Security key enroll callback is not implemented"); + return SSH_ERROR; + } + + /* Validate required fields */ + if (context->sk_application == NULL || *context->sk_application == '\0') { + SSH_LOG(SSH_LOG_WARN, "Application identifier cannot be NULL or empty"); + return SSH_ERROR; + } + + /* Extract parameters from context */ + challenge_buffer = context->sk_challenge_buffer; + + /* Determine algorithm based on key type */ + switch (key_type) { +#ifdef HAVE_ECC + case SSH_KEYTYPE_SK_ECDSA: + algorithm = SSH_SK_ECDSA; + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_SK_ED25519: + algorithm = SSH_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, + "Unsupported key type for security key enrollment"); + goto out; + } + + /* Determine challenge to use */ + if (challenge_buffer == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Using randomly generated challenge"); + + rc = ssh_get_random(random_challenge, sizeof(random_challenge), 0); + if (rc != 1) { + SSH_LOG(SSH_LOG_WARN, "Failed to generate random challenge"); + goto out; + } + + challenge = random_challenge; + challenge_length = sizeof(random_challenge); + + } else { + challenge_length = ssh_buffer_get_len(challenge_buffer); + if (challenge_length == 0) { + SSH_LOG(SSH_LOG_WARN, "Challenge buffer cannot be empty"); + goto out; + } + + challenge = ssh_buffer_get(challenge_buffer); + SSH_LOG(SSH_LOG_DEBUG, + "Using provided challenge of length %zu", + challenge_length); + } + + if (context->sk_pin_callback != NULL) { + rc = context->sk_pin_callback(DEFAULT_PIN_PROMPT, + pin_buf, + sizeof(pin_buf), + 0, + 0, + context->sk_userdata); + if (rc == SSH_OK) { + pin_to_use = pin_buf; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback"); + ssh_burn(pin_buf, sizeof(pin_buf)); + goto out; + } + } else { + SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN"); + } + + rc = sk_callbacks->enroll(algorithm, + challenge, + challenge_length, + context->sk_application, + context->sk_flags, + pin_to_use, + context->sk_callbacks_options, + &enroll_response); + ssh_burn(pin_buf, sizeof(pin_buf)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Security key enroll callback failed: %s (%d)", + ssh_sk_err_to_string(rc), + rc); + goto out; + } + + /* Convert SK enroll response to ssh_key */ + rc = pki_sk_enroll_response_to_ssh_key(algorithm, + context->sk_application, + enroll_response, + &enrolled_key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert enroll response to ssh_key"); + goto out; + } + + /* Try to serialize attestation data and store in context */ + attestation = ssh_buffer_new(); + if (attestation == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate attestation buffer"); + goto out; + } else { + rc = pki_sk_serialise_attestation_cert(enroll_response, attestation); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to serialize attestation data, continuing without " + "attestation"); + } else { + context->sk_attestation_buffer = attestation; + attestation = NULL; + } + } + + *enrolled_key_result = enrolled_key; + enrolled_key = NULL; + ret = SSH_OK; + +out: + if (challenge == random_challenge) { + ssh_burn(random_challenge, sizeof(random_challenge)); + } + + SK_ENROLL_RESPONSE_FREE(enroll_response); + SSH_KEY_FREE(enrolled_key); + SSH_BUFFER_FREE(attestation); + + return ret; +} + +static int +pki_sk_pack_ecdsa_signature(const struct sk_sign_response *sign_response, + ssh_buffer sig_buffer) +{ + + bignum r_bn = NULL, s_bn = NULL; + ssh_buffer inner_buffer = NULL; + int rc = SSH_ERROR; + + /* Convert raw r and s bytes to bignums */ + bignum_bin2bn(sign_response->sig_r, (int)sign_response->sig_r_len, &r_bn); + if (r_bn == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert sig_r to bignum"); + goto out; + } + + bignum_bin2bn(sign_response->sig_s, (int)sign_response->sig_s_len, &s_bn); + if (s_bn == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert sig_s to bignum"); + goto out; + } + + /* Create inner buffer with r and s as SSH strings */ + inner_buffer = ssh_buffer_new(); + if (inner_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create inner buffer"); + goto out; + } + ssh_buffer_set_secure(inner_buffer); + + rc = ssh_buffer_pack(inner_buffer, "BB", r_bn, s_bn); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack r and s into inner buffer"); + goto out; + } + + rc = ssh_buffer_pack(sig_buffer, + "P", + (size_t)ssh_buffer_get_len(inner_buffer), + ssh_buffer_get(inner_buffer)); + if (rc != SSH_OK) { + goto out; + } + + rc = SSH_OK; + +out: + SSH_BUFFER_FREE(inner_buffer); + bignum_safe_free(s_bn); + bignum_safe_free(r_bn); + + return rc; +} + +static int +pki_sk_pack_ed25519_signature(const struct sk_sign_response *sign_response, + ssh_buffer sig_buffer) +{ + int rc = SSH_ERROR; + + rc = ssh_buffer_pack(sig_buffer, + "P", + sign_response->sig_r_len, + sign_response->sig_r); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @brief Create an ssh_signature from a sk_sign_response structure + * + * Serializes a security key sign response into an ssh_signature structure + * for both ECDSA and Ed25519 algorithms. + * + * @param[in] algorithm The algorithm used (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param[in] key_type The SSH key type for setting signature type + * @param[in] sign_response The sk_sign_response containing signature data + * @param[out] ssh_signature_result Pointer to store the created ssh_signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int pki_sk_sign_response_to_ssh_signature( + int algorithm, + enum ssh_keytypes_e key_type, + const struct sk_sign_response *sign_response, + ssh_signature *ssh_signature_result) +{ + ssh_signature signature_to_build = NULL; + ssh_buffer sig_buffer = NULL; + int rc; + + /* Validate input parameters */ + if (ssh_signature_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "ssh_signature pointer cannot be NULL"); + return SSH_ERROR; + } + + *ssh_signature_result = NULL; + + if (sign_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Sign response cannot be NULL"); + return SSH_ERROR; + } + + /* Validate response data based on algorithm */ + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + if (sign_response->sig_r == NULL || sign_response->sig_s == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Invalid ECDSA sign response: missing sig_r or sig_s"); + return SSH_ERROR; + } + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + if (sign_response->sig_r == NULL || + sign_response->sig_r_len != ED25519_SIG_LEN) { + SSH_LOG(SSH_LOG_WARN, "Invalid sig_r in Ed25519 sign response"); + return SSH_ERROR; + } + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + return SSH_ERROR; + } + + /* Create new ssh_signature */ + signature_to_build = ssh_signature_new(); + if (signature_to_build == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate new ssh_signature"); + return SSH_ERROR; + } + + /* Set signature type and metadata */ + signature_to_build->type = key_type; + signature_to_build->type_c = ssh_key_type_to_char(key_type); + + /* Set security key specific fields */ + signature_to_build->sk_flags = sign_response->flags; + signature_to_build->sk_counter = sign_response->counter; + + /* Create a buffer to hold the signature data */ + sig_buffer = ssh_buffer_new(); + if (sig_buffer == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create signature buffer"); + goto error; + } + ssh_buffer_set_secure(sig_buffer); + + /* Build the signature based on algorithm */ + switch (algorithm) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + signature_to_build->hash_type = SSH_DIGEST_SHA256; + + rc = pki_sk_pack_ecdsa_signature(sign_response, sig_buffer); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack ECDSA signature"); + goto error; + } + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + signature_to_build->hash_type = SSH_DIGEST_AUTO; + + rc = pki_sk_pack_ed25519_signature(sign_response, sig_buffer); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to pack Ed25519 signature"); + goto error; + } + break; + } + + /* Set the signature data */ + signature_to_build->raw_sig = + ssh_string_from_data(ssh_buffer_get(sig_buffer), + ssh_buffer_get_len(sig_buffer)); + if (signature_to_build->raw_sig == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create raw signature string"); + goto error; + } + + *ssh_signature_result = signature_to_build; + SSH_BUFFER_FREE(sig_buffer); + + return SSH_OK; + +error: + SSH_SIGNATURE_FREE(signature_to_build); + SSH_BUFFER_FREE(sig_buffer); + + return SSH_ERROR; +} + +ssh_signature pki_sk_do_sign(ssh_pki_ctx context, + const ssh_key key, + const unsigned char *data, + size_t data_len) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + struct sk_sign_response *sign_response = NULL; + ssh_signature signature = NULL; + + char pin_buf[PIN_BUF_SIZE] = {0}; + const char *pin_to_use = NULL; + + int algorithm; + int rc = SSH_ERROR; + + /* Validate input parameters */ + if (context == NULL) { + SSH_LOG(SSH_LOG_WARN, "Context cannot be NULL"); + return NULL; + } + + /* Get security key callbacks from context */ + sk_callbacks = context->sk_callbacks; + if (sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL"); + return NULL; + } + + if (!ssh_callbacks_exists(sk_callbacks, sign)) { + SSH_LOG(SSH_LOG_WARN, "Security key sign callback is not implemented"); + return NULL; + } + + if (key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Key cannot be NULL"); + return NULL; + } + + if (data == NULL || data_len == 0) { + SSH_LOG(SSH_LOG_WARN, "Data cannot be NULL or empty"); + return NULL; + } + + /* Validate key type and determine algorithm */ + switch (key->type) { +#ifdef HAVE_ECC + case SSH_KEYTYPE_SK_ECDSA: + algorithm = SSH_SK_ECDSA; + break; +#endif /* HAVE_ECC */ + case SSH_KEYTYPE_SK_ED25519: + algorithm = SSH_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported key type for security key signing"); + return NULL; + } + + /* Validate security key specific fields */ + if (key->sk_key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key handle cannot be NULL"); + return NULL; + } + + if (key->sk_application == NULL || + ssh_string_len(key->sk_application) == 0) { + SSH_LOG(SSH_LOG_WARN, + "Security key application cannot be NULL or empty"); + return NULL; + } + + if (context->sk_pin_callback != NULL) { + rc = context->sk_pin_callback(DEFAULT_PIN_PROMPT, + pin_buf, + sizeof(pin_buf), + 0, + 0, + context->sk_userdata); + if (rc == SSH_OK) { + pin_to_use = pin_buf; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback"); + ssh_burn(pin_buf, sizeof(pin_buf)); + goto error; + } + } else { + SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN"); + } + + rc = sk_callbacks->sign(algorithm, + data, + data_len, + ssh_string_get_char(key->sk_application), + ssh_string_data(key->sk_key_handle), + ssh_string_len(key->sk_key_handle), + key->sk_flags, + pin_to_use, + context->sk_callbacks_options, + &sign_response); + ssh_burn(pin_buf, sizeof(pin_buf)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Security key sign callback failed: %s (%d)", + ssh_sk_err_to_string(rc), + rc); + goto error; + } + + /* Convert SK sign response to ssh_signature */ + rc = pki_sk_sign_response_to_ssh_signature(algorithm, + key->type, + sign_response, + &signature); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to convert sign response to signature"); + goto error; + } + + SK_SIGN_RESPONSE_FREE(sign_response); + return signature; + +error: + SK_SIGN_RESPONSE_FREE(sign_response); + SSH_SIGNATURE_FREE(signature); + return NULL; +} + +/** + * @brief Load resident keys from FIDO2 security keys + * + * This function loads all resident keys (discoverable credentials) stored + * on FIDO2 security keys using the context's security key callbacks. + * Resident keys are credentials stored directly on the security key device + * and can be discovered without prior knowledge of key handles. + * + * Only resident keys with SSH application identifiers (starting with + * "ssh:") are returned. + * + * @param[in] pki_context The PKI context containing security key callbacks. + * Can be NULL, in which case a default context with + * default callbacks will be used. If provided, the context + * must have valid sk_callbacks configured. + * @param[out] resident_keys_result Array of ssh_key structs representing the + * resident keys found and loaded + * @param[out] num_keys_found_result Number of resident keys found and loaded + * + * @return SSH_OK on success, SSH_ERROR on error + * + * @note The resident_keys_result array and its contents must be freed by + * the caller using ssh_sk_resident_key_free() for each key and then + * freeing the array itself when no longer needed. + */ +int ssh_sk_resident_keys_load(const struct ssh_pki_ctx_struct *pki_context, + ssh_key **resident_keys_result, + size_t *num_keys_found_result) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + struct sk_resident_key **raw_resident_keys = NULL; + + ssh_key cur_resident_key = NULL, *result_keys = NULL, *temp_keys = NULL; + ssh_pki_ctx temp_ctx = NULL; + const struct ssh_pki_ctx_struct *ctx_to_use = NULL; + + size_t raw_keys_count = 0, result_keys_count = 0, i; + uint8_t sk_flags; + + char pin_buf[PIN_BUF_SIZE] = {0}; + const char *pin_to_use = NULL; + + int rc = SSH_ERROR; + + /* If no context provided, create a temporary default one */ + if (pki_context == NULL) { + SSH_LOG(SSH_LOG_INFO, "No PKI context provided, using the default one"); + + temp_ctx = ssh_pki_ctx_new(); + if (temp_ctx == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create temporary PKI context"); + return SSH_ERROR; + } + ctx_to_use = temp_ctx; + } else { + ctx_to_use = pki_context; + } + + /* Get security key callbacks from context */ + sk_callbacks = ctx_to_use->sk_callbacks; + if (sk_callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "Security key callbacks cannot be NULL"); + goto out; + } + + if (!ssh_callbacks_exists(sk_callbacks, load_resident_keys)) { + SSH_LOG(SSH_LOG_WARN, + "Security key load resident keys callback is not implemented"); + goto out; + } + + if (resident_keys_result == NULL || num_keys_found_result == NULL) { + SSH_LOG(SSH_LOG_WARN, "Result pointers cannot be NULL"); + goto out; + } + + /* Initialize output parameters */ + *resident_keys_result = NULL; + *num_keys_found_result = 0; + + if (ctx_to_use->sk_pin_callback != NULL) { + rc = ctx_to_use->sk_pin_callback(DEFAULT_PIN_PROMPT, + pin_buf, + sizeof(pin_buf), + 0, + 0, + ctx_to_use->sk_userdata); + if (rc == SSH_OK) { + pin_to_use = pin_buf; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to fetch PIN from callback"); + ssh_burn(pin_buf, sizeof(pin_buf)); + goto out; + } + } else { + SSH_LOG(SSH_LOG_INFO, "Trying operation without PIN"); + } + + rc = sk_callbacks->load_resident_keys(pin_to_use, + ctx_to_use->sk_callbacks_options, + &raw_resident_keys, + &raw_keys_count); + ssh_burn(pin_buf, sizeof(pin_buf)); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Security key load_resident_keys callback failed: %s (%d)", + ssh_sk_err_to_string(rc), + rc); + goto out; + } + + /* Process each raw resident key */ + for (i = 0; i < raw_keys_count; i++) { + SSH_LOG( + SSH_LOG_DEBUG, + "Processing resident key %zu: alg %d, app \"%s\", user_id_len %zu", + i, + raw_resident_keys[i]->alg, + raw_resident_keys[i]->application, + raw_resident_keys[i]->user_id_len); + + /* Filter out non-SSH applications */ + if (strncmp(raw_resident_keys[i]->application, "ssh:", 4) != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Skipping non-SSH application: %s", + raw_resident_keys[i]->application); + continue; + } + + /* Check supported algorithms */ + switch (raw_resident_keys[i]->alg) { +#ifdef HAVE_ECC + case SSH_SK_ECDSA: + break; +#endif /* HAVE_ECC */ + case SSH_SK_ED25519: + break; + default: + SSH_LOG(SSH_LOG_WARN, + "Unsupported algorithm %d, skipping", + raw_resident_keys[i]->alg); + continue; + } + + /* Set up security key flags */ + sk_flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY; + if (raw_resident_keys[i]->flags & SSH_SK_USER_VERIFICATION_REQD) { + sk_flags |= SSH_SK_USER_VERIFICATION_REQD; + } + + /* Convert raw resident key to libssh key structure */ + rc = + pki_sk_enroll_response_to_ssh_key(raw_resident_keys[i]->alg, + raw_resident_keys[i]->application, + &raw_resident_keys[i]->key, + &cur_resident_key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to convert resident key %zu to ssh_key", + i); + continue; + } + + /* Set the security key flags on the converted key */ + cur_resident_key->sk_flags = sk_flags; + + /* Copy user ID if present */ + if (raw_resident_keys[i]->user_id != NULL && + raw_resident_keys[i]->user_id_len > 0) { + + cur_resident_key->sk_user_id = + ssh_string_from_data(raw_resident_keys[i]->user_id, + raw_resident_keys[i]->user_id_len); + if (cur_resident_key->sk_user_id == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate user_id string for key %zu", + i); + goto out; + } + } + + /* Grow the result array */ + temp_keys = + realloc(result_keys, sizeof(ssh_key) * (result_keys_count + 1)); + if (temp_keys == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to reallocate result keys array"); + goto out; + } + + /* Add the current resident key to the result array */ + result_keys = temp_keys; + result_keys[result_keys_count] = cur_resident_key; + result_keys_count++; + cur_resident_key = NULL; + } + + /* Set output parameters */ + *resident_keys_result = result_keys; + *num_keys_found_result = result_keys_count; + result_keys = NULL; + result_keys_count = 0; + rc = SSH_OK; + +out: + + if (raw_resident_keys != NULL) { + for (i = 0; i < raw_keys_count; i++) { + SK_RESIDENT_KEY_FREE(raw_resident_keys[i]); + } + SAFE_FREE(raw_resident_keys); + } + + SSH_KEY_FREE(cur_resident_key); + for (i = 0; i < result_keys_count; i++) { + SSH_KEY_FREE(result_keys[i]); + } + SAFE_FREE(result_keys); + + /* Clean up temporary context if we created one */ + if (temp_ctx != NULL) { + SSH_PKI_CTX_FREE(temp_ctx); + } + + return rc; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/poll.c b/src/libs/libssh-0.12.2/src/poll.c new file mode 100644 index 000000000000..23d18256cf24 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/poll.c @@ -0,0 +1,1216 @@ +/* + * poll.c - poll wrapper + * + * This file is part of the SSH Library + * + * Copyright (c) 2009-2013 by Andreas Schneider + * Copyright (c) 2003-2013 by Aris Adamantiadis + * Copyright (c) 2009 Aleksandar Kanchev + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/poll.h" +#include "libssh/socket.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#ifdef WITH_SERVER +#include "libssh/server.h" +#endif + + +#ifndef SSH_POLL_CTX_CHUNK +#define SSH_POLL_CTX_CHUNK 5 +#endif + +/** + * @defgroup libssh_poll The SSH poll functions + * @ingroup libssh + * + * Add a generic way to handle sockets asynchronously. + * + * It's based on poll objects, each of which store a socket, its events and a + * callback, which gets called whenever an event is set. The poll objects are + * attached to a poll context, which should be allocated on a per thread basis. + * + * Polling the poll context will poll all the attached poll objects and call + * their callbacks (handlers) if any of the socket events are set. This should + * be done within the main loop of an application. + * + * @{ + */ + +struct ssh_poll_handle_struct { + ssh_poll_ctx ctx; + ssh_session session; + union { + socket_t fd; + size_t idx; + } x; + short events; + uint32_t lock_cnt; + ssh_poll_callback cb; + void *cb_data; +}; + +struct ssh_poll_ctx_struct { + ssh_poll_handle *pollptrs; + ssh_pollfd_t *pollfds; + size_t polls_allocated; + size_t polls_used; + size_t chunk_size; +}; + +#ifdef HAVE_POLL +#include + +void ssh_poll_init(void) +{ + return; +} + +void ssh_poll_cleanup(void) +{ + return; +} + +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ + return poll((struct pollfd *)fds, nfds, timeout); +} + +#else /* HAVE_POLL */ + +typedef int (*poll_fn)(ssh_pollfd_t *, nfds_t, int); +static poll_fn ssh_poll_emu; + +#include +#include + +#ifdef _WIN32 +#ifndef STRICT +#define STRICT +#endif /* STRICT */ + +#include +#include +#include +#else /* _WIN32 */ +#include +#include + +# ifdef HAVE_SYS_TIME_H +# include +# endif + +#endif /* _WIN32 */ + +#ifdef HAVE_UNISTD_H +#include +#endif + +static bool bsd_socket_not_connected(int sock_err) +{ + switch (sock_err) { +#ifdef _WIN32 + case WSAENOTCONN: +#else + case ENOTCONN: +#endif + return true; + default: + return false; + } + + return false; +} + +static bool bsd_socket_reset(int sock_err) +{ + switch (sock_err) { +#ifdef _WIN32 + case WSAECONNABORTED: + case WSAECONNRESET: + case WSAENETRESET: + case WSAESHUTDOWN: + case WSAECONNREFUSED: + case WSAETIMEDOUT: +#else + case ECONNABORTED: + case ECONNRESET: + case ENETRESET: + case ESHUTDOWN: +#endif + return true; + default: + return false; + } + + return false; +} + +static short bsd_socket_compute_revents(int fd, short events) +{ + int save_errno = errno; + int sock_errno = errno; + char data[64] = {0}; + short revents = 0; + int flags = MSG_PEEK; + int ret; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + /* support for POLLHUP */ +#ifdef _WIN32 + WSASetLastError(0); +#endif + + ret = recv(fd, data, 64, flags); + + errno = save_errno; + +#ifdef _WIN32 + sock_errno = WSAGetLastError(); + WSASetLastError(0); +#endif + + if (ret > 0 || bsd_socket_not_connected(sock_errno)) { + revents = (POLLIN | POLLRDNORM) & events; + } else if (ret == 0 || bsd_socket_reset(sock_errno)) { + errno = sock_errno; + revents = POLLHUP; + } else { + revents = POLLERR; + } + + return revents; +} + +/* + * This is a poll(2)-emulation using select for systems not providing a native + * poll implementation. + * + * Keep in mind that select is terribly inefficient. The interface is simply not + * meant to be used with maximum descriptor value greater than, say, 32 or so. + * With a value as high as 1024 on Linux you'll pay dearly in every single call. + * poll() will be orders of magnitude faster. + */ +static int bsd_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ + fd_set readfds, writefds, exceptfds; + struct timeval tv, *ptv = NULL; + socket_t max_fd; + int rc; + nfds_t i; + + if (fds == NULL) { + errno = EFAULT; + return -1; + } + + ZERO_STRUCT(readfds); + FD_ZERO(&readfds); + ZERO_STRUCT(writefds); + FD_ZERO(&writefds); + ZERO_STRUCT(exceptfds); + FD_ZERO(&exceptfds); + + /* compute fd_sets and find largest descriptor */ + for (rc = -1, max_fd = 0, i = 0; i < nfds; i++) { + if (fds[i].fd == SSH_INVALID_SOCKET) { + continue; + } +#ifndef _WIN32 + if (fds[i].fd >= FD_SETSIZE) { + rc = -1; + break; + } +#endif + + // we use the readfds to get POLLHUP and POLLERR, which are provided + // even when not requested + FD_SET(fds[i].fd, &readfds); + + if (fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND)) { + FD_SET(fds[i].fd, &writefds); + } + if (fds[i].events & (POLLPRI | POLLRDBAND)) { + FD_SET(fds[i].fd, &exceptfds); + } + + if (fds[i].fd > max_fd) { + max_fd = fds[i].fd; + rc = 0; + } + } + + if (max_fd == SSH_INVALID_SOCKET || rc == -1) { + errno = EINVAL; + return -1; + } + + if (timeout < 0) { + ptv = NULL; + } else { + ptv = &tv; + if (timeout == 0) { + tv.tv_sec = 0; + tv.tv_usec = 0; + } else { + tv.tv_sec = timeout / 1000; + tv.tv_usec = (timeout % 1000) * 1000; + } + } + + rc = select(max_fd + 1, &readfds, &writefds, &exceptfds, ptv); + if (rc < 0) { + return -1; + } + /* A timeout occurred */ + if (rc == 0) { + return 0; + } + + for (rc = 0, i = 0; i < nfds; i++) { + if (fds[i].fd >= 0) { + fds[i].revents = 0; + + if (FD_ISSET(fds[i].fd, &readfds)) { + fds[i].revents = + bsd_socket_compute_revents(fds[i].fd, fds[i].events); + } + if (FD_ISSET(fds[i].fd, &writefds)) { + fds[i].revents |= + fds[i].events & (POLLOUT | POLLWRNORM | POLLWRBAND); + } + + if (FD_ISSET(fds[i].fd, &exceptfds)) { + fds[i].revents |= fds[i].events & (POLLPRI | POLLRDBAND); + } + + if (fds[i].revents != 0) { + rc++; + } + } else { + fds[i].revents = POLLNVAL; + } + } + + return rc; +} + +void ssh_poll_init(void) +{ + ssh_poll_emu = bsd_poll; +} + +void ssh_poll_cleanup(void) +{ + ssh_poll_emu = bsd_poll; +} + +int ssh_poll(ssh_pollfd_t *fds, nfds_t nfds, int timeout) +{ + return (ssh_poll_emu)(fds, nfds, timeout); +} + +#endif /* HAVE_POLL */ + +/** + * @brief Allocate a new poll object, which could be used within a poll + * context. + * + * @param[in] fd Socket that will be polled. + * @param[in] events Poll events that will be monitored for the socket. + * i.e. POLLIN, POLLPRI, POLLOUT + * @param[in] cb Function to be called if any of the events are set. + * The prototype of cb is: + * int (*ssh_poll_callback)(ssh_poll_handle p, + * socket_t fd, + * int revents, + * void *userdata); + * @param[in] userdata Userdata to be passed to the callback function. + * NULL if not needed. + * + * @return A new poll object, NULL on error + */ + +ssh_poll_handle +ssh_poll_new(socket_t fd, short events, ssh_poll_callback cb, void *userdata) +{ + ssh_poll_handle p = NULL; + + p = malloc(sizeof(struct ssh_poll_handle_struct)); + if (p == NULL) { + return NULL; + } + ZERO_STRUCTP(p); + + p->x.fd = fd; + p->events = events; + p->cb = cb; + p->cb_data = userdata; + + return p; +} + +/** + * @brief Free a poll object. + * + * @param p Pointer to an already allocated poll object. + */ + +void ssh_poll_free(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + ssh_poll_ctx_remove(p->ctx, p); + p->ctx = NULL; + } + SAFE_FREE(p); +} + +/** + * @brief Get the poll context of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Poll context or NULL if the poll object isn't attached. + */ +ssh_poll_ctx ssh_poll_get_ctx(ssh_poll_handle p) +{ + return p->ctx; +} + +/** + * @brief Get the events of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Poll events. + */ +short ssh_poll_get_events(ssh_poll_handle p) +{ + return p->events; +} + +/** + * @brief Set the events of a poll object. The events will also be propagated + * to an associated poll context unless the fd is locked. In that case, + * only the POLLOUT can be set. + * + * @param p Pointer to an already allocated poll object. + * @param events Poll events. + */ +void ssh_poll_set_events(ssh_poll_handle p, short events) +{ + p->events = events; + if (p->ctx != NULL) { + if (!ssh_poll_is_locked(p)) { + p->ctx->pollfds[p->x.idx].events = events; + } else if (!(p->ctx->pollfds[p->x.idx].events & POLLOUT)) { + /* if locked, allow only setting POLLOUT to prevent recursive + * callbacks */ + p->ctx->pollfds[p->x.idx].events = events & POLLOUT; + } + } +} + +/** + * @brief Set the file descriptor of a poll object. The FD will also be + * propagated to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param fd New file descriptor. + */ +void ssh_poll_set_fd(ssh_poll_handle p, socket_t fd) +{ + if (p->ctx != NULL) { + p->ctx->pollfds[p->x.idx].fd = fd; + } else { + p->x.fd = fd; + } +} + +/** + * @brief Add extra events to a poll object. Duplicates are ignored. + * The events will also be propagated to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param events Poll events. + */ +void ssh_poll_add_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) | events); +} + +/** + * @brief Remove events from a poll object. Non-existent are ignored. + * The events will also be propagated to an associated poll context. + * + * @param p Pointer to an already allocated poll object. + * @param events Poll events. + */ +void ssh_poll_remove_events(ssh_poll_handle p, short events) +{ + ssh_poll_set_events(p, ssh_poll_get_events(p) & ~events); +} + +/** + * @brief Get the raw socket of a poll object. + * + * @param p Pointer to an already allocated poll object. + * + * @return Raw socket. + */ + +socket_t ssh_poll_get_fd(ssh_poll_handle p) +{ + if (p->ctx != NULL) { + return p->ctx->pollfds[p->x.idx].fd; + } + + return p->x.fd; +} +/** + * @brief Set the callback of a poll object. + * + * @param p Pointer to an already allocated poll object. + * @param cb Function to be called if any of the events are set. + * @param userdata Userdata to be passed to the callback function. NULL if + * not needed. + */ +void ssh_poll_set_callback(ssh_poll_handle p, + ssh_poll_callback cb, + void *userdata) +{ + if (cb != NULL) { + p->cb = cb; + p->cb_data = userdata; + } +} + +/** + * @brief Create a new poll context. It could be associated with many poll object + * which are going to be polled at the same time as the poll context. You + * would need a single poll context per thread. + * + * @param chunk_size The size of the memory chunk that will be allocated, when + * more memory is needed. This is for efficiency reasons, + * i.e. don't allocate memory for each new poll object, but + * for the next 5. Set it to 0 if you want to use the + * library's default value. + */ +ssh_poll_ctx ssh_poll_ctx_new(size_t chunk_size) +{ + ssh_poll_ctx ctx; + + ctx = malloc(sizeof(struct ssh_poll_ctx_struct)); + if (ctx == NULL) { + return NULL; + } + ZERO_STRUCTP(ctx); + + if (chunk_size == 0) { + chunk_size = SSH_POLL_CTX_CHUNK; + } + + ctx->chunk_size = chunk_size; + + return ctx; +} + +/** + * @brief Free a poll context. + * + * @param ctx Pointer to an already allocated poll context. + */ +void ssh_poll_ctx_free(ssh_poll_ctx ctx) +{ + if (ctx->polls_allocated > 0) { + while (ctx->polls_used > 0) { + ssh_poll_handle p = ctx->pollptrs[0]; + /* + * The free function calls ssh_poll_ctx_remove() and decrements + * ctx->polls_used + */ + ssh_poll_free(p); + } + + SAFE_FREE(ctx->pollptrs); + SAFE_FREE(ctx->pollfds); + } + + SAFE_FREE(ctx); +} + +static int ssh_poll_ctx_resize(ssh_poll_ctx ctx, size_t new_size) +{ + ssh_poll_handle *pollptrs = NULL; + ssh_pollfd_t *pollfds = NULL; + + pollptrs = realloc(ctx->pollptrs, sizeof(ssh_poll_handle) * new_size); + if (pollptrs == NULL) { + /* Fail, but keep the old value to be freed later */ + return SSH_ERROR; + } + ctx->pollptrs = pollptrs; + + pollfds = realloc(ctx->pollfds, sizeof(ssh_pollfd_t) * new_size); + if (pollfds == NULL) { + if (ctx->polls_allocated == 0) { + /* This was initial allocation -- just free what we allocated above + * and fail */ + SAFE_FREE(ctx->pollptrs); + return SSH_ERROR; + } + /* Try to realloc the pollptrs back to the original size */ + pollptrs = realloc(ctx->pollptrs, + sizeof(ssh_poll_handle) * ctx->polls_allocated); + if (pollptrs == NULL) { + return SSH_ERROR; + } + ctx->pollptrs = pollptrs; + return SSH_ERROR; + } + + ctx->pollfds = pollfds; + ctx->polls_allocated = new_size; + + return SSH_OK; +} + +/** + * @brief Add a poll object to a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param p Pointer to an already allocated poll object. + * + * @return 0 on success, < 0 on error + */ +int ssh_poll_ctx_add(ssh_poll_ctx ctx, ssh_poll_handle p) +{ + socket_t fd; + + if (p->ctx != NULL) { + /* already attached to a context */ + return -1; + } + + if (ctx->polls_used == ctx->polls_allocated && + ssh_poll_ctx_resize(ctx, ctx->polls_allocated + ctx->chunk_size) < 0) { + return -1; + } + + fd = p->x.fd; + p->x.idx = ctx->polls_used++; + ctx->pollptrs[p->x.idx] = p; + ctx->pollfds[p->x.idx].fd = fd; + ctx->pollfds[p->x.idx].events = p->events; + ctx->pollfds[p->x.idx].revents = 0; + p->ctx = ctx; + + return 0; +} + +/** + * @brief Add a socket object to a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param s A SSH socket handle + * + * @return 0 on success, < 0 on error + */ +int ssh_poll_ctx_add_socket(ssh_poll_ctx ctx, ssh_socket s) +{ + ssh_poll_handle p = NULL; + + p = ssh_socket_get_poll_handle(s); + if (p == NULL) { + return -1; + } + return ssh_poll_ctx_add(ctx, p); +} + +/** + * @brief Remove a poll object from a poll context. + * + * @param ctx Pointer to an already allocated poll context. + * @param p Pointer to an already allocated poll object. + */ +void ssh_poll_ctx_remove(ssh_poll_ctx ctx, ssh_poll_handle p) +{ + size_t i; + + i = p->x.idx; + p->x.fd = ctx->pollfds[i].fd; + p->ctx = NULL; + + ctx->polls_used--; + + /* fill the empty poll slot with the last one */ + if (ctx->polls_used > 0 && ctx->polls_used != i) { + ctx->pollfds[i] = ctx->pollfds[ctx->polls_used]; + ctx->pollptrs[i] = ctx->pollptrs[ctx->polls_used]; + ctx->pollptrs[i]->x.idx = i; + } + + /* this will always leave at least chunk_size polls allocated */ + if (ctx->polls_allocated - ctx->polls_used > ctx->chunk_size) { + ssh_poll_ctx_resize(ctx, ctx->polls_allocated - ctx->chunk_size); + } +} + +/** + * @brief Returns if a poll object is locked. + * + * @param p Pointer to an already allocated poll object. + * @returns true if the poll object is locked; false otherwise. + */ +bool ssh_poll_is_locked(ssh_poll_handle p) +{ + if (p == NULL) { + return false; + } + return p->lock_cnt > 0; +} + +/** + * @brief Poll all the sockets associated through a poll object with a + * poll context. If any of the events are set after the poll, the + * call back function of the socket will be called. + * This function should be called once within the program's main loop. + * + * @param ctx Pointer to an already allocated poll context. + * @param timeout An upper limit on the time for which ssh_poll_ctx() will + * block, in milliseconds. Specifying a negative value + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns SSH_OK No error. + * SSH_ERROR Error happened during the poll. + * SSH_AGAIN Timeout occurred + */ + +int ssh_poll_ctx_dopoll(ssh_poll_ctx ctx, int timeout) +{ + int rc; + size_t i, used; + ssh_poll_handle p = NULL; + socket_t fd; + int revents; + struct ssh_timestamp ts; + + if (ctx->polls_used == 0) { + return SSH_ERROR; + } + + /* Allow only POLLOUT events on locked sockets as that means we are called + * recursively and we only want process the POLLOUT events here to flush + * output buffer */ + for (i = 0; i < ctx->polls_used; i++) { + /* The lock allows only POLLOUT events: drop the rest */ + if (ssh_poll_is_locked(ctx->pollptrs[i])) { + ctx->pollfds[i].events &= POLLOUT; + } + } + ssh_timestamp_init(&ts); + do { + int tm = ssh_timeout_update(&ts, timeout); + rc = ssh_poll(ctx->pollfds, ctx->polls_used, tm); + } while (rc == -1 && errno == EINTR); + + if (rc < 0) { + return SSH_ERROR; + } + if (rc == 0) { + return SSH_AGAIN; + } + + used = ctx->polls_used; + for (i = 0; i < used && rc > 0; ) { + revents = ctx->pollfds[i].revents; + /* Do not pass any other events except for POLLOUT to callback when + * called recursively more than 2 times. On s390x the poll will be + * spammed with POLLHUP events causing infinite recursion when the user + * callback issues some write/flush/poll calls. */ + if (ctx->pollptrs[i]->lock_cnt > 2) { + revents &= POLLOUT; + } + if (revents == 0) { + i++; + } else { + int ret; + + p = ctx->pollptrs[i]; + fd = ctx->pollfds[i].fd; + /* avoid having any event caught during callback */ + ctx->pollfds[i].events = 0; + p->lock_cnt++; + if (p->cb && (ret = p->cb(p, fd, revents, p->cb_data)) < 0) { + if (ret == -2) { + return -1; + } + /* the poll was removed, reload the used counter and start again + */ + used = ctx->polls_used; + i = 0; + } else { + ctx->pollfds[i].revents = 0; + ctx->pollfds[i].events = p->events; + p->lock_cnt--; + i++; + } + + rc--; + } + } + + return rc; +} + +/** + * @internal + * @brief gets the default poll structure for the current session, + * when used in blocking mode. + * @param session SSH session + * @returns the default ssh_poll_ctx + */ +ssh_poll_ctx ssh_poll_get_default_ctx(ssh_session session) +{ + if (session->default_poll_ctx != NULL) { + return session->default_poll_ctx; + } + /* 2 is enough for the default one */ + session->default_poll_ctx = ssh_poll_ctx_new(2); + return session->default_poll_ctx; +} + +/* public event API */ + +struct ssh_event_fd_wrapper { + ssh_event_callback cb; + void *userdata; +}; + +struct ssh_event_struct { + ssh_poll_ctx ctx; +#ifdef WITH_SERVER + struct ssh_list *sessions; +#endif +}; + +/** + * @brief Create a new event context. It could be associated with many + * ssh_session objects and socket fd which are going to be polled at the + * same time as the event context. You would need a single event context + * per thread. + * + * @return The ssh_event object on success, NULL on failure. + */ +ssh_event ssh_event_new(void) +{ + ssh_event event; + + event = malloc(sizeof(struct ssh_event_struct)); + if (event == NULL) { + return NULL; + } + ZERO_STRUCTP(event); + + event->ctx = ssh_poll_ctx_new(2); + if (event->ctx == NULL) { + free(event); + return NULL; + } + +#ifdef WITH_SERVER + event->sessions = ssh_list_new(); + if (event->sessions == NULL) { + ssh_poll_ctx_free(event->ctx); + free(event); + return NULL; + } +#endif + + return event; +} + +static int ssh_event_fd_wrapper_callback(ssh_poll_handle p, + socket_t fd, + int revents, + void *userdata) +{ + struct ssh_event_fd_wrapper *pw = (struct ssh_event_fd_wrapper *)userdata; + + (void)p; + if (pw->cb != NULL) { + return pw->cb(fd, revents, pw->userdata); + } + return 0; +} + +/** + * @brief Add a fd to the event and assign it a callback, + * when used in blocking mode. + * @param event The ssh_event + * @param fd Socket that will be polled. + * @param events Poll events that will be monitored for the socket. i.e. + * POLLIN, POLLPRI, POLLOUT + * @param cb Function to be called if any of the events are set. + * The prototype of cb is: + * int (*ssh_event_callback)(socket_t fd, int revents, + * void *userdata); + * @param userdata Userdata to be passed to the callback function. NULL if + * not needed. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_fd(ssh_event event, + socket_t fd, + short events, + ssh_event_callback cb, + void *userdata) +{ + ssh_poll_handle p = NULL; + struct ssh_event_fd_wrapper *pw = NULL; + int rc; + + if (event == NULL || event->ctx == NULL || cb == NULL || + fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + pw = malloc(sizeof(struct ssh_event_fd_wrapper)); + if (pw == NULL) { + return SSH_ERROR; + } + + pw->cb = cb; + pw->userdata = userdata; + + /* pw is freed by ssh_event_remove_fd */ + p = ssh_poll_new(fd, events, ssh_event_fd_wrapper_callback, pw); + if (p == NULL) { + free(pw); + return SSH_ERROR; + } + + rc = ssh_poll_ctx_add(event->ctx, p); + if (rc < 0) { + free(pw); + ssh_poll_free(p); + return SSH_ERROR; + } + return SSH_OK; +} + +/** + * @brief Add a poll handle to the event. + * + * @param event the ssh_event + * + * @param p the poll handle + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_poll(ssh_event event, ssh_poll_handle p) +{ + return ssh_poll_ctx_add(event->ctx, p); +} + +/** + * @brief remove a poll handle to the event. + * + * @param event the ssh_event + * + * @param p the poll handle + */ +void ssh_event_remove_poll(ssh_event event, ssh_poll_handle p) +{ + ssh_poll_ctx_remove(event->ctx, p); +} + +/** + * @brief remove the poll handle from session and assign them to an event, + * when used in blocking mode. + * + * @param event The ssh_event object + * @param session The session to add to the event. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_add_session(ssh_event event, ssh_session session) +{ + ssh_poll_handle p = NULL; +#ifdef WITH_SERVER + struct ssh_iterator *iterator = NULL; +#endif + int rc; + + if (event == NULL || event->ctx == NULL || session == NULL) { + return SSH_ERROR; + } + if (session->default_poll_ctx == NULL) { + return SSH_ERROR; + } + while (session->default_poll_ctx->polls_used > 0) { + p = session->default_poll_ctx->pollptrs[0]; + /* + * ssh_poll_ctx_remove() decrements + * session->default_poll_ctx->polls_used + */ + ssh_poll_ctx_remove(session->default_poll_ctx, p); + rc = ssh_poll_ctx_add(event->ctx, p); + if (rc != SSH_OK) { + return rc; + } + /* associate the pollhandler with a session so we can put it back + * at ssh_event_free() + */ + p->session = session; + } +#ifdef WITH_SERVER + iterator = ssh_list_get_iterator(event->sessions); + while (iterator != NULL) { + if ((ssh_session)iterator->data == session) { + /* allow only one instance of this session */ + return SSH_OK; + } + iterator = iterator->next; + } + if (ssh_list_append(event->sessions, session) == SSH_ERROR) { + return SSH_ERROR; + } +#endif + return SSH_OK; +} + +/** + * @brief Add a connector to the SSH event loop + * + * @param[in] event The SSH event loop + * + * @param[in] connector The connector object + * + * @return SSH_OK + * + * @return SSH_ERROR in case of error + */ +int ssh_event_add_connector(ssh_event event, ssh_connector connector) +{ + return ssh_connector_set_event(connector, event); +} + +/** + * @brief Poll all the sockets and sessions associated through an event object. + * + * If any of the events are set after the poll, the call back functions of the + * sessions or sockets will be called. + * This function should be called once within the programs main loop. + * In case of failure, the errno should be consulted to find more information + * about the failure set by underlying poll imlpementation. + * + * @param event The ssh_event object to poll. + * + * @param timeout An upper limit on the time for which the poll will + * block, in milliseconds. Specifying a negative value + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns SSH_OK on success. + * SSH_ERROR Error happened during the poll. Check errno to get more + * details about why it failed. + * SSH_AGAIN Timeout occurred + */ +int ssh_event_dopoll(ssh_event event, int timeout) +{ + int rc; + + if (event == NULL || event->ctx == NULL) { + return SSH_ERROR; + } + rc = ssh_poll_ctx_dopoll(event->ctx, timeout); + return rc; +} + +/** + * @brief Remove a socket fd from an event context. + * + * @param event The ssh_event object. + * @param fd The fd to remove. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_remove_fd(ssh_event event, socket_t fd) +{ + register size_t i, used; + int rc = SSH_ERROR; + + if (event == NULL || event->ctx == NULL) { + return SSH_ERROR; + } + + used = event->ctx->polls_used; + for (i = 0; i < used; i++) { + if (fd == event->ctx->pollfds[i].fd) { + ssh_poll_handle p = event->ctx->pollptrs[i]; + if (p->session != NULL) { + /* we cannot free that handle, it's owned by its session */ + continue; + } + if (p->cb == ssh_event_fd_wrapper_callback) { + struct ssh_event_fd_wrapper *pw = p->cb_data; + SAFE_FREE(pw); + } + + /* + * The free function calls ssh_poll_ctx_remove() and decrements + * event->ctx->polls_used. + */ + ssh_poll_free(p); + rc = SSH_OK; + + /* restart the loop */ + used = event->ctx->polls_used; + i = 0; + } + } + + return rc; +} + +/** + * @brief Remove a session object from an event context. + * + * @param event The ssh_event object. + * @param session The session to remove. + * + * @returns SSH_OK on success + * SSH_ERROR on failure + */ +int ssh_event_remove_session(ssh_event event, ssh_session session) +{ + ssh_poll_handle p = NULL; + register size_t i, used; + int rc = SSH_ERROR; +#ifdef WITH_SERVER + struct ssh_iterator *iterator = NULL; +#endif + + if (event == NULL || event->ctx == NULL || session == NULL) { + return SSH_ERROR; + } + + used = event->ctx->polls_used; + for (i = 0; i < used; i++) { + p = event->ctx->pollptrs[i]; + if (p->session == session) { + /* + * ssh_poll_ctx_remove() decrements + * event->ctx->polls_used + */ + ssh_poll_ctx_remove(event->ctx, p); + p->session = NULL; + rc = ssh_poll_ctx_add(session->default_poll_ctx, p); + if (rc != SSH_OK) { + return rc; + } + rc = SSH_OK; + /* + * Restart the loop! + * A session can initially have two pollhandlers. + */ + used = event->ctx->polls_used; + i = 0; + } + } +#ifdef WITH_SERVER + iterator = ssh_list_get_iterator(event->sessions); + while (iterator != NULL) { + if ((ssh_session)iterator->data == session) { + ssh_list_remove(event->sessions, iterator); + /* there should be only one instance of this session */ + break; + } + iterator = iterator->next; + } +#endif + + return rc; +} + +/** @brief Remove a connector from an event context + * @param[in] event The ssh_event object. + * @param[in] connector connector object to remove + * @return SSH_OK on success + * @return SSH_ERROR on failure + */ +int ssh_event_remove_connector(ssh_event event, ssh_connector connector) +{ + (void)event; + return ssh_connector_remove_event(connector); +} + +/** + * @brief Free an event context. + * + * @param event The ssh_event object to free. + * Note: you have to manually remove sessions and socket + * fds before freeing the event object. + * + */ +void ssh_event_free(ssh_event event) +{ + size_t used, i; + ssh_poll_handle p = NULL; + + if (event == NULL) { + return; + } + + if (event->ctx != NULL) { + used = event->ctx->polls_used; + for (i = 0; i < used; i++) { + p = event->ctx->pollptrs[i]; + if (p->session != NULL) { + ssh_poll_ctx_remove(event->ctx, p); + ssh_poll_ctx_add(p->session->default_poll_ctx, p); + p->session = NULL; + used = 0; + } + } + + ssh_poll_ctx_free(event->ctx); + } +#ifdef WITH_SERVER + if (event->sessions != NULL) { + ssh_list_free(event->sessions); + } +#endif + free(event); +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/scp.c b/src/libs/libssh-0.12.2/src/scp.c new file mode 100644 index 000000000000..ec2d1a2e9cbc --- /dev/null +++ b/src/libs/libssh-0.12.2/src/scp.c @@ -0,0 +1,1222 @@ +/* + * scp - SSH scp wrapper functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/scp.h" +#include "libssh/misc.h" +#include "libssh/session.h" + +/** + * @defgroup libssh_scp The SSH scp functions + * @ingroup libssh + * + * SCP protocol over SSH functions + * + * @deprecated Please use SFTP instead + * + * @{ + */ + +/** + * @deprecated Please use SFTP instead + * + * @brief Create a new scp session. + * + * @param[in] session The SSH session to use. + * + * @param[in] mode One of SSH_SCP_WRITE or SSH_SCP_READ, depending if you + * need to drop files remotely or read them. + * It is not possible to combine read and write. + * SSH_SCP_RECURSIVE Flag can be or'ed to this to indicate + * that you're going to use recursion. Browsing through + * directories is not possible without this. + * + * @param[in] location The directory in which write or read will be done. Any + * push or pull will be relative to this place. + * This can also be a pattern of files to download (read). + * + * @returns A ssh_scp handle, NULL if the creation was impossible. + */ +ssh_scp ssh_scp_new(ssh_session session, int mode, const char *location) +{ + ssh_scp scp = NULL; + + if (session == NULL || location == NULL) { + goto error; + } + + scp = (ssh_scp)calloc(1, sizeof(struct ssh_scp_struct)); + if (scp == NULL) { + ssh_set_error(session, SSH_FATAL, + "Error allocating memory for ssh_scp"); + goto error; + } + + if ((mode & ~SSH_SCP_RECURSIVE) != SSH_SCP_WRITE && + (mode & ~SSH_SCP_RECURSIVE) != SSH_SCP_READ) + { + ssh_set_error(session, SSH_FATAL, + "Invalid mode %d for ssh_scp_new()", mode); + goto error; + } + + if (strlen(location) > 32 * 1024) { + ssh_set_error(session, SSH_FATAL, + "Location path is too long"); + goto error; + } + + scp->location = strdup(location); + if (scp->location == NULL) { + ssh_set_error(session, SSH_FATAL, + "Error allocating memory for ssh_scp"); + goto error; + } + + scp->session = session; + scp->mode = mode & ~SSH_SCP_RECURSIVE; + scp->recursive = (mode & SSH_SCP_RECURSIVE) != 0; + scp->channel = NULL; + scp->state = SSH_SCP_NEW; + + return scp; + +error: + ssh_scp_free(scp); + return NULL; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Initialize the scp channel. + * + * @param[in] scp The scp context to initialize. + * + * @return SSH_OK on success or an SSH error code. + * + * @see ssh_scp_new() + */ +int ssh_scp_init(ssh_scp scp) +{ + int rc; + char execbuffer[PATH_MAX] = {0}; + char *quoted_location = NULL; + size_t quoted_location_len = 0; + size_t scp_location_len; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_NEW) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_init called under invalid state"); + return SSH_ERROR; + } + + if (scp->location == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Invalid scp context: location is NULL"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, "Initializing scp session %s %son location '%s'", + scp->mode == SSH_SCP_WRITE?"write":"read", + scp->recursive ? "recursive " : "", + scp->location); + + scp->channel = ssh_channel_new(scp->session); + if (scp->channel == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Channel creation failed for scp"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_channel_open_session(scp->channel); + if (rc == SSH_ERROR) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to open channel for scp"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + /* In the worst case, each character would be replaced by 3 plus the string + * terminator '\0' */ + scp_location_len = strlen(scp->location); + quoted_location_len = ((size_t)3 * scp_location_len) + 1; + /* Paranoia check */ + if (quoted_location_len < scp_location_len) { + ssh_set_error(scp->session, SSH_FATAL, + "Buffer overflow detected"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + quoted_location = (char *)calloc(1, quoted_location_len); + if (quoted_location == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to allocate memory for quoted location"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_quote_file_name(scp->location, quoted_location, + quoted_location_len); + if (rc <= 0) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to single quote command location"); + SAFE_FREE(quoted_location); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + /* Some servers do not handle the quoting well. Pass in the raw file + * location */ + if (scp->session->flags & SSH_SESSION_FLAG_SCP_QUOTING_BROKEN) { + free(quoted_location); + quoted_location = strdup(scp->location); + if (quoted_location == NULL) { + ssh_set_error_oom(scp->session); + return SSH_ERROR; + } + } + + if (scp->mode == SSH_SCP_WRITE) { + snprintf(execbuffer, sizeof(execbuffer), "scp -t %s %s", + scp->recursive ? "-r" : "", quoted_location); + } else { + snprintf(execbuffer, sizeof(execbuffer), "scp -f %s %s", + scp->recursive ? "-r" : "", quoted_location); + } + + SAFE_FREE(quoted_location); + + SSH_LOG(SSH_LOG_DEBUG, "Executing command: %s", execbuffer); + + rc = ssh_channel_request_exec(scp->channel, execbuffer); + if (rc == SSH_ERROR){ + ssh_set_error(scp->session, SSH_FATAL, + "Failed executing command: %s", execbuffer); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + if (scp->mode == SSH_SCP_WRITE) { + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + } else { + ssh_channel_write(scp->channel, "", 1); + } + + if (scp->mode == SSH_SCP_WRITE) { + scp->state = SSH_SCP_WRITE_INITED; + } else { + scp->state = SSH_SCP_READ_INITED; + } + + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Close the scp channel. + * + * @param[in] scp The scp context to close. + * + * @return SSH_OK on success or an SSH error code. + * + * @see ssh_scp_init() + */ +int ssh_scp_close(ssh_scp scp) +{ + char buffer[128] = {0}; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->channel != NULL) { + if (ssh_channel_send_eof(scp->channel) == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + /* avoid situations where data are buffered and + * not yet stored on disk. This can happen if the close is sent + * before we got the EOF back + */ + while (!ssh_channel_is_eof(scp->channel)) { + rc = ssh_channel_read(scp->channel, buffer, sizeof(buffer), 0); + if (rc == SSH_ERROR || rc == SSH_AGAIN || rc == 0) { + break; + } + } + + if (ssh_channel_close(scp->channel) == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + ssh_channel_free(scp->channel); + scp->channel = NULL; + } + + scp->state = SSH_SCP_NEW; + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Free a scp context. + * + * @param[in] scp The context to free. + * + * @see ssh_scp_new() + */ +void ssh_scp_free(ssh_scp scp) +{ + if (scp == NULL) { + return; + } + + if (scp->state != SSH_SCP_NEW) { + ssh_scp_close(scp); + } + + if (scp->channel) { + ssh_channel_free(scp->channel); + } + + SAFE_FREE(scp->location); + SAFE_FREE(scp->request_name); + SAFE_FREE(scp->warning); + SAFE_FREE(scp); +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Create a directory in a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] dirname The name of the directory being created. + * + * @param[in] mode The UNIX permissions for the new directory, e.g. 0755. + * + * @returns SSH_OK if the directory has been created, SSH_ERROR if + * an error occurred. + * + * @see ssh_scp_leave_directory() + */ +int ssh_scp_push_directory(ssh_scp scp, const char *dirname, int mode) +{ + char buffer[PATH_MAX] = {0}; + int rc; + char *dir = NULL; + char *perms = NULL; + char *vis_encoded = NULL; + size_t vis_encoded_len; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_push_directory called under invalid state"); + return SSH_ERROR; + } + + dir = ssh_basename(dirname); + if (dir == NULL) { + ssh_set_error_oom(scp->session); + return SSH_ERROR; + } + + vis_encoded_len = (2 * strlen(dir)) + 1; + vis_encoded = (char *)calloc(1, vis_encoded_len); + if (vis_encoded == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to allocate buffer to vis encode directory name"); + goto error; + } + + rc = ssh_newline_vis(dir, vis_encoded, vis_encoded_len); + if (rc <= 0) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to vis encode directory name"); + goto error; + } + + perms = ssh_scp_string_mode(mode); + if (perms == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to get directory permission string"); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "SCP pushing directory %s with permissions '%s'", + vis_encoded, perms); + + /* Use vis encoded directory name */ + snprintf(buffer, sizeof(buffer), + "D%s 0 %s\n", + perms, vis_encoded); + + SAFE_FREE(dir); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; + +error: + SAFE_FREE(dir); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + return SSH_ERROR; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Leave a directory. + * + * @returns SSH_OK if the directory has been left, SSH_ERROR if an + * error occurred. + * + * @see ssh_scp_push_directory() + */ +int ssh_scp_leave_directory(ssh_scp scp) +{ + char buffer[] = "E\n"; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_leave_directory called under invalid state"); + return SSH_ERROR; + } + + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Initialize the sending of a file to a scp in sink mode, using a 64-bit + * size. + * + * @param[in] scp The scp handle. + * + * @param[in] filename The name of the file being sent. It should not contain + * any path indicator + * + * @param[in] size Exact size in bytes of the file being sent. + * + * @param[in] mode The UNIX permissions for the new file, e.g. 0644. + * + * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an + * error occurred. + * + * @see ssh_scp_push_file() + */ +int ssh_scp_push_file64(ssh_scp scp, const char *filename, uint64_t size, + int mode) +{ + char buffer[PATH_MAX] = {0}; + int rc; + char *file = NULL; + char *perms = NULL; + char *vis_encoded = NULL; + size_t vis_encoded_len; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_push_file called under invalid state"); + return SSH_ERROR; + } + + file = ssh_basename(filename); + if (file == NULL) { + ssh_set_error_oom(scp->session); + return SSH_ERROR; + } + + vis_encoded_len = (2 * strlen(file)) + 1; + vis_encoded = (char *)calloc(1, vis_encoded_len); + if (vis_encoded == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to allocate buffer to vis encode file name"); + goto error; + } + + rc = ssh_newline_vis(file, vis_encoded, vis_encoded_len); + if (rc <= 0) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to vis encode file name"); + goto error; + } + + perms = ssh_scp_string_mode(mode); + if (perms == NULL) { + ssh_set_error(scp->session, SSH_FATAL, + "Failed to get file permission string"); + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, + "SCP pushing file %s, size %" PRIu64 " with permissions '%s'", + vis_encoded, size, perms); + + /* Use vis encoded file name */ + snprintf(buffer, sizeof(buffer), + "C%s %" PRIu64 " %s\n", + perms, size, vis_encoded); + + SAFE_FREE(file); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + rc = ssh_channel_write(scp->channel, buffer, strlen(buffer)); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + + scp->filelen = size; + scp->processed = 0; + scp->state = SSH_SCP_WRITE_WRITING; + + return SSH_OK; + +error: + SAFE_FREE(file); + SAFE_FREE(perms); + SAFE_FREE(vis_encoded); + + return SSH_ERROR; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Initialize the sending of a file to a scp in sink mode. + * + * @param[in] scp The scp handle. + * + * @param[in] filename The name of the file being sent. It should not contain + * any path indicator + * + * @param[in] size Exact size in bytes of the file being sent. + * + * @param[in] mode The UNIX permissions for the new file, e.g. 0644. + * + * @returns SSH_OK if the file is ready to be sent, SSH_ERROR if an + * error occurred. + */ +int ssh_scp_push_file(ssh_scp scp, const char *filename, size_t size, int mode) +{ + return ssh_scp_push_file64(scp, filename, (uint64_t) size, mode); +} + +/** + * @internal + * + * @deprecated Please use SFTP instead + * + * @brief Wait for a response of the scp server. + * + * @param[in] scp The scp handle. + * + * @param[out] response A pointer where the response message must be copied if + * any. This pointer must then be free'd. + * + * @returns The return code, SSH_ERROR a error occurred. + */ +int ssh_scp_response(ssh_scp scp, char **response) +{ + unsigned char code; + int rc; + char msg[128] = {0}; + + if (scp == NULL) { + return SSH_ERROR; + } + + rc = ssh_channel_read(scp->channel, &code, 1, 0); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + if (rc == SSH_AGAIN) { + ssh_set_error(scp->session, SSH_FATAL, "SCP: ssh_channel_read timeout"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + if (code == 0) { + return 0; + } + + if (code > 2) { + ssh_set_error(scp->session, SSH_FATAL, + "SCP: invalid status code %u received", code); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + rc = ssh_scp_read_string(scp, msg, sizeof(msg)); + if (rc == SSH_ERROR) { + return rc; + } + + /* Warning */ + if (code == 1) { + ssh_set_error(scp->session, SSH_REQUEST_DENIED, + "SCP: Warning: status code 1 received: %s", msg); + SSH_LOG(SSH_LOG_RARE, + "SCP: Warning: status code 1 received: %s", msg); + if (response) { + *response = strdup(msg); + } + return 1; + } + + if (code == 2) { + ssh_set_error(scp->session, SSH_FATAL, + "SCP: Error: status code 2 received: %s", msg); + if (response) { + *response = strdup(msg); + } + return 2; + } + + /* Not reached */ + return SSH_ERROR; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Write into a remote scp file. + * + * @param[in] scp The scp handle. + * + * @param[in] buffer The buffer to write. + * + * @param[in] len The number of bytes to write. + * + * @returns SSH_OK if the write was successful, SSH_ERROR an error + * occurred while writing. + */ +int ssh_scp_write(ssh_scp scp, const void *buffer, size_t len) +{ + int w; + int rc; + uint8_t code; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_WRITE_WRITING) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_write called under invalid state"); + return SSH_ERROR; + } + + if (scp->processed + len > scp->filelen) { + len = (size_t) (scp->filelen - scp->processed); + } + + /* hack to avoid waiting for window change */ + rc = ssh_channel_poll(scp->channel, 0); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + w = ssh_channel_write(scp->channel, buffer, len); + if (w != SSH_ERROR) { + scp->processed += w; + } else { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + /* Far end sometimes send a status message, which we need to read + * and handle */ + rc = ssh_channel_poll(scp->channel, 0); + if (rc > 0) { + rc = ssh_scp_response(scp, NULL); + if (rc != 0) { + return SSH_ERROR; + } + } + + /* Check if we arrived at end of file */ + if (scp->processed == scp->filelen) { + code = 0; + w = ssh_channel_write(scp->channel, &code, 1); + if (w == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + scp->processed = scp->filelen = 0; + scp->state = SSH_SCP_WRITE_INITED; + } + + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Read a string on a channel, terminated by '\n' + * + * @param[in] scp The scp handle. + * + * @param[out] buffer A pointer to a buffer to place the string. + * + * @param[in] len The size of the buffer in bytes. If the string is bigger + * than len-1, only len-1 bytes are read and the string is + * null-terminated. + * + * @returns SSH_OK if the string was read, SSH_ERROR if an error + * occurred while reading. + */ +int ssh_scp_read_string(ssh_scp scp, char *buffer, size_t len) +{ + size_t read = 0; + int err = SSH_OK; + + if (scp == NULL) { + return SSH_ERROR; + } + + while (read < len - 1) { + err = ssh_channel_read(scp->channel, &buffer[read], 1, 0); + if (err == SSH_ERROR) { + break; + } + + if (err == 0) { + ssh_set_error(scp->session, SSH_FATAL, + "End of file while reading string"); + err = SSH_ERROR; + break; + } + + if (err == SSH_AGAIN) { + ssh_set_error(scp->session, + SSH_FATAL, + "SCP: ssh_channel_read timeout"); + err = SSH_ERROR; + break; + } + + read++; + if (buffer[read - 1] == '\n') { + break; + } + } + + buffer[read] = 0; + return err; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Wait for a scp request (file, directory). + * + * @returns SSH_SCP_REQUEST_NEWFILE: The other side is sending + * a file + * SSH_SCP_REQUEST_NEWDIR: The other side is sending + * a directory + * SSH_SCP_REQUEST_ENDDIR: The other side has + * finished with the current + * directory + * SSH_SCP_REQUEST_WARNING: The other side sent us a warning + * SSH_SCP_REQUEST_EOF: The other side finished sending us + * files and data. + * SSH_ERROR: Some error happened + * + * @see ssh_scp_read() + * @see ssh_scp_deny_request() + * @see ssh_scp_accept_request() + * @see ssh_scp_request_get_warning() + */ +int ssh_scp_pull_request(ssh_scp scp) +{ + char buffer[PATH_MAX] = {0}; + char *mode = NULL; + char *p, *tmp; + uint64_t size; + char *name = NULL; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_READ_INITED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_pull_request called under invalid state"); + return SSH_ERROR; + } + + rc = ssh_scp_read_string(scp, buffer, sizeof(buffer)); + if (rc == SSH_ERROR) { + if (ssh_channel_is_eof(scp->channel)) { + scp->state = SSH_SCP_TERMINATED; + return SSH_SCP_REQUEST_EOF; + } + return rc; + } + + p = strchr(buffer, '\n'); + if (p != NULL) { + *p = '\0'; + } + + SSH_LOG(SSH_LOG_DEBUG, "Received SCP request: '%s'", buffer); + switch(buffer[0]) { + case 'C': + /* File */ + case 'D': + /* Directory */ + p = strchr(buffer, ' '); + if (p == NULL) { + goto error; + } + *p = '\0'; + p++; + //mode = strdup(&buffer[1]); + scp->request_mode = ssh_scp_integer_mode(&buffer[1]); + tmp = p; + p = strchr(p, ' '); + if (p == NULL) { + goto error; + } + *p = 0; + size = strtoull(tmp, NULL, 10); + p++; + name = strdup(p); + /* Catch invalid name: + * - empty ones + * - containing any forward slash -- directory traversal handled + * differently + * - special names "." and ".." referring to the current and parent + * directories -- they are not expected either + */ + if (name == NULL || name[0] == '\0' || strchr(name, '/') || + strcmp(name, ".") == 0 || strcmp(name, "..") == 0) { + ssh_set_error(scp->session, + SSH_FATAL, + "Received invalid filename: %s", + name == NULL ? "" : name); + SAFE_FREE(name); + goto error; + } + SAFE_FREE(scp->request_name); + scp->request_name = name; + if (buffer[0] == 'C') { + scp->filelen = size; + scp->request_type = SSH_SCP_REQUEST_NEWFILE; + } else { + scp->filelen = '0'; + scp->request_type = SSH_SCP_REQUEST_NEWDIR; + } + scp->state = SSH_SCP_READ_REQUESTED; + scp->processed = 0; + return scp->request_type; + break; + case 'E': + scp->request_type = SSH_SCP_REQUEST_ENDDIR; + ssh_channel_write(scp->channel, "", 1); + return scp->request_type; + case 0x1: + ssh_set_error(scp->session, SSH_REQUEST_DENIED, + "SCP: Warning: %s", &buffer[1]); + scp->request_type = SSH_SCP_REQUEST_WARNING; + SAFE_FREE(scp->warning); + scp->warning = strdup(&buffer[1]); + return scp->request_type; + case 0x2: + ssh_set_error(scp->session, SSH_FATAL, + "SCP: Error: %s", &buffer[1]); + return SSH_ERROR; + case 'T': + /* Timestamp */ + default: + ssh_set_error(scp->session, SSH_FATAL, + "Unhandled message: (%d)%s", buffer[0], buffer); + return SSH_ERROR; + } + + /* a parsing error occurred */ +error: + SAFE_FREE(name); + SAFE_FREE(mode); + ssh_set_error(scp->session, SSH_FATAL, + "Parsing error while parsing message: %s", buffer); + return SSH_ERROR; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Deny the transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * @param[in] reason A nul-terminated string with a human-readable + * explanation of the deny. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if the sending + * the message failed, or sending it in a bad state. + */ +int ssh_scp_deny_request(ssh_scp scp, const char *reason) +{ + char *buffer = NULL; + size_t len; + int rc; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_READ_REQUESTED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + + len = strlen(reason) + 3; + buffer = malloc(len); + if (buffer == NULL) { + return SSH_ERROR; + } + + snprintf(buffer, len, "%c%s\n", 2, reason); + rc = ssh_channel_write(scp->channel, buffer, len - 1); + free(buffer); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + else { + scp->state = SSH_SCP_READ_INITED; + return SSH_OK; + } +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Accepts transfer of a file or creation of a directory coming from the + * remote party. + * + * @param[in] scp The scp handle. + * + * @returns SSH_OK if the message was sent, SSH_ERROR if sending the + * message failed, or sending it in a bad state. + */ +int ssh_scp_accept_request(ssh_scp scp) +{ + char buffer[] = {0x00}; + int rc; + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state != SSH_SCP_READ_REQUESTED) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_deny_request called under invalid state"); + return SSH_ERROR; + } + + rc = ssh_channel_write(scp->channel, buffer, 1); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + if (scp->request_type == SSH_SCP_REQUEST_NEWFILE) { + scp->state = SSH_SCP_READ_READING; + } else { + scp->state = SSH_SCP_READ_INITED; + } + + return SSH_OK; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Read from a remote scp file + * + * @param[in] scp The scp handle. + * + * @param[in] buffer The destination buffer. + * + * @param[in] size The size of the buffer. + * + * @returns The number of bytes read, SSH_ERROR if an error occurred + * while reading. + */ +int ssh_scp_read(ssh_scp scp, void *buffer, size_t size) +{ + int rc; + int code; + + if (scp == NULL) { + return SSH_ERROR; + } + + if (scp->state == SSH_SCP_READ_REQUESTED && + scp->request_type == SSH_SCP_REQUEST_NEWFILE) + { + rc = ssh_scp_accept_request(scp); + if (rc == SSH_ERROR) { + return rc; + } + } + + if (scp->state != SSH_SCP_READ_READING) { + ssh_set_error(scp->session, SSH_FATAL, + "ssh_scp_read called under invalid state"); + return SSH_ERROR; + } + + if (scp->processed + size > scp->filelen) { + size = (size_t) (scp->filelen - scp->processed); + } + + if (size > 65536) { + size = 65536; /* avoid too large reads */ + } + + rc = ssh_channel_read(scp->channel, buffer, size, 0); + if (rc == SSH_ERROR) { + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + if (rc == SSH_AGAIN) { + ssh_set_error(scp->session, SSH_FATAL, "SCP: ssh_channel_read timeout"); + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + scp->processed += rc; + + /* Check if we arrived at end of file */ + if (scp->processed == scp->filelen) { + scp->processed = scp->filelen = 0; + ssh_channel_write(scp->channel, "", 1); + code = ssh_scp_response(scp, NULL); + if (code == 0) { + scp->state = SSH_SCP_READ_INITED; + return rc; + } + if (code == 1) { + scp->state = SSH_SCP_READ_INITED; + return SSH_ERROR; + } + scp->state = SSH_SCP_ERROR; + return SSH_ERROR; + } + + return rc; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the name of the directory or file being pushed from the other + * party. + * + * @returns The file name, NULL on error. The string should not be + * freed. + */ +const char *ssh_scp_request_get_filename(ssh_scp scp) +{ + if (scp == NULL) { + return NULL; + } + + return scp->request_name; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the permissions of the directory or file being pushed from the + * other party. + * + * @returns The UNIX permission, e.g 0644, -1 on error. + */ +int ssh_scp_request_get_permissions(ssh_scp scp) +{ + if (scp == NULL) { + return -1; + } + + return scp->request_mode; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. + * + * @returns The numeric size of the file being read. + * @warning The real size may not fit in a 32 bits field and may + * be truncated. + * @see ssh_scp_request_get_size64() + */ +size_t ssh_scp_request_get_size(ssh_scp scp) +{ + if (scp == NULL) { + return 0; + } + return (size_t)scp->filelen; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the size of the file being pushed from the other party. + * + * @returns The numeric size of the file being read. + */ +uint64_t ssh_scp_request_get_size64(ssh_scp scp) +{ + if (scp == NULL) { + return 0; + } + return scp->filelen; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Convert a scp text mode to an integer. + * + * @param[in] mode The mode to convert, e.g. "0644". + * + * @returns An integer value, e.g. 420 for "0644". + */ +int ssh_scp_integer_mode(const char *mode) +{ + int value = strtoul(mode, NULL, 8) & 0xffff; + return value; +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Convert a unix mode into a scp string. + * + * @param[in] mode The mode to convert, e.g. 420 or 0644. + * + * @returns A pointer to a malloc'ed string containing the scp mode, + * e.g. "0644". + */ +char *ssh_scp_string_mode(int mode) +{ + char buffer[16] = {0}; + snprintf(buffer, sizeof(buffer), "%.4o", mode); + return strdup(buffer); +} + +/** + * @deprecated Please use SFTP instead + * + * @brief Get the warning string from a scp handle. + * + * @param[in] scp The scp handle. + * + * @returns A warning string, or NULL on error. The string should + * not be freed. + */ +const char *ssh_scp_request_get_warning(ssh_scp scp) +{ + if (scp == NULL) { + return NULL; + } + + return scp->warning; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/server.c b/src/libs/libssh-0.12.2/src/server.c new file mode 100644 index 000000000000..7873666f1278 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/server.c @@ -0,0 +1,1656 @@ +/* + * server.c - functions for creating a SSH server + * + * This file is part of the SSH Library + * + * Copyright (c) 2004-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#ifdef _WIN32 +# include +# include + + /* + * is necessary for getaddrinfo before Windows XP, but it isn't + * available on some platforms like MinGW. + */ +# ifdef HAVE_WSPIAPI_H +# include +# endif +#else +# include +#endif + +#include "libssh/buffer.h" +#include "libssh/curve25519.h" +#include "libssh/dh.h" +#include "libssh/gssapi.h" +#include "libssh/kex.h" +#include "libssh/libssh.h" +#include "libssh/messages.h" +#include "libssh/misc.h" +#include "libssh/options.h" +#include "libssh/packet.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/session.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#include "libssh/token.h" + +#define set_status(session, status) do {\ + if (session->common.callbacks && session->common.callbacks->connect_status_function) \ + session->common.callbacks->connect_status_function(session->common.callbacks->userdata, status); \ + } while (0) + +/** + * @addtogroup libssh_server + * + * @{ + */ + +/** + * @internal + * @brief Sets the server's key exchange, encryption, MAC, and compression + * algorithms. + * + * Prepares the server key exchange (KEX) proposals by prioritizing the + * available host keys (Ed25519, ECDSA, RSA) based on their strength and fills + * in the KEX method lists based on session options or defaults. This is + * essential for negotiating secure communication parameters in the SSH + * handshake. + * + * @param[in] session The SSH session to set up. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure (e.g., no host keys + * available, random number generation error, or memory allocation failure). + */ +int server_set_kex(ssh_session session) +{ + struct ssh_kex_struct *server = &session->next_crypto->server_kex; + int i, j, rc; + const char *wanted = NULL, *allowed = NULL; + char *kept = NULL; + char hostkeys[128] = {0}; + enum ssh_keytypes_e keytype; + size_t len; + int ok; +#ifdef WITH_GSSAPI + char *gssapi_algs = NULL; +#endif /* WITH_GSSAPI */ + + /* Skip if already set, for example for the rekey or when we do the guessing + * it could have been already used to make some protocol decisions. */ + if (server->methods[0] != NULL) { + return SSH_OK; + } + + ok = ssh_get_random(server->cookie, 16, 0); + if (!ok) { + ssh_set_error(session, SSH_FATAL, "PRNG error"); + return -1; + } + + if (session->srv.ed25519_key != NULL) { + snprintf(hostkeys, + sizeof(hostkeys), + "%s", + ssh_key_type_to_char(ssh_key_type(session->srv.ed25519_key))); + } +#ifdef HAVE_ECC + if (session->srv.ecdsa_key != NULL) { + len = strlen(hostkeys); + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", session->srv.ecdsa_key->type_c); + } +#endif + if (session->srv.rsa_key != NULL) { + /* We support also the SHA2 variants */ + len = strlen(hostkeys); + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",rsa-sha2-512,rsa-sha2-256"); + + len = strlen(hostkeys); + keytype = ssh_key_type(session->srv.rsa_key); + + snprintf(hostkeys + len, sizeof(hostkeys) - len, + ",%s", ssh_key_type_to_char(keytype)); + } + + if (session->opts.wanted_methods[SSH_HOSTKEYS]) { + allowed = session->opts.wanted_methods[SSH_HOSTKEYS]; + } else { + if (ssh_fips_mode()) { + allowed = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + allowed = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + + if (strlen(hostkeys) != 0) { + /* It is expected for the list of allowed hostkeys to be ordered by + * preference */ + kept = + ssh_find_all_matching(hostkeys[0] == ',' ? hostkeys + 1 : hostkeys, + allowed); + if (kept == NULL) { + /* Nothing was allowed */ + return -1; + } + + rc = ssh_options_set_algo(session, + SSH_HOSTKEYS, + kept, + &session->opts.wanted_methods[SSH_HOSTKEYS]); + SAFE_FREE(kept); + if (rc < 0) { + return -1; + } + } +#ifdef WITH_GSSAPI + if (session->opts.gssapi_key_exchange) { + ok = ssh_gssapi_init(session); + if (ok != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + gssapi_algs = ssh_gssapi_kex_mechs(session); + if (gssapi_algs == NULL) { + return SSH_ERROR; + } + ssh_gssapi_free(session); + + /* Prefix the default algorithms with gsskex algs */ + session->opts.wanted_methods[SSH_KEX] = + ssh_prefix_without_duplicates(ssh_kex_get_default_methods(SSH_KEX), + gssapi_algs); + + if (strlen(hostkeys) == 0) { + session->opts.wanted_methods[SSH_HOSTKEYS] = strdup("null"); + } + + SAFE_FREE(gssapi_algs); + } +#endif /* WITH_GSSAPI */ + + for (i = 0; i < SSH_KEX_METHODS; i++) { + wanted = session->opts.wanted_methods[i]; + if (wanted == NULL) { + if (ssh_fips_mode()) { + wanted = ssh_kex_get_fips_methods(i); + } else { + wanted = ssh_kex_get_default_methods(i); + } + } + if (wanted == NULL) { + for (j = 0; j < i; j++) { + SAFE_FREE(server->methods[j]); + } + return -1; + } + + server->methods[i] = strdup(wanted); + if (server->methods[i] == NULL) { + for (j = 0; j < i; j++) { + SAFE_FREE(server->methods[j]); + } + return -1; + } + } + + /* Do not append the extensions during rekey */ + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) { + return SSH_OK; + } + + rc = ssh_kex_append_extensions(session, server); + return rc; +} + +int ssh_server_init_kex(ssh_session session) { + int i; + + if (session->session_state > SSH_SESSION_STATE_BANNER_RECEIVED) { + return SSH_ERROR; + } + + /* free any currently-set methods: server_set_kex will allocate new ones */ + for (i = 0; i < SSH_KEX_METHODS; i++) { + SAFE_FREE(session->next_crypto->server_kex.methods[i]); + } + + return server_set_kex(session); +} + +/** + * @internal + * + * @brief Sends SSH extension information from the server to client. + * + * A server may send this message (`SSH_MSG_EXT_INFO`) after its first + * `SSH_MSG_NEWKEYS` message or just before sending `SSH_MSG_USERAUTH_SUCCESS` + * to provide additional extensions support that are not meant for an + * unauthenticated client. + * + * If any error occurs during the packing or sending of the packet, the function + * aborts to avoid partial or corrupted sends. + * + * @param[in] session The SSH session. + * + * @return `SSH_OK` on success, `SSH_ERROR` on failure. + */ +static int ssh_server_send_extensions(ssh_session session) +{ + int rc; + const char *hostkey_algorithms = NULL; + + SSH_LOG(SSH_LOG_PACKET, "Sending SSH_MSG_EXT_INFO"); + + if (session->opts.pubkey_accepted_types) { + hostkey_algorithms = session->opts.pubkey_accepted_types; + } else { + if (ssh_fips_mode()) { + hostkey_algorithms = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + /* There are no restrictions to the accepted public keys */ + hostkey_algorithms = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdssss", + SSH2_MSG_EXT_INFO, + 2, /* nr. of extensions */ + "server-sig-algs", + hostkey_algorithms, + "publickey-hostbound@openssh.com", + "0"); + if (rc != SSH_OK) { + goto error; + } + + if (ssh_packet_send(session) == SSH_ERROR) { + goto error; + } + + return 0; +error: + ssh_buffer_reinit(session->out_buffer); + + return -1; +} + +SSH_PACKET_CALLBACK(ssh_packet_kexdh_init){ + (void)packet; + (void)type; + (void)user; + + SSH_LOG(SSH_LOG_PACKET,"Received SSH_MSG_KEXDH_INIT"); + if(session->dh_handshake_state != DH_STATE_INIT){ + SSH_LOG(SSH_LOG_RARE,"Invalid state for SSH_MSG_KEXDH_INIT"); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; + } + + /* If first_kex_packet_follows guess was wrong, ignore this message. */ + if (session->first_kex_follows_guess_wrong != 0) { + SSH_LOG(SSH_LOG_RARE, "first_kex_packet_follows guess was wrong, " + "ignoring first SSH_MSG_KEXDH_INIT message"); + session->first_kex_follows_guess_wrong = 0; + + return SSH_PACKET_USED; + } + SSH_LOG(SSH_LOG_DEBUG, "Calling next KEXDH handler"); + return SSH_PACKET_NOT_USED; +} + +/** + * @brief Prepares server host key parameters for the key exchange process. + * + * Selects the appropriate private host key (RSA, ECDSA, or Ed25519) based on + * the session's configured host key type, sets the corresponding @p digest + * algorithm, and imports the public key blob into the Diffie-Hellman key + * exchange state. + * + * @param[in] session The SSH session to which we are preparing host key + * parameters. + * @param[out] privkey Pointer to receive the selected private host key. + * @param[out] digest Pointer to receive the host key digest algorithm. + * + * @return `SSH_OK` on success; `SSH_ERROR` on failure (invalid key type, export + * failure, or DH import error). + */ +int +ssh_get_key_params(ssh_session session, + ssh_key *privkey, + enum ssh_digest_e *digest) +{ + ssh_key pubkey = NULL; + ssh_string pubkey_blob = NULL; + int rc; + + switch(session->srv.hostkey) { + case SSH_KEYTYPE_RSA: + *privkey = session->srv.rsa_key; + break; + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + *privkey = session->srv.ecdsa_key; + break; + case SSH_KEYTYPE_ED25519: + *privkey = session->srv.ed25519_key; + break; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_UNKNOWN: + default: + *privkey = NULL; + } + + *digest = session->srv.hostkey_digest; + rc = ssh_pki_export_privkey_to_pubkey(*privkey, &pubkey); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "Could not get the public key from the private key"); + + return -1; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc < 0) { + ssh_set_error_oom(session); + return -1; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Could not import server public key"); + return -1; + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief A function to be called each time a step has been done in the + * connection. + */ +static void ssh_server_connection_callback(ssh_session session) +{ + int rc; + + switch (session->session_state) { + case SSH_SESSION_STATE_NONE: + case SSH_SESSION_STATE_CONNECTING: + case SSH_SESSION_STATE_SOCKET_CONNECTED: + break; + case SSH_SESSION_STATE_BANNER_RECEIVED: + if (session->clientbanner == NULL) { + goto error; + } + set_status(session, 0.4f); + SSH_LOG(SSH_LOG_DEBUG, + "SSH client banner: %s", session->clientbanner); + + /* Here we analyze the different protocols the server allows. */ + rc = ssh_analyze_banner(session, 1); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, + "No version of SSH protocol usable (banner: %s)", + session->clientbanner); + goto error; + } + + /* from now, the packet layer is handling incoming packets */ + ssh_packet_register_socket_callback(session, session->socket); + + ssh_packet_set_default_callbacks(session); + set_status(session, 0.5f); + session->session_state = SSH_SESSION_STATE_INITIAL_KEX; + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + break; + case SSH_SESSION_STATE_INITIAL_KEX: + /* TODO: This state should disappear in favor of get_key handle */ + break; + case SSH_SESSION_STATE_KEXINIT_RECEIVED: + set_status(session, 0.6f); + if ((session->flags & SSH_SESSION_FLAG_KEXINIT_SENT) == 0) { + rc = server_set_kex(session); + if (rc == SSH_ERROR) { + goto error; + } + /* We are in a rekeying, so we need to send the server kex */ + rc = ssh_send_kex(session); + if (rc < 0) { + goto error; + } + } + ssh_list_kex(&session->next_crypto->client_kex); // log client kex + rc = ssh_kex_select_methods(session); + if (rc < 0) { + goto error; + } + rc = crypt_set_algorithms_server(session); + if (rc == SSH_ERROR) { + goto error; + } + set_status(session, 0.8f); + session->session_state = SSH_SESSION_STATE_DH; + break; + case SSH_SESSION_STATE_DH: + if (session->dh_handshake_state == DH_STATE_FINISHED) { + + rc = ssh_packet_set_newkeys(session, SSH_DIRECTION_IN); + if (rc != SSH_OK) { + goto error; + } + + /* + * If the client supports extension negotiation, we will send + * our supported extensions now. This is the first message after + * sending NEWKEYS message and after turning on crypto. + */ + if (session->extensions & SSH_EXT_NEGOTIATION && + session->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + /* + * Only send an SSH_MSG_EXT_INFO message the first time the + * client undergoes NEWKEYS. It is unexpected for this message + * to be sent upon rekey, and may cause clients to log error + * messages. + * + * The session_state can not be used for this purpose because it + * is re-set to SSH_SESSION_STATE_KEXINIT_RECEIVED during rekey. + * So, use the connected flag which transitions from non-zero + * below. + * + * See also: + * - https://bugzilla.mindrot.org/show_bug.cgi?id=2929 + */ + if (session->connected == 0) { + ssh_server_send_extensions(session); + } + } + + set_status(session, 1.0f); + session->connected = 1; + session->session_state = SSH_SESSION_STATE_AUTHENTICATING; + if (session->flags & SSH_SESSION_FLAG_AUTHENTICATED) + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + + } + break; + case SSH_SESSION_STATE_AUTHENTICATING: + break; + case SSH_SESSION_STATE_ERROR: + goto error; + default: + ssh_set_error(session, SSH_FATAL, "Invalid state %d", + session->session_state); + } + + return; +error: + ssh_socket_close(session->socket); + session->alive = 0; + session->session_state = SSH_SESSION_STATE_ERROR; +} + +/** + * @internal + * + * @brief Gets the banner from socket and saves it in session. + * Updates the session state + * + * @param data pointer to the beginning of header + * @param len size of the banner + * @param user is a pointer to session + * @returns Number of bytes processed, or zero if the banner is not complete. + */ +static size_t callback_receive_banner(const void *data, size_t len, void *user) +{ + char *buffer = (char *)data; + ssh_session session = (ssh_session)user; + char *str = NULL; + size_t i; + size_t processed = 0; + + for (i = 0; i < len; i++) { +#ifdef WITH_PCAP + if (session->pcap_ctx && buffer[i] == '\n') { + ssh_pcap_context_write(session->pcap_ctx, + SSH_PCAP_DIR_IN, + buffer, + (uint32_t)(i + 1), + (uint32_t)(i + 1)); + } +#endif + if (buffer[i] == '\r') { + buffer[i] = '\0'; + } + + if (buffer[i] == '\n') { + buffer[i] = '\0'; + + str = strdup(buffer); + if (str == NULL) { + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_set_error_oom(session); + return 0; + } + /* number of bytes read */ + processed = i + 1; + session->clientbanner = str; + session->session_state = SSH_SESSION_STATE_BANNER_RECEIVED; + SSH_LOG(SSH_LOG_PACKET, "Received banner: %s", str); + session->ssh_connection_callback(session); + + return processed; + } + + if (i > 127) { + /* Too big banner */ + session->session_state = SSH_SESSION_STATE_ERROR; + ssh_set_error(session, SSH_FATAL, + "Receiving banner: too large banner"); + + return 0; + } + } + + return processed; +} + +/* returns 0 until the key exchange is not finished */ +static int ssh_server_kex_termination(void *s){ + ssh_session session = s; + if (session->session_state != SSH_SESSION_STATE_ERROR && + session->session_state != SSH_SESSION_STATE_AUTHENTICATING && + session->session_state != SSH_SESSION_STATE_AUTHENTICATED && + session->session_state != SSH_SESSION_STATE_DISCONNECTED) + return 0; + else + return 1; +} + +/* FIXME: auth_methods should be unsigned */ +void ssh_set_auth_methods(ssh_session session, int auth_methods) +{ + /* accept only methods in range */ + session->auth.supported_methods = (uint32_t)auth_methods & 0x3fU; +} + +int ssh_send_issue_banner(ssh_session session, const ssh_string banner) +{ + int rc = SSH_ERROR; + + if (session == NULL) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sending a server issue banner"); + + rc = ssh_buffer_pack(session->out_buffer, + "bSs", + SSH2_MSG_USERAUTH_BANNER, + banner, + ""); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = ssh_packet_send(session); + return rc; +} + +/* Do the banner and key exchange */ +int ssh_handle_key_exchange(ssh_session session) +{ + int rc; + + if (session->session_state != SSH_SESSION_STATE_NONE) { + goto pending; + } + + rc = ssh_send_banner(session, 1); + if (rc < 0) { + return SSH_ERROR; + } + + session->alive = 1; + + session->ssh_connection_callback = ssh_server_connection_callback; + session->session_state = SSH_SESSION_STATE_SOCKET_CONNECTED; + ssh_socket_set_callbacks(session->socket,&session->socket_callbacks); + session->socket_callbacks.data = callback_receive_banner; + session->socket_callbacks.exception = ssh_socket_exception_callback; + session->socket_callbacks.userdata = session; + + rc = server_set_kex(session); + if (rc < 0) { + return SSH_ERROR; + } +pending: + rc = ssh_handle_packets_termination(session, SSH_TIMEOUT_USER, + ssh_server_kex_termination,session); + SSH_LOG(SSH_LOG_PACKET, "ssh_handle_key_exchange: current state : %d", + session->session_state); + if (rc != SSH_OK) { + return rc; + } + if (session->session_state == SSH_SESSION_STATE_ERROR || + session->session_state == SSH_SESSION_STATE_DISCONNECTED) { + return SSH_ERROR; + } + + return SSH_OK; +} + +/* messages */ + +/** @internal + * replies to an SSH_AUTH packet with a default (denied) response. + */ +int ssh_auth_reply_default(ssh_session session,int partial) { + char methods_c[128] = {0}; + int rc = SSH_ERROR; + + + if (session->auth.supported_methods == 0) { + session->auth.supported_methods = SSH_AUTH_METHOD_PUBLICKEY | SSH_AUTH_METHOD_PASSWORD; + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_PUBLICKEY) { + strncat(methods_c, "publickey,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_GSSAPI_MIC){ + strncat(methods_c,"gssapi-with-mic,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + /* Check if GSSAPI Key exchange was performed */ + if (session->auth.supported_methods & SSH_AUTH_METHOD_GSSAPI_KEYEX) { + if (ssh_session_kex_is_gss(session)) { + strncat(methods_c, + "gssapi-keyex,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_INTERACTIVE) { + strncat(methods_c, "keyboard-interactive,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_PASSWORD) { + strncat(methods_c, "password,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + if (session->auth.supported_methods & SSH_AUTH_METHOD_HOSTBASED) { + strncat(methods_c, "hostbased,", + sizeof(methods_c) - strlen(methods_c) - 1); + } + + if (methods_c[0] == '\0' || methods_c[strlen(methods_c)-1] != ',') { + return SSH_ERROR; + } + + /* Strip the comma. */ + methods_c[strlen(methods_c) - 1] = '\0'; // strip the comma. We are sure there is at + + SSH_LOG(SSH_LOG_PACKET, + "Sending a auth failure. methods that can continue: %s", methods_c); + + rc = ssh_buffer_pack(session->out_buffer, + "bsb", + SSH2_MSG_USERAUTH_FAILURE, + methods_c, + partial ? 1 : 0); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_packet_send(session); + return rc; +} + +/** + * @internal + * + * @brief Sends default refusal for a channel open request. + * + * Default handler that rejects incoming SSH channel open requests by sending + * a `SSH2_MSG_CHANNEL_OPEN_FAILURE` packet with "administratively prohibited" + * reason code. Used when no custom channel open handler is registered. + * + * @param[in] msg The SSH message containing the channel open request details. + * + * @return `SSH_OK` on successful packet send; `SSH_ERROR` on buffer allocation + * or packet send failure. + */ +static int ssh_message_channel_request_open_reply_default(ssh_message msg) { + int rc; + + SSH_LOG(SSH_LOG_FUNCTIONS, "Refusing a channel"); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bdddd", + SSH2_MSG_CHANNEL_OPEN_FAILURE, + msg->channel_request_open.sender, + SSH2_OPEN_ADMINISTRATIVELY_PROHIBITED, + 0, /* reason is empty string */ + 0); /* language string */ + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + rc = ssh_packet_send(msg->session); + return rc; +} + +/** + * @internal + * + * @brief Sends default refusal for a channel request. + * + * Default handler that rejects incoming SSH channel requests. If the client + * requested a reply (`want_reply`), sends `SSH2_MSG_CHANNEL_FAILURE` to the + * specific channel. If no reply requested, logs the refusal and returns + * success. + * + * @param[in] msg The SSH message containing the channel request details. + * + * @return `SSH_OK` on success; `SSH_ERROR` if buffer allocation or packet send + * fails. + */ +static int ssh_message_channel_request_reply_default(ssh_message msg) { + uint32_t channel; + int rc; + + if (msg->channel_request.want_reply) { + channel = msg->channel_request.channel->remote_channel; + + SSH_LOG(SSH_LOG_PACKET, + "Sending a default channel_request denied to channel %" PRIu32, channel); + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bd", + SSH2_MSG_CHANNEL_FAILURE, + channel); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + return ssh_packet_send(msg->session); + } + + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request failed!"); + + return SSH_OK; +} + +static int ssh_message_service_request_reply_default(ssh_message msg) { + /* The only return code accepted by specifications are success or disconnect */ + return ssh_message_service_reply_success(msg); +} + +/** + * @brief Sends `SSH2_MSG_SERVICE_ACCEPT` to the client + * + * @param msg The message to reply to + * + * @returns `SSH_OK` when success otherwise `SSH_ERROR` + */ +int ssh_message_service_reply_success(ssh_message msg) +{ + ssh_session session = NULL; + int rc; + + if (msg == NULL) { + return SSH_ERROR; + } + session = msg->session; + + SSH_LOG(SSH_LOG_PACKET, + "Sending a SERVICE_ACCEPT for service %s", msg->service_request.service); + + rc = ssh_buffer_pack(session->out_buffer, + "bs", + SSH2_MSG_SERVICE_ACCEPT, + msg->service_request.service); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_packet_send(msg->session); + return rc; +} + +/** + * @brief Send a global request success message + * + * @param msg The message + * + * @param bound_port The remote bind port + * + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` + */ +int ssh_message_global_request_reply_success(ssh_message msg, uint16_t bound_port) { + int rc; + + SSH_LOG(SSH_LOG_FUNCTIONS, "Accepting a global request"); + + if (msg->global_request.want_reply) { + if (ssh_buffer_add_u8(msg->session->out_buffer + , SSH2_MSG_REQUEST_SUCCESS) < 0) { + goto error; + } + + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + && msg->global_request.bind_port == 0) { + rc = ssh_buffer_pack(msg->session->out_buffer, "d", bound_port); + if (rc != SSH_OK) { + ssh_set_error_oom(msg->session); + goto error; + } + } + + return ssh_packet_send(msg->session); + } + + if(msg->global_request.type == SSH_GLOBAL_REQUEST_TCPIP_FORWARD + && msg->global_request.bind_port == 0) { + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the remote port!"); + } + + return SSH_OK; +error: + return SSH_ERROR; +} + +/** + * @internal + * + * @brief Sends default refusal for a global request. + * + * Default handler that rejects incoming SSH global requests. If the client + * requested a reply (`want_reply`), sends `SSH2_MSG_REQUEST_FAILURE`. If no + * reply requested, logs the refusal and returns success immediately. + * + * @param[in] msg The SSH message containing the global request details. + * + * @return `SSH_OK` on success; `SSH_ERROR` if buffer allocation or packet send + * fails. + */ +static int ssh_message_global_request_reply_default(ssh_message msg) { + SSH_LOG(SSH_LOG_FUNCTIONS, "Refusing a global request"); + + if (msg->global_request.want_reply) { + if (ssh_buffer_add_u8(msg->session->out_buffer + , SSH2_MSG_REQUEST_FAILURE) < 0) { + goto error; + } + return ssh_packet_send(msg->session); + } + SSH_LOG(SSH_LOG_PACKET, + "The client doesn't want to know the request failed!"); + + return SSH_OK; +error: + return SSH_ERROR; +} + +int ssh_message_reply_default(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + switch(msg->type) { + case SSH_REQUEST_AUTH: + return ssh_auth_reply_default(msg->session, 0); + case SSH_REQUEST_CHANNEL_OPEN: + return ssh_message_channel_request_open_reply_default(msg); + case SSH_REQUEST_CHANNEL: + return ssh_message_channel_request_reply_default(msg); + case SSH_REQUEST_SERVICE: + return ssh_message_service_request_reply_default(msg); + case SSH_REQUEST_GLOBAL: + return ssh_message_global_request_reply_default(msg); + default: + SSH_LOG(SSH_LOG_PACKET, + "Don't know what to default reply to %d type", + msg->type); + break; + } + + return -1; +} + +/** + * @brief Gets the service name from the service request message + * + * @param msg The service request message + * + * @returns the service name from the message + */ +const char *ssh_message_service_service(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + return msg->service_request.service; +} + +const char *ssh_message_auth_user(ssh_message msg) { + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.username; +} + +const char *ssh_message_auth_password(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.password; +} + +ssh_key ssh_message_auth_pubkey(ssh_message msg) { + if (msg == NULL) { + return NULL; + } + + return msg->auth_request.pubkey; +} + +ssh_public_key ssh_message_auth_publickey(ssh_message msg){ + if (msg == NULL) { + return NULL; + } + + return ssh_pki_convert_key_to_publickey(msg->auth_request.pubkey); +} + +enum ssh_publickey_state_e ssh_message_auth_publickey_state(ssh_message msg){ + if (msg == NULL) { + return -1; + } + return msg->auth_request.signature_state; +} + +/** + * @brief Check if the message is a keyboard-interactive response + * + * @param msg The message to check + * + * @returns 1 if the message is a response, otherwise 0 + */ +int ssh_message_auth_kbdint_is_response(ssh_message msg) { + if (msg == NULL) { + return -1; + } + + return msg->auth_request.kbdint_response != 0; +} + +/* FIXME: methods should be unsigned */ +/** + * @brief Sets the supported authentication methods to a message + * + * @param msg The message + * + * @param methods Methods to set to the message. + * The supported methods are listed in ssh_set_auth_methods + * @see ssh_set_auth_methods + * + * @returns 0 on success, otherwise -1 + */ +int ssh_message_auth_set_methods(ssh_message msg, int methods) { + if (msg == NULL || msg->session == NULL) { + return -1; + } + + if (methods < 0) { + return -1; + } + + msg->session->auth.supported_methods = (uint32_t)methods; + + return 0; +} + +/** + * @brief Sends an interactive authentication request message. + * + * Builds and sends an `SSH2_MSG_USERAUTH_INFO_REQUEST` packet containing the + * given name and @p instruction, followed by a number of @p prompts with + * associated @p echo flags to control whether user input is echoed. + * It initializes the keyboard-interactive state in the session. + * + * @param[in] msg The SSH message representing the client + * authentication request. + * @param[in] name The name of the authentication request. + * @param[in] instruction Instruction string with information for the user. + * @param[in] num_prompts Number of prompts to send. The arrays prompts and + * echo must both have num_prompts elements. + * @param[in] prompts Array of @p num_prompts prompt strings to display. + * @param[in] echo Array of num_prompts boolean values (0 or 1). A + * non-zero value means the user input for that prompt + * is echoed (visible); 0 means the input is hidden + * (typically for passwords). + * + * @return `SSH_OK` on successful send; `SSH_ERROR` on failure. + */ +int ssh_message_auth_interactive_request(ssh_message msg, const char *name, + const char *instruction, unsigned int num_prompts, + const char **prompts, char *echo) { + int rc; + unsigned int i = 0; + + if(name == NULL || instruction == NULL) { + return SSH_ERROR; + } + if(num_prompts > 0 && (prompts == NULL || echo == NULL)) { + return SSH_ERROR; + } + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bsssd", + SSH2_MSG_USERAUTH_INFO_REQUEST, + name, + instruction, + "", /* language tag */ + num_prompts); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + for(i = 0; i < num_prompts; i++) { + rc = ssh_buffer_pack(msg->session->out_buffer, + "sb", + prompts[i], + echo[i] ? 1 : 0); + if (rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + } + + rc = ssh_packet_send(msg->session); + + /* fill in the kbdint structure */ + if (msg->session->kbdint == NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Warning: Got a keyboard-interactive response " + "but it seems we didn't send the request."); + + msg->session->kbdint = ssh_kbdint_new(); + if (msg->session->kbdint == NULL) { + ssh_set_error_oom(msg->session); + + return SSH_ERROR; + } + } else { + ssh_kbdint_clean(msg->session->kbdint); + } + + msg->session->kbdint->name = strdup(name); + if(msg->session->kbdint->name == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + msg->session->kbdint->instruction = strdup(instruction); + if(msg->session->kbdint->instruction == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + + msg->session->kbdint->nprompts = num_prompts; + if(num_prompts > 0) { + msg->session->kbdint->prompts = calloc(num_prompts, sizeof(char *)); + if (msg->session->kbdint->prompts == NULL) { + msg->session->kbdint->nprompts = 0; + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + msg->session->kbdint->echo = calloc(num_prompts, sizeof(unsigned char)); + if (msg->session->kbdint->echo == NULL) { + ssh_set_error_oom(msg->session); + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_ERROR; + } + for (i = 0; i < num_prompts; i++) { + msg->session->kbdint->echo[i] = echo[i]; + msg->session->kbdint->prompts[i] = strdup(prompts[i]); + if (msg->session->kbdint->prompts[i] == NULL) { + ssh_set_error_oom(msg->session); + msg->session->kbdint->nprompts = i; + ssh_kbdint_free(msg->session->kbdint); + msg->session->kbdint = NULL; + return SSH_PACKET_USED; + } + } + } else { + msg->session->kbdint->prompts = NULL; + msg->session->kbdint->echo = NULL; + } + msg->session->auth.state = SSH_AUTH_STATE_INFO; + + return rc; +} + +/** + * @brief Sends `SSH2_MSG_USERAUTH_SUCCESS` or `SSH2_MSG_USERAUTH_FAILURE` + * message depending on the success of the authentication method + * + * @param session The session to reply to + * + * @param partial Denotes if the authentication process was partially completed + * (unsuccessful) + * + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` + */ +int ssh_auth_reply_success(ssh_session session, int partial) +{ + struct ssh_crypto_struct *crypto = NULL; + int r; + + if (session == NULL) { + return SSH_ERROR; + } + + if (partial) { + return ssh_auth_reply_default(session, partial); + } + + r = ssh_buffer_add_u8(session->out_buffer,SSH2_MSG_USERAUTH_SUCCESS); + if (r < 0) { + return SSH_ERROR; + } + + r = ssh_packet_send(session); + + /* + * Consider the session as having been authenticated only after sending + * the `USERAUTH_SUCCESS` message. Setting these flags after + * ssh_packet_send ensures that a rekey is not triggered prematurely, + * causing the message to be queued. + */ + session->session_state = SSH_SESSION_STATE_AUTHENTICATED; + session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_OUT); + if (crypto != NULL && crypto->delayed_compress_out) { + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression OUT"); + crypto->do_compress_out = 1; + } + + crypto = ssh_packet_get_current_crypto(session, SSH_DIRECTION_IN); + if (crypto != NULL && crypto->delayed_compress_in) { + SSH_LOG(SSH_LOG_DEBUG, "Enabling delayed compression IN"); + crypto->do_compress_in = 1; + } + return r; +} + +/** + * @brief Replies to an authentication request with success. + * + * Sends an authentication success message (`SSH2_MSG_USERAUTH_SUCCESS`) to the + * client, or a partial success if further authentication steps are required. + * + * @param[in] msg The SSH authentication message being handled. + * @param[in] partial Set to nonzero if partial success (more auth needed), zero + * for full success. + * + * @return `SSH_OK` on success, `SSH_ERROR` if msg is NULL or on send failure. + */ +int ssh_message_auth_reply_success(ssh_message msg, int partial) { + if(msg == NULL) + return SSH_ERROR; + return ssh_auth_reply_success(msg->session, partial); +} + +/** + * @brief Answer `SSH2_MSG_USERAUTH_PK_OK` to a pubkey authentication request + * + * @param msg The message + * + * @param algo The algorithm of the accepted public key + * + * @param pubkey The accepted public key + * + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` + */ +int ssh_message_auth_reply_pk_ok(ssh_message msg, ssh_string algo, ssh_string pubkey) { + int rc; + if (msg == NULL) { + return SSH_ERROR; + } + + rc = ssh_buffer_pack(msg->session->out_buffer, + "bSS", + SSH2_MSG_USERAUTH_PK_OK, + algo, + pubkey); + if(rc != SSH_OK){ + ssh_set_error_oom(msg->session); + return SSH_ERROR; + } + + rc = ssh_packet_send(msg->session); + return rc; +} + +/** + * @brief Answer `SSH2_MSG_USERAUTH_PK_OK` to a pubkey authentication request + * + * @param msg The message + * + * @returns `SSH_OK` on success, otherwise `SSH_ERROR` + */ +int ssh_message_auth_reply_pk_ok_simple(ssh_message msg) +{ + ssh_string algo = NULL; + ssh_string pubkey_blob = NULL; + int ret; + + algo = ssh_string_from_char(msg->auth_request.sigtype); + if (algo == NULL) { + return SSH_ERROR; + } + + ret = ssh_pki_export_pubkey_blob(msg->auth_request.pubkey, &pubkey_blob); + if (ret < 0) { + SSH_STRING_FREE(algo); + return SSH_ERROR; + } + + ret = ssh_message_auth_reply_pk_ok(msg, algo, pubkey_blob); + + SSH_STRING_FREE(algo); + SSH_STRING_FREE(pubkey_blob); + + return ret; +} + +/** + * @brief Get the originator address from the channel open message. + * + * @param[in] msg The message. + * + * @return The originator address, or NULL. + */ +const char *ssh_message_channel_request_open_originator(ssh_message msg){ + return msg->channel_request_open.originator; +} + +/** + * @brief Get the originator port from the channel open message. + * + * @param[in] msg The message. + * + * @return The originator port. + */ +int ssh_message_channel_request_open_originator_port(ssh_message msg){ + return msg->channel_request_open.originator_port; +} + +/** + * @brief Get the destination address from the channel open message. + * + * @param[in] msg The message. + * + * @return The destination address, or NULL. + */ +const char *ssh_message_channel_request_open_destination(ssh_message msg){ + return msg->channel_request_open.destination; +} + +/** + * @brief Get the destination port from the channel open message. + * + * @param[in] msg The message. + * + * @return The destination port. + */ +int ssh_message_channel_request_open_destination_port(ssh_message msg){ + return msg->channel_request_open.destination_port; +} + +/** + * @brief Get the channel associated with the message. + * + * @param[in] msg The message. + * + * @return The channel associated with the message. + */ +ssh_channel ssh_message_channel_request_channel(ssh_message msg){ + return msg->channel_request.channel; +} + +/** + * @brief Get the terminal type from the message. + * + * @param[in] msg The message. + * + * @return The terminal type (e.g. "xterm"), or NULL. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +const char *ssh_message_channel_request_pty_term(ssh_message msg){ + return msg->channel_request.TERM; +} + +/** + * @brief Get the terminal width from the message. + * + * @param[in] msg The message. + * + * @return The terminal width in characters. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +int ssh_message_channel_request_pty_width(ssh_message msg){ + return msg->channel_request.width; +} + +/** + * @brief Get the terminal height from the message. + * + * @param[in] msg The message. + * + * @return The terminal height in characters. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +int ssh_message_channel_request_pty_height(ssh_message msg){ + return msg->channel_request.height; +} + +/** + * @brief Get the terminal pixel width from the message. + * + * @param[in] msg The message. + * + * @return The pixel width. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +int ssh_message_channel_request_pty_pxwidth(ssh_message msg){ + return msg->channel_request.pxwidth; +} + +/** + * @brief Get the terminal pixel height from the message. + * + * @param[in] msg The message. + * + * @return The pixel height. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function channel_pty_request_function. + * + * @see channel_pty_request_function. + */ +int ssh_message_channel_request_pty_pxheight(ssh_message msg){ + return msg->channel_request.pxheight; +} + +/** + * @brief Get the name of the environment variable from the message. + * + * @param[in] msg The message. + * + * @return The variable name, or NULL. + */ +const char *ssh_message_channel_request_env_name(ssh_message msg){ + return msg->channel_request.var_name; +} + +/** + * @brief Get the value of the environment variable from the message. + * + * @param[in] msg The message. + * + * @return The variable value, or NULL. + */ +const char *ssh_message_channel_request_env_value(ssh_message msg){ + return msg->channel_request.var_value; +} + +/** + * @brief Get the command from a channel request message. + * + * @param[in] msg The message. + * + * @return The command, or NULL. + */ +const char *ssh_message_channel_request_command(ssh_message msg){ + return msg->channel_request.command; +} + +/** + * @brief Get the subsystem from a channel request message. + * + * @param[in] msg The message. + * + * @return The subsystem, or NULL. + */ +const char *ssh_message_channel_request_subsystem(ssh_message msg){ + return msg->channel_request.subsystem; +} + +/** + * @brief Check if the X11 request is for a single connection. + * + * @param[in] msg The message. + * + * @return 1 if single connection, 0 otherwise. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ +int ssh_message_channel_request_x11_single_connection(ssh_message msg){ + return msg->channel_request.x11_single_connection ? 1 : 0; +} + +/** + * @brief Get the X11 authentication protocol from the message. + * + * @param[in] msg The message. + * + * @return The authentication protocol, or NULL. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ +const char *ssh_message_channel_request_x11_auth_protocol(ssh_message msg){ + return msg->channel_request.x11_auth_protocol; +} + +/** + * @brief Get the X11 authentication cookie from the message. + * + * @param[in] msg The message. + * + * @return The authentication cookie, or NULL. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ +const char *ssh_message_channel_request_x11_auth_cookie(ssh_message msg){ + return msg->channel_request.x11_auth_cookie; +} + +/** + * @brief Get the X11 screen number from the message. + * + * @param[in] msg The message. + * + * @return The screen number. + * + * @deprecated This function should not be used anymore as there is a + * callback based server implementation function + * channel_open_request_x11_function. + * + * @see channel_open_request_x11_function. + */ +int ssh_message_channel_request_x11_screen_number(ssh_message msg){ + return msg->channel_request.x11_screen_number; +} + +/** + * @brief Get the bind address from the global request message. + * + * @param[in] msg The message. + * + * @return The bind address, or NULL. + */ +const char *ssh_message_global_request_address(ssh_message msg){ + return msg->global_request.bind_address; +} + +/** + * @brief Get the bind port from the global request message. + * + * @param[in] msg The message. + * + * @return The bind port. + */ +int ssh_message_global_request_port(ssh_message msg){ + return msg->global_request.bind_port; +} + +/** @brief defines the ssh_message callback + * @param session the current ssh session + * @param[in] ssh_bind_message_callback a function pointer to a callback taking the + * current ssh session and received message as parameters. the function returns + * 0 if the message has been parsed and treated successfully, 1 otherwise (libssh + * must take care of the response). + * @param[in] data void pointer to be passed to callback functions + */ +void ssh_set_message_callback(ssh_session session, + int(*ssh_bind_message_callback)(ssh_session session, ssh_message msg, void *data), + void *data) { + session->ssh_message_callback = ssh_bind_message_callback; + session->ssh_message_callback_data = data; +} + +/** + * @brief Execute callbacks for the messages in the queue. + * + * @param[in] session The session. + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + */ +int ssh_execute_message_callbacks(ssh_session session){ + ssh_message msg=NULL; + int ret; + ssh_handle_packets(session, SSH_TIMEOUT_NONBLOCKING); + if(!session->ssh_message_list) + return SSH_OK; + if(session->ssh_message_callback){ + while((msg=ssh_message_pop_head(session)) != NULL) { + ret=session->ssh_message_callback(session,msg, + session->ssh_message_callback_data); + if(ret==1){ + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) + return ret; + } else { + ssh_message_free(msg); + } + } + } else { + while((msg=ssh_message_pop_head(session)) != NULL) { + ret = ssh_message_reply_default(msg); + ssh_message_free(msg); + if(ret != SSH_OK) + return ret; + } + } + return SSH_OK; +} + +/** + * @brief Sends a keepalive message to the session + * + * @param session The session to send the message to + * + * @returns `SSH_OK` + */ +int ssh_send_keepalive(ssh_session session) +{ + /* Client denies the request, so the error code is not meaningful */ + (void)ssh_global_request(session, "keepalive@openssh.com", NULL, 1); + + return SSH_OK; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/session.c b/src/libs/libssh-0.12.2/src/session.c new file mode 100644 index 000000000000..e9e91f54d7af --- /dev/null +++ b/src/libs/libssh-0.12.2/src/session.c @@ -0,0 +1,1397 @@ +/* + * session.c - non-networking functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#ifdef _WIN32 +#include +#endif + +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/crypto.h" +#include "libssh/server.h" +#include "libssh/socket.h" +#include "libssh/ssh2.h" +#include "libssh/agent.h" +#include "libssh/packet.h" +#include "libssh/kex.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" +#include "libssh/pki.h" +#include "libssh/gssapi.h" + +#define FIRST_CHANNEL 42 // why not ? it helps to find bugs. + +/** + * @defgroup libssh_session The SSH session functions + * @ingroup libssh + * + * Functions that manage a session. + * + * @{ + */ + +/** + * @brief Create a new ssh session. + * + * @returns A new ssh_session pointer, NULL on error. + */ +ssh_session ssh_new(void) +{ + ssh_session session = NULL; + char *id = NULL; + int rc; + + session = calloc(1, sizeof (struct ssh_session_struct)); + if (session == NULL) { + return NULL; + } + + session->next_crypto = crypto_new(); + if (session->next_crypto == NULL) { + goto err; + } + + session->socket = ssh_socket_new(session); + if (session->socket == NULL) { + goto err; + } + + session->out_buffer = ssh_buffer_new(); + if (session->out_buffer == NULL) { + goto err; + } + + session->in_buffer = ssh_buffer_new(); + if (session->in_buffer == NULL) { + goto err; + } + + session->out_queue = ssh_list_new(); + if (session->out_queue == NULL) { + goto err; + } + + session->alive = 0; + session->auth.supported_methods = 0; + ssh_set_blocking(session, 1); + session->maxchannel = FIRST_CHANNEL; + session->proxy_root = true; + + session->agent = ssh_agent_new(session); + if (session->agent == NULL) { + goto err; + } + + /* Initialise a default PKI context */ + session->pki_context = ssh_pki_ctx_new(); + if (session->pki_context == NULL) { + goto err; + } + + /* OPTIONS */ + session->opts.StrictHostKeyChecking = 1; + session->opts.port = 22; + session->opts.fd = -1; + session->opts.compressionlevel = 7; + session->opts.nodelay = 0; + session->opts.identities_only = false; + session->opts.control_master = SSH_CONTROL_MASTER_NO; + + session->opts.flags = SSH_OPT_FLAG_PASSWORD_AUTH | + SSH_OPT_FLAG_PUBKEY_AUTH | + SSH_OPT_FLAG_KBDINT_AUTH | + SSH_OPT_FLAG_GSSAPI_AUTH; + + session->opts.exp_flags = 0; + + session->opts.identity = ssh_list_new(); + if (session->opts.identity == NULL) { + goto err; + } + session->opts.identity_non_exp = ssh_list_new(); + if (session->opts.identity_non_exp == NULL) { + goto err; + } + + session->opts.certificate = ssh_list_new(); + if (session->opts.certificate == NULL) { + goto err; + } + session->opts.certificate_non_exp = ssh_list_new(); + if (session->opts.certificate_non_exp == NULL) { + goto err; + } + /* the default certificates are loaded automatically from the default + * identities later */ + + session->opts.proxy_jumps = ssh_list_new(); + if (session->opts.proxy_jumps == NULL) { + goto err; + } + + session->opts.proxy_jumps_user_cb = ssh_list_new(); + if (session->opts.proxy_jumps_user_cb == NULL) { + goto err; + } + +#ifdef WITH_GSSAPI + session->opts.gssapi_key_exchange_algs = + strdup(GSSAPI_KEY_EXCHANGE_SUPPORTED); + if (session->opts.gssapi_key_exchange_algs == NULL) { + goto err; + } +#endif /* WITH_GSSAPI */ + + id = strdup("%d/.ssh/id_ed25519"); + if (id == NULL) { + goto err; + } + + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef HAVE_ECC + id = strdup("%d/.ssh/id_ecdsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } +#endif + + id = strdup("%d/.ssh/id_rsa"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef WITH_FIDO2 + /* Add security key identities */ + id = strdup("%d/.ssh/id_ed25519_sk"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } + +#ifdef HAVE_ECC + id = strdup("%d/.ssh/id_ecdsa_sk"); + if (id == NULL) { + goto err; + } + rc = ssh_list_append(session->opts.identity_non_exp, id); + if (rc == SSH_ERROR) { + goto err; + } +#endif /* HAVE_ECC */ +#endif /* WITH_FIDO2 */ + + /* Explicitly initialize states */ + session->session_state = SSH_SESSION_STATE_NONE; + session->pending_call_state = SSH_PENDING_CALL_NONE; + session->packet_state = PACKET_STATE_INIT; + session->dh_handshake_state = DH_STATE_INIT; + session->global_req_state = SSH_CHANNEL_REQ_STATE_NONE; + + session->auth.state = SSH_AUTH_STATE_NONE; + session->auth.service_state = SSH_AUTH_SERVICE_NONE; + + return session; + +err: + free(id); + ssh_free(session); + return NULL; +} + +/** + * @brief Deallocate a SSH session handle. + * + * @param[in] session The SSH session to free. + * + * @see ssh_disconnect() + * @see ssh_new() + */ +void ssh_free(ssh_session session) +{ + int i; + struct ssh_iterator *it = NULL; + struct ssh_buffer_struct *b = NULL; + + if (session == NULL) { + return; + } + + /* + * Delete all channels + * + * This needs the first thing we clean up cause if there is still an open + * channel we call ssh_channel_close() first. So we need a working socket + * and poll context for it. + */ + for (it = ssh_list_get_iterator(session->channels); + it != NULL; + it = ssh_list_get_iterator(session->channels)) { + ssh_channel_do_free(ssh_iterator_value(ssh_channel,it)); + ssh_list_remove(session->channels, it); + } + ssh_list_free(session->channels); + session->channels = NULL; + +#ifdef WITH_PCAP + if (session->pcap_ctx) { + ssh_pcap_context_free(session->pcap_ctx); + session->pcap_ctx = NULL; + } +#endif + + ssh_socket_free(session->socket); + session->socket = NULL; + + if (session->default_poll_ctx) { + ssh_poll_ctx_free(session->default_poll_ctx); + } + + SSH_BUFFER_FREE(session->in_buffer); + SSH_BUFFER_FREE(session->out_buffer); + session->in_buffer = session->out_buffer = NULL; + + if (session->in_hashbuf != NULL) { + SSH_BUFFER_FREE(session->in_hashbuf); + } + if (session->out_hashbuf != NULL) { + SSH_BUFFER_FREE(session->out_hashbuf); + } + + crypto_free(session->current_crypto); + crypto_free(session->next_crypto); + + ssh_agent_free(session->agent); + + SSH_PKI_CTX_FREE(session->pki_context); + + ssh_key_free(session->srv.rsa_key); + session->srv.rsa_key = NULL; + ssh_key_free(session->srv.ecdsa_key); + session->srv.ecdsa_key = NULL; + ssh_key_free(session->srv.ed25519_key); + session->srv.ed25519_key = NULL; + + if (session->ssh_message_list) { + ssh_message msg; + + for (msg = ssh_list_pop_head(ssh_message, session->ssh_message_list); + msg != NULL; + msg = ssh_list_pop_head(ssh_message, session->ssh_message_list)) { + ssh_message_free(msg); + } + ssh_list_free(session->ssh_message_list); + } + + if (session->kbdint != NULL) { + ssh_kbdint_free(session->kbdint); + } + + if (session->packet_callbacks) { + ssh_list_free(session->packet_callbacks); + } + +#ifdef WITH_GSSAPI + ssh_gssapi_free(session); + SAFE_FREE(session->opts.gssapi_key_exchange_algs); +#endif + + /* options */ + if (session->opts.identity) { + char *id = NULL; + + for (id = ssh_list_pop_head(char *, session->opts.identity); + id != NULL; + id = ssh_list_pop_head(char *, session->opts.identity)) { + SAFE_FREE(id); + } + ssh_list_free(session->opts.identity); + } + + if (session->opts.identity_non_exp) { + char *id = NULL; + + for (id = ssh_list_pop_head(char *, session->opts.identity_non_exp); + id != NULL; + id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) { + SAFE_FREE(id); + } + ssh_list_free(session->opts.identity_non_exp); + } + + if (session->opts.certificate) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate); + } + + if (session->opts.certificate_non_exp) { + char *cert = NULL; + + for (cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp); + cert != NULL; + cert = ssh_list_pop_head(char *, session->opts.certificate_non_exp)) { + SAFE_FREE(cert); + } + ssh_list_free(session->opts.certificate_non_exp); + } + + ssh_proxyjumps_free(session->opts.proxy_jumps); + SSH_LIST_FREE(session->opts.proxy_jumps); + SSH_LIST_FREE(session->opts.proxy_jumps_user_cb); + SAFE_FREE(session->opts.proxy_jumps_str); + + while ((b = ssh_list_pop_head(struct ssh_buffer_struct *, + session->out_queue)) != NULL) { + SSH_BUFFER_FREE(b); + } + ssh_list_free(session->out_queue); + + ssh_agent_state_free(session->agent_state); + session->agent_state = NULL; + + SAFE_FREE(session->auth.auto_state); + SAFE_FREE(session->serverbanner); + SAFE_FREE(session->clientbanner); + SAFE_FREE(session->banner); + SAFE_FREE(session->disconnect_message); + SAFE_FREE(session->peer_discon_msg); + + SAFE_FREE(session->opts.agent_socket); + SAFE_FREE(session->opts.bindaddr); + SAFE_FREE(session->opts.username); + SAFE_FREE(session->opts.host); + SAFE_FREE(session->opts.homedir); + SAFE_FREE(session->opts.sshdir); + SAFE_FREE(session->opts.knownhosts); + SAFE_FREE(session->opts.global_knownhosts); + SAFE_FREE(session->opts.ProxyCommand); + SAFE_FREE(session->opts.gss_server_identity); + SAFE_FREE(session->opts.gss_client_identity); + SAFE_FREE(session->opts.pubkey_accepted_types); + SAFE_FREE(session->opts.control_path); + + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (session->opts.wanted_methods[i]) { + SAFE_FREE(session->opts.wanted_methods[i]); + } + } + + SAFE_FREE(session->server_opts.custombanner); + SAFE_FREE(session->server_opts.moduli_file); + + _ssh_remove_legacy_log_cb(); + + /* burn connection, it could contain sensitive data */ + ssh_burn(session, sizeof(struct ssh_session_struct)); + SAFE_FREE(session); +} + +/** + * @brief get the client banner + * + * @param[in] session The SSH session + * + * @return Returns the client banner string or NULL. + */ +const char* ssh_get_clientbanner(ssh_session session) { + if (session == NULL) { + return NULL; + } + + return session->clientbanner; +} + +/** + * @brief get the server banner + * + * @param[in] session The SSH session + * + * @return Returns the server banner string or NULL. + */ +const char* ssh_get_serverbanner(ssh_session session) { + if (!session) { + return NULL; + } + return session->serverbanner; +} + +/** + * @brief get the name of the current key exchange algorithm. + * + * @param[in] session The SSH session + * + * @return Returns the key exchange algorithm string or NULL. + */ +const char* ssh_get_kex_algo(ssh_session session) { + if ((session == NULL) || + (session->current_crypto == NULL)) { + return NULL; + } + + switch (session->current_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + return "diffie-hellman-group1-sha1"; + case SSH_KEX_DH_GROUP14_SHA1: + return "diffie-hellman-group14-sha1"; + case SSH_KEX_DH_GROUP14_SHA256: + return "diffie-hellman-group14-sha256"; + case SSH_GSS_KEX_DH_GROUP14_SHA256: + return "gss-group14-sha256-"; + case SSH_KEX_DH_GROUP16_SHA512: + return "diffie-hellman-group16-sha512"; + case SSH_GSS_KEX_DH_GROUP16_SHA512: + return "gss-group16-sha512-"; + case SSH_KEX_DH_GROUP18_SHA512: + return "diffie-hellman-group18-sha512"; + case SSH_KEX_ECDH_SHA2_NISTP256: + return "ecdh-sha2-nistp256"; + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + return "gss-nistp256-sha256-"; + case SSH_KEX_ECDH_SHA2_NISTP384: + return "ecdh-sha2-nistp384"; + case SSH_KEX_ECDH_SHA2_NISTP521: + return "ecdh-sha2-nistp521"; + case SSH_KEX_CURVE25519_SHA256: + return "curve25519-sha256"; + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + return "curve25519-sha256@libssh.org"; + case SSH_GSS_KEX_CURVE25519_SHA256: + return "gss-curve25519-sha256-"; + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + return "sntrup761x25519-sha512@openssh.com"; + case SSH_KEX_SNTRUP761X25519_SHA512: + return "sntrup761x25519-sha512"; + case SSH_KEX_MLKEM768X25519_SHA256: + return "mlkem768x25519-sha256"; + case SSH_KEX_MLKEM768NISTP256_SHA256: + return "mlkem768nistp256-sha256"; +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: + return "mlkem1024nistp384-sha384"; +#endif /* HAVE_MLKEM1024 */ +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + return "diffie-hellman-group-exchange-sha1"; + case SSH_KEX_DH_GEX_SHA256: + return "diffie-hellman-group-exchange-sha256"; +#endif /* WITH_GEX */ + } + return NULL; +} + +/** + * @brief Check if current session keys were exchanged using + * a GSSAPI key exchange method. + * + * @param[in] session The SSH session. + * + * @return True if GSSAPI key exchange took place, false otherwise + * (other or no key exchange took place). + */ +bool ssh_session_kex_is_gss(ssh_session session) +{ + if (session == NULL || session->current_crypto == NULL) { + return false; + } + return ssh_kex_is_gss(session->current_crypto); +} +/** + * @brief get the name of the input cipher for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns cipher name or NULL. + */ +const char* ssh_get_cipher_in(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL) && + (session->current_crypto->in_cipher != NULL)) { + return session->current_crypto->in_cipher->name; + } + return NULL; +} + +/** + * @brief get the name of the output cipher for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns cipher name or NULL. + */ +const char* ssh_get_cipher_out(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL) && + (session->current_crypto->out_cipher != NULL)) { + return session->current_crypto->out_cipher->name; + } + return NULL; +} + +/** + * @brief get the name of the input HMAC algorithm for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns HMAC algorithm name or NULL if unknown. + */ +const char* ssh_get_hmac_in(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL)) { + return ssh_hmac_type_to_string(session->current_crypto->in_hmac, session->current_crypto->in_hmac_etm); + } + return NULL; +} + +/** + * @brief get the name of the output HMAC algorithm for the given session. + * + * @param[in] session The SSH session. + * + * @return Returns HMAC algorithm name or NULL if unknown. + */ +const char* ssh_get_hmac_out(ssh_session session) { + if ((session != NULL) && + (session->current_crypto != NULL)) { + return ssh_hmac_type_to_string(session->current_crypto->out_hmac, session->current_crypto->out_hmac_etm); + } + return NULL; +} + +/** + * @internal + * @brief Close the connection socket if it is a socket created by us. + * Does not close the sockets provided by the user through options API. + */ +void +ssh_session_socket_close(ssh_session session) +{ + if (session->opts.fd == SSH_INVALID_SOCKET) { + ssh_socket_close(session->socket); + } + session->alive = 0; + session->session_state = SSH_SESSION_STATE_ERROR; +} + +/** + * @brief Disconnect impolitely from a remote host by closing the socket. + * + * Suitable if you forked and want to destroy this session. + * + * @param[in] session The SSH session to disconnect. + */ +void +ssh_silent_disconnect(ssh_session session) +{ + if (session == NULL) { + return; + } + + ssh_session_socket_close(session); + ssh_disconnect(session); +} + +/** + * @brief Set the session in blocking/nonblocking mode. + * + * @param[in] session The ssh session to change. + * + * @param[in] blocking Zero for nonblocking mode. + */ +void ssh_set_blocking(ssh_session session, int blocking) +{ + if (session == NULL) { + return; + } + session->flags &= ~SSH_SESSION_FLAG_BLOCKING; + session->flags |= blocking ? SSH_SESSION_FLAG_BLOCKING : 0; +} + +/** + * @brief Return the blocking mode of libssh + * @param[in] session The SSH session + * @returns 0 if the session is nonblocking, + * @returns 1 if the functions may block. + */ +int ssh_is_blocking(ssh_session session) +{ + return (session->flags & SSH_SESSION_FLAG_BLOCKING) ? 1 : 0; +} + +/* Waits until the output socket is empty */ +static int ssh_flush_termination(void *c){ + ssh_session session = c; + if (ssh_socket_buffered_write_bytes(session->socket) == 0 || + session->session_state == SSH_SESSION_STATE_ERROR) + return 1; + else + return 0; +} + +/** + * @brief Blocking flush of the outgoing buffer + * @param[in] session The SSH session + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying -1 + * means an infinite timeout. This parameter is passed to + * the poll() function. + * @returns `SSH_OK` on success, `SSH_AGAIN` if timeout occurred, + * `SSH_ERROR` otherwise. + */ +int ssh_blocking_flush(ssh_session session, int timeout){ + int rc; + if (session == NULL) { + return SSH_ERROR; + } + + rc = ssh_handle_packets_termination(session, timeout, + ssh_flush_termination, session); + if (rc == SSH_ERROR) { + return rc; + } + if (!ssh_flush_termination(session)) { + rc = SSH_AGAIN; + } + + return rc; +} + +/** + * @brief Check if we are connected. + * + * @param[in] session The session to check if it is connected. + * + * @return 1 if we are connected, 0 if not. + */ +int ssh_is_connected(ssh_session session) { + if (session == NULL) { + return 0; + } + + return session->alive; +} + +/** + * @brief Get the fd of a connection. + * + * In case you'd need the file descriptor of the connection to the server/client. + * + * @param[in] session The ssh session to use. + * + * @return The file descriptor of the connection, or -1 if it is + * not connected + */ +socket_t ssh_get_fd(ssh_session session) { + if (session == NULL) { + return -1; + } + + return ssh_socket_get_fd(session->socket); +} + +/** + * @brief Tell the session it has data to read on the file descriptor without + * blocking. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_toread(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_read_wontblock(session->socket); +} + +/** + * @brief Tell the session it may write to the file descriptor without blocking. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_towrite(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_write_wontblock(session->socket); +} + +/** + * @brief Tell the session it has an exception to catch on the file descriptor. + * + * @param[in] session The ssh session to use. + */ +void ssh_set_fd_except(ssh_session session) { + if (session == NULL) { + return; + } + + ssh_socket_set_except(session->socket); +} + +/** + * @internal + * + * @brief Poll the current session for an event and call the appropriate + * callbacks. This function will not loop until the @p timeout is expired. + * + * This will block until one event happens. + * + * @param[in] session The session handle to use. + * + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying + * `SSH_TIMEOUT_INFINITE` + * (-1) means an infinite timeout. + * Specifying `SSH_TIMEOUT_USER` means to use the timeout + * specified in options. 0 means poll will return + * immediately. + * This parameter is passed to the poll() function. + * + * @return `SSH_OK` on success, `SSH_ERROR` otherwise. + */ +int ssh_handle_packets(ssh_session session, int timeout) +{ + ssh_poll_handle spoll = NULL; + ssh_poll_ctx ctx = NULL; + int tm = timeout; + int rc; + + if (session == NULL || session->socket == NULL) { + return SSH_ERROR; + } + + spoll = ssh_socket_get_poll_handle(session->socket); + if (spoll == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + ssh_poll_add_events(spoll, POLLIN); + ctx = ssh_poll_get_ctx(spoll); + + if (ctx == NULL) { + ctx = ssh_poll_get_default_ctx(session); + if (ctx == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + rc = ssh_poll_ctx_add(ctx, spoll); + if (rc != SSH_OK) { + return SSH_ERROR; + } + } + + if (timeout == SSH_TIMEOUT_USER) { + if (ssh_is_blocking(session)) { + tm = ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + } else { + tm = 0; + } + } + rc = ssh_poll_ctx_dopoll(ctx, tm); + if (rc == SSH_ERROR) { + session->session_state = SSH_SESSION_STATE_ERROR; + } + + return rc; +} + +/** + * @internal + * + * @brief Poll the current session for an event and call the appropriate + * callbacks. + * + * This will block until termination function returns true, or @p timeout + * expired. + * + * @param[in] session The session handle to use. + * + * @param[in] timeout Set an upper limit on the time for which this function + * will block, in milliseconds. Specifying + * `SSH_TIMEOUT_INFINITE` (-1) means an infinite timeout. + * Specifying SSH_TIMEOUT_USER means using the timeout + * specified in options. 0 means poll will return + * immediately. + * SSH_TIMEOUT_DEFAULT uses the session timeout if set or + * uses blocking parameters of the session. + * This parameter is passed to the poll() function. + * + * @param[in] fct Termination function to be used to determine if it is + * possible to stop polling. + * @param[in] user User parameter to be passed to fct termination function. + * @returns `SSH_OK` on success, `SSH_AGAIN` if timeout occurred, + * `SSH_ERROR` otherwise. + */ +int ssh_handle_packets_termination(ssh_session session, + int timeout, + ssh_termination_function fct, + void *user) +{ + struct ssh_timestamp ts; + int timeout_ms = SSH_TIMEOUT_INFINITE; + int tm; + int ret = SSH_OK; + + /* If a timeout has been provided, use it */ + if (timeout >= 0) { + timeout_ms = timeout; + } else { + if (ssh_is_blocking(session)) { + if (timeout == SSH_TIMEOUT_USER || timeout == SSH_TIMEOUT_DEFAULT) { + if (session->opts.timeout > 0 || + session->opts.timeout_usec > 0) { + timeout_ms = + ssh_make_milliseconds(session->opts.timeout, + session->opts.timeout_usec); + } + } + } else { + timeout_ms = SSH_TIMEOUT_NONBLOCKING; + } + } + + /* avoid unnecessary syscall for the SSH_TIMEOUT_NONBLOCKING case */ + if (timeout_ms != SSH_TIMEOUT_NONBLOCKING) { + ssh_timestamp_init(&ts); + } + + tm = timeout_ms; + while(!fct(user)) { + ret = ssh_handle_packets(session, tm); + if (ret == SSH_ERROR) { + break; + } + if (ssh_timeout_elapsed(&ts, timeout_ms)) { + ret = fct(user) ? SSH_OK : SSH_AGAIN; + break; + } + + tm = ssh_timeout_update(&ts, timeout_ms); + } + + return ret; +} + +/** + * @brief Get session status + * + * @param session The ssh session to use. + * + * @returns A bitmask including `SSH_CLOSED`, `SSH_READ_PENDING`, + * `SSH_WRITE_PENDING` or `SSH_CLOSED_ERROR` which + * respectively means the session is closed, has data to + * read on the connection socket and session was closed + * due to an error. + */ +int ssh_get_status(ssh_session session) { + int socketstate; + int r = 0; + + if (session == NULL) { + return 0; + } + + socketstate = ssh_socket_get_status(session->socket); + + if (session->session_state == SSH_SESSION_STATE_DISCONNECTED) { + r |= SSH_CLOSED; + } + if (socketstate & SSH_READ_PENDING) { + r |= SSH_READ_PENDING; + } + if (socketstate & SSH_WRITE_PENDING) { + r |= SSH_WRITE_PENDING; + } + if ((session->session_state == SSH_SESSION_STATE_DISCONNECTED && + (socketstate & SSH_CLOSED_ERROR)) || + session->session_state == SSH_SESSION_STATE_ERROR) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +/** + * @brief Get poll flags for an external mainloop + * + * @param session The ssh session to use. + * + * @returns A bitmask including `SSH_READ_PENDING` or `SSH_WRITE_PENDING`. + * For `SSH_READ_PENDING`, your invocation of poll() should include + * POLLIN. For `SSH_WRITE_PENDING`, your invocation of poll() should + * include POLLOUT. + */ +int ssh_get_poll_flags(ssh_session session) +{ + if (session == NULL) { + return 0; + } + + return ssh_socket_get_poll_flags (session->socket); +} + +/** + * @brief Get the disconnect message from the server. + * + * @param[in] session The ssh session to use. + * + * @return The message sent by the server along with the + * disconnect, or NULL in which case the reason of the + * disconnect may be found with ssh_get_error. + * + * @see ssh_get_error() + */ +const char *ssh_get_disconnect_message(ssh_session session) { + if (session == NULL) { + return NULL; + } + + if (session->session_state != SSH_SESSION_STATE_DISCONNECTED) { + ssh_set_error(session, SSH_REQUEST_DENIED, + "Connection not closed yet"); + } else if(!session->peer_discon_msg) { + ssh_set_error(session, SSH_FATAL, + "Connection correctly closed but no disconnect message"); + } else { + return session->peer_discon_msg; + } + + return NULL; +} + +/** + * @brief Get the protocol version of the session. + * + * @param session The ssh session to use. + * + * @return The SSH version as integer, < 0 on error. + */ +int ssh_get_version(ssh_session session) { + if (session == NULL) { + return -1; + } + + return 2; +} + +/** + * @internal + * @brief Callback to be called when the socket received an exception code. + * @param user is a pointer to session + */ +void ssh_socket_exception_callback(int code, int errno_code, void *user){ + ssh_session session = (ssh_session)user; + + SSH_LOG(SSH_LOG_RARE, + "Socket exception callback: %d (%d)", + code, + errno_code); + session->session_state = SSH_SESSION_STATE_ERROR; + if (errno_code == 0 && code == SSH_SOCKET_EXCEPTION_EOF) { + ssh_set_error(session, SSH_FATAL, "Socket error: disconnected"); +#ifdef _WIN32 + } else if (errno_code == WSAENETDOWN) { + ssh_set_error(session, SSH_FATAL, "Socket error: network down"); + } else if (errno_code == WSAENETUNREACH) { + ssh_set_error(session, SSH_FATAL, "Socket error: network unreachable"); + } else if (errno_code == WSAENETRESET) { + ssh_set_error(session, SSH_FATAL, "Socket error: network reset"); + } else if (errno_code == WSAECONNABORTED) { + ssh_set_error(session, SSH_FATAL, "Socket error: connection aborted"); + } else if (errno_code == WSAECONNRESET) { + ssh_set_error(session, + SSH_FATAL, + "Socket error: connection reset by peer"); + } else if (errno_code == WSAETIMEDOUT) { + ssh_set_error(session, SSH_FATAL, "Socket error: connection timed out"); + } else if (errno_code == WSAECONNREFUSED) { + ssh_set_error(session, SSH_FATAL, "Socket error: connection refused"); + } else if (errno_code == WSAEHOSTUNREACH) { + ssh_set_error(session, SSH_FATAL, "Socket error: host unreachable"); +#endif + } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_FATAL, + "Socket error: %s", + ssh_strerror(errno_code, err_msg, SSH_ERRNO_MSG_MAX)); + } + + session->ssh_connection_callback(session); +} + +/** + * @brief Send a message that should be ignored + * + * @param[in] session The SSH session + * @param[in] data Data to be sent + * + * @return `SSH_OK` on success, `SSH_ERROR` otherwise. + */ +int ssh_send_ignore (ssh_session session, const char *data) { + const int type = SSH2_MSG_IGNORE; + int rc; + + if (ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bs", + type, + data); + if (rc != SSH_OK){ + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + ssh_handle_packets(session, 0); + } + + return SSH_OK; + +error: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + +/** + * @brief Send a debug message + * + * @param[in] session The SSH session + * @param[in] message Data to be sent + * @param[in] always_display Message SHOULD be displayed by the server. It + * SHOULD NOT be displayed unless debugging + * information has been explicitly requested. + * + * @return `SSH_OK` on success, `SSH_ERROR` otherwise. + */ +int ssh_send_debug (ssh_session session, const char *message, int always_display) { + int rc; + + if (ssh_socket_is_open(session->socket)) { + rc = ssh_buffer_pack(session->out_buffer, + "bbsd", + SSH2_MSG_DEBUG, + always_display != 0 ? 1 : 0, + message, + 0); /* empty language tag */ + if (rc != SSH_OK) { + ssh_set_error_oom(session); + goto error; + } + ssh_packet_send(session); + ssh_handle_packets(session, 0); + } + + return SSH_OK; + +error: + ssh_buffer_reinit(session->out_buffer); + return SSH_ERROR; +} + + /** + * @brief Set the session data counters. + * + * This function sets the counter structures to be used to calculate data + * which comes in and goes out through the session at different levels. + * + * @code + * struct ssh_counter_struct scounter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * struct ssh_counter_struct rcounter = { + * .in_bytes = 0, + * .out_bytes = 0, + * .in_packets = 0, + * .out_packets = 0 + * }; + * + * ssh_set_counters(session, &scounter, &rcounter); + * @endcode + * + * @param[in] session The SSH session. + * + * @param[in] scounter Counter for byte data handled by the session sockets. + * + * @param[in] rcounter Counter for byte and packet data handled by the session, + * prior compression and SSH overhead. + */ +void ssh_set_counters(ssh_session session, ssh_counter scounter, + ssh_counter rcounter) { + if (session != NULL) { + session->socket_counter = scounter; + session->raw_counter = rcounter; + } +} + +/** + * @deprecated Use ssh_get_publickey_hash() + */ +int ssh_get_pubkey_hash(ssh_session session, unsigned char **hash) +{ + ssh_key pubkey = NULL; + ssh_string pubkey_blob = NULL; + MD5CTX ctx = NULL; + unsigned char *h = NULL; + int rc; + + if (session == NULL || hash == NULL) { + return SSH_ERROR; + } + + /* In FIPS mode, we cannot use MD5 */ + if (ssh_fips_mode()) { + ssh_set_error(session, + SSH_FATAL, + "In FIPS mode MD5 is not allowed." + "Try ssh_get_publickey_hash() with" + "SSH_PUBLICKEY_HASH_SHA256"); + return SSH_ERROR; + } + + *hash = NULL; + if (session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + ssh_set_error(session, SSH_FATAL, "No current cryptographic context"); + return SSH_ERROR; + } + + rc = ssh_get_server_publickey(session, &pubkey); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + rc = ssh_pki_export_pubkey_blob(pubkey, &pubkey_blob); + ssh_key_free(pubkey); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + h = calloc(MD5_DIGEST_LEN, sizeof(unsigned char)); + if (h == NULL) { + SSH_STRING_FREE(pubkey_blob); + return SSH_ERROR; + } + + ctx = md5_init(); + if (ctx == NULL) { + SSH_STRING_FREE(pubkey_blob); + SAFE_FREE(h); + return SSH_ERROR; + } + + rc = md5_update(ctx, + ssh_string_data(pubkey_blob), + ssh_string_len(pubkey_blob)); + if (rc != SSH_OK) { + SSH_STRING_FREE(pubkey_blob); + md5_ctx_free(ctx); + SAFE_FREE(h); + return rc; + } + SSH_STRING_FREE(pubkey_blob); + rc = md5_final(h, ctx); + if (rc != SSH_OK) { + SAFE_FREE(h); + return rc; + } + + *hash = h; + + return MD5_DIGEST_LEN; +} + +/** + * @brief Deallocate the hash obtained by ssh_get_pubkey_hash. + * + * This is required under Microsoft platform as this library might use a + * different C library than your software, hence a different heap. + * + * @param[in] hash The buffer to deallocate. + * + * @see ssh_get_pubkey_hash() + */ +void ssh_clean_pubkey_hash(unsigned char **hash) +{ + SAFE_FREE(*hash); +} + +/** + * @brief Get the server public key from a session. + * + * @param[in] session The session to get the key from. + * + * @param[out] key A pointer to store the allocated key. You need to free + * the key using ssh_key_free(). + * + * @return `SSH_OK` on success, `SSH_ERROR` on error. + * + * @see ssh_key_free() + */ +int ssh_get_server_publickey(ssh_session session, ssh_key *key) +{ + ssh_key pubkey = NULL; + + if (session == NULL || + session->current_crypto == NULL || + session->current_crypto->server_pubkey == NULL) { + return SSH_ERROR; + } + + pubkey = ssh_key_dup(session->current_crypto->server_pubkey); + if (pubkey == NULL) { + return SSH_ERROR; + } + + *key = pubkey; + return SSH_OK; +} + +/** + * @deprecated Use ssh_get_server_publickey() + */ +int ssh_get_publickey(ssh_session session, ssh_key *key) +{ + return ssh_get_server_publickey(session, key); +} + +/** + * @brief Allocates a buffer with the hash of the public key. + * + * This function allows you to get a @p hash of the public @p key. You can then + * print this hash in a human-readable form to the user so that he is able to + * verify it. Use ssh_get_hexa() or ssh_print_hash() to display it. + * + * @param[in] key The public key to create the hash for. + * + * @param[in] type The type of the hash you want. + * + * @param[out] hash A pointer to store the allocated buffer. It can be + * freed using ssh_clean_pubkey_hash(). + * + * @param[in] hlen The length of the hash. + * + * @return SSH_OK on success, SSH_ERROR if an error occurred. + * + * @warning It is very important that you verify at some moment that the hash + * matches a known server. If you don't do it, cryptography won't help + * you at making things secure. + * OpenSSH uses SHA256 to print public key digests. + * + * @see ssh_session_update_known_hosts() + * @see ssh_get_hexa() + * @see ssh_print_hash() + * @see ssh_clean_pubkey_hash() + */ +int ssh_get_publickey_hash(const ssh_key key, + enum ssh_publickey_hash_type type, + unsigned char **hash, + size_t *hlen) +{ + ssh_string blob = NULL; + unsigned char *h = NULL; + int (*digest)(const unsigned char *, size_t, unsigned char *) = NULL; + int rc, ret = SSH_ERROR; + + rc = ssh_pki_export_pubkey_blob(key, &blob); + if (rc < 0) { + goto out; + } + + switch (type) { + case SSH_PUBLICKEY_HASH_SHA1: + digest = sha1; + *hlen = SHA_DIGEST_LEN; + break; + case SSH_PUBLICKEY_HASH_SHA256: + digest = sha256; + *hlen = SHA256_DIGEST_LEN; + break; + case SSH_PUBLICKEY_HASH_MD5: +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER < 0x30000000L + /* In FIPS mode without OpenSSL providers, we cannot use MD5 */ + if (ssh_fips_mode()) { + SSH_LOG(SSH_LOG_TRACE, + "In FIPS mode MD5 is not allowed." + "Try using SSH_PUBLICKEY_HASH_SHA256"); + goto out; + } +#endif + digest = md5; + *hlen = MD5_DIGEST_LEN; + break; + default: + goto out; + } + + h = calloc(1, *hlen); + if (h == NULL) { + goto out; + } + + rc = digest(ssh_string_data(blob), ssh_string_len(blob), h); + if (rc != SSH_OK) { + free(h); + goto out; + } + + *hash = h; + ret = SSH_OK; + +out: + SSH_STRING_FREE(blob); + return ret; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/sftp.c b/src/libs/libssh-0.12.2/src/sftp.c new file mode 100644 index 000000000000..562efbd08c39 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sftp.c @@ -0,0 +1,3631 @@ +/* + * sftp.c - Secure FTP functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* This file contains code written by Nick Zitzmann */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/ssh2.h" +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/channels.h" +#include "libssh/session.h" +#include "libssh/misc.h" +#include "libssh/bytearray.h" + +#ifdef WITH_SFTP + +struct sftp_ext_struct { + uint32_t count; + char **name; + char **data; +}; + +static sftp_ext sftp_ext_new(void) { + sftp_ext ext; + + ext = calloc(1, sizeof(struct sftp_ext_struct)); + if (ext == NULL) { + return NULL; + } + + return ext; +} + +static void sftp_ext_free(sftp_ext ext) +{ + size_t i; + + if (ext == NULL) { + return; + } + + if (ext->count > 0) { + if (ext->name != NULL) { + for (i = 0; i < ext->count; i++) { + SAFE_FREE(ext->name[i]); + } + SAFE_FREE(ext->name); + } + + if (ext->data != NULL) { + for (i = 0; i < ext->count; i++) { + SAFE_FREE(ext->data[i]); + } + SAFE_FREE(ext->data); + } + } + + SAFE_FREE(ext); +} + +sftp_session sftp_new(ssh_session session) +{ + sftp_session sftp = NULL; + int rc; + + if (session == NULL) { + return NULL; + } + + if (!ssh_is_blocking(session)) { + ssh_set_error(session, + SSH_FATAL, + "The SSH session needs to be set to blocking mode for " + "SFTP to work correctly."); + return NULL; + } + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + + return NULL; + } + + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->read_packet->payload = ssh_buffer_new(); + if (sftp->read_packet->payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->session = session; + sftp->channel = ssh_channel_new(session); + if (sftp->channel == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->outstanding_ids = ssh_list_new(); + if (sftp->outstanding_ids == NULL) { + ssh_set_error_oom(session); + goto error; + } + + /* + * The following two calls shouldn't return SSH_AGAIN + * as the code has validated above that the SSH session + * is in blocking mode. + */ + rc = ssh_channel_open_session(sftp->channel); + if (rc != SSH_OK) { + goto error; + } + + rc = ssh_channel_request_sftp(sftp->channel); + if (rc != SSH_OK) { + goto error; + } + + return sftp; +error: + if (sftp->ext != NULL) { + sftp_ext_free(sftp->ext); + } + if (sftp->channel != NULL) { + ssh_channel_free(sftp->channel); + } + ssh_list_free(sftp->outstanding_ids); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); + } + SAFE_FREE(sftp->read_packet); + } + SAFE_FREE(sftp); + return NULL; +} + +sftp_session +sftp_new_channel(ssh_session session, ssh_channel channel) +{ + sftp_session sftp = NULL; + + if (session == NULL) { + return NULL; + } + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + sftp->ext = sftp_ext_new(); + if (sftp->ext == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->outstanding_ids = ssh_list_new(); + if (sftp->outstanding_ids == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->read_packet->payload = ssh_buffer_new(); + if (sftp->read_packet->payload == NULL) { + ssh_set_error_oom(session); + goto error; + } + + sftp->session = session; + sftp->channel = channel; + + return sftp; + +error: + if (sftp->ext != NULL) { + sftp_ext_free(sftp->ext); + } + ssh_list_free(sftp->outstanding_ids); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); + } + SAFE_FREE(sftp->read_packet); + } + SAFE_FREE(sftp); + return NULL; +} + +#ifdef WITH_SERVER +sftp_session +sftp_server_new(ssh_session session, ssh_channel chan) +{ + sftp_session sftp = NULL; + + sftp = calloc(1, sizeof(struct sftp_session_struct)); + if (sftp == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + sftp->read_packet = calloc(1, sizeof(struct sftp_packet_struct)); + if (sftp->read_packet == NULL) { + goto error; + } + + sftp->read_packet->payload = ssh_buffer_new(); + if (sftp->read_packet->payload == NULL) { + goto error; + } + + sftp->session = session; + sftp->channel = chan; + + return sftp; + +error: + ssh_set_error_oom(session); + if (sftp->read_packet != NULL) { + if (sftp->read_packet->payload != NULL) { + SSH_BUFFER_FREE(sftp->read_packet->payload); + } + SAFE_FREE(sftp->read_packet); + } + SAFE_FREE(sftp); + return NULL; +} + +/* @deprecated in favor of sftp_server_new() and callbacks based sftp server */ +int sftp_server_init(sftp_session sftp) +{ + ssh_session session = sftp->session; + sftp_client_message msg = NULL; + int rc; + + /* handles setting the sftp->client_version */ + msg = sftp_get_client_message(sftp); + if (msg == NULL) { + return -1; + } + + if (msg->type != SSH_FXP_INIT) { + ssh_set_error(session, + SSH_FATAL, + "Packet read of type %d instead of SSH_FXP_INIT", + msg->type); + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, "Received SSH_FXP_INIT"); + + rc = sftp_reply_version(msg); + if (rc != SSH_OK) { + ssh_set_error(session, + SSH_FATAL, + "Failed to process the SSH_FXP_INIT message"); + return -1; + } + + return 0; +} + +void sftp_server_free(sftp_session sftp) +{ + sftp_request_queue ptr; + + if (sftp == NULL) { + return; + } + + ptr = sftp->queue; + while(ptr) { + sftp_request_queue old; + sftp_message_free(ptr->message); + old = ptr->next; + SAFE_FREE(ptr); + ptr = old; + } + + SAFE_FREE(sftp->handles); + SSH_BUFFER_FREE(sftp->read_packet->payload); + SAFE_FREE(sftp->read_packet); + + sftp_ext_free(sftp->ext); + + SAFE_FREE(sftp); +} + +#endif /* WITH_SERVER */ + +void sftp_free(sftp_session sftp) +{ + sftp_request_queue ptr = NULL; + struct ssh_iterator *id_it = NULL; + + if (sftp == NULL) { + return; + } + + if (sftp->channel != NULL) { + ssh_channel_send_eof(sftp->channel); + ptr = sftp->queue; + while(ptr) { + sftp_request_queue old; + sftp_message_free(ptr->message); + old = ptr->next; + SAFE_FREE(ptr); + ptr = old; + } + + ssh_channel_free(sftp->channel); + sftp->channel = NULL; + } + + SAFE_FREE(sftp->handles); + SSH_BUFFER_FREE(sftp->read_packet->payload); + SAFE_FREE(sftp->read_packet); + + sftp_ext_free(sftp->ext); + sftp_limits_free(sftp->limits); + + id_it = ssh_list_get_iterator(sftp->outstanding_ids); + for (; id_it != NULL; id_it = id_it->next) { + free((uint32_t *)id_it->data); + } + ssh_list_free(sftp->outstanding_ids); + + SAFE_FREE(sftp); +} + +/* @internal + * Process the incoming data and copy them from the SSH packet buffer to the + * SFTP packet buffer. + * @returns number of decoded bytes. + */ +int +sftp_decode_channel_data_to_packet(sftp_session sftp, void *data, uint32_t len) +{ + sftp_packet packet = sftp->read_packet; + size_t nread; + size_t payload_len; + size_t data_offset; + size_t to_read, rc; + + if (packet->sftp == NULL) { + packet->sftp = sftp; + } + + data_offset = sizeof(uint32_t) + sizeof(uint8_t); + /* not enough bytes to read */ + if (len < data_offset) { + return SSH_ERROR; + } + + payload_len = PULL_BE_U32(data, 0); + packet->type = PULL_BE_U8(data, 4); + + /* We should check the legality of payload length */ + if (payload_len > len - sizeof(uint32_t) || payload_len < sizeof(uint8_t)) { + return SSH_ERROR; + } + + to_read = payload_len - sizeof(uint8_t); + rc = ssh_buffer_add_data(packet->payload, + (void*)((uint8_t *)data + data_offset), + to_read); + if (rc != 0) { + return SSH_ERROR; + } + nread = ssh_buffer_get_len(packet->payload); + + /* We should check if we copied the whole data */ + if (nread != to_read) { + return SSH_ERROR; + } + + /* + * We should return how many bytes we decoded, including packet length + * header and the payload length. + * This can't overflow as we pulled this from unit32_t and checked this fits + * into the buffer's max size of 0x10000000 (256MB). + */ + return (int)(payload_len + sizeof(uint32_t)); +} + +/* Get the last sftp error */ +int sftp_get_error(sftp_session sftp) { + if (sftp == NULL) { + return -1; + } + + return sftp->errnum; +} + +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp); +static sftp_limits_t sftp_limits_use_default(sftp_session sftp); + +/* Initialize the sftp session with the server. */ +int sftp_init(sftp_session sftp) +{ + sftp_packet packet = NULL; + ssh_buffer buffer = NULL; + char *ext_name = NULL; + char *ext_data = NULL; + uint32_t version; + int rc; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); + if (rc == SSH_ERROR) { + SSH_BUFFER_FREE(buffer); + return -1; + } + + SSH_BUFFER_FREE(buffer); + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + return -1; + } + + if (packet->type != SSH_FXP_VERSION) { + ssh_set_error(sftp->session, SSH_FATAL, + "Received a %d messages instead of SSH_FXP_VERSION", + packet->type); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* TODO: are we sure there are 4 bytes ready? */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Unable to unpack SSH_FXP_VERSION packet"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server version %" PRIu32, + version); + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); + while (rc == SSH_OK) { + uint32_t count = sftp->ext->count; + char **tmp; + + rc = ssh_buffer_unpack(packet->payload, "s", &ext_data); + if (rc == SSH_ERROR) { + break; + } + + SSH_LOG(SSH_LOG_DEBUG, + "SFTP server extension: %s, version: %s", + ext_name, ext_data); + + count++; + tmp = realloc(sftp->ext->name, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + tmp[count - 1] = ext_name; + sftp->ext->name = tmp; + + tmp = realloc(sftp->ext->data, count * sizeof(char *)); + if (tmp == NULL) { + ssh_set_error_oom(sftp->session); + SAFE_FREE(ext_name); + SAFE_FREE(ext_data); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + tmp[count - 1] = ext_data; + sftp->ext->data = tmp; + + sftp->ext->count = count; + + rc = ssh_buffer_unpack(packet->payload, "s", &ext_name); + } + + sftp->version = sftp->server_version = (int)version; + + /* Set the limits */ + rc = sftp_extension_supported(sftp, "limits@openssh.com", "1"); + if (rc == 1) { + /* Get the ssh and sftp errors */ + const char *static_ssh_err_msg = ssh_get_error(sftp->session); + int ssh_err_code = ssh_get_error_code(sftp->session); + int sftp_err_code = sftp_get_error(sftp); + char *ssh_err_msg = strdup(static_ssh_err_msg); + if (ssh_err_msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + sftp->limits = sftp_limits_use_extension(sftp); + if (sftp->limits == NULL) { + /* fallback and use the default limits on failure */ + SSH_LOG(SSH_LOG_TRACE, + "Failed to get the limits from a server claiming to " + "support the limits@openssh.com extension, falling back " + "and using the default limits"); + + /* Restore the sftp and ssh errors to their previous state */ + ssh_set_error(sftp->session, ssh_err_code, "%s", ssh_err_msg); + sftp_set_error(sftp, sftp_err_code); + SAFE_FREE(ssh_err_msg); + + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } + } else { + SAFE_FREE(ssh_err_msg); + } + } else { + sftp->limits = sftp_limits_use_default(sftp); + if (sftp->limits == NULL) { + return -1; + } + } + + return 0; +} + +unsigned int sftp_extensions_get_count(sftp_session sftp) +{ + if (sftp == NULL || sftp->ext == NULL) { + return 0; + } + + return sftp->ext->count; +} + +const char *sftp_extensions_get_name(sftp_session sftp, unsigned int idx) +{ + if (sftp == NULL) { + return NULL; + } + + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + return sftp->ext->name[idx]; +} + +const char *sftp_extensions_get_data(sftp_session sftp, unsigned int idx) +{ + if (sftp == NULL) { + return NULL; + } + + if (sftp->ext == NULL || sftp->ext->name == NULL) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + if (idx >= sftp->ext->count) { + ssh_set_error_invalid(sftp->session); + return NULL; + } + + return sftp->ext->data[idx]; +} + +int +sftp_extension_supported(sftp_session sftp, + const char *name, + const char *data) +{ + unsigned int i, n; + + if (sftp == NULL || name == NULL || data == NULL) { + return 0; + } + + n = sftp_extensions_get_count(sftp); + for (i = 0; i < n; i++) { + const char *ext_name = sftp_extensions_get_name(sftp, i); + const char *ext_data = sftp_extensions_get_data(sftp, i); + + if (ext_name != NULL && ext_data != NULL && + strcmp(ext_name, name) == 0 && + strcmp(ext_data, data) == 0) { + return 1; + } + } + + return 0; +} + +static sftp_file parse_handle_msg(sftp_message msg){ + sftp_file file; + + if(msg->packet_type != SSH_FXP_HANDLE) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_handle message passed in!"); + return NULL; + } + + file = calloc(1, sizeof(struct sftp_file_struct)); + if (file == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + file->handle = ssh_buffer_get_ssh_string(msg->payload); + if (file->handle == NULL) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_HANDLE message"); + SAFE_FREE(file); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + file->sftp = msg->sftp; + file->offset = 0; + file->eof = 0; + + return file; +} + +/* Open a directory */ +sftp_dir sftp_opendir(sftp_session sftp, const char *path) +{ + sftp_message msg = NULL; + sftp_file file = NULL; + sftp_dir dir = NULL; + sftp_status_message status; + ssh_buffer payload = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(payload, + "ds", + id, + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(payload); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_OPENDIR, payload); + SSH_BUFFER_FREE(payload); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return NULL; + case SSH_FXP_HANDLE: + file = parse_handle_msg(msg); + sftp_message_free(msg); + if (file != NULL) { + dir = calloc(1, sizeof(struct sftp_dir_struct)); + if (dir == NULL) { + ssh_set_error_oom(sftp->session); + free(file); + return NULL; + } + + dir->sftp = sftp; + dir->name = strdup(path); + if (dir->name == NULL) { + SAFE_FREE(dir); + SAFE_FREE(file); + return NULL; + } + dir->handle = file->handle; + SAFE_FREE(file); + } + return dir; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during opendir!", + msg->packet_type); + sftp_message_free(msg); + } + + return NULL; +} + +/* Get the version of the SFTP protocol supported by the server */ +int sftp_server_version(sftp_session sftp) { + return sftp->server_version; +} + +/* Get a single file attributes structure of a directory. */ +sftp_attributes sftp_readdir(sftp_session sftp, sftp_dir dir) +{ + sftp_message msg = NULL; + sftp_status_message status; + sftp_attributes attr; + ssh_buffer payload; + uint32_t id; + int rc; + + if (dir->buffer == NULL) { + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + payload = ssh_buffer_new(); + if (payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(payload, + "dS", + id, + dir->handle); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(payload); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_READDIR, payload); + SSH_BUFFER_FREE(payload); + if (rc < 0) { + return NULL; + } + + SSH_LOG(SSH_LOG_PACKET, + "Sent a ssh_fxp_readdir with id %" PRIu32, id); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + switch (msg->packet_type){ + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_EOF: + dir->eof = 1; + status_msg_free(status); + return NULL; + default: + break; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Unknown error status: %" PRIu32, status->status); + status_msg_free(status); + + return NULL; + case SSH_FXP_NAME: + ssh_buffer_get_u32(msg->payload, &dir->count); + dir->count = ntohl(dir->count); + dir->buffer = msg->payload; + msg->payload = NULL; + sftp_message_free(msg); + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Unsupported message back %d", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + + return NULL; + } + } + + /* now dir->buffer contains a buffer and dir->count != 0 */ + if (dir->count == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Count of files sent by the server is zero, which is invalid, or " + "libsftp bug"); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, "Count is %" PRIu32, dir->count); + + attr = sftp_parse_attr(sftp, dir->buffer, 1); + if (attr == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } + + dir->count--; + if (dir->count == 0) { + SSH_BUFFER_FREE(dir->buffer); + dir->buffer = NULL; + } + + return attr; +} + +/* Tell if the directory has reached EOF (End Of File). */ +int sftp_dir_eof(sftp_dir dir) { + return dir->eof; +} + +/* Free a SFTP_ATTRIBUTE handle */ +void sftp_attributes_free(sftp_attributes file){ + if (file == NULL) { + return; + } + + SSH_STRING_FREE(file->acl); + SSH_STRING_FREE(file->extended_data); + SSH_STRING_FREE(file->extended_type); + + SAFE_FREE(file->name); + SAFE_FREE(file->longname); + SAFE_FREE(file->group); + SAFE_FREE(file->owner); + + SAFE_FREE(file); +} + +static int sftp_handle_close(sftp_session sftp, ssh_string handle) +{ + sftp_status_message status; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "dS", + id, + handle); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_CLOSE, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if(status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + break; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during sftp_handle_close!", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Close an open file handle. */ +int sftp_close(sftp_file file){ + int err = SSH_NO_ERROR; + + if (file == NULL) { + return err; + } + + SAFE_FREE(file->name); + if (file->handle){ + err = sftp_handle_close(file->sftp,file->handle); + SSH_STRING_FREE(file->handle); + } + /* FIXME: check server response and implement errno */ + SAFE_FREE(file); + + return err; +} + +/* Close an open directory. */ +int sftp_closedir(sftp_dir dir){ + int err = SSH_NO_ERROR; + + SAFE_FREE(dir->name); + if (dir->handle) { + err = sftp_handle_close(dir->sftp, dir->handle); + SSH_STRING_FREE(dir->handle); + } + /* FIXME: check server response and implement errno */ + SSH_BUFFER_FREE(dir->buffer); + SAFE_FREE(dir); + + return err; +} + +/* Open a file on the server. */ +sftp_file sftp_open(sftp_session sftp, + const char *file, + int flags, + mode_t mode) +{ + sftp_message msg = NULL; + sftp_status_message status; + struct sftp_attributes_struct attr; + sftp_file handle; + ssh_buffer buffer = NULL; + sftp_attributes stat_data; + uint32_t sftp_flags = 0; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return NULL; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + if ((flags & O_RDWR) == O_RDWR) { + sftp_flags |= (SSH_FXF_WRITE | SSH_FXF_READ); + } else if ((flags & O_WRONLY) == O_WRONLY) { + sftp_flags |= SSH_FXF_WRITE; + } else { + sftp_flags |= SSH_FXF_READ; + } + if ((flags & O_CREAT) == O_CREAT) + sftp_flags |= SSH_FXF_CREAT; + if ((flags & O_TRUNC) == O_TRUNC) + sftp_flags |= SSH_FXF_TRUNC; + if ((flags & O_EXCL) == O_EXCL) + sftp_flags |= SSH_FXF_EXCL; + if ((flags & O_APPEND) == O_APPEND) { + sftp_flags |= SSH_FXF_APPEND; + } + SSH_LOG(SSH_LOG_PACKET, "Opening file %s with sftp flags %" PRIx32, + file, sftp_flags); + + rc = ssh_buffer_pack(buffer, + "dsd", + id, + file, + sftp_flags); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = buffer_add_attributes(buffer, &attr); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_OPEN, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + + return NULL; + case SSH_FXP_HANDLE: + handle = parse_handle_msg(msg); + if (handle == NULL) { + return NULL; + } + sftp_message_free(msg); + if ((flags & O_APPEND) == O_APPEND) { + stat_data = sftp_stat(sftp, file); + if (stat_data == NULL) { + sftp_close(handle); + return NULL; + } + if ((stat_data->flags & SSH_FILEXFER_ATTR_SIZE) != SSH_FILEXFER_ATTR_SIZE) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Cannot open in append mode. Unknown file size."); + sftp_attributes_free(stat_data); + sftp_close(handle); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + handle->offset = stat_data->size; + sftp_attributes_free(stat_data); + } + return handle; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during open!", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +void sftp_file_set_nonblocking(sftp_file handle) +{ + handle->nonblocking = 1; +} +void sftp_file_set_blocking(sftp_file handle) +{ + handle->nonblocking = 0; +} + +/* Read from a file using an opened sftp file handle. */ +ssize_t +sftp_read(sftp_file handle, void *buf, size_t count) +{ + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring = NULL; + size_t datalen; + ssh_buffer buffer = NULL; + uint32_t id, read_len; + int rc; + + if (handle == NULL) { + return -1; + } + sftp = handle->sftp; + + if (handle->eof) { + return 0; + } + + /* + * limit the reads to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. + * + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + read_len = (uint32_t)MIN(sftp->limits->max_read_length, count); + + rc = sftp_get_new_id(handle->sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + handle->handle, + handle->offset, + read_len); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + if (sftp_packet_write(handle->sftp, SSH_FXP_READ, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(handle->sftp, id, !handle->nonblocking, &msg); + if (rc == SSH_ERROR) { + return -1; + } + + if (rc == SSH_AGAIN) { + /* + * file opened in non blocking mode and the response has not arrived + * yet. Since we cannot block, return 0 as the number of bytes read. + */ + return 0; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_EOF: + handle->eof = 1; + status_msg_free(status); + return 0; + default: + break; + } + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + return -1; + case SSH_FXP_DATA: + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return -1; + } + + datalen = ssh_string_len(datastring); + if (datalen > count) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%zu and asked for %zu", + datalen, + count); + SSH_STRING_FREE(datastring); + return -1; + } + handle->offset += (uint64_t)datalen; + memcpy(buf, ssh_string_data(datastring), datalen); + SSH_STRING_FREE(datastring); + return datalen; + default: + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d during read!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return -1; + } + + return -1; /* not reached */ +} + +/* Start an asynchronous read from a file using an opened sftp file handle. */ +int +sftp_async_read_begin(sftp_file file, uint32_t len) +{ + sftp_session sftp = file->sftp; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, "dSqd", id, file->handle, file->offset, len); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_READ, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + file->offset += len; /* assume we'll read len bytes */ + + return id; +} + +/* Wait for an asynchronous read to complete and save the data. */ +int +sftp_async_read(sftp_file file, void *data, uint32_t size, uint32_t id) +{ + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status; + ssh_string datastring = NULL; + int rc, err = SSH_OK; + size_t len; + + if (file == NULL) { + return SSH_ERROR; + } + sftp = file->sftp; + + if (file->eof) { + return 0; + } + + /* handle an existing request */ + rc = sftp_recv_response_msg(sftp, id, !file->nonblocking, &msg); + if (rc == SSH_ERROR || rc == SSH_AGAIN) { + return rc; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + if (status->status != SSH_FX_EOF) { + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server : %s", + status->errormsg); + err = SSH_ERROR; + } else { + file->eof = 1; + } + status_msg_free(status); + return err; + case SSH_FXP_DATA: + datastring = ssh_buffer_get_ssh_string(msg->payload); + sftp_message_free(msg); + if (datastring == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received invalid DATA packet from sftp server"); + return SSH_ERROR; + } + if (ssh_string_len(datastring) > size) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received a too big DATA packet from sftp server: " + "%zu and asked for %" PRIu32, + ssh_string_len(datastring), + size); + SSH_STRING_FREE(datastring); + return SSH_ERROR; + } + len = ssh_string_len(datastring); + /* Update the offset with the correct value */ + file->offset = file->offset - (size - len); + memcpy(data, ssh_string_data(datastring), len); + SSH_STRING_FREE(datastring); + return (int)len; + default: + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d during read!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return SSH_ERROR; + } + + return SSH_ERROR; +} + +ssize_t +sftp_write(sftp_file file, const void *buf, size_t count) +{ + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status; + ssh_buffer buffer = NULL; + uint32_t id, write_len; + ssize_t len; + size_t packetlen; + int rc; + + if (file == NULL) { + return -1; + } + sftp = file->sftp; + + rc = sftp_get_new_id(file->sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* + * limit the writes to the maximum specified in Section 3 of + * https://datatracker.ietf.org/doc/html/draft-ietf-secsh-filexfer-02 + * or to the values provided by the limits@openssh.com extension. + * + * TODO: We should iterate over the blocks rather than writing less than + * requested to provide less surprises to the calling applications. + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + write_len = (uint32_t)MIN(sftp->limits->max_write_length, count); + + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + write_len, /* len of datastring */ + (size_t)write_len, + buf); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + len = sftp_packet_write(file->sftp, SSH_FXP_WRITE, buffer); + packetlen = ssh_buffer_get_len(buffer); + SSH_BUFFER_FREE(buffer); + if (len < 0) { + return -1; + } else if ((size_t)len != packetlen) { + SSH_LOG(SSH_LOG_PACKET, "Could not write as much data as expected"); + } + + /* Wait for the response in blocking mode */ + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + file->offset += write_len; + status_msg_free(status); + return write_len; + default: + break; + } + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + file->offset += write_len; + status_msg_free(status); + return -1; + default: + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d during write!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return -1; + } + + return -1; /* not reached */ +} + +/* Seek to a specific location in a file. */ +int +sftp_seek(sftp_file file, uint32_t new_offset) +{ + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +int +sftp_seek64(sftp_file file, uint64_t new_offset) +{ + if (file == NULL) { + return -1; + } + + file->offset = new_offset; + file->eof = 0; + + return 0; +} + +/* Report current byte position in file. */ +unsigned long sftp_tell(sftp_file file) { + return (unsigned long)file->offset; +} +/* Report current byte position in file. */ +uint64_t sftp_tell64(sftp_file file) { + return (uint64_t) file->offset; +} + +/* Rewinds the position of the file pointer to the beginning of the file.*/ +void sftp_rewind(sftp_file file) { + file->offset = 0; + file->eof = 0; +} + +/* code written by Nick */ +int sftp_unlink(sftp_session sftp, const char *file) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if (sftp_packet_write(sftp, SSH_FXP_REMOVE, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + if (msg->packet_type == SSH_FXP_STATUS) { + /* by specification, this command's only supposed to return SSH_FXP_STATUS */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session,SSH_FATAL, + "Received message %d when attempting to remove file", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* code written by Nick */ +int sftp_rmdir(sftp_session sftp, const char *directory) { + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + directory); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + if (sftp_packet_write(sftp, SSH_FXP_RMDIR, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to remove directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Code written by Nick */ +int sftp_mkdir(sftp_session sftp, const char *directory, mode_t mode) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_attributes errno_attr = NULL; + struct sftp_attributes_struct attr; + ssh_buffer buffer; + uint32_t id; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + rc = ssh_buffer_pack(buffer, + "ds", + id, + directory); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, &attr); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_MKDIR, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_FAILURE: + /* + * mkdir always returns a failure, even if the path already exists. + * To be POSIX conform and to be able to map it to EEXIST a stat + * call is needed here. + */ + errno_attr = sftp_lstat(sftp, directory); + if (errno_attr != NULL) { + SAFE_FREE(errno_attr); + sftp_set_error(sftp, SSH_FX_FILE_ALREADY_EXISTS); + } + break; + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to make directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* code written by nick */ +int sftp_rename(sftp_session sftp, const char *original, const char *newname) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + const char *extension_name = "posix-rename@openssh.com"; + int request_type; + int rc; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* + * posix-rename@openssh.com extension will be used + * if it is supported by sftp + */ + if (sftp_extension_supported(sftp, + extension_name, + "1")) { + rc = ssh_buffer_pack(buffer, + "dsss", + id, + extension_name, + original, + newname); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + request_type = SSH_FXP_EXTENDED; + } else { + rc = ssh_buffer_pack(buffer, + "dss", + id, + original, + newname); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if (sftp->version >= 4) { + /* + * POSIX rename atomically replaces newpath, + * we should do the same only available on >=v4 + */ + ssh_buffer_add_u32(buffer, SSH_FXF_RENAME_OVERWRITE); + } + + request_type = SSH_FXP_RENAME; + } + + rc = sftp_packet_write(sftp, request_type, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to rename", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Code written by Nick */ +/* Set file attributes on a file, directory or symbolic link. */ +int sftp_setstat(sftp_session sftp, const char *file, sftp_attributes attr) +{ + uint32_t id; + ssh_buffer buffer; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + if (sftp == NULL || file == NULL || attr == NULL) { + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, attr); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_SETSTAT, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +int +sftp_lsetstat(sftp_session sftp, const char *file, sftp_attributes attr) +{ + uint32_t id; + ssh_buffer buffer = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + const char *extension_name = "lsetstat@openssh.com"; + int rc; + + if (sftp == NULL || file == NULL || attr == NULL) { + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, "dss", id, extension_name, file); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = buffer_add_attributes(buffer, attr); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to lsetstat", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +/* Change the file owner and group */ +int sftp_chown(sftp_session sftp, const char *file, uid_t owner, gid_t group) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.uid = owner; + attr.gid = group; + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change permissions of a file */ +int sftp_chmod(sftp_session sftp, const char *file, mode_t mode) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + attr.permissions = mode; + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + + return sftp_setstat(sftp, file, &attr); +} + +/* Change the last modification and access time of a file. */ +int sftp_utimes(sftp_session sftp, const char *file, + const struct timeval *times) { + struct sftp_attributes_struct attr; + ZERO_STRUCT(attr); + + attr.atime = times[0].tv_sec; + attr.atime_nseconds = times[0].tv_usec; + + attr.mtime = times[1].tv_sec; + attr.mtime_nseconds = times[1].tv_usec; + + attr.flags |= SSH_FILEXFER_ATTR_ACCESSTIME | SSH_FILEXFER_ATTR_MODIFYTIME | + SSH_FILEXFER_ATTR_SUBSECOND_TIMES; + + return sftp_setstat(sftp, file, &attr); +} + +int sftp_symlink(sftp_session sftp, const char *target, const char *dest) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return -1; + if (target == NULL || dest == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* The OpenSSH sftp server has order of the arguments reversed, see the + * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in + * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL + * for more information */ + if (ssh_get_openssh_version(sftp->session)) { + rc = ssh_buffer_pack(buffer, + "dss", + id, + target, + dest); + } else { + rc = ssh_buffer_pack(buffer, + "dss", + id, + dest, + target); + } + if (rc != SSH_OK){ + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if (sftp_packet_write(sftp, SSH_FXP_SYMLINK, buffer) < 0) { + SSH_BUFFER_FREE(buffer); + return -1; + } + SSH_BUFFER_FREE(buffer); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +char *sftp_readlink(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error_invalid(sftp); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_readlink",sftp->version); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + path); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_READLINK, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *lnk = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &lnk); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to retrieve link"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return lnk; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +int sftp_hardlink(sftp_session sftp, const char *oldpath, const char *newpath) +{ + ssh_buffer buffer = NULL; + uint32_t id; + const char *extension_name = "hardlink@openssh.com"; + sftp_status_message status = NULL; + sftp_message msg = NULL; + int rc; + + if (sftp == NULL) { + return -1; + } + + if (oldpath == NULL || newpath == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "dsss", + id, + extension_name, + oldpath, + newpath); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + status_msg_free(status); + return 0; + default: + break; + } + /* + * Status should be SSH_FX_OK if the command was successful, + * if it didn't, then there was an error + */ + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return -1; + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to create hardlink", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +static sftp_statvfs_t sftp_parse_statvfs(sftp_session sftp, ssh_buffer buf) { + sftp_statvfs_t statvfs; + int rc; + + statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); + if (statvfs == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqqqqqqqqq", + &statvfs->f_bsize, /* file system block size */ + &statvfs->f_frsize, /* fundamental fs block size */ + &statvfs->f_blocks, /* number of blocks (unit f_frsize) */ + &statvfs->f_bfree, /* free blocks in file system */ + &statvfs->f_bavail, /* free blocks for non-root */ + &statvfs->f_files, /* total file inodes */ + &statvfs->f_ffree, /* free file inodes */ + &statvfs->f_favail, /* free file inodes for to non-root */ + &statvfs->f_fsid, /* file system id */ + &statvfs->f_flag, /* bit mask of f_flag values */ + &statvfs->f_namemax/* maximum filename length */ + ); + if (rc != SSH_OK) { + SAFE_FREE(statvfs); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid statvfs structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return statvfs; +} + +sftp_statvfs_t sftp_statvfs(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + if (sftp->version < 3){ + ssh_set_error(sftp,SSH_REQUEST_DENIED,"sftp version %d does not support sftp_statvfs",sftp->version); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "statvfs@openssh.com", + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to get statvfs", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +int sftp_fsync(sftp_file file) +{ + sftp_session sftp; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return -1; + } + sftp = file->sftp; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, + "dsS", + id, + "fsync@openssh.com", + file->handle); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto done; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + goto done; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + /* By specification, this command only returns SSH_FXP_STATUS */ + if (msg->packet_type == SSH_FXP_STATUS) { + sftp_status_message status; + + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + rc = -1; + goto done; + } + + sftp_set_error(sftp, status->status); + switch (status->status) { + case SSH_FX_OK: + /* SUCCESS, LEAVE */ + status_msg_free(status); + rc = 0; + goto done; + default: + break; + } + + /* + * The status should be SSH_FX_OK if the command was successful, if it + * didn't, then there was an error + */ + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + + rc = -1; + goto done; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to set stats", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + rc = -1; +done: + SSH_BUFFER_FREE(buffer); + + return rc; +} + +sftp_statvfs_t sftp_fstatvfs(sftp_file file) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + sftp_session sftp; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return NULL; + } + sftp = file->sftp; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dsS", + id, + "fstatvfs@openssh.com", + file->handle); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc == -1) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_statvfs_t buf = sftp_parse_statvfs(sftp, msg->payload); + sftp_message_free(msg); + if (buf == NULL) { + return NULL; + } + + return buf; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +void sftp_statvfs_free(sftp_statvfs_t statvfs) { + if (statvfs == NULL) { + return; + } + + SAFE_FREE(statvfs); +} + +static sftp_limits_t sftp_limits_new(void) +{ + return calloc(1, sizeof(struct sftp_limits_struct)); +} + +static sftp_limits_t sftp_parse_limits(sftp_session sftp, ssh_buffer buf) +{ + sftp_limits_t limits = NULL; + int rc; + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(buf, "qqqq", + &limits->max_packet_length, /** maximum number of bytes in a single sftp packet */ + &limits->max_read_length, /** maximum length in a SSH_FXP_READ packet */ + &limits->max_write_length, /** maximum length in a SSH_FXP_WRITE packet */ + &limits->max_open_handles /** maximum number of active handles allowed by server */ + ); + if (rc != SSH_OK) { + SAFE_FREE(limits); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid limits structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + /* cap the max read and write length to UINT32_MAX as we really can not read + * nor write more as the len member of the SSH_FXP_READ/WRITE packets is + * uint32 */ + limits->max_read_length = MIN(limits->max_read_length, UINT32_MAX); + limits->max_write_length = MIN(limits->max_write_length, UINT32_MAX); + + return limits; +} + +static sftp_limits_t sftp_limits_use_extension(sftp_session sftp) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + "limits@openssh.com"); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + sftp_limits_t limits = sftp_parse_limits(sftp, msg->payload); + sftp_message_free(msg); + if (limits == NULL) { + return NULL; + } + + return limits; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to get limits", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +static sftp_limits_t sftp_limits_use_default(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits->max_packet_length = 34000; + limits->max_read_length = 32768; + limits->max_write_length = 32768; + + /* + * For max-open-handles field openssh says : + * If the server doesn't enforce a specific limit, then the field may + * be set to 0. This implies the server relies on the OS to enforce + * limits (e.g. available memory or file handles), and such limits + * might be dynamic. The client SHOULD take care to not try to exceed + * reasonable limits. + */ + limits->max_open_handles = 0; + + return limits; +} + +sftp_limits_t sftp_limits(sftp_session sftp) +{ + sftp_limits_t limits = NULL; + + if (sftp == NULL) { + return NULL; + } + + if (sftp->limits == NULL) { + ssh_set_error(sftp, SSH_FATAL, + "Uninitialized sftp session, " + "sftp_init() was not called or failed"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + limits = sftp_limits_new(); + if (limits == NULL) { + ssh_set_error_oom(sftp); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + memcpy(limits, sftp->limits, sizeof(struct sftp_limits_struct)); + return limits; +} + +void sftp_limits_free(sftp_limits_t limits) +{ + if (limits == NULL) { + return; + } + + SAFE_FREE(limits); +} + +/* another code written by Nick */ +char *sftp_canonicalize_path(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) + return NULL; + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + path); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_REALPATH, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc == -1) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *cname = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &cname); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse canonicalized path"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return cname; + } else if (msg->packet_type == SSH_FXP_STATUS) { /* bad response (error) */ + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { /* this shouldn't happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to set stats", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +static sftp_attributes sftp_xstat(sftp_session sftp, + const char *path, + int param) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "ds", + id, + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, param, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_ATTRS) { + sftp_attributes attr = sftp_parse_attr(sftp, msg->payload, 0); + sftp_message_free(msg); + + return attr; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return NULL; + } + ssh_set_error(sftp->session, SSH_FATAL, + "Received mesg %d during stat()", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + + return NULL; +} + +sftp_attributes sftp_stat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_STAT); +} + +sftp_attributes sftp_lstat(sftp_session session, const char *path) { + return sftp_xstat(session, path, SSH_FXP_LSTAT); +} + +sftp_attributes sftp_fstat(sftp_file file) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer; + uint32_t id; + int rc; + + if (file == NULL) { + return NULL; + } + + rc = sftp_get_new_id(file->sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(file->sftp->session); + sftp_set_error(file->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dS", + id, + file->handle); + if (rc != SSH_OK) { + ssh_set_error_oom(file->sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(file->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(file->sftp, SSH_FXP_FSTAT, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(file->sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_ATTRS){ + sftp_attributes attr = sftp_parse_attr(file->sftp, msg->payload, 0); + sftp_message_free(msg); + + return attr; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(file->sftp, status->status); + ssh_set_error(file->sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + + return NULL; + } + ssh_set_error(file->sftp->session, SSH_FATAL, + "Received msg %d during fstat()", msg->packet_type); + sftp_message_free(msg); + sftp_set_error(file->sftp, SSH_FX_BAD_MESSAGE); + + return NULL; +} + +char *sftp_expand_path(sftp_session sftp, const char *path) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + if (path == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "NULL received as an argument instead of the path to expand"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "expand-path@openssh.com", + path); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t ignored = 0; + char *cname = NULL; + + rc = ssh_buffer_unpack(msg->payload, + "ds", + &ignored, + &cname); + sftp_message_free(msg); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse expanded path"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + return cname; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + } else { + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d when attempting to expand path", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +char * +sftp_home_directory(sftp_session sftp, const char *username) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return NULL; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return NULL; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_pack(buffer, + "dss", + id, + "home-directory", + username ? username : ""); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return NULL; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return NULL; + } + + if (msg->packet_type == SSH_FXP_NAME) { + uint32_t count = 0; + char *homepath = NULL; + char *longpath = NULL; + sftp_attributes attr = NULL; + + rc = ssh_buffer_unpack(msg->payload, "ds", &count, &homepath); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to query user home directory"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + /* + for SFTP version > 3, longname field in SSH_FXP_NAME is omitted. + */ + if (sftp->version <= 3) { + rc = ssh_buffer_unpack(msg->payload, "s", &longpath); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to extract longname from payload"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } + attr = sftp_parse_attr(sftp, msg->payload, 0); + if (attr == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Couldn't parse the SFTP attributes"); + return NULL; + } + sftp_message_free(msg); + + if (count != 1) { + if (count > 1) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Multiple results returned"); + } else { + ssh_set_error(sftp->session, SSH_ERROR, "No result returned"); + } + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + if (longpath) { + free(longpath); + } + sftp_attributes_free(attr); + return homepath; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return NULL; + } + + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { + ssh_set_error( + sftp->session, + SSH_FATAL, + "Received message %d when attempting to query user home directory", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return NULL; +} + +sftp_name_id_map sftp_name_id_map_new(uint32_t count) +{ + sftp_name_id_map map = NULL; + + map = calloc(1, sizeof(struct sftp_name_id_map_struct)); + if (map == NULL) { + return NULL; + } + + map->count = count; + + map->ids = calloc(count, sizeof(uint32_t)); + if (map->ids == NULL) { + SAFE_FREE(map); + return NULL; + } + + map->names = calloc(count, sizeof(char *)); + if (map->names == NULL) { + SAFE_FREE(map->ids); + SAFE_FREE(map); + return NULL; + } + + return map; +} + +void sftp_name_id_map_free(sftp_name_id_map map) +{ + if (map == NULL) { + return; + } + + SAFE_FREE(map->ids); + + if (map->names != NULL) { + for (uint32_t i = 0; i < map->count; i++) { + SAFE_FREE(map->names[i]); + } + SAFE_FREE(map->names); + } + + SAFE_FREE(map); +} + +static int sftp_buffer_add_ids(ssh_buffer buffer, sftp_name_id_map map) +{ + uint32_t id_count = map ? map->count : 0; + int rc; + + rc = ssh_buffer_pack(buffer, "d", sizeof(uint32_t) * id_count); + if (rc != SSH_OK) { + return -1; + } + + for (uint32_t i = 0; i < id_count; i++) { + rc = ssh_buffer_pack(buffer, "d", map->ids[i]); + if (rc != SSH_OK) { + return -1; + } + } + + return 0; +} + +static int sftp_parse_names(ssh_buffer buffer, sftp_name_id_map map) +{ + uint32_t name_buf_len = 0; + char *name = NULL; + uint32_t id_count = map ? map->count : 0; + int rc; + + rc = ssh_buffer_unpack(buffer, "d", &name_buf_len); + if (rc != SSH_OK) { + return -1; + } + + for (uint32_t i = 0; i < id_count; i++) { + rc = ssh_buffer_unpack(buffer, "s", &name); + if (rc != SSH_OK) { + return -1; + } + + name_buf_len -= strlen(name) + sizeof(uint32_t); + map->names[i] = name; + } + + if (name_buf_len != 0) { + return -1; + } + + return 0; +} + +int sftp_get_users_groups_by_id(sftp_session sftp, + sftp_name_id_map users_map, + sftp_name_id_map groups_map) +{ + sftp_status_message status = NULL; + sftp_message msg = NULL; + ssh_buffer buffer = NULL; + uint32_t id; + int rc; + + if (sftp == NULL) { + return -1; + } + + /* check if the user has provided the correct arguments */ + if (users_map == NULL && groups_map == NULL) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Both users map and groups map cannot be NULL"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return -1; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = ssh_buffer_pack(buffer, "ds", id, "users-groups-by-id@openssh.com"); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* pack all uids */ + rc = sftp_buffer_add_ids(buffer, users_map); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + /* pack all gids */ + rc = sftp_buffer_add_ids(buffer, groups_map); + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + SSH_BUFFER_FREE(buffer); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_EXTENDED, buffer); + SSH_BUFFER_FREE(buffer); + if (rc < 0) { + return -1; + } + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + if (rc != SSH_OK) { + return -1; + } + + if (msg->packet_type == SSH_FXP_EXTENDED_REPLY) { + rc = sftp_parse_names(msg->payload, users_map); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse usernames"); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return -1; + } + + rc = sftp_parse_names(msg->payload, groups_map); + if (rc != SSH_OK) { + ssh_set_error(sftp->session, + SSH_ERROR, + "Failed to parse groupnames"); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return -1; + } + + sftp_message_free(msg); + return 0; + } else if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return -1; + } + + sftp_set_error(sftp, status->status); + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "SFTP server: %s", + status->errormsg); + status_msg_free(status); + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received message %d when attempting to get user and " + "group names by id", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + } + + return -1; +} + +#endif /* WITH_SFTP */ diff --git a/src/libs/libssh-0.12.2/src/sftp_aio.c b/src/libs/libssh-0.12.2/src/sftp_aio.c new file mode 100644 index 000000000000..ae4f2d119989 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sftp_aio.c @@ -0,0 +1,501 @@ +/* + * sftp_aio.c - Secure FTP functions for asynchronous i/o + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider + * Copyright (c) 2023 by Eshan Kelkar + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" + +#ifdef WITH_SFTP + +struct sftp_aio_struct { + sftp_file file; + uint32_t id; + size_t len; +}; + +static sftp_aio sftp_aio_new(void) +{ + sftp_aio aio = NULL; + aio = calloc(1, sizeof(struct sftp_aio_struct)); + return aio; +} + +void sftp_aio_free(sftp_aio aio) +{ + SAFE_FREE(aio); +} + +ssize_t sftp_aio_begin_read(sftp_file file, size_t len, sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id, read_len; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number of " + "bytes to read"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + /* Apply a cap on the length a user is allowed to read + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + read_len = (uint32_t)MIN(sftp->limits->max_read_length, len); + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(buffer, + "dSqd", + id, + file->handle, + file->offset, + read_len); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = read_len; + + rc = sftp_packet_write(sftp, SSH_FXP_READ, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we read len bytes from the file */ + file->offset += read_len; + *aio = aio_handle; + return read_len; +} + +ssize_t sftp_aio_wait_read(sftp_aio *aio, + void *buf, + size_t buf_size) +{ + sftp_file file = NULL; + size_t bytes_requested; + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + uint32_t string_len, host_len; + int rc, err; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed " + "instead of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (buf_size < bytes_requested) { + ssh_set_error(sftp->session, SSH_FATAL, + "Buffer size (%zu, passed by the caller) is " + "smaller than the number of bytes requested " + "to read (%zu, as per the supplied sftp aio)", + buf_size, bytes_requested); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + /* handle an existing request */ + rc = sftp_recv_response_msg(sftp, (*aio)->id, !file->nonblocking, &msg); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (rc == SSH_AGAIN) { + /* return without freeing the (*aio) */ + return SSH_AGAIN; + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + switch (msg->packet_type) { + case SSH_FXP_STATUS: + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status != SSH_FX_EOF) { + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server : %s", status->errormsg); + err = SSH_ERROR; + } else { + file->eof = 1; + /* Update the offset correctly */ + file->offset = file->offset - bytes_requested; + err = SSH_OK; + } + + status_msg_free(status); + return err; + + case SSH_FXP_DATA: + rc = ssh_buffer_get_u32(msg->payload, &string_len); + if (rc == 0) { + /* Insufficient data in the buffer */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + host_len = ntohl(string_len); + if (host_len > buf_size) { + /* + * This should never happen, as according to the + * SFTP protocol the server reads bytes less than + * or equal to the number of bytes requested to read. + * + * And we have checked before that the buffer size is + * greater than or equal to the number of bytes requested + * to read, hence code of this if block should never + * get executed. + */ + ssh_set_error(sftp->session, SSH_FATAL, + "DATA packet (%u bytes) received from sftp server " + "cannot fit into the supplied buffer (%zu bytes)", + host_len, buf_size); + sftp_set_error(sftp, SSH_FX_FAILURE); + sftp_message_free(msg); + return SSH_ERROR; + } + + string_len = ssh_buffer_get_data(msg->payload, buf, host_len); + if (string_len != host_len) { + /* should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Received invalid DATA packet from sftp server"); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + /* Update the offset with the correct value */ + file->offset = file->offset - (bytes_requested - string_len); + sftp_message_free(msg); + return string_len; + + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during read!", msg->packet_type); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + sftp_message_free(msg); + return SSH_ERROR; + } + + return SSH_ERROR; /* not reached */ +} + +ssize_t sftp_aio_begin_write(sftp_file file, + const void *buf, + size_t len, + sftp_aio *aio) +{ + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_aio aio_handle = NULL; + uint32_t id, write_len; + int rc; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + return SSH_ERROR; + } + + sftp = file->sftp; + if (buf == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead " + "of a buffer's address"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (len == 0) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, 0 passed as the number " + "of bytes to write"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + /* Apply a cap on the length a user is allowed to write + * + * The limits are in theory uint64, but packet contain data length in uint32 + * so in practice, the limit will never be larger than UINT32_MAX + */ + write_len = (uint32_t)MIN(sftp->limits->max_write_length, len); + + if (aio == NULL) { + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid argument, NULL passed instead of a pointer to " + "a location to store an sftp aio handle"); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + rc = sftp_get_new_id(sftp, &id); + if (rc != SSH_OK) { + return SSH_ERROR; + } + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + rc = ssh_buffer_pack(buffer, + "dSqdP", + id, + file->handle, + file->offset, + write_len, /* len of datastring */ + (size_t)write_len, + buf); + + if (rc != SSH_OK) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle = sftp_aio_new(); + if (aio_handle == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + SSH_BUFFER_FREE(buffer); + return SSH_ERROR; + } + + aio_handle->file = file; + aio_handle->id = id; + aio_handle->len = write_len; + + rc = sftp_packet_write(sftp, SSH_FXP_WRITE, buffer); + SSH_BUFFER_FREE(buffer); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(aio_handle); + return SSH_ERROR; + } + + /* Assume we wrote len bytes to the file */ + file->offset += write_len; + *aio = aio_handle; + return write_len; +} + +ssize_t sftp_aio_wait_write(sftp_aio *aio) +{ + sftp_file file = NULL; + size_t bytes_requested; + + sftp_session sftp = NULL; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + /* + * This function releases the memory of the structure + * that (*aio) points to in all cases except when the + * return value is SSH_AGAIN. + * + * If the return value is SSH_AGAIN, the user should call this + * function again to get the response for the request corresponding + * to the structure that (*aio) points to, hence we don't release the + * structure's memory when SSH_AGAIN is returned. + */ + + if (aio == NULL || *aio == NULL) { + return SSH_ERROR; + } + + file = (*aio)->file; + bytes_requested = (*aio)->len; + + if (file == NULL || + file->sftp == NULL || + file->sftp->session == NULL) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + sftp = file->sftp; + if (bytes_requested == 0) { + /* This should never happen */ + ssh_set_error(sftp->session, SSH_FATAL, + "Invalid sftp aio, len for requested i/o is 0"); + sftp_set_error(sftp, SSH_FX_FAILURE); + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + rc = sftp_recv_response_msg(sftp, (*aio)->id, !file->nonblocking, &msg); + if (rc == SSH_ERROR) { + SFTP_AIO_FREE(*aio); + return SSH_ERROR; + } + + if (rc == SSH_AGAIN) { + /* Return without freeing the (*aio) */ + return SSH_AGAIN; + } + + /* + * Release memory for the structure that (*aio) points to + * as all further points of return are for success or + * failure. + */ + SFTP_AIO_FREE(*aio); + + if (msg->packet_type == SSH_FXP_STATUS) { + status = parse_status_msg(msg); + sftp_message_free(msg); + if (status == NULL) { + return SSH_ERROR; + } + + sftp_set_error(sftp, status->status); + if (status->status == SSH_FX_OK) { + status_msg_free(status); + return bytes_requested; + } + + ssh_set_error(sftp->session, SSH_REQUEST_DENIED, + "SFTP server: %s", status->errormsg); + status_msg_free(status); + return SSH_ERROR; + } + + ssh_set_error(sftp->session, SSH_FATAL, + "Received message %d during write!", + msg->packet_type); + sftp_message_free(msg); + sftp_set_error(sftp, SSH_FX_BAD_MESSAGE); + return SSH_ERROR; +} + +#endif /* WITH_SFTP */ diff --git a/src/libs/libssh-0.12.2/src/sftp_common.c b/src/libs/libssh-0.12.2/src/sftp_common.c new file mode 100644 index 000000000000..006c41b15f90 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sftp_common.c @@ -0,0 +1,1082 @@ +/* + * sftp_common.c - Secure FTP functions which are private and are used + * internally by other sftp api functions spread across + * various source files. + * + * This file is part of the SSH Library + * + * Copyright (c) 2005-2008 by Aris Adamantiadis + * Copyright (c) 2008-2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/buffer.h" +#include "libssh/session.h" +#include "libssh/bytearray.h" + +#ifdef WITH_SFTP + +/* Buffer size maximum is 256M */ +#define SFTP_PACKET_SIZE_MAX 0x10000000 + +sftp_packet sftp_packet_read(sftp_session sftp) +{ + uint8_t tmpbuf[4]; + uint8_t *buffer = NULL; + sftp_packet packet = sftp->read_packet; + uint32_t size; + int nread; + bool is_eof; + int rc; + + packet->sftp = sftp; + + /* + * If the packet has a payload, then just reinit the buffer, otherwise + * allocate a new one. + */ + if (packet->payload != NULL) { + rc = ssh_buffer_reinit(packet->payload); + if (rc != 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } else { + packet->payload = ssh_buffer_new(); + if (packet->payload == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + } + + nread = 0; + do { + int s; + + /* read from channel until 4 bytes have been read or an error occurs */ + s = ssh_channel_read(sftp->channel, tmpbuf + nread, 4 - nread, 0); + if (s < 0) { + goto error; + } else if (s == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet size"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Timeout while reading sftp packet size"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + } else { + nread += s; + } + } while (nread < 4); + + size = PULL_BE_U32(tmpbuf, 0); + if (size == 0 || size > SFTP_PACKET_SIZE_MAX) { + ssh_set_error(sftp->session, SSH_FATAL, "Invalid sftp packet size!"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + + do { + nread = ssh_channel_read(sftp->channel, tmpbuf, 1, 0); + if (nread < 0) { + goto error; + } else if (nread == 0) { + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received EOF while reading sftp packet type"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Timeout while reading sftp packet type"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + } + } while (nread < 1); + + packet->type = tmpbuf[0]; + + /* Remove the packet type size */ + size -= sizeof(uint8_t); + + /* Allocate the receive buffer from payload */ + buffer = ssh_buffer_allocate(packet->payload, size); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + while (size > 0 && size < SFTP_PACKET_SIZE_MAX) { + nread = ssh_channel_read(sftp->channel, buffer, size, 0); + if (nread < 0) { + /* TODO: check if there are cases where an error needs to be set here */ + goto error; + } + + if (nread > 0) { + buffer += nread; + size -= nread; + } else { /* nread == 0 */ + /* Retry the reading unless the remote was closed */ + is_eof = ssh_channel_is_eof(sftp->channel); + if (is_eof) { + ssh_set_error(sftp->session, + SSH_REQUEST_DENIED, + "Received EOF while reading sftp packet"); + sftp_set_error(sftp, SSH_FX_EOF); + goto error; + } else { + ssh_set_error(sftp->session, + SSH_FATAL, + "Timeout while reading sftp packet"); + sftp_set_error(sftp, SSH_FX_FAILURE); + goto error; + } + } + } + + return packet; +error: + ssh_buffer_reinit(packet->payload); + return NULL; +} + +int sftp_packet_write(sftp_session sftp, uint8_t type, ssh_buffer payload) +{ + uint8_t header[5] = {0}; + uint32_t payload_size; + int size; + int rc; + + /* Add size of type */ + payload_size = ssh_buffer_get_len(payload) + sizeof(uint8_t); + PUSH_BE_U32(header, 0, payload_size); + PUSH_BE_U8(header, 4, type); + + rc = ssh_buffer_prepend_data(payload, header, sizeof(header)); + if (rc < 0) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + size = ssh_channel_write(sftp->channel, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (size < 0) { + sftp_set_error(sftp, SSH_FX_FAILURE); + return -1; + } + + if ((uint32_t)size != ssh_buffer_get_len(payload)) { + SSH_LOG(SSH_LOG_PACKET, + "Had to write %" PRIu32 " bytes, wrote only %d", + ssh_buffer_get_len(payload), + size); + } + + return size; +} + +void sftp_packet_free(sftp_packet packet) +{ + if (packet == NULL) { + return; + } + + SSH_BUFFER_FREE(packet->payload); + free(packet); +} + +int buffer_add_attributes(ssh_buffer buffer, sftp_attributes attr) +{ + uint32_t flags = (attr ? attr->flags : 0); + int rc; + + flags &= (SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_ACMODTIME); + + rc = ssh_buffer_pack(buffer, "d", flags); + if (rc != SSH_OK) { + return -1; + } + + if (attr != NULL) { + if (flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_pack(buffer, "q", attr->size); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_pack(buffer, "dd", attr->uid, attr->gid); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_pack(buffer, "d", attr->permissions); + if (rc != SSH_OK) { + return -1; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_pack(buffer, "dd", attr->atime, attr->mtime); + if (rc != SSH_OK) { + return -1; + } + } + } + + return 0; +} + +/* + * Parse the attributes from a payload from some messages. It is coded on + * baselines from the protocol version 4. + * This code is more or less dead but maybe we will need it in the future. + */ +static sftp_attributes sftp_parse_attr_4(sftp_session sftp, + ssh_buffer buf, + int expectnames) +{ + sftp_attributes attr = NULL; + ssh_string owner = NULL; + ssh_string group = NULL; + uint32_t flags = 0; + int ok = 0; + + /* unused member variable */ + (void) expectnames; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + /* This isn't really a loop, but it is like a try..catch.. */ + do { + if (ssh_buffer_get_u32(buf, &flags) != 4) { + break; + } + + flags = ntohl(flags); + attr->flags = flags; + + if (flags & SSH_FILEXFER_ATTR_SIZE) { + if (ssh_buffer_get_u64(buf, &attr->size) != 8) { + break; + } + attr->size = ntohll(attr->size); + } + + if (flags & SSH_FILEXFER_ATTR_OWNERGROUP) { + owner = ssh_buffer_get_ssh_string(buf); + if (owner == NULL) { + break; + } + attr->owner = ssh_string_to_char(owner); + SSH_STRING_FREE(owner); + if (attr->owner == NULL) { + break; + } + + group = ssh_buffer_get_ssh_string(buf); + if (group == NULL) { + break; + } + attr->group = ssh_string_to_char(group); + SSH_STRING_FREE(group); + if (attr->group == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + if (ssh_buffer_get_u32(buf, &attr->permissions) != 4) { + break; + } + attr->permissions = ntohl(attr->permissions); + + /* FIXME on windows! */ + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_ACCESSTIME) { + if (ssh_buffer_get_u64(buf, &attr->atime64) != 8) { + break; + } + attr->atime64 = ntohll(attr->atime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->atime_nseconds) != 4) { + break; + } + attr->atime_nseconds = ntohl(attr->atime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_CREATETIME) { + if (ssh_buffer_get_u64(buf, &attr->createtime) != 8) { + break; + } + attr->createtime = ntohll(attr->createtime); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->createtime_nseconds) != 4) { + break; + } + attr->createtime_nseconds = ntohl(attr->createtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_MODIFYTIME) { + if (ssh_buffer_get_u64(buf, &attr->mtime64) != 8) { + break; + } + attr->mtime64 = ntohll(attr->mtime64); + + if (flags & SSH_FILEXFER_ATTR_SUBSECOND_TIMES) { + if (ssh_buffer_get_u32(buf, &attr->mtime_nseconds) != 4) { + break; + } + attr->mtime_nseconds = ntohl(attr->mtime_nseconds); + } + } + + if (flags & SSH_FILEXFER_ATTR_ACL) { + if ((attr->acl = ssh_buffer_get_ssh_string(buf)) == NULL) { + break; + } + } + + if (flags & SSH_FILEXFER_ATTR_EXTENDED) { + if (ssh_buffer_get_u32(buf,&attr->extended_count) != 4) { + break; + } + attr->extended_count = ntohl(attr->extended_count); + + while (attr->extended_count && + (attr->extended_type = ssh_buffer_get_ssh_string(buf)) && + (attr->extended_data = ssh_buffer_get_ssh_string(buf))) { + attr->extended_count--; + /* just ignore the extensions -- we can't interpret them */ + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + } + + if (attr->extended_count) { + break; + } + } + ok = 1; + } while (0); + + if (ok == 0) { + /* break issued somewhere */ + SSH_STRING_FREE(attr->acl); + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + + return NULL; + } + + return attr; +} + +enum sftp_longname_field_e { + SFTP_LONGNAME_PERM = 0, + SFTP_LONGNAME_FIXME, + SFTP_LONGNAME_OWNER, + SFTP_LONGNAME_GROUP, + SFTP_LONGNAME_SIZE, + SFTP_LONGNAME_DATE, + SFTP_LONGNAME_TIME, + SFTP_LONGNAME_NAME, +}; + +static char * sftp_parse_longname(const char *longname, + enum sftp_longname_field_e longname_field) +{ + const char *p = NULL, *q = NULL; + size_t len, field = 0; + + if (longname == NULL || longname_field < SFTP_LONGNAME_PERM || + longname_field > SFTP_LONGNAME_NAME) { + return NULL; + } + + p = longname; + /* + * Find the beginning of the field which is specified + * by sftp_longname_field_e. + */ + while (*p != '\0' && field != longname_field) { + if (isspace(*p)) { + field++; + p++; + while (*p != '\0' && isspace(*p)) { + p++; + } + } else { + p++; + } + } + + /* If we reached NULL before we got our field fail */ + if (field != longname_field) { + return NULL; + } + + q = p; + while (*q != '\0' && !isspace(*q)) { + q++; + } + + len = q - p; + + return strndup(p, len); +} + +/* sftp version 0-3 code. It is different from the v4 */ +/* maybe a paste of the draft is better than the code */ +/* + uint32 flags + uint64 size present only if flag SSH_FILEXFER_ATTR_SIZE + uint32 uid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 gid present only if flag SSH_FILEXFER_ATTR_UIDGID + uint32 permissions present only if flag SSH_FILEXFER_ATTR_PERMISSIONS + uint32 atime present only if flag SSH_FILEXFER_ACMODTIME + uint32 mtime present only if flag SSH_FILEXFER_ACMODTIME + uint32 extended_count present only if flag SSH_FILEXFER_ATTR_EXTENDED + string extended_type + string extended_data + ... more extended data (extended_type - extended_data pairs), + so that number of pairs equals extended_count */ +static sftp_attributes sftp_parse_attr_3(sftp_session sftp, + ssh_buffer buf, + int expectname) +{ + sftp_attributes attr; + int rc; + + attr = calloc(1, sizeof(struct sftp_attributes_struct)); + if (attr == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return NULL; + } + + if (expectname) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->name, + &attr->longname); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Name: %s", attr->name); + + /* Set owner and group if we talk to openssh and have the longname */ + if (ssh_get_openssh_version(sftp->session)) { + attr->owner = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_OWNER); + if (attr->owner == NULL) { + goto error; + } + + attr->group = sftp_parse_longname(attr->longname, + SFTP_LONGNAME_GROUP); + if (attr->group == NULL) { + goto error; + } + } + } + + rc = ssh_buffer_unpack(buf, "d", &attr->flags); + if (rc != SSH_OK){ + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Flags: %.8" PRIx32, attr->flags); + + if (attr->flags & SSH_FILEXFER_ATTR_SIZE) { + rc = ssh_buffer_unpack(buf, "q", &attr->size); + if(rc != SSH_OK) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "Size: %" PRIu64, (uint64_t)attr->size); + } + + if (attr->flags & SSH_FILEXFER_ATTR_UIDGID) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->uid, + &attr->gid); + if (rc != SSH_OK) { + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rc = ssh_buffer_unpack(buf, "d", &attr->permissions); + if (rc != SSH_OK) { + goto error; + } + + switch (attr->permissions & SSH_S_IFMT) { + case SSH_S_IFSOCK: + case SSH_S_IFBLK: + case SSH_S_IFCHR: + case SSH_S_IFIFO: + attr->type = SSH_FILEXFER_TYPE_SPECIAL; + break; + case SSH_S_IFLNK: + attr->type = SSH_FILEXFER_TYPE_SYMLINK; + break; + case SSH_S_IFREG: + attr->type = SSH_FILEXFER_TYPE_REGULAR; + break; + case SSH_S_IFDIR: + attr->type = SSH_FILEXFER_TYPE_DIRECTORY; + break; + default: + attr->type = SSH_FILEXFER_TYPE_UNKNOWN; + break; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_ACMODTIME) { + rc = ssh_buffer_unpack(buf, "dd", + &attr->atime, + &attr->mtime); + if (rc != SSH_OK) { + goto error; + } + } + + if (attr->flags & SSH_FILEXFER_ATTR_EXTENDED) { + rc = ssh_buffer_unpack(buf, "d", &attr->extended_count); + if (rc != SSH_OK) { + goto error; + } + + if (attr->extended_count > 0) { + rc = ssh_buffer_unpack(buf, "ss", + &attr->extended_type, + &attr->extended_data); + if (rc != SSH_OK) { + goto error; + } + attr->extended_count--; + } + /* just ignore the remaining extensions */ + + while (attr->extended_count > 0) { + ssh_string tmp1,tmp2; + rc = ssh_buffer_unpack(buf, "SS", &tmp1, &tmp2); + if (rc != SSH_OK){ + goto error; + } + SAFE_FREE(tmp1); + SAFE_FREE(tmp2); + attr->extended_count--; + } + } + + return attr; + +error: + SSH_STRING_FREE(attr->extended_type); + SSH_STRING_FREE(attr->extended_data); + SAFE_FREE(attr->name); + SAFE_FREE(attr->longname); + SAFE_FREE(attr->owner); + SAFE_FREE(attr->group); + SAFE_FREE(attr); + ssh_set_error(sftp->session, SSH_FATAL, "Invalid ATTR structure"); + sftp_set_error(sftp, SSH_FX_FAILURE); + + return NULL; +} + +sftp_attributes sftp_parse_attr(sftp_session session, + ssh_buffer buf, + int expectname) +{ + switch (session->version) { + case 4: + return sftp_parse_attr_4(session, buf, expectname); + case 3: + case 2: + case 1: + case 0: + return sftp_parse_attr_3(session, buf, expectname); + default: + ssh_set_error(session->session, SSH_FATAL, + "Version %d unsupported by client", + session->server_version); + return NULL; + } + + return NULL; +} + +void sftp_set_error(sftp_session sftp, int errnum) +{ + if (sftp != NULL) { + sftp->errnum = errnum; + } +} + +void sftp_message_free(sftp_message msg) +{ + if (msg == NULL) { + return; + } + + SSH_BUFFER_FREE(msg->payload); + SAFE_FREE(msg); +} + +static sftp_request_queue request_queue_new(sftp_message msg) +{ + sftp_request_queue queue = NULL; + + queue = calloc(1, sizeof(struct sftp_request_queue_struct)); + if (queue == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + queue->message = msg; + + return queue; +} + +static void request_queue_free(sftp_request_queue queue) +{ + if (queue == NULL) { + return; + } + + ZERO_STRUCTP(queue); + SAFE_FREE(queue); +} + +static int +sftp_enqueue(sftp_session sftp, sftp_message msg) +{ + sftp_request_queue queue = NULL; + sftp_request_queue ptr; + + queue = request_queue_new(msg); + if (queue == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PACKET, + "Queued msg id %" PRIu32 " type %d", + msg->id, msg->packet_type); + + if(sftp->queue == NULL) { + sftp->queue = queue; + } else { + ptr = sftp->queue; + while(ptr->next) { + ptr=ptr->next; /* find end of linked list */ + } + ptr->next = queue; /* add it on bottom */ + } + + return 0; +} + +/* + * Pulls a message from the queue based on the ID. + * Returns NULL if no message has been found. + */ +sftp_message sftp_dequeue(sftp_session sftp, uint32_t id) +{ + sftp_request_queue prev = NULL; + sftp_request_queue queue; + sftp_message msg; + + if(sftp->queue == NULL) { + return NULL; + } + + queue = sftp->queue; + while (queue) { + if (queue->message->id == id) { + /* remove from queue */ + if (prev == NULL) { + sftp->queue = queue->next; + } else { + prev->next = queue->next; + } + msg = queue->message; + request_queue_free(queue); + SSH_LOG(SSH_LOG_PACKET, + "Dequeued msg id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + return msg; + } + prev = queue; + queue = queue->next; + } + + return NULL; +} + +static sftp_message sftp_get_message(sftp_packet packet) +{ + sftp_session sftp = packet->sftp; + sftp_message msg = NULL; + struct ssh_iterator *id_it = NULL; + bool id_found = false; + int rc; + + switch (packet->type) { + case SSH_FXP_STATUS: + case SSH_FXP_HANDLE: + case SSH_FXP_DATA: + case SSH_FXP_ATTRS: + case SSH_FXP_NAME: + case SSH_FXP_EXTENDED_REPLY: + break; + default: + ssh_set_error(packet->sftp->session, + SSH_FATAL, + "Unknown packet type %d", + packet->type); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg = calloc(1, sizeof(struct sftp_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + msg->sftp = packet->sftp; + msg->packet_type = packet->type; + + /* Move the payload from the packet to the message */ + msg->payload = packet->payload; + packet->payload = NULL; + + rc = ssh_buffer_unpack(msg->payload, "d", &msg->id); + if (rc != SSH_OK) { + ssh_set_error(packet->sftp->session, SSH_FATAL, + "Invalid packet %d: no ID", packet->type); + sftp_message_free(msg); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + SSH_LOG(SSH_LOG_PACKET, + "Packet with id %" PRIu32 " type %d", + msg->id, + msg->packet_type); + + /* Validate that this ID is in our outstanding requests list */ + id_it = ssh_list_get_iterator(sftp->outstanding_ids); + for (; id_it != NULL; id_it = id_it->next) { + uint32_t *stored_id = (uint32_t *)id_it->data; + if (*stored_id == msg->id) { + id_found = true; + ssh_list_remove(sftp->outstanding_ids, id_it); + free(stored_id); + break; + } + } + + if (!id_found) { + ssh_set_error(packet->sftp->session, + SSH_FATAL, + "Unknown request ID %" PRIu32, + msg->id); + sftp_message_free(msg); + sftp_set_error(packet->sftp, SSH_FX_FAILURE); + return NULL; + } + + return msg; +} + +int sftp_get_new_id(sftp_session sftp, uint32_t *id_out) +{ + uint32_t *id = NULL; + int rc; + + if (id_out == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + id = malloc(sizeof(uint32_t)); + if (id == NULL) { + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + *id = ++sftp->id_counter; + rc = ssh_list_append(sftp->outstanding_ids, id); + if (rc != SSH_OK) { + free(id); + ssh_set_error_oom(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + *id_out = *id; + + return SSH_OK; +} + +int sftp_read_and_dispatch(sftp_session sftp) +{ + sftp_packet packet = NULL; + sftp_message msg = NULL; + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + /* something nasty happened reading the packet */ + return -1; + } + + msg = sftp_get_message(packet); + if (msg == NULL) { + return -1; + } + + if (sftp_enqueue(sftp, msg) < 0) { + sftp_message_free(msg); + return -1; + } + + return 0; +} + +int sftp_recv_response_msg(sftp_session sftp, + uint32_t id, + bool blocking, + sftp_message *msg_ptr) +{ + sftp_message msg = NULL; + int rc; + + if (sftp == NULL) { + return SSH_ERROR; + } + + if (msg_ptr == NULL) { + ssh_set_error_invalid(sftp->session); + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PACKET, + "Trying to receive response of request id %" PRIu32 " in %s mode", + id, + blocking ? "blocking" : "non-blocking"); + + /* + * We deliberately check the queue first for the response before + * polling/blocking on the channel. The reason for this approach is + * explained by the following example (And a similar scenario can occur when + * the async sftp aio API is used, because it provides the control of which + * responses to receive (and in what order) to the user via the + * sftp_aio_wait_*() functions) + * + * Its possible that while this function is trying to receive some + * specific response (based on request id), other responses have already + * arrived (or may arrive) before that specific response on the channel. In + * that case, this function would collect those other responses from the + * channel, add them to the sftp response queue (using + * sftp_read_and_dipatch()) and finally provide the caller with the + * required specific response. + * + * Now, whenever the caller will call this function again to get one of + * those other responses, it won't be on the channel, instead it would be + * present in the queue. + * + * Assuming that no new response ever comes on the channel, if we don't + * check the queue first and instead: + * - (In non blocking mode) poll on the channel, then we'd always get 0 + * bytes of data and return SSH_AGAIN. + * - (In blocking mode) wait on the channel, then + * sftp_read_and_dispatch() would block infinitely by default if the + * user has not set any timeout. + * + * Hence checking the queue for the response first and if not found there, + * polling/blocking on the channel is advised. + */ + while (msg == NULL) { + /* + * Before trying to poll/block on the channel for data, probe the queue + * to check whether the response is already present in it. + */ + msg = sftp_dequeue(sftp, id); + if (msg != NULL) { + break; + } + + if (!blocking) { + rc = ssh_channel_poll(sftp->channel, 0); + if (rc == SSH_ERROR) { + sftp_set_error(sftp, SSH_FX_FAILURE); + return SSH_ERROR; + } + + if (rc == 0) { + /* nothing available and we cannot block */ + return SSH_AGAIN; + } + } + + rc = sftp_read_and_dispatch(sftp); + if (rc == -1) { + /* something nasty has happened */ + return SSH_ERROR; + } + } + + *msg_ptr = msg; + return SSH_OK; +} + +sftp_status_message parse_status_msg(sftp_message msg) +{ + sftp_status_message status = NULL; + int rc; + + if (msg->packet_type != SSH_FXP_STATUS) { + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Not a ssh_fxp_status message passed in!"); + sftp_set_error(msg->sftp, SSH_FX_BAD_MESSAGE); + return NULL; + } + + status = calloc(1, sizeof(struct sftp_status_message_struct)); + if (status == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + status->id = msg->id; + rc = ssh_buffer_unpack(msg->payload, "d", + &status->status); + if (rc != SSH_OK) { + SAFE_FREE(status); + ssh_set_error(msg->sftp->session, SSH_FATAL, + "Invalid SSH_FXP_STATUS message"); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + return NULL; + } + + rc = ssh_buffer_unpack(msg->payload, "ss", + &status->errormsg, + &status->langmsg); + + if (rc != SSH_OK && msg->sftp->version >= 3) { + SSH_LOG(SSH_LOG_WARN, + "Invalid SSH_FXP_STATUS message. Missing error message."); + } + + if (status->errormsg == NULL) + status->errormsg = strdup("No error message in packet"); + + if (status->langmsg == NULL) + status->langmsg = strdup(""); + + if (status->errormsg == NULL || status->langmsg == NULL) { + ssh_set_error_oom(msg->sftp->session); + sftp_set_error(msg->sftp, SSH_FX_FAILURE); + status_msg_free(status); + return NULL; + } + + return status; +} + +void status_msg_free(sftp_status_message status) +{ + if (status == NULL) { + return; + } + + SAFE_FREE(status->errormsg); + SAFE_FREE(status->langmsg); + SAFE_FREE(status); +} + +#endif /* WITH_SFTP */ diff --git a/src/libs/libssh-0.12.2/src/sftpserver.c b/src/libs/libssh-0.12.2/src/sftpserver.c new file mode 100644 index 000000000000..e926ccc6f13d --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sftpserver.c @@ -0,0 +1,2173 @@ +/* + * sftpserver.c - server based function for the sftp protocol + * + * This file is part of the SSH Library + * + * Copyright (c) 2005 Aris Adamantiadis + * Copyright (c) 2022 Zeyu Sheng + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + + +#ifndef _WIN32 +#include +#include +#include +#include +#endif + +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#ifdef HAVE_SYS_UTIME_H +#include +#endif /* HAVE_SYS_UTIME_H */ + +#include +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/sftp.h" +#include "libssh/sftp_priv.h" +#include "libssh/sftpserver.h" +#include "libssh/ssh2.h" +#include "libssh/priv.h" +#include "libssh/buffer.h" +#include "libssh/misc.h" + +#define SFTP_HANDLES 256 + +#define MAX_ENTRIES_NUM_IN_PACKET 50 +#define MAX_LONG_NAME_LEN 350 + +/** + * @internal + * + * @brief Creates an SFTP client message from a received packet. + * + * Allocates and initializes a sftp_client_message structure from a raw + * SFTP packet. Copies the complete @p packet payload and parses common + * fields such as message type, request id, and message-specific data + * (handle, filename, attributes, offsets, etc.) depending on the SFTP + * message type. + * + * On success, the returned message owns its internal buffers and must + * be freed with sftp_client_message_free(). + * + * @param[in] sftp The SFTP session associated with the packet. + * @param[in] packet The received SFTP packet to decode. + * + * @return A newly allocated sftp_client_message on success, or NULL on + * error (memory allocation failure, malformed packet, or + * unsupported message type). + */ +static sftp_client_message +sftp_make_client_message(sftp_session sftp, sftp_packet packet) +{ + ssh_session session = sftp->session; + sftp_client_message msg = NULL; + ssh_buffer payload = NULL; + int rc; + int version; + + msg = calloc(1, sizeof(struct sftp_client_message_struct)); + if (msg == NULL) { + ssh_set_error_oom(session); + return NULL; + } + + payload = packet->payload; + msg->type = packet->type; + msg->sftp = sftp; + + /* take a copy of the whole packet */ + msg->complete_message = ssh_buffer_new(); + if (msg->complete_message == NULL) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_add_data(msg->complete_message, + ssh_buffer_get(payload), + ssh_buffer_get_len(payload)); + if (rc < 0) { + goto error; + } + + if (msg->type != SSH_FXP_INIT) { + rc = ssh_buffer_get_u32(payload, &msg->id); + if (rc != sizeof(uint32_t)) { + goto error; + } + } + + switch (msg->type) { + case SSH_FXP_INIT: + rc = ssh_buffer_unpack(payload, + "d", + &version); + if (rc != SSH_OK) { + printf("unpack init failed!\n"); + goto error; + } + sftp->client_version = version; + break; + case SSH_FXP_CLOSE: + case SSH_FXP_READDIR: + msg->handle = ssh_buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + goto error; + } + break; + case SSH_FXP_READ: + rc = ssh_buffer_unpack(payload, + "Sqd", + &msg->handle, + &msg->offset, + &msg->len); + if (rc != SSH_OK) { + goto error; + } + if (msg->len > MAX_PACKET_LEN - 1024) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Too large SSH_FXP_READ length: %" PRIu32, + msg->len); + goto error; + } + break; + case SSH_FXP_WRITE: + rc = ssh_buffer_unpack(payload, + "SqS", + &msg->handle, + &msg->offset, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_REMOVE: + case SSH_FXP_RMDIR: + case SSH_FXP_OPENDIR: + case SSH_FXP_READLINK: + case SSH_FXP_REALPATH: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_RENAME: + case SSH_FXP_SYMLINK: + rc = ssh_buffer_unpack(payload, + "sS", + &msg->filename, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_MKDIR: + case SSH_FXP_SETSTAT: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_FSETSTAT: + msg->handle = ssh_buffer_get_ssh_string(payload); + if (msg->handle == NULL) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_LSTAT: + case SSH_FXP_STAT: + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + if (sftp->version > 3) { + ssh_buffer_unpack(payload, "d", &msg->flags); + } + break; + case SSH_FXP_OPEN: + rc = ssh_buffer_unpack(payload, + "sd", + &msg->filename, + &msg->flags); + if (rc != SSH_OK) { + goto error; + } + msg->attr = sftp_parse_attr(sftp, payload, 0); + if (msg->attr == NULL) { + goto error; + } + break; + case SSH_FXP_FSTAT: + rc = ssh_buffer_unpack(payload, + "S", + &msg->handle); + if (rc != SSH_OK) { + goto error; + } + break; + case SSH_FXP_EXTENDED: + rc = ssh_buffer_unpack(payload, + "s", + &msg->submessage); + if (rc != SSH_OK) { + goto error; + } + + if (strcmp(msg->submessage, "hardlink@openssh.com") == 0 || + strcmp(msg->submessage, "posix-rename@openssh.com") == 0) { + rc = ssh_buffer_unpack(payload, + "sS", + &msg->filename, + &msg->data); + if (rc != SSH_OK) { + goto error; + } + } else if (strcmp(msg->submessage, "statvfs@openssh.com") == 0 ){ + rc = ssh_buffer_unpack(payload, + "s", + &msg->filename); + if (rc != SSH_OK) { + goto error; + } + } + break; + default: + ssh_set_error(sftp->session, SSH_FATAL, + "Received unhandled sftp message %d", msg->type); + goto error; + } + + return msg; + +error: + sftp_client_message_free(msg); + return NULL; +} + +/** + * @brief Reads the next SFTP client message from the session. + * + * Reads a single SFTP packet from the given SFTP session and converts it + * into a parsed sftp_client_message structure. + * + * @param[in] sftp The SFTP session to read from. + * + * @return A newly allocated sftp_client_message on success; NULL if no packet + * is available or an error occurs. + */ +sftp_client_message sftp_get_client_message(sftp_session sftp) +{ + sftp_packet packet = NULL; + + packet = sftp_packet_read(sftp); + if (packet == NULL) { + return NULL; + } + return sftp_make_client_message(sftp, packet); +} + +/** + * @brief Get the client message from a sftp packet. + * + * @param sftp The sftp session handle. + * + * @return The pointer to the generated sftp client message. + */ +static sftp_client_message +sftp_get_client_message_from_packet(sftp_session sftp) +{ + sftp_packet packet = NULL; + + packet = sftp->read_packet; + if (packet == NULL) { + return NULL; + } + return sftp_make_client_message(sftp, packet); +} + +/** + * @brief Send an SFTP client message. + * + * Writes the given client message as a packet using the stored message + * type and complete_message buffer. Can be used in case of proxying. + * + * @param[in] sftp The SFTP session. + * @param[in] msg The client message to send. + * + * @return 0 on success; -1 on error from sftp_packet_write(). + */ +int sftp_send_client_message(sftp_session sftp, sftp_client_message msg) +{ + return sftp_packet_write(sftp, msg->type, msg->complete_message); +} + +/** + * @brief Get the SFTP client message type. + * + * Returns the SFTP packet type associated with the given client message + * (for example `SSH_FXP_READ`, `SSH_FXP_WRITE`, `SSH_FXP_OPEN`, ...). + * + * @param[in] msg The SFTP client message. + * + * @return The SFTP message type as an unsigned 8-bit value. + */ +uint8_t sftp_client_message_get_type(sftp_client_message msg) +{ + return msg->type; +} + +/** + * @brief Get the filename associated with an SFTP client message. + * + * Returns the filename carried by the given SFTP client message, if the + * message type includes a filename field (for example OPEN, REMOVE, RENAME). + * + * @param[in] msg The SFTP client message. + * + * @return Filename string, or NULL if no filename + * is associated with the message. + */ +const char *sftp_client_message_get_filename(sftp_client_message msg) +{ + return msg->filename; +} + +/** + * @brief Set the filename associated with an SFTP client message. + * + * Replaces the current filename stored in the client message with a copy + * of the given @p newname string. + * + * @param[in] msg The SFTP client message to modify. + * @param[in] newname The new filename to store in the message. + * + * @warn On failure, the filename in the message is set to `NULL`. Users of + * sftp_client_message_get_filename() need to check the return value! + */ +void +sftp_client_message_set_filename(sftp_client_message msg, const char *newname) +{ + free(msg->filename); + msg->filename = strdup(newname); +} + +/** + * @brief Get the data field of an SFTP client message as a string. + * + * Converts the internal ssh_string data field to a string + * on first use and caches the result in the message. Subsequent calls + * return the cached pointer. + * + * @param[in] msg The SFTP client message. + * + * @return The data as string, or NULL on error. + */ +const char *sftp_client_message_get_data(sftp_client_message msg) +{ + if (msg->str_data == NULL) + msg->str_data = ssh_string_to_char(msg->data); + return msg->str_data; +} + +/** + * @brief Get the flags associated with an SFTP client message. + * + * Returns the flags field stored in the given SFTP client message. The exact + * meaning of the flags depends on the SFTP message type (for example, open + * or stat flags). + * + * @param[in] msg The SFTP client message. + * + * @return The flags value as an unsigned 32-bit integer. + */ +uint32_t sftp_client_message_get_flags(sftp_client_message msg) +{ + return msg->flags; +} + +/** + * @brief Get the submessage name associated with an SFTP client message. + * + * Returns the submessage string stored in the given SFTP client message. + * This is typically used for vendor-specific SFTP operations. + * + * @param[in] msg The SFTP client message. + * + * @return The submessage name as a string, or NULL if no + * submessage is associated with the message. + */ +const char *sftp_client_message_get_submessage(sftp_client_message msg) +{ + return msg->submessage; +} + +/** + * @brief Free an SFTP client message and its associated resources. + * + * Releases all dynamically allocated fields in the SFTP client message + * (such as filename, submessage, data, handle, attributes, and cached + * buffers) and then frees the message structure itself. The function + * does nothing if msg is NULL. + * + * @param[in] msg The SFTP client message to free, or NULL. + */ +void sftp_client_message_free(sftp_client_message msg) +{ + if (msg == NULL) { + return; + } + + SAFE_FREE(msg->filename); + SAFE_FREE(msg->submessage); + SSH_STRING_FREE(msg->data); + SSH_STRING_FREE(msg->handle); + sftp_attributes_free(msg->attr); + SSH_BUFFER_FREE(msg->complete_message); + SAFE_FREE(msg->str_data); + ZERO_STRUCTP(msg); + SAFE_FREE(msg); +} + +/** + * @brief Send an SFTP NAME reply for a client message. + * + * Builds and sends an `SSH_FXP_NAME` packet in response to the given + * SFTP client message, containing a single filename and its attributes. + * The function encodes the message id, the count of returned names + * (always 1), the filename fields and the provided attributes. + * + * @param[in] msg The SFTP client message being answered. + * @param[in] name The filename to return to the client. + * @param[in] attr The file attributes associated with the filename. + * + * @return 0 on success; -1 on memory allocation failure or packet send error. + */ +int +sftp_reply_name(sftp_client_message msg, const char *name, sftp_attributes attr) +{ + ssh_buffer out = NULL; + ssh_string file = NULL; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + file = ssh_string_from_char(name); + if (file == NULL) { + SSH_BUFFER_FREE(out); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending name %s", ssh_string_get_char(file)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(1)) < 0 || + ssh_buffer_add_ssh_string(out, file) < 0 || + ssh_buffer_add_ssh_string(out, file) < 0 || /* The protocol is broken here between 3 & 4 */ + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(file); + return -1; + } + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(file); + + return 0; +} + +/** + * @brief Send an SFTP HANDLE reply for a client message. + * + * Builds and sends an `SSH_FXP_HANDLE` packet in response to the given + * SFTP client message, containing the provided file @p handle. The message + * id is taken from the client message and the handle is encoded as an + * SSH string. + * + * @param[in] msg The SFTP client message being answered. + * @param[in] handle The file handle to return to the client. + * + * @return 0 on success; -1 on memory allocation failure or packet send error. + */ +int sftp_reply_handle(sftp_client_message msg, ssh_string handle) +{ + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + ssh_log_hexdump("Sending handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_ssh_string(out, handle) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_HANDLE, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } + SSH_BUFFER_FREE(out); + + return 0; +} + +/** + * @brief Send an SFTP ATTRS reply for a client message. + * + * Builds and sends an `SSH_FXP_ATTRS` packet in response to the given + * SFTP client message, encoding the message id and the provided file + * attributes. + * + * @param[in] msg The SFTP client message being answered. + * @param[in] attr The file attributes to return to the client. + * + * @return 0 on success; -1 on memory allocation failure or packet send error. + */ +int sftp_reply_attr(sftp_client_message msg, sftp_attributes attr) +{ + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending attr"); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + buffer_add_attributes(out, attr) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_ATTRS, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } + SSH_BUFFER_FREE(out); + + return 0; +} + +/** + * @brief Add one name entry to a multi-name SFTP reply. + * + * Appends a @p file name, @p longname and attributes to the buffered NAME reply + * stored in the client message. Can be called multiple times before the + * reply is sent. + * + * @param[in] msg The SFTP client message being prepared. + * @param[in] file The filename to add. + * @param[in] longname The long name to add. + * @param[in] attr The file attributes for this entry. + * + * @return 0 on success; -1 on memory allocation or buffer write error. + */ +int +sftp_reply_names_add(sftp_client_message msg, const char *file, + const char *longname, sftp_attributes attr) +{ + ssh_string name = NULL; + + name = ssh_string_from_char(file); + if (name == NULL) { + return -1; + } + + if (msg->attrbuf == NULL) { + msg->attrbuf = ssh_buffer_new(); + if (msg->attrbuf == NULL) { + SSH_STRING_FREE(name); + return -1; + } + } + + if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0) { + SSH_STRING_FREE(name); + return -1; + } + + SSH_STRING_FREE(name); + name = ssh_string_from_char(longname); + if (name == NULL) { + return -1; + } + if (ssh_buffer_add_ssh_string(msg->attrbuf, name) < 0 || + buffer_add_attributes(msg->attrbuf, attr) < 0) { + SSH_STRING_FREE(name); + return -1; + } + SSH_STRING_FREE(name); + msg->attr_num++; + + return 0; +} + +/** + * @brief Send a multi-name SFTP reply. + * + * Sends an `SSH_FXP_NAME` packet for the given client message using the + * accumulated name entries stored in msg->attrbuf and msg->attr_num. + * After sending, the buffer and counter are reset. + * + * @param[in] msg The SFTP client message to reply to. + * + * @return 0 on success; -1 on memory allocation or packet send error. + */ +int sftp_reply_names(sftp_client_message msg) +{ + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + SSH_BUFFER_FREE(msg->attrbuf); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending %d names", msg->attr_num); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(msg->attr_num)) < 0 || + ssh_buffer_add_data(out, ssh_buffer_get(msg->attrbuf), + ssh_buffer_get_len(msg->attrbuf)) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_NAME, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_BUFFER_FREE(msg->attrbuf); + return -1; + } + + SSH_BUFFER_FREE(out); + SSH_BUFFER_FREE(msg->attrbuf); + + msg->attr_num = 0; + msg->attrbuf = NULL; + + return 0; +} + +/** + * @brief Send an SFTP STATUS reply. + * + * Sends an `SSH_FXP_STATUS` packet for the given client message, including + * the @p status code and an optional human readable @p message. The language + * tag is sent as an empty string. + * + * @param[in] msg The SFTP client message to reply to. + * @param[in] status The SFTP status code to send (e.g. `SSH_FX_OK`, + * `SSH_FX_FAILURE`). + * @param[in] message Optional text message describing the status, or NULL. + * + * @return 0 on success; -1 on memory allocation or packet send error. + */ +int +sftp_reply_status(sftp_client_message msg, uint32_t status, const char *message) +{ + ssh_buffer out = NULL; + ssh_string s = NULL; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + s = ssh_string_from_char(message ? message : ""); + if (s == NULL) { + SSH_BUFFER_FREE(out); + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending status %d, message: %s", status, + ssh_string_get_char(s)); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, htonl(status)) < 0 || + ssh_buffer_add_ssh_string(out, s) < 0 || + ssh_buffer_add_u32(out, 0) < 0 || /* language string */ + sftp_packet_write(msg->sftp, SSH_FXP_STATUS, out) < 0) { + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(s); + return -1; + } + + SSH_BUFFER_FREE(out); + SSH_STRING_FREE(s); + + return 0; +} + +/** + * @brief Send an SFTP DATA reply. + * + * Sends an `SSH_FXP_DATA` packet for the given client message, containing + * the provided data buffer and its length. + * + * @param[in] msg The SFTP client message to reply to. + * @param[in] data The data buffer to send. + * @param[in] len Number of bytes from data to send. + * + * @return 0 on success; -1 on memory allocation or packet send error. + */ +int sftp_reply_data(sftp_client_message msg, const void *data, int len) +{ + ssh_buffer out; + + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending data, length: %d", len); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u32(out, ntohl(len)) < 0 || + ssh_buffer_add_data(out, data, len) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_DATA, out) < 0) { + SSH_BUFFER_FREE(out); + return -1; + } + SSH_BUFFER_FREE(out); + + return 0; +} + +/** + * @brief Handle the statvfs request, return information the mounted file system. + * + * @param msg The sftp client message. + * + * @param st The statvfs state of target file. + * + * @return 0 on success, < 0 on error with ssh and sftp error set. + * + * @see sftp_get_error() + */ +static int +sftp_reply_statvfs(sftp_client_message msg, sftp_statvfs_t st) +{ + int ret = 0; + ssh_buffer out; + out = ssh_buffer_new(); + if (out == NULL) { + return -1; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending statvfs reply"); + + if (ssh_buffer_add_u32(out, msg->id) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bsize)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_frsize)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_blocks)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bfree)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_bavail)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_files)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_ffree)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_favail)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_fsid)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_flag)) < 0 || + ssh_buffer_add_u64(out, ntohll(st->f_namemax)) < 0 || + sftp_packet_write(msg->sftp, SSH_FXP_EXTENDED_REPLY, out) < 0) { + ret = -1; + } + SSH_BUFFER_FREE(out); + + return ret; +} + +int sftp_reply_version(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_session session = sftp->session; + int version; + ssh_buffer reply; + int rc; + + SSH_LOG(SSH_LOG_PROTOCOL, "Sending version packet"); + + /* The SSH_FXP_INIT can be received only once -- repeated initialization + * is not supported */ + if (sftp->version > 0) { + ssh_set_error(sftp->session, + SSH_FATAL, + "Received duplicate INIT message"); + return SSH_ERROR; + } + + /* from draft-spaghetti-sshm-filexfer-00 Section 4: + * > The server responds with a SSH_FXP_VERSION packet, supplying the + * > lowest of its own and the client's version number. + */ + version = MIN(sftp->client_version, LIBSFTP_VERSION); + + reply = ssh_buffer_new(); + if (reply == NULL) { + ssh_set_error_oom(session); + return -1; + } + + rc = ssh_buffer_pack(reply, + "dssssss", + version, + "posix-rename@openssh.com", + "1", + "hardlink@openssh.com", + "1", + "statvfs@openssh.com", + "2"); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(reply); + return -1; + } + + rc = sftp_packet_write(sftp, SSH_FXP_VERSION, reply); + if (rc < 0) { + SSH_BUFFER_FREE(reply); + return -1; + } + SSH_BUFFER_FREE(reply); + + SSH_LOG(SSH_LOG_PROTOCOL, "Server version sent"); + + sftp->version = version; + + return SSH_OK; +} + +/** + * @brief Allocate a new SFTP handle slot. + * + * Finds a free handle slot in the SFTP session, stores the given @p info + * there and returns a 4-byte ssh_string that encodes the handle + * index. + * + * @param[in] sftp The SFTP session. + * @param[in] info Info to be stored in the handle slot. + * + * @return A new handle as ssh_string on success; NULL if no slot is + * available or on memory allocation failure. + */ +ssh_string sftp_handle_alloc(sftp_session sftp, void *info) +{ + ssh_string ret = NULL; + uint32_t val; + uint32_t i; + + if (sftp->handles == NULL) { + sftp->handles = calloc(SFTP_HANDLES, sizeof(void *)); + if (sftp->handles == NULL) { + return NULL; + } + } + + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == NULL) { + break; + } + } + + if (i == SFTP_HANDLES) { + return NULL; /* no handle available */ + } + + val = i; + ret = ssh_string_new(4); + if (ret == NULL) { + return NULL; + } + + memcpy(ssh_string_data(ret), &val, sizeof(uint32_t)); + sftp->handles[i] = info; + + return ret; +} + +/** + * @brief Resolve an SFTP handle to its stored info. + * + * Decodes the 4-byte @p handle value, checks bounds and returns the pointer + * stored in the corresponding handle slot in the SFTP session. + * + * @param[in] sftp The SFTP session. + * @param[in] handle The handle value as ssh_string. + * + * @return The stored pointer on success, or NULL if the handle table is + * not initialized, the handle size is invalid, or the index is + * out of range. + */ +void *sftp_handle(sftp_session sftp, ssh_string handle) +{ + uint32_t val; + + if (sftp->handles == NULL) { + return NULL; + } + + if (ssh_string_len(handle) != sizeof(uint32_t)) { + return NULL; + } + + memcpy(&val, ssh_string_data(handle), sizeof(uint32_t)); + + if (val >= SFTP_HANDLES) { + return NULL; + } + + return sftp->handles[val]; +} + +void sftp_handle_remove(sftp_session sftp, void *handle) +{ + int i; + + for (i = 0; i < SFTP_HANDLES; i++) { + if (sftp->handles[i] == handle) { + sftp->handles[i] = NULL; + break; + } + } +} + +/* Default SFTP handlers */ + +static const char * +ssh_str_error(int u_errno) +{ + switch (u_errno) { + case SSH_FX_NO_SUCH_FILE: + return "No such file"; + case SSH_FX_PERMISSION_DENIED: + return "Permission denied"; + case SSH_FX_BAD_MESSAGE: + return "Bad message"; + case SSH_FX_OP_UNSUPPORTED: + return "Operation not supported"; + default: + return "Operation failed"; + } +} + +static int +unix_errno_to_ssh_stat(int u_errno) +{ + int ret = SSH_OK; + switch (u_errno) { + case 0: + break; + case ENOENT: + case ENOTDIR: + case EBADF: + case ELOOP: + ret = SSH_FX_NO_SUCH_FILE; + break; + case EPERM: + case EACCES: + case EFAULT: + ret = SSH_FX_PERMISSION_DENIED; + break; + case ENAMETOOLONG: + case EINVAL: + ret = SSH_FX_BAD_MESSAGE; + break; + case ENOSYS: + ret = SSH_FX_OP_UNSUPPORTED; + break; + default: + ret = SSH_FX_FAILURE; + break; + } + + return ret; +} + +static void +stat_to_filexfer_attrib(const struct stat *z_st, struct sftp_attributes_struct *z_attr) +{ + z_attr->flags = 0 | (uint32_t)SSH_FILEXFER_ATTR_SIZE; + z_attr->size = z_st->st_size; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_UIDGID; + z_attr->uid = z_st->st_uid; + z_attr->gid = z_st->st_gid; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS; + z_attr->permissions = z_st->st_mode; + + z_attr->flags |= (uint32_t)SSH_FILEXFER_ATTR_ACMODTIME; + z_attr->atime = (uint32_t)z_st->st_atime; + z_attr->mtime = (uint32_t)z_st->st_mtime; +} + +static void +clear_filexfer_attrib(struct sftp_attributes_struct *z_attr) +{ + z_attr->flags = 0; + z_attr->size = 0; + z_attr->uid = 0; + z_attr->gid = 0; + z_attr->permissions = 0; + z_attr->atime = 0; + z_attr->mtime = 0; +} + +#ifndef _WIN32 +/* internal */ +enum sftp_handle_type +{ + SFTP_NULL_HANDLE, + SFTP_DIR_HANDLE, + SFTP_FILE_HANDLE +}; + +struct sftp_handle +{ + enum sftp_handle_type type; + int fd; + DIR *dirp; + char *name; +}; + +SSH_SFTP_CALLBACK(process_unsupported); +SSH_SFTP_CALLBACK(process_open); +SSH_SFTP_CALLBACK(process_read); +SSH_SFTP_CALLBACK(process_write); +SSH_SFTP_CALLBACK(process_close); +SSH_SFTP_CALLBACK(process_opendir); +SSH_SFTP_CALLBACK(process_readdir); +SSH_SFTP_CALLBACK(process_rmdir); +SSH_SFTP_CALLBACK(process_realpath); +SSH_SFTP_CALLBACK(process_mkdir); +SSH_SFTP_CALLBACK(process_lstat); +SSH_SFTP_CALLBACK(process_stat); +SSH_SFTP_CALLBACK(process_readlink); +SSH_SFTP_CALLBACK(process_symlink); +SSH_SFTP_CALLBACK(process_remove); +SSH_SFTP_CALLBACK(process_extended_statvfs); +SSH_SFTP_CALLBACK(process_setstat); + +const struct sftp_message_handler message_handlers[] = { + {"open", NULL, SSH_FXP_OPEN, process_open}, + {"close", NULL, SSH_FXP_CLOSE, process_close}, + {"read", NULL, SSH_FXP_READ, process_read}, + {"write", NULL, SSH_FXP_WRITE, process_write}, + {"lstat", NULL, SSH_FXP_LSTAT, process_lstat}, + {"fstat", NULL, SSH_FXP_FSTAT, process_unsupported}, + {"setstat", NULL, SSH_FXP_SETSTAT, process_setstat}, + {"fsetstat", NULL, SSH_FXP_FSETSTAT, process_unsupported}, + {"opendir", NULL, SSH_FXP_OPENDIR, process_opendir}, + {"readdir", NULL, SSH_FXP_READDIR, process_readdir}, + {"remove", NULL, SSH_FXP_REMOVE, process_remove}, + {"mkdir", NULL, SSH_FXP_MKDIR, process_mkdir}, + {"rmdir", NULL, SSH_FXP_RMDIR, process_rmdir}, + {"realpath", NULL, SSH_FXP_REALPATH, process_realpath}, + {"stat", NULL, SSH_FXP_STAT, process_stat}, + {"rename", NULL, SSH_FXP_RENAME, process_unsupported}, + {"readlink", NULL, SSH_FXP_READLINK, process_readlink}, + {"symlink", NULL, SSH_FXP_SYMLINK, process_symlink}, + {"init", NULL, SSH_FXP_INIT, sftp_reply_version}, + {NULL, NULL, 0, NULL}, +}; + +const struct sftp_message_handler extended_handlers[] = { + /* here are some extended type handlers */ + {"statvfs", "statvfs@openssh.com", 0, process_extended_statvfs}, + {NULL, NULL, 0, NULL}, +}; + +static int +process_open(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + uint32_t msg_flag = sftp_client_message_get_flags(client_msg); + uint32_t mode = client_msg->attr->permissions; + ssh_string handle_s = NULL; + struct sftp_handle *h = NULL; + int file_flag; + int fd = -1; + int status; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing open: filename %s, mode=0%o" PRIu32, + filename, mode); + + if ((msg_flag & (uint32_t)SSH_FXF_WRITE) == SSH_FXF_WRITE) { + if ((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) { + /* Both read and write */ + file_flag = O_RDWR; + } else { + /* Only write */ + file_flag = O_WRONLY; + } + + if ((msg_flag & (uint32_t)SSH_FXF_APPEND) == SSH_FXF_APPEND) { + file_flag |= O_APPEND; + } + + if ((msg_flag & (uint32_t)SSH_FXF_CREAT) == SSH_FXF_CREAT) { + file_flag |= O_CREAT; + } + + if ((msg_flag & (uint32_t)SSH_FXF_TRUNC) == SSH_FXF_TRUNC) { + file_flag |= O_TRUNC; + } + } else if ((msg_flag & (uint32_t)SSH_FXF_READ) == SSH_FXF_READ) { + file_flag = O_RDONLY; + } else { + SSH_LOG(SSH_LOG_PROTOCOL, "undefined message flag: %" PRIu32, msg_flag); + sftp_reply_status(client_msg, SSH_FX_FAILURE, "Flag error"); + return SSH_ERROR; + } + + fd = open(filename, file_flag, mode); + if (fd == -1) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "error open file with error: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, "Write error"); + return SSH_OK; + } + + h = calloc(1, sizeof (struct sftp_handle)); + if (h == NULL) { + close(fd); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to allocate a new handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->fd = fd; + h->type = SFTP_FILE_HANDLE; + handle_s = sftp_handle_alloc(client_msg->sftp, h); + if (handle_s != NULL) { + sftp_reply_handle(client_msg, handle_s); + ssh_string_free(handle_s); + } else { + free(h); + close(fd); + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate handle"); + } + + return SSH_OK; +} + +static int +process_read(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + ssize_t readn = 0; + int fd = -1; + char *buffer = NULL; + off_t off; + + ssh_log_hexdump("Processing read: handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h != NULL && h->type == SFTP_FILE_HANDLE) { + fd = h->fd; + } + + if (fd < 0) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "invalid fd (%d) received from handle", fd); + return SSH_ERROR; + } + off = lseek(fd, client_msg->offset, SEEK_SET); + if (off == -1) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, + "error seeking file fd: %d at offset: %" PRIu64, + fd, client_msg->offset); + return SSH_OK; + } + + buffer = malloc(client_msg->len); + if (buffer == NULL) { + ssh_set_error_oom(sftp->session); + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate memory for read data"); + return SSH_ERROR; + } + readn = ssh_readn(fd, buffer, client_msg->len); + if (readn < 0) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "read file error!"); + free(buffer); + return SSH_OK; + } else if (readn > 0) { + sftp_reply_data(client_msg, buffer, readn); + } else { + sftp_reply_status(client_msg, SSH_FX_EOF, NULL); + } + + free(buffer); + return SSH_OK; +} + +static int +process_write(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + ssize_t written = 0; + int fd = -1; + const char *msg_data = NULL; + uint32_t len; + off_t off; + + ssh_log_hexdump("Processing write: handle", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h != NULL && h->type == SFTP_FILE_HANDLE) { + fd = h->fd; + } + if (fd < 0) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, "write file fd error!"); + return SSH_ERROR; + } + + msg_data = ssh_string_get_char(client_msg->data); + len = ssh_string_len(client_msg->data); + + off = lseek(fd, client_msg->offset, SEEK_SET); + if (off == -1) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + SSH_LOG(SSH_LOG_PROTOCOL, + "error seeking file at offset: %" PRIu64, + client_msg->offset); + return SSH_OK; + } + written = ssh_writen(fd, msg_data, len); + if (written != (ssize_t)len) { + sftp_reply_status(client_msg, SSH_FX_FAILURE, "Write error"); + SSH_LOG(SSH_LOG_PROTOCOL, "file write error!"); + return SSH_OK; + } + + sftp_reply_status(client_msg, SSH_FX_OK, NULL); + + return SSH_OK; +} + +static int +process_close(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + int ret; + + ssh_log_hexdump("Processing close: handle:", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, handle); + if (h == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "invalid handle"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, "Invalid handle"); + return SSH_OK; + } else if (h->type == SFTP_FILE_HANDLE) { + int fd = h->fd; + close(fd); + ret = SSH_OK; + } else if (h->type == SFTP_DIR_HANDLE) { + DIR *dir = h->dirp; + closedir(dir); + ret = SSH_OK; + } else { + ret = SSH_ERROR; + } + SAFE_FREE(h->name); + sftp_handle_remove(sftp, h); + SAFE_FREE(h); + + if (ret == SSH_OK) { + sftp_reply_status(client_msg, SSH_FX_OK, NULL); + } else { + SSH_LOG(SSH_LOG_PROTOCOL, "closing file failed"); + sftp_reply_status(client_msg, SSH_FX_BAD_MESSAGE, "Invalid handle"); + } + + return ret; +} + +static int +process_opendir(sftp_client_message client_msg) +{ + DIR *dir = NULL; + const char *dir_name = sftp_client_message_get_filename(client_msg); + ssh_string handle_s = NULL; + struct sftp_handle *h = NULL; + + if (dir_name == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing dir_name from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing opendir %s", dir_name); + + dir = opendir(dir_name); + if (dir == NULL) { + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "No such directory"); + return SSH_OK; + } + + h = calloc(1, sizeof (struct sftp_handle)); + if (h == NULL) { + closedir(dir); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to allocate a new handle"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->dirp = dir; + h->name = strdup(dir_name); + if (h->name == NULL) { + free(h); + closedir(dir); + SSH_LOG(SSH_LOG_PROTOCOL, "failed to duplicate directory name"); + sftp_reply_status(client_msg, + SSH_FX_FAILURE, + "Failed to allocate new handle"); + return SSH_ERROR; + } + h->type = SFTP_DIR_HANDLE; + handle_s = sftp_handle_alloc(client_msg->sftp, h); + + if (handle_s != NULL) { + sftp_reply_handle(client_msg, handle_s); + ssh_string_free(handle_s); + } else { + SAFE_FREE(h->name); + free(h); + closedir(dir); + sftp_reply_status(client_msg, SSH_FX_FAILURE, "No handle available"); + } + + return SSH_OK; +} + +static int +readdir_long_name(char *z_file_name, struct stat *z_st, char *z_long_name) +{ + char time[50]; + char *ptr = z_long_name, *nl = NULL; + int mode = z_st->st_mode; + + *ptr = '\0'; + + switch (mode & S_IFMT) { + case S_IFDIR: + *ptr++ = 'd'; + break; + default: + *ptr++ = '-'; + break; + } + + /* user */ + if (mode & 0400) + *ptr++ = 'r'; + else + *ptr++ = '-'; + + if (mode & 0200) + *ptr++ = 'w'; + else + *ptr++ = '-'; + + if (mode & 0100) { + if (mode & S_ISUID) + *ptr++ = 's'; + else + *ptr++ = 'x'; + } else + *ptr++ = '-'; + + /* group */ + if (mode & 040) + *ptr++ = 'r'; + else + *ptr++ = '-'; + if (mode & 020) + *ptr++ = 'w'; + else + *ptr++ = '-'; + if (mode & 010) + *ptr++ = 'x'; + else + *ptr++ = '-'; + + /* other */ + if (mode & 04) + *ptr++ = 'r'; + else + *ptr++ = '-'; + if (mode & 02) + *ptr++ = 'w'; + else + *ptr++ = '-'; + if (mode & 01) + *ptr++ = 'x'; + else + *ptr++ = '-'; + + *ptr++ = ' '; + *ptr = '\0'; + + ctime_r(&z_st->st_mtime, time); + if ((nl = strchr(time, '\n'))) { + *nl = '\0'; + } + snprintf(ptr, + MAX_LONG_NAME_LEN - strlen(z_long_name), + "%3d %d %d %d %s %s", + (int)z_st->st_nlink, + (int)z_st->st_uid, + (int)z_st->st_gid, + (int)z_st->st_size, + time + 4, + z_file_name); + + return SSH_OK; +} + +static int +process_readdir(sftp_client_message client_msg) +{ + sftp_session sftp = client_msg->sftp; + ssh_string handle = client_msg->handle; + struct sftp_handle *h = NULL; + int ret = SSH_OK; + int entries = 0; + struct dirent *dentry = NULL; + DIR *dir = NULL; + char long_path[PATH_MAX]; + int srclen; + const char *handle_name = NULL; + + ssh_log_hexdump("Processing readdir: handle", + (const unsigned char *)ssh_string_get_char(handle), + ssh_string_len(handle)); + + h = sftp_handle(sftp, client_msg->handle); + if (h != NULL && h->type == SFTP_DIR_HANDLE) { + dir = h->dirp; + handle_name = h->name; + } + if (dir == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "got wrong handle from msg"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + if (handle_name == NULL) { + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + srclen = strlen(handle_name); + if (srclen + 2 >= PATH_MAX) { + SSH_LOG(SSH_LOG_PROTOCOL, "handle string length exceed max length!"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + + for (int i = 0; i < MAX_ENTRIES_NUM_IN_PACKET; i++) { + dentry = readdir(dir); + + if (dentry != NULL) { + struct sftp_attributes_struct attr; + struct stat st; + char long_name[MAX_LONG_NAME_LEN]; + + if (strlen(dentry->d_name) + srclen + 1 >= PATH_MAX) { + SSH_LOG(SSH_LOG_PROTOCOL, + "handle string length exceed max length!"); + sftp_reply_status(client_msg, SSH_FX_INVALID_HANDLE, NULL); + return SSH_ERROR; + } + snprintf(long_path, PATH_MAX, "%s/%s", handle_name, dentry->d_name); + + if (lstat(long_path, &st) == 0) { + stat_to_filexfer_attrib(&st, &attr); + } else { + clear_filexfer_attrib(&attr); + } + + if (readdir_long_name(dentry->d_name, &st, long_name) == 0) { + sftp_reply_names_add(client_msg, dentry->d_name, long_name, &attr); + } else { + printf("readdir long name error\n"); + } + + entries++; + } else { + break; + } + } + + if (entries > 0) { + ret = sftp_reply_names(client_msg); + } else { + sftp_reply_status(client_msg, SSH_FX_EOF, NULL); + } + + return ret; +} + +static int +process_mkdir(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + uint32_t msg_flags = client_msg->attr->flags; + uint32_t permission = client_msg->attr->permissions; + uint32_t mode = (msg_flags & (uint32_t)SSH_FILEXFER_ATTR_PERMISSIONS) + ? permission & (uint32_t)07777 : 0777; + int status = SSH_FX_OK; + int rv; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing mkdir %s, mode=0%o" PRIu32, + filename, mode); + + rv = mkdir(filename, mode); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "failed to mkdir: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + } + + sftp_reply_status(client_msg, status, NULL); + + return SSH_OK; +} + +static int +process_rmdir(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + int status = SSH_FX_OK; + int rv; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing rmdir %s", filename); + + rv = rmdir(filename); + if (rv < 0) { + status = unix_errno_to_ssh_stat(errno); + } + + sftp_reply_status(client_msg, status, NULL); + + return SSH_OK; +} + +static int +process_realpath(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + char *path = NULL; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing realpath %s", filename); + + if (filename[0] == '\0') { + path = realpath(".", NULL); + } else { + path = realpath(filename, NULL); + } + if (path == NULL) { + int saved_errno = errno; + int status = unix_errno_to_ssh_stat(saved_errno); + const char *err_msg = ssh_str_error(status); + + SSH_LOG(SSH_LOG_PROTOCOL, "realpath failed: %s", strerror(saved_errno)); + sftp_reply_status(client_msg, status, err_msg); + return SSH_ERROR; + } + sftp_reply_name(client_msg, path, NULL); + free(path); + return SSH_OK; +} + +static int +process_lstat(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + struct sftp_attributes_struct attr; + struct stat st; + int status = SSH_FX_OK; + int rv; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing lstat %s", filename); + + rv = lstat(filename, &st); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + } else { + stat_to_filexfer_attrib(&st, &attr); + sftp_reply_attr(client_msg, &attr); + } + + return SSH_OK; +} + +static int +process_stat(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + struct sftp_attributes_struct attr; + struct stat st; + int status = SSH_FX_OK; + int rv; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing stat %s", filename); + + rv = stat(filename, &st); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "lstat failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + } else { + stat_to_filexfer_attrib(&st, &attr); + sftp_reply_attr(client_msg, &attr); + } + + return SSH_OK; +} + +static int +process_setstat(sftp_client_message client_msg) +{ + int rv; + int status = SSH_FX_OK; + uint32_t msg_flags = client_msg->attr->flags; + const char *filename = sftp_client_message_get_filename(client_msg); + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing setstat %s", filename); + + if (msg_flags & SSH_FILEXFER_ATTR_SIZE) { + rv = truncate(filename, client_msg->attr->size); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "changing size failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_PERMISSIONS) { + rv = chmod(filename, client_msg->attr->permissions); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "chmod failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_UIDGID) { + rv = chown(filename, client_msg->attr->uid, client_msg->attr->gid); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "chwon failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } + } + + if (msg_flags & SSH_FILEXFER_ATTR_ACMODTIME) { +#ifdef HAVE_SYS_TIME_H + struct timeval tv[2]; + + tv[0].tv_sec = client_msg->attr->atime; + tv[0].tv_usec = 0; + tv[1].tv_sec = client_msg->attr->mtime; + tv[1].tv_usec = 0; + + rv = utimes(filename, tv); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "utimes failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } +#else + struct _utimbuf tf; + + tf.actime = client_msg->attr->atime; + tf.modtime = client_msg->attr->mtime; + + rv = _utime(filename, &tf); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, + "utimes failed: %s", + strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } +#endif + } + + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; +} + +static int +process_readlink(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + char buf[PATH_MAX]; + int len = -1; + const char *err_msg = NULL; + int status = SSH_FX_OK; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Processing readlink %s", filename); + + len = readlink(filename, buf, sizeof(buf) - 1); + if (len < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "readlink failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + err_msg = ssh_str_error(status); + sftp_reply_status(client_msg, status, err_msg); + } else { + buf[len] = '\0'; + sftp_reply_name(client_msg, buf, NULL); + } + + return SSH_OK; +} + +/* Note, that this function is using reversed order of the arguments than the + * OpenSSH sftp server as they have the arguments switched. See + * section "4.1 sftp: Reversal of arguments to SSH_FXP_SYMLINK' in + * https://github.com/openssh/openssh-portable/blob/master/PROTOCOL + * for more information */ +static int +process_symlink(sftp_client_message client_msg) +{ + const char *destpath = sftp_client_message_get_filename(client_msg); + const char *srcpath = ssh_string_get_char(client_msg->data); + int status = SSH_FX_OK; + int rv; + + if (srcpath == NULL || destpath == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "processing symlink: src=%s dest=%s", + srcpath, destpath); + + rv = symlink(srcpath, destpath); + if (rv < 0) { + int saved_errno = errno; + status = unix_errno_to_ssh_stat(saved_errno); + SSH_LOG(SSH_LOG_PROTOCOL, "symlink failed: %s", strerror(saved_errno)); + sftp_reply_status(client_msg, status, "Write error"); + } else { + sftp_reply_status(client_msg, SSH_FX_OK, "write success"); + } + + return SSH_OK; +} + +static int +process_remove(sftp_client_message client_msg) +{ + const char *filename = sftp_client_message_get_filename(client_msg); + int rv; + int status = SSH_FX_OK; + + if (filename == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "processing remove: %s", filename); + + rv = unlink(filename); + if (rv < 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "unlink failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + } + + sftp_reply_status(client_msg, status, NULL); + + return SSH_OK; +} + +static int +process_unsupported(sftp_client_message client_msg) +{ + sftp_reply_status(client_msg, SSH_FX_OP_UNSUPPORTED, + "Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Message type %d not implemented", + sftp_client_message_get_type(client_msg)); + return SSH_OK; +} + +static int +process_extended_statvfs(sftp_client_message client_msg) +{ + const char *path = sftp_client_message_get_filename(client_msg); + sftp_statvfs_t sftp_statvfs; + struct statvfs st; + uint64_t flag; + int status; + int rv; + + if (path == NULL) { + SSH_LOG(SSH_LOG_WARNING, "missing filename from in message"); + sftp_reply_status(client_msg, SSH_FX_NO_SUCH_FILE, "File name error"); + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_PROTOCOL, "processing extended statvfs: %s", path); + + rv = statvfs(path, &st); + if (rv != 0) { + int saved_errno = errno; + SSH_LOG(SSH_LOG_PROTOCOL, "statvfs failed: %s", strerror(saved_errno)); + status = unix_errno_to_ssh_stat(saved_errno); + sftp_reply_status(client_msg, status, NULL); + return SSH_OK; + } + + sftp_statvfs = calloc(1, sizeof(struct sftp_statvfs_struct)); + if (sftp_statvfs == NULL) { + SSH_LOG(SSH_LOG_PROTOCOL, "Failed to allocate statvfs structure"); + sftp_reply_status(client_msg, SSH_FX_FAILURE, NULL); + return SSH_ERROR; + } + flag = (st.f_flag & ST_RDONLY) ? SSH_FXE_STATVFS_ST_RDONLY : 0; + flag |= (st.f_flag & ST_NOSUID) ? SSH_FXE_STATVFS_ST_NOSUID : 0; + + sftp_statvfs->f_bsize = st.f_bsize; + sftp_statvfs->f_frsize = st.f_frsize; + sftp_statvfs->f_blocks = st.f_blocks; + sftp_statvfs->f_bfree = st.f_bfree; + sftp_statvfs->f_bavail = st.f_bavail; + sftp_statvfs->f_files = st.f_files; + sftp_statvfs->f_ffree = st.f_ffree; + sftp_statvfs->f_favail = st.f_favail; + sftp_statvfs->f_fsid = st.f_fsid; + sftp_statvfs->f_flag = flag; + sftp_statvfs->f_namemax = st.f_namemax; + + rv = sftp_reply_statvfs(client_msg, sftp_statvfs); + free(sftp_statvfs); + if (rv == 0) { + return SSH_OK; + } + return SSH_ERROR; +} + +static int +process_extended(sftp_client_message sftp_msg) +{ + int status = SSH_ERROR; + const char *subtype = sftp_msg->submessage; + sftp_server_message_callback handler = NULL; + + SSH_LOG(SSH_LOG_PROTOCOL, "processing extended message: %s", subtype); + + for (int i = 0; extended_handlers[i].cb != NULL; i++) { + if (strcmp(subtype, extended_handlers[i].extended_name) == 0) { + handler = extended_handlers[i].cb; + break; + } + } + if (handler != NULL) { + status = handler(sftp_msg); + return status; + } + + sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, + "Extended Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Extended Message type %s not implemented", + subtype); + return SSH_OK; +} + +static int +dispatch_sftp_request(sftp_client_message sftp_msg) +{ + int status = SSH_ERROR; + sftp_server_message_callback handler = NULL; + uint8_t type = sftp_client_message_get_type(sftp_msg); + + SSH_LOG(SSH_LOG_PROTOCOL, "processing request type: %u", type); + + for (int i = 0; message_handlers[i].cb != NULL; i++) { + if (type == message_handlers[i].type) { + handler = message_handlers[i].cb; + break; + } + } + + if (handler != NULL) { + status = handler(sftp_msg); + } else { + sftp_reply_status(sftp_msg, SSH_FX_OP_UNSUPPORTED, + "Operation not supported"); + SSH_LOG(SSH_LOG_PROTOCOL, "Message type %u not implemented", type); + return SSH_OK; + } + + return status; +} + +static int +process_client_message(sftp_client_message client_msg) +{ + int status = SSH_OK; + if (client_msg == NULL) { + return SSH_ERROR; + } + + switch (client_msg->type) { + case SSH_FXP_EXTENDED: + status = process_extended(client_msg); + break; + default: + status = dispatch_sftp_request(client_msg); + } + + if (status != SSH_OK) + SSH_LOG(SSH_LOG_PROTOCOL, + "error occurred during processing client message!"); + + return status; +} + +/** + * @brief Default subsystem request handler for SFTP subsystem + * + * @param[in] session The ssh session + * @param[in] channel The existing ssh channel + * @param[in] subsystem The subsystem name. Only "sftp" is handled + * @param[out] userdata The pointer to sftp_session which will get the + * resulting SFTP session + * + * @return `SSH_OK` when the SFTP server was successfully initialized, + * `SSH_ERROR` otherwise. + */ +int +sftp_channel_default_subsystem_request(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + if (strcmp(subsystem, "sftp") == 0) { + sftp_session *sftp = (sftp_session *)userdata; + + /* The SFTP subsystem was already initialized on this channel */ + if (*sftp != NULL) { + return SSH_ERROR; + } + + /* initialize sftp session and file handler */ + *sftp = sftp_server_new(session, channel); + if (*sftp == NULL) { + return SSH_ERROR; + } + + return SSH_OK; + } + return SSH_ERROR; +} + +/** + * @brief Default data callback for sftp server + * + * @param[in] session The ssh session + * @param[in] channel The ssh channel with SFTP session opened + * @param[in] data The data to be processed. + * @param[in] len The length of input data to be processed + * @param[in] is_stderr Unused channel flag for stderr flagging + * @param[in] userdata The pointer to sftp_session + * + * @return number of bytes processed, -1 when error occurs. + */ +int +sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + sftp_session *sftpp = (sftp_session *)userdata; + sftp_session sftp = NULL; + sftp_client_message msg; + uint32_t undecoded_len = len; + int rc; + + if (sftpp == NULL || *sftpp == NULL) { + SSH_LOG(SSH_LOG_WARNING, "invalid userdata passed to callback"); + return SSH_ERROR; + } + sftp = *sftpp; + + do { + int decode_len = + sftp_decode_channel_data_to_packet(sftp, data, undecoded_len); + if (decode_len == SSH_ERROR) { + return SSH_ERROR; + } + + msg = sftp_get_client_message_from_packet(sftp); + rc = process_client_message(msg); + sftp_client_message_free(msg); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_PROTOCOL, "process sftp failed!"); + ssh_channel_send_eof(sftp->channel); + ssh_channel_close(sftp->channel); + // Leave freeing resources on caller + return rc; + } + + undecoded_len -= decode_len; + data = (uint8_t *)data + decode_len; + } while (undecoded_len > 0); + + return len; +} +#else +/* Not available on Windows for now */ +int +sftp_channel_default_data_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(void *data), + UNUSED_PARAM(uint32_t len), + UNUSED_PARAM(int is_stderr), + UNUSED_PARAM(void *userdata)) +{ + return -1; +} + +int +sftp_channel_default_subsystem_request(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *subsystem), + UNUSED_PARAM(void *userdata)) +{ + return SSH_ERROR; +} +#endif diff --git a/src/libs/libssh-0.12.2/src/sk_common.c b/src/libs/libssh-0.12.2/src/sk_common.c new file mode 100644 index 000000000000..883d15689c32 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sk_common.c @@ -0,0 +1,290 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include "libssh/callbacks.h" +#include "libssh/priv.h" +#include "libssh/sk_common.h" + +#ifdef HAVE_LIBFIDO2 +#include "libssh/sk_usbhid.h" +#endif + +const char *ssh_sk_err_to_string(int sk_err) +{ + switch (sk_err) { + case SSH_SK_ERR_UNSUPPORTED: + return "Unsupported operation"; + case SSH_SK_ERR_PIN_REQUIRED: + return "PIN required but is either missing or invalid"; + case SSH_SK_ERR_DEVICE_NOT_FOUND: + return "No suitable device found"; + case SSH_SK_ERR_CREDENTIAL_EXISTS: + return "Credential already exists"; + case SSH_SK_ERR_GENERAL: + return "General error"; + default: + return "Unknown error"; + } +} + +void sk_enroll_response_burn(struct sk_enroll_response *enroll_response) +{ + if (enroll_response == NULL) { + return; + } + + BURN_FREE(enroll_response->public_key, enroll_response->public_key_len); + BURN_FREE(enroll_response->key_handle, enroll_response->key_handle_len); + BURN_FREE(enroll_response->signature, enroll_response->signature_len); + BURN_FREE(enroll_response->attestation_cert, + enroll_response->attestation_cert_len); + BURN_FREE(enroll_response->authdata, enroll_response->authdata_len); + + ssh_burn(enroll_response, sizeof(*enroll_response)); +} + +void sk_enroll_response_free(struct sk_enroll_response *enroll_response) +{ + sk_enroll_response_burn(enroll_response); + SAFE_FREE(enroll_response); +} + +void sk_sign_response_free(struct sk_sign_response *sign_response) +{ + if (sign_response == NULL) { + return; + } + + BURN_FREE(sign_response->sig_r, sign_response->sig_r_len); + BURN_FREE(sign_response->sig_s, sign_response->sig_s_len); + SAFE_FREE(sign_response); +} + +void sk_resident_key_free(struct sk_resident_key *resident_key) +{ + if (resident_key == NULL) { + return; + } + + SAFE_FREE(resident_key->application); + BURN_FREE(resident_key->user_id, resident_key->user_id_len); + sk_enroll_response_burn(&resident_key->key); + SAFE_FREE(resident_key); +} + +void sk_options_free(struct sk_option **options) +{ + size_t i; + + if (options == NULL) { + return; + } + + for (i = 0; options[i] != NULL; i++) { + SAFE_FREE(options[i]->name); + SAFE_FREE(options[i]->value); + SAFE_FREE(options[i]); + } + SAFE_FREE(options); +} + +int sk_options_validate_get(const struct sk_option **options, + const char **keys, + char ***values) +{ + size_t i, j; + size_t key_count = 0; + int found; + + if (keys == NULL || values == NULL || options == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid parameter(s) provided"); + return SSH_ERROR; + } + + while (keys[key_count] != NULL) { + key_count++; + } + + *values = calloc(key_count + 1, sizeof(char *)); + if (*values == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate values array"); + return SSH_ERROR; + } + + for (i = 0; options[i] != NULL; i++) { + const struct sk_option *option = options[i]; + + found = 0; + + /* Look for this option name in the supported keys */ + for (j = 0; j < key_count; j++) { + + if (strcmp(option->name, keys[j]) == 0) { + /* Copy the value string if it exists */ + if (option->value != NULL) { + (*values)[j] = strdup(option->value); + if ((*values)[j] == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy option value"); + goto error; + } + + } else { + (*values)[j] = NULL; + } + found = 1; + break; + } + } + + /* If option is required but not supported, fail */ + if (!found && option->required) { + SSH_LOG(SSH_LOG_WARN, + "Required option '%s' is not supported", + option->name); + goto error; + } + } + + return SSH_OK; + +error: + for (j = 0; j < key_count; j++) { + SAFE_FREE((*values)[j]); + } + + SAFE_FREE(*values); + return SSH_ERROR; +} + +struct sk_option **sk_options_dup(const struct sk_option **options) +{ + struct sk_option **new_options = NULL; + size_t count = 0; + size_t i; + + if (options == NULL) { + return NULL; + } + + /* Count the number of options */ + while (options[count] != NULL) { + count++; + } + + new_options = calloc(count + 1, sizeof(struct sk_option *)); + if (new_options == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for options array"); + return NULL; + } + + /* Copy each option */ + for (i = 0; i < count; i++) { + const struct sk_option *option = options[i]; + struct sk_option *new_option = NULL; + + new_option = calloc(1, sizeof(struct sk_option)); + if (new_option == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for option"); + goto error; + } + + /* Copy option name */ + if (option->name != NULL) { + new_option->name = strdup(option->name); + if (new_option->name == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy option name"); + SAFE_FREE(new_option); + goto error; + } + } + + /* Copy option value */ + if (option->value != NULL) { + new_option->value = strdup(option->value); + if (new_option->value == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to copy option value"); + SAFE_FREE(new_option->name); + SAFE_FREE(new_option); + goto error; + } + } + + new_option->required = option->required; + new_options[i] = new_option; + } + + new_options[count] = NULL; + return new_options; + +error: + SK_OPTIONS_FREE(new_options); + return NULL; +} + +bool sk_callbacks_check_compatibility( + const struct ssh_sk_callbacks_struct *callbacks) +{ + uint32_t callback_version; + uint32_t callback_version_major; + uint32_t libssh_version_major; + + if (callbacks == NULL) { + SSH_LOG(SSH_LOG_WARN, "SK callbacks cannot be NULL"); + return false; + } + + /* Check if the api_version callback is provided */ + if (!ssh_callbacks_exists(callbacks, api_version)) { + SSH_LOG(SSH_LOG_WARN, "SK callbacks missing api_version callback"); + return false; + } + + /* Extract major version from callback provider */ + callback_version = callbacks->api_version(); + callback_version_major = callback_version & SSH_SK_VERSION_MAJOR_MASK; + + libssh_version_major = SSH_SK_VERSION_MAJOR; + + /* Check if major versions are compatible */ + if (callback_version_major != libssh_version_major) { + SSH_LOG(SSH_LOG_WARN, + "SK API major version mismatch: callback provides 0x%08x, " + "libssh supports 0x%08x", + callback_version_major, + libssh_version_major); + return false; + } + + return true; +} + +const struct ssh_sk_callbacks_struct *ssh_sk_get_default_callbacks(void) +{ +#ifdef HAVE_LIBFIDO2 + return ssh_sk_get_usbhid_callbacks(); +#else + return NULL; +#endif /* HAVE_LIBFIDO2 */ +} diff --git a/src/libs/libssh-0.12.2/src/sk_usbhid.c b/src/libs/libssh-0.12.2/src/sk_usbhid.c new file mode 100644 index 000000000000..782df0ae8fa4 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sk_usbhid.c @@ -0,0 +1,2239 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/callbacks.h" +#include "libssh/misc.h" +#include "libssh/pki.h" +#include "libssh/sk_api.h" +#include "libssh/sk_common.h" +#include "libssh/sk_usbhid.h" + +#include +#include +#include + +#include +#include + +#ifdef _WIN32 +#include +#endif + +#define SK_USBHID_API_VERSION 0x000a0000 + +#define ECDSA_P256_PUBKEY_LEN 64 + +/* Maximum number of FIDO2/U2F devices that can be connected */ +#define MAX_FIDO_DEVICES 8 + +/* Timeout for touch detection on single FIDO2/U2F device during each polling */ +#define FIDO_POLL_MS 50 + +/* Sleep between each consecutive polling */ +#define POLL_SLEEP_NS 200000000 + +/* The entire timeout for user to touch any of the connected devices */ +#define SELECT_MS 15000 + +/* DER encoding constants */ +#define DER_SEQUENCE_TAG 0x30 +#define DER_INTEGER_TAG 0x02 +#define DER_MAX_LEN_BYTES 2 + +struct sk_device { + char *path; + fido_dev_t *fido_device; +}; + +/** + * libfido2 log handler that prints libfido2 debug messages. + * + * @param msg The log message from libfido2 + */ +static void fido_log_handler(const char *msg) +{ + if (msg == NULL) { + return; + } + + SSH_LOG(SSH_LOG_TRACE, "libfido2: %s", msg); +} + +/** + * Initialize libfido2 with appropriate logging settings based on + * current libssh log level. + */ +static void sk_fido_init(void) +{ + int fido_flags = 0; + int log_level = ssh_get_log_level(); + + /* Enable libfido2 debug output if libssh is at TRACE level */ + if (log_level == SSH_LOG_TRACE) { + fido_flags |= FIDO_DEBUG; + fido_set_log_handler(fido_log_handler); + } + + fido_init(fido_flags); +} + +/** + * Convert a libfido2 error code to a libssh security key error code. + * + * @param fido_err The FIDO error code to convert + * + * @return The corresponding SSH_SK_ERR_* error code + */ +static int fido_err_to_ssh_sk_err(int fido_err) +{ + switch (fido_err) { + case FIDO_ERR_UNSUPPORTED_OPTION: + case FIDO_ERR_UNSUPPORTED_ALGORITHM: + case FIDO_ERR_UNSUPPORTED_EXTENSION: + return SSH_SK_ERR_UNSUPPORTED; + case FIDO_ERR_PIN_REQUIRED: + case FIDO_ERR_PIN_INVALID: + return SSH_SK_ERR_PIN_REQUIRED; + default: + return SSH_SK_ERR_GENERAL; + } +} + +static void sk_device_close(struct sk_device *device) +{ + int rc; + + if (device == NULL) { + return; + } + + if (device->fido_device != NULL) { + rc = fido_dev_cancel(device->fido_device); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to cancel device operations: %s", + fido_strerr(rc)); + } + + rc = fido_dev_close(device->fido_device); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to close device: %s", + fido_strerr(rc)); + } + + fido_dev_free(&device->fido_device); + } + + SAFE_FREE(device->path); + SAFE_FREE(device); +} + +static struct sk_device *sk_device_open(const char *device_path) +{ + int rc; + struct sk_device *device = NULL; + + if (device_path == NULL) { + SSH_LOG(SSH_LOG_WARN, "Device path cannot be NULL"); + goto error; + } + + device = calloc(1, sizeof(struct sk_device)); + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for sk_device"); + goto error; + } + + device->fido_device = fido_dev_new(); + if (device->fido_device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new fido device instance"); + goto error; + } + + device->path = strdup(device_path); + if (device->path == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for device path"); + goto error; + } + + rc = fido_dev_open(device->fido_device, device->path); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to open FIDO2/U2F device at %s: %s", + device->path, + fido_strerr(rc)); + goto error; + } + + return device; + +error: + sk_device_close(device); + return NULL; +} + +static void sk_device_close_list(struct sk_device **devices, size_t num_devices) +{ + size_t i; + + if (devices == NULL) { + return; + } + + for (i = 0; i < num_devices; i++) { + sk_device_close(devices[i]); + } + SAFE_FREE(devices); +} + +static struct sk_device ** +sk_device_open_list(const fido_dev_info_t *device_list, + size_t num_devices, + size_t *num_opened) +{ + size_t i; + const char *device_path = NULL; + struct sk_device **devices = NULL; + const fido_dev_info_t *device_info = NULL; + + *num_opened = 0; + + devices = calloc(num_devices, sizeof(struct sk_device *)); + if (devices == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for device list"); + return NULL; + } + + for (i = 0; i < num_devices; i++) { + device_info = fido_dev_info_ptr(device_list, i); + if (device_info == NULL) { + SSH_LOG(SSH_LOG_INFO, "Failed to get device info for index %zu", i); + continue; + } + + device_path = fido_dev_info_path(device_info); + devices[*num_opened] = sk_device_open(device_path); + if (devices[*num_opened] == NULL) { + SSH_LOG(SSH_LOG_INFO, + "Failed to open device %zu at %s", + *num_opened, + device_path); + } else { + (*num_opened)++; + } + } + + if (*num_opened == 0) { + sk_device_close_list(devices, num_devices); + devices = NULL; + } + + return devices; +} + +/** + * Check if given device has the credentials corresponding to the given + * key_handle. + * + * @param device The security key device to check + * @param application The application identifier (relying party ID) + * @param key_handle The key handle to check for + * @param key_handle_len The length of the key handle in bytes + * + * @return FIDO_OK if resident key exists, FIDO_ERR_NO_CREDENTIALS if it + * doesn't, other FIDO_ERR_* codes on failure + */ +static int sk_device_check_key_handle(const struct sk_device *device, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len) +{ + int ret = FIDO_ERR_INTERNAL; + uint8_t dummy_data[32] = {0}; + fido_assert_t *assert = NULL; + bool is_dev_fido2 = false; + + /* + * We make use of the pre-flight checking as described in + * https://fidoalliance.org/specs/fido-v2.1-ps-20210615/fido-client-to-authenticator-protocol-v2.1-ps-20210615.html#pre-flight + * to identify whether the device knows of the passed key_handle. + */ + + assert = fido_assert_new(); + if (assert == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO assertion"); + return FIDO_ERR_INTERNAL; + } + + ret = fido_assert_set_clientdata(assert, dummy_data, sizeof(dummy_data)); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set client data for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_assert_set_rp(assert, application); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set Relying Party for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_assert_set_up(assert, FIDO_OPT_FALSE); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user presence for assertion: %s", + fido_strerr(ret)); + goto out; + } + + /* Allow assertions only from this particular key_handle */ + ret = fido_assert_allow_cred(assert, key_handle, key_handle_len); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allow credential for assertion: %s", + fido_strerr(ret)); + goto out; + } + + is_dev_fido2 = fido_dev_is_fido2(device->fido_device); + + ret = fido_dev_get_assert(device->fido_device, assert, NULL); + + if (!is_dev_fido2 && ret == FIDO_ERR_USER_PRESENCE_REQUIRED) { + /* U2F devices might return this */ + ret = FIDO_OK; + } else if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to get assertion from device: %s", + fido_strerr(ret)); + } + +out: + fido_assert_free(&assert); + return ret; +} + +/** + * Check if given device has the resident key with given user_id and + * application. + * + * @param device The security key device to check + * @param application The application identifier (relying party ID) + * @param user_id The binary user ID to search for in resident keys + * @param user_id_len Length of binary user_id + * @param pin The PIN for the device (can be NULL if not required) + * + * @return FIDO_OK if resident key exists, FIDO_ERR_NO_CREDENTIALS if it + * doesn't, other FIDO_ERR_* codes on failure + */ +static int sk_device_check_resident_key(const struct sk_device *device, + const char *application, + const uint8_t *user_id, + size_t user_id_len, + const char *pin) +{ + int rc, ret = FIDO_ERR_INTERNAL; + bool supports_uv = false; + size_t i, num_asserts = 0, len; + const uint8_t *ptr = NULL; + uint8_t dummy_data[32] = {0}; + fido_opt_t user_verification = FIDO_OPT_OMIT; + fido_assert_t *assert = NULL; + + /* If no user_id or zero length provided, nothing to compare */ + if (user_id == NULL || user_id_len == 0) { + return FIDO_ERR_NO_CREDENTIALS; + } + + assert = fido_assert_new(); + if (assert == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO assertion"); + goto out; + } + + ret = fido_assert_set_clientdata(assert, dummy_data, sizeof(dummy_data)); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set client data for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_assert_set_rp(assert, application); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set Relying Party for assertion: %s", + fido_strerr(ret)); + goto out; + } + + /* Check if device supports internal user verification methods such as + * biometric */ + supports_uv = fido_dev_supports_uv(device->fido_device); + + /* + * Determine user verification strategy for resident key enumeration: + * - If PIN is provided, rely on PIN-based authentication (UV = OMIT) + * + * - If no PIN is provided but device supports internal UV (biometric/etc), + * enable UV to ensure we can access all resident keys regardless of their + * credential protection while minimising user friction. + * + * - If no PIN is provided and device does not support internal UV, we will + * only be able to access resident keys without user-verification + * protection. + * + * Read about credential protection and resident keys: + * (https://developers.yubico.com/WebAuthn/WebAuthn_Developer_Guide/Resident_Keys.html) + */ + user_verification = + (pin == NULL && supports_uv) ? FIDO_OPT_TRUE : FIDO_OPT_OMIT; + ret = fido_assert_set_uv(assert, user_verification); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user verification for assertion: %s", + fido_strerr(ret)); + goto out; + } + + ret = fido_dev_get_assert(device->fido_device, assert, pin); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to get assertion from device: %s", + fido_strerr(ret)); + goto out; + } + + ret = FIDO_ERR_NO_CREDENTIALS; + + num_asserts = fido_assert_count(assert); + for (i = 0; i < num_asserts; i++) { + ptr = fido_assert_user_id_ptr(assert, i); + len = fido_assert_user_id_len(assert, i); + + if (len != user_id_len) { + continue; + } + + rc = memcmp(ptr, user_id, user_id_len); + if (rc == 0) { + SSH_LOG(SSH_LOG_INFO, "Resident key with given user ID exists"); + ret = FIDO_OK; + break; + } + } + +out: + fido_assert_free(&assert); + return ret; +} + +/** + * Begin touch detection on all devices in the provided list. + * + * @param devices Array of device pointers + * @param num_devices Number of devices in the array + * + * @return SSH_OK if at least one device started touch detection successfully, + * SSH_ERROR if all devices failed + */ +static int sk_device_touch_begin(struct sk_device **devices, size_t num_devices) +{ + int rc; + size_t i, num_success = 0; + + for (i = 0; i < num_devices; i++) { + rc = fido_dev_get_touch_begin(devices[i]->fido_device); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to begin touch on device %s: %s", + devices[i]->path, + fido_strerr(rc)); + } else { + num_success++; + } + } + + return (num_success > 0) ? SSH_OK : SSH_ERROR; +} + +/** + * Poll the touch status on all devices and return the index of the device + * on which touch was detected. + * + * @param devices Array of device pointers to poll for touch status + * @param num_devices Number of devices in the array + * @param touch_detected Pointer to store whether touch was detected (0 or 1) + * @param chosen_idx Pointer to store the index of the device that was touched + * + * @return SSH_OK on successful polling (regardless of whether touch was + * detected), SSH_ERROR if no devices left to poll. + * + * @warning Automatically closes the device if any error occurs + * while detecting if it was touched. + */ +static int sk_device_touch_poll(struct sk_device **devices, + size_t num_devices, + int *touch_detected, + size_t *chosen_idx) +{ + int rc; + size_t i, n_failed = 0; + + for (i = 0; i < num_devices; i++) { + if (devices[i] == NULL) { + continue; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Polling touch status on device %s", + devices[i]->path); + + rc = fido_dev_get_touch_status(devices[i]->fido_device, + touch_detected, + FIDO_POLL_MS); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to get touch status on device %s: %s", + devices[i]->path, + fido_strerr(rc)); + sk_device_close(devices[i]); + devices[i] = NULL; + + n_failed++; + if (n_failed == num_devices) { + SSH_LOG(SSH_LOG_WARN, "No devices left to poll"); + return SSH_ERROR; + } + } else if (*touch_detected) { + *chosen_idx = i; + return SSH_OK; + } + } + + *touch_detected = 0; + return SSH_OK; +} + +/** + * Select a device from the list of devices which has the given + * application and key handle. + * + * @param device_list Array of device information structures + * @param num_devices Number of devices in the device_list array + * @param application The application identifier (relying party ID) + * @param key_handle The key handle to look for + * @param key_handle_len The length of the key handle in bytes + * + * @return The selected device on success, NULL if no device found or on error. + */ +static struct sk_device * +sk_device_select_by_credential(const fido_dev_info_t *device_list, + size_t num_devices, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len) +{ + int rc; + size_t num_opened = 0, i; + struct sk_device **devices = NULL, *selected_device = NULL; + + devices = sk_device_open_list(device_list, num_devices, &num_opened); + if (devices == NULL) { + SSH_LOG(SSH_LOG_WARN, "No FIDO2/U2F devices opened"); + return NULL; + } + + selected_device = NULL; + for (i = 0; i < num_opened; i++) { + rc = sk_device_check_key_handle(devices[i], + application, + key_handle, + key_handle_len); + if (rc == FIDO_OK) { + selected_device = devices[i]; + devices[i] = NULL; + SSH_LOG(SSH_LOG_DEBUG, + "Selected device %s for key handle", + selected_device->path); + break; + } + } + + sk_device_close_list(devices, num_opened); + return selected_device; +} + +/** + * Select a device by touch, where the user touches the key they want to use. + * The function will block until a touch is detected or the timeout is reached. + * + * @param device_list Array of device information structures + * @param num_devices Number of devices in the device_list array + * + * @return The selected device on success, NULL if no device found or on error. + */ +static struct sk_device * +sk_device_select_by_touch(const fido_dev_info_t *device_list, + size_t num_devices) +{ + int rc, touch = 0; + size_t num_opened = 0, chosen_idx; + struct sk_device **devices = NULL, *selected_device = NULL; + struct ssh_timestamp ts; + +#ifndef _WIN32 + struct timespec poll_sleep = {.tv_sec = 0, .tv_nsec = POLL_SLEEP_NS}; +#endif + + devices = sk_device_open_list(device_list, num_devices, &num_opened); + if (devices == NULL) { + SSH_LOG(SSH_LOG_WARN, "No FIDO2/U2F devices opened"); + return NULL; + } + + if (num_opened == 1) { + selected_device = devices[0]; + devices[0] = NULL; + SSH_LOG(SSH_LOG_DEBUG, + "Only one device opened, automatically selected %s", + selected_device->path); + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "%zu FIDO2/U2F device(s) opened", num_opened); + + rc = sk_device_touch_begin(devices, num_opened); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to begin touch on any device"); + goto out; + } + + ssh_timestamp_init(&ts); + do { + rc = sk_device_touch_poll(devices, num_opened, &touch, &chosen_idx); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to poll touch status"); + goto out; + } else if (touch) { + selected_device = devices[chosen_idx]; + devices[chosen_idx] = NULL; + goto out; + } + + if (ssh_timeout_elapsed(&ts, SELECT_MS)) { + SSH_LOG(SSH_LOG_WARN, "Touch selection timed out"); + break; + } + +#ifdef _WIN32 + /* Sleep expects milliseconds; convert nanoseconds (round down). */ + Sleep((DWORD)(POLL_SLEEP_NS / 1000000)); +#else + nanosleep(&poll_sleep, NULL); +#endif /* _WIN32 */ + + } while (true); + +out: + sk_device_close_list(devices, num_opened); + return selected_device; +} + +/** + * Probe for FIDO2/U2F devices and choose one based on the provided application + * and key handle. If application or key handle are NULL, the user will be + * prompted to touch the key they want to use. + * + * @param application The application identifier (relying party ID), can be NULL + * @param key_handle The key handle to look for, can be NULL + * @param key_handle_len The length of the key handle in bytes + * @param probe_resident Whether to probe for resident keys + * + * @return The selected device on success, NULL if no device found or on error. + */ +static struct sk_device *sk_device_probe(const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + bool probe_resident) +{ + int rc; + size_t num_devices = 0; + struct sk_device *device = NULL; + fido_dev_info_t *device_list = NULL; + +#ifdef _WIN32 + if (!probe_resident) { + device = sk_device_open("windows://hello"); + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open Windows Hello device"); + return NULL; + } + + SSH_LOG(SSH_LOG_DEBUG, "Using Windows Hello device"); + return device; + } +#endif /* _WIN32 */ + + device_list = fido_dev_info_new(MAX_FIDO_DEVICES); + if (device_list == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create device info list"); + return NULL; + } + + rc = fido_dev_info_manifest(device_list, MAX_FIDO_DEVICES, &num_devices); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to get device info manifest: %s", + fido_strerr(rc)); + goto out; + } + if (num_devices == 0) { + SSH_LOG(SSH_LOG_WARN, "No FIDO2/U2F devices found"); + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "%zu FIDO2/U2F device(s) detected", num_devices); + + /* + * If key_handle and application are specified, then we find the key which + * has the corresponding credentials, otherwise, we rely on the user to + * touch the key that they want to use. + */ + if (application != NULL && key_handle != NULL) { + SSH_LOG(SSH_LOG_DEBUG, "Selecting device by credential"); + device = sk_device_select_by_credential(device_list, + num_devices, + application, + key_handle, + key_handle_len); + } else { + SSH_LOG(SSH_LOG_DEBUG, "Selecting device by touch"); + device = sk_device_select_by_touch(device_list, num_devices); + } + +out: + fido_dev_info_free(&device_list, MAX_FIDO_DEVICES); + return device; +} + +/** + * Export an ECDSA public key from a FIDO2/U2F credential. + * + * The format returned by libfido2 is different from the expected SEC1 octet + * string representation, so this function performs the necessary conversion. + * + * @param credential The FIDO2/U2F credential containing the ECDSA public key + * @param response The enrollment response structure to fill with the public key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_public_key_ecdsa(const fido_cred_t *credential, + struct sk_enroll_response *response) +{ + size_t len; + const uint8_t *ptr = NULL; + + response->public_key = NULL; + response->public_key_len = 0; + + ptr = fido_cred_pubkey_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get FIDO2/U2F credential public key"); + return SSH_ERROR; + } + + len = fido_cred_pubkey_len(credential); + if (len != ECDSA_P256_PUBKEY_LEN) { + SSH_LOG(SSH_LOG_WARN, + "Bad FIDO2/U2F credential public key length %zu" + "(expected ecdsa public key length %d)", + len, + ECDSA_P256_PUBKEY_LEN); + return SSH_ERROR; + } + + /* + * Convert from libfido2's raw coordinate format to SEC1 octet string + * format. + * + * libfido2 returns: x_coordinate (32 bytes) + y_coordinate (32 + * bytes) + * + * SEC1 format expects: 0x04 + x_coordinate (32 bytes) + y_coordinate (32 + * bytes) + */ + response->public_key_len = 1 + ECDSA_P256_PUBKEY_LEN; + response->public_key = calloc(response->public_key_len, sizeof(uint8_t)); + if (response->public_key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for public key"); + return SSH_ERROR; + } + + /* SEC1 uncompressed point format: 0x04 prefix + raw coordinates */ + response->public_key[0] = 0x04; + memcpy(response->public_key + 1, ptr, ECDSA_P256_PUBKEY_LEN); + + return SSH_OK; +} + +/** + * Export an Ed25519 public key from a FIDO2 credential. + * + * @param credential The FIDO2 credential containing the Ed25519 public key + * @param response The enrollment response structure to fill with the public key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_public_key_ed25519(const fido_cred_t *credential, + struct sk_enroll_response *response) +{ + size_t len; + const uint8_t *ptr = NULL; + + response->public_key = NULL; + response->public_key_len = 0; + + ptr = fido_cred_pubkey_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get FIDO2 credential public key"); + return SSH_ERROR; + } + + len = fido_cred_pubkey_len(credential); + if (len != ED25519_KEY_LEN) { + SSH_LOG(SSH_LOG_WARN, + "Bad FIDO2 credential public key length %zu" + " (expected ed25519 public key length %d)", + len, + ED25519_KEY_LEN); + return SSH_ERROR; + } + + response->public_key_len = len; + response->public_key = calloc(response->public_key_len, sizeof(uint8_t)); + if (response->public_key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for public key"); + return SSH_ERROR; + } + + memcpy(response->public_key, ptr, len); + return SSH_OK; +} + +/** + * Export a public key from a FIDO2/U2F credential based on the specified + * algorithm. + * + * @param algorithm The key algorithm (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param credential The FIDO2/U2F credential containing the public key + * @param response The enrollment response structure to fill with the public key + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_public_key(int algorithm, + const fido_cred_t *credential, + struct sk_enroll_response *response) +{ + int ret; + + switch (algorithm) { + case SSH_SK_ECDSA: + ret = export_public_key_ecdsa(credential, response); + break; + case SSH_SK_ED25519: + ret = export_public_key_ed25519(credential, response); + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + ret = SSH_ERROR; + } + + return ret; +} + +/** + * Parse DER length encoding. + * + * @param p Pointer to the current position in DER data (updated on success) + * @param end Pointer to the end of DER data + * @param length Pointer to store the parsed length + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int +parse_der_length(const uint8_t **p, const uint8_t *end, size_t *length) +{ + int len_bytes = 0; + + if (*p >= end) { + SSH_LOG(SSH_LOG_WARN, "Insufficient data for DER length"); + return SSH_ERROR; + } + + /* If the MSB is set, it indicates a long form length + * where the lower 7 bits indicate the number of + * subsequent bytes that represent the length. + * + * If the MSB is not set, it indicates a short form + * length where the length is directly represented + * in the the byte itself. + */ + if (**p & 0x80) { + /* Long form length */ + len_bytes = **p & 0x7f; + (*p)++; + + if (len_bytes > DER_MAX_LEN_BYTES) { + SSH_LOG( + SSH_LOG_WARN, + "Invalid DER length bytes: %d. Should not be greater than %d", + len_bytes, + DER_MAX_LEN_BYTES); + return SSH_ERROR; + } + + if (*p + len_bytes > end) { + SSH_LOG(SSH_LOG_WARN, "Insufficient data for length bytes"); + return SSH_ERROR; + } + + *length = 0; + while (len_bytes--) { + *length = (*length << 8) | **p; + (*p)++; + } + } else { + /* Short form length */ + *length = **p; + (*p)++; + } + + return SSH_OK; +} + +/** + * Parse a single DER-encoded INTEGER. + * + * @param p Pointer to the current position in DER data (updated on success) + * @param end Pointer to the end of DER data + * @param component_name Name of the component for error messages + * @param int_ptr Pointer to store the integer data + * @param int_len Pointer to store the integer length + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int parse_der_integer(const uint8_t **p, + const uint8_t *end, + const char *component_name, + uint8_t **int_ptr, + size_t *int_len) +{ + size_t length = 0; + const uint8_t *data_ptr = NULL; + int rc = SSH_ERROR; + + if (int_ptr == NULL || int_len == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Invalid arguments provided for %s component", + component_name); + return SSH_ERROR; + } + + *int_ptr = NULL; + *int_len = 0; + + /* Check for INTEGER tag */ + if (*p >= end || **p != DER_INTEGER_TAG) { + SSH_LOG(SSH_LOG_WARN, + "Expected INTEGER tag for %s component", + component_name); + return SSH_ERROR; + } + (*p)++; + + /* Parse length */ + rc = parse_der_length(p, end, &length); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Invalid %s component length", component_name); + return SSH_ERROR; + } + + /* Verify we have enough data */ + if (*p + length > end) { + SSH_LOG(SSH_LOG_WARN, + "%s component extends beyond signature", + component_name); + return SSH_ERROR; + } + + /* Skip leading zero if present (The leading zero is placed when the MSB of + * the actual number is 1, so that it is not confused as a negative number + * in 2's complement) */ + data_ptr = *p; + if (length > 0 && **p == 0x00) { + data_ptr++; + length--; + } + + /* Allocate memory for the integer data */ + if (length > 0) { + *int_ptr = calloc(length, sizeof(uint8_t)); + if (*int_ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for %s component", + component_name); + return SSH_ERROR; + } + memcpy(*int_ptr, data_ptr, length); + } + + *int_len = length; + *p = data_ptr + length; + + return SSH_OK; +} + +/** + * Parse DER-encoded ECDSA signature and extract r and s components. + * + * @param der_sig DER-encoded signature data + * @param der_len Length of DER data + * @param r_ptr Pointer to store r component + * @param r_len Pointer to store r length + * @param s_ptr Pointer to store s component + * @param s_len Pointer to store s length + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int parse_ecdsa_der_signature(const uint8_t *der_sig, + size_t der_len, + uint8_t **r_ptr, + size_t *r_len, + uint8_t **s_ptr, + size_t *s_len) +{ + const uint8_t *p = der_sig; + const uint8_t *end = der_sig + der_len; + size_t seq_len = 0; + int rc; + + if (r_ptr == NULL || r_len == NULL || s_ptr == NULL || s_len == NULL || + der_sig == NULL) { + SSH_LOG(SSH_LOG_WARN, "Invalid arguments provided"); + return SSH_ERROR; + } + + *r_ptr = NULL; + *r_len = 0; + *s_ptr = NULL; + *s_len = 0; + + /* Parse SEQUENCE tag */ + if (p >= end || *(p++) != DER_SEQUENCE_TAG) { + SSH_LOG(SSH_LOG_WARN, "Expected SEQUENCE tag in DER signature"); + return SSH_ERROR; + } + + /* Parse sequence length */ + rc = parse_der_length(&p, end, &seq_len); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Invalid DER sequence length"); + return SSH_ERROR; + } + + /* Verify sequence length matches remaining data */ + if (p + seq_len != end) { + SSH_LOG(SSH_LOG_WARN, "DER sequence length mismatch"); + return SSH_ERROR; + } + + /* Parse first INTEGER (r component) */ + rc = parse_der_integer(&p, end, "r", r_ptr, r_len); + if (rc != SSH_OK) { + goto error; + } + + /* Parse second INTEGER (s component) */ + rc = parse_der_integer(&p, end, "s", s_ptr, s_len); + if (rc != SSH_OK) { + goto error; + } + + /* Verify we consumed all data */ + if (p != end) { + SSH_LOG(SSH_LOG_WARN, "Unexpected data after s component"); + goto error; + } + + return SSH_OK; + +error: + SAFE_FREE(*r_ptr); + *r_len = 0; + SAFE_FREE(*s_ptr); + *s_len = 0; + return SSH_ERROR; +} + +/** + * Export an ECDSA signature from a FIDO2/U2F assertion. + * + * @param assert The FIDO2/U2F assertion containing the signature + * @param response The sign response structure to fill with the signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_signature_ecdsa(fido_assert_t *assert, + struct sk_sign_response *response) +{ + size_t len = 0; + const uint8_t *ptr = NULL; + int rc; + + len = fido_assert_sig_len(assert, 0); + ptr = fido_assert_sig_ptr(assert, 0); + + if (ptr == NULL || len == 0) { + SSH_LOG(SSH_LOG_WARN, + "Invalid signature data from FIDO2/U2F assertion"); + return SSH_ERROR; + } + + /* This will allocate and populate response->sig_r/_s (+ lengths) */ + rc = parse_ecdsa_der_signature(ptr, + len, + &response->sig_r, + &response->sig_r_len, + &response->sig_s, + &response->sig_s_len); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to parse DER ECDSA signature"); + return SSH_ERROR; + } + + return SSH_OK; +} + +/** + * Export an Ed25519 signature from a FIDO2 assertion. + * + * @param assert The FIDO2 assertion containing the signature + * @param response The sign response structure to fill with the signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_signature_ed25519(fido_assert_t *assert, + struct sk_sign_response *response) +{ + const uint8_t *ptr = NULL; + size_t len; + + ptr = fido_assert_sig_ptr(assert, 0); + len = fido_assert_sig_len(assert, 0); + if (len != ED25519_SIG_LEN) { + SSH_LOG(SSH_LOG_WARN, "Bad ED25519 signature length %zu", len); + return SSH_ERROR; + } + + response->sig_r_len = len; + response->sig_r = calloc(response->sig_r_len, sizeof(uint8_t)); + if (response->sig_r == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for signature"); + return SSH_ERROR; + } + + memcpy(response->sig_r, ptr, len); + response->sig_s = NULL; + response->sig_s_len = 0; + return SSH_OK; +} + +/** + * Export a signature from a FIDO2/U2F assertion based on the specified + * algorithm. + * + * @param algorithm The signature algorithm (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param assert The FIDO2/U2F assertion containing the signature + * @param response The sign response structure to fill with the signature + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int export_signature(int algorithm, + fido_assert_t *assert, + struct sk_sign_response *response) +{ + int ret; + + switch (algorithm) { + case SSH_SK_ECDSA: + ret = export_signature_ecdsa(assert, response); + break; + case SSH_SK_ED25519: + ret = export_signature_ed25519(assert, response); + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %d", algorithm); + ret = SSH_ERROR; + } + + return ret; +} + +static uint32_t ssh_sk_usbhid_api_version(void) +{ + return SK_USBHID_API_VERSION; +} + +/** + * Create and configure a new FIDO2/U2F credential for enrollment. + * + * @param device The FIDO2/U2F device to use + * @param alg The algorithm to use (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param challenge The challenge data + * @param challenge_len The length of the challenge data + * @param application The application identifier (relying party ID) + * @param flags The enrollment flags + * @param pin The PIN for the device (can be NULL) + * @param user_id The binary user ID buffer (can be NULL) + * @param user_id_len Length of user_id buffer in bytes (0 if none) + * @param credential_ptr Pointer to store the created credential + * + * @return FIDO_OK on success, FIDO_ERR_* codes on failure + */ +static int create_new_fido_credential(struct sk_device *device, + uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + const uint8_t *user_id, + size_t user_id_len, + fido_cred_t **credential_ptr) +{ + int ret = FIDO_ERR_INTERNAL; + int cose_algorithm, cred_protection; + bool cred_prot_support = false; + fido_opt_t set_resident_key = FIDO_OPT_OMIT; + fido_cred_t *credential = NULL; + + /* Set the COSE algorithm based on the requested algorithm */ + switch (alg) { + case SSH_SK_ECDSA: + cose_algorithm = COSE_ES256; + break; + case SSH_SK_ED25519: + cose_algorithm = COSE_EDDSA; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %u", alg); + return FIDO_ERR_UNSUPPORTED_ALGORITHM; + } + + credential = fido_cred_new(); + if (credential == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO2/U2F credential"); + goto error; + } + + ret = fido_cred_set_type(credential, cose_algorithm); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set credential type: %s", + fido_strerr(ret)); + goto error; + } + + ret = fido_cred_set_clientdata(credential, challenge, challenge_len); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set client data: %s", + fido_strerr(ret)); + goto error; + } + + if (flags & SSH_SK_RESIDENT_KEY) { + set_resident_key = FIDO_OPT_TRUE; + } + ret = fido_cred_set_rk(credential, set_resident_key); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set resident key option: %s", + fido_strerr(ret)); + goto error; + } + + /* TODO: Add an additional option to set display_name, icon ..etc */ + ret = + fido_cred_set_user(credential, user_id, user_id_len, NULL, NULL, NULL); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user information: %s", + fido_strerr(ret)); + goto error; + } + + ret = fido_cred_set_rp(credential, application, NULL); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set Relying Party: %s", + fido_strerr(ret)); + goto error; + } + + if (flags & (SSH_SK_USER_VERIFICATION_REQD | SSH_SK_RESIDENT_KEY)) { + cred_prot_support = fido_dev_supports_cred_prot(device->fido_device); + if (!cred_prot_support) { + SSH_LOG(SSH_LOG_WARN, + "Device does not support credential protection"); + ret = FIDO_ERR_UNSUPPORTED_EXTENSION; + goto error; + } + + if (flags & SSH_SK_USER_VERIFICATION_REQD) { + cred_protection = FIDO_CRED_PROT_UV_REQUIRED; + } else { + cred_protection = FIDO_CRED_PROT_UV_OPTIONAL_WITH_ID; + } + + ret = fido_cred_set_prot(credential, cred_protection); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set credential protection: %s", + fido_strerr(ret)); + goto error; + } + } + + ret = fido_dev_make_cred(device->fido_device, credential, pin); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to make credential: %s", + fido_strerr(ret)); + goto error; + } + + *credential_ptr = credential; + return FIDO_OK; + +error: + fido_cred_free(&credential); + return ret; +} + +/** + * Construct an enrollment response from a FIDO2/U2F credential. + * This function extracts and copies all necessary data from the fido_cred_t + * into the response structure. + * + * @param alg The algorithm used (SSH_SK_ECDSA or SSH_SK_ED25519) + * @param credential The FIDO2/U2F credential containing the enrollment data + * @param flags The enrollment flags + * @param response_ptr Pointer to store the constructed enrollment response + * + * @return SSH_OK on success, SSH_ERROR on failure + */ +static int +fido_cred_export_sk_enroll_response(uint32_t alg, + const fido_cred_t *credential, + uint8_t flags, + struct sk_enroll_response **response_ptr) +{ + const uint8_t *ptr = NULL; + const char *fmt = NULL; + struct sk_enroll_response *response = NULL; + int rc; + + response = calloc(1, sizeof(struct sk_enroll_response)); + if (response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for enroll response"); + return SSH_ERROR; + } + + response->flags = flags; + + /* Export public key */ + rc = export_public_key(alg, credential, response); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to export public key from credential"); + goto error; + } + + /* Export the key handle */ + ptr = fido_cred_id_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get key handle"); + goto error; + } + + response->key_handle_len = fido_cred_id_len(credential); + response->key_handle = calloc(response->key_handle_len, sizeof(uint8_t)); + if (response->key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for key handle"); + goto error; + } + memcpy(response->key_handle, ptr, response->key_handle_len); + + /* Export challenge signature */ + fmt = fido_cred_fmt(credential); + if (fmt == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get attestation format"); + goto error; + } + + ptr = fido_cred_sig_ptr(credential); + if (ptr != NULL) { + response->signature_len = fido_cred_sig_len(credential); + response->signature = calloc(response->signature_len, sizeof(uint8_t)); + if (response->signature == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for signature"); + goto error; + } + memcpy(response->signature, ptr, response->signature_len); + } else if (strcmp(fmt, "none") == 0) { + /* No signature for "none" attestation format */ + response->signature = NULL; + response->signature_len = 0; + } else { + SSH_LOG(SSH_LOG_WARN, "Failed to get signature"); + goto error; + } + + /* Export attestation information if available */ + ptr = fido_cred_x5c_ptr(credential); + if (ptr != NULL) { + response->attestation_cert_len = fido_cred_x5c_len(credential); + response->attestation_cert = + calloc(response->attestation_cert_len, sizeof(uint8_t)); + if (response->attestation_cert == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for attestation cert"); + goto error; + } + memcpy(response->attestation_cert, ptr, response->attestation_cert_len); + } + + /* Export authdata */ + ptr = fido_cred_authdata_ptr(credential); + if (ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get authdata"); + goto error; + } + + response->authdata_len = fido_cred_authdata_len(credential); + response->authdata = calloc(response->authdata_len, sizeof(uint8_t)); + if (response->authdata == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for authdata"); + goto error; + } + memcpy(response->authdata, ptr, response->authdata_len); + + *response_ptr = response; + response = NULL; + return SSH_OK; + +error: + sk_enroll_response_free(response); + return SSH_ERROR; +} + +static int ssh_sk_usbhid_enroll(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response) +{ + int rc, ret = SSH_SK_ERR_GENERAL; + const uint8_t *ptr = NULL; + const char *attestation_format = NULL; + const char *supported_options[] = {SSH_SK_OPTION_NAME_DEVICE_PATH, + SSH_SK_OPTION_NAME_USER_ID, + NULL}; + char **option_values = NULL; + const char *device_path = NULL; + uint8_t user_id[SK_MAX_USER_ID_LEN] = {0}; + size_t user_id_len = 0; + size_t j; + + struct sk_device *device = NULL; + struct sk_enroll_response *response = NULL; + + fido_cred_t *credential = NULL; + + if (enroll_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "enroll_response cannot be NULL"); + goto out; + } + *enroll_response = NULL; + + switch (alg) { + case SSH_SK_ECDSA: + case SSH_SK_ED25519: + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %u", alg); + ret = SSH_SK_ERR_UNSUPPORTED; + goto out; + } + + if (challenge == NULL || challenge_len == 0) { + SSH_LOG(SSH_LOG_WARN, "challenge cannot be NULL or empty"); + goto out; + } + + if (application == NULL || application[0] == '\0') { + SSH_LOG(SSH_LOG_WARN, "application cannot be NULL or empty"); + goto out; + } + + /* Extract device path from options if provided */ + rc = sk_options_validate_get((const struct sk_option **)options, + supported_options, + &option_values); + if (rc == SSH_OK && option_values != NULL) { + device_path = option_values[0]; /* device path is first in the array */ + + /* + * The user id is actually binary data according to the FIDO2 + * specification, but since we want to remain compatible with OpenSSH + * sk-api, so we are restricted to only obtain the user_id as a char * + * from the sk_option struct. + */ + if (option_values[1] != NULL) { + user_id_len = strlen(option_values[1]); + + if (user_id_len > SK_MAX_USER_ID_LEN) { + SSH_LOG(SSH_LOG_WARN, + "user_id length exceeds maximum of %d characters", + SK_MAX_USER_ID_LEN); + goto out; + } + + memcpy((char *)user_id, option_values[1], user_id_len); + } + } + + sk_fido_init(); + + if (device_path != NULL) { + device = sk_device_open(device_path); + } else { + device = sk_device_probe(NULL, NULL, 0, false); + } + + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open FIDO2/U2F device"); + ret = SSH_SK_ERR_DEVICE_NOT_FOUND; + goto out; + } + + SSH_LOG(SSH_LOG_DEBUG, "Using FIDO2/U2F device: %s", device->path); + + /* + * Check whether a resident key with same user_id exists to avoid + * overwriting, unless operation is marked as forceful. + */ + if ((flags & SSH_SK_RESIDENT_KEY) != 0 && + (flags & SSH_SK_FORCE_OPERATION) == 0) { + + rc = sk_device_check_resident_key(device, + application, + (uint8_t *)user_id, + SK_MAX_USER_ID_LEN, + pin); + if (rc == FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, "Resident key already exists"); + ret = SSH_SK_ERR_CREDENTIAL_EXISTS; + goto out; + } else if (rc != FIDO_ERR_NO_CREDENTIALS) { + SSH_LOG(SSH_LOG_WARN, + "Failed to check for resident key: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + } + + /* Create and configure the FIDO2/U2F credential */ + ret = create_new_fido_credential(device, + alg, + challenge, + challenge_len, + application, + flags, + pin, + (uint8_t *)user_id, + SK_MAX_USER_ID_LEN, + &credential); + if (ret != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO2/U2F credential"); + ret = fido_err_to_ssh_sk_err(ret); + goto out; + } + + ptr = fido_cred_x5c_ptr(credential); + attestation_format = fido_cred_fmt(credential); + + if (attestation_format == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to get attestation format"); + goto out; + } + + rc = strcmp(attestation_format, "none"); + + /* + * If the x509 certificate is available, we can assume attestation type to + * be Basic Attestation and verify the attestation using the + * fido_cred_verify function, which checks the attestation signature using + * the attestation key mentioned in the x509 certificate. + * + * If the x509 certificate is not available, we check the attestation format + * to see whether it's type is Self attestation or None. If it + * is Self Attestation, we use fido_cred_verify_self to verify the + * credential, which checks the attestation signature against the public key + * of the credential itself. + * + * For more details, refer: + * https://developers.yubico.com/libfido2/Manuals/fido_cred_verify.html + * https://www.w3.org/TR/webauthn-2/#sctn-attestation + */ + if (ptr != NULL) { + SSH_LOG(SSH_LOG_DEBUG, + "Verifying attestation (type: Basic Attestation)"); + rc = fido_cred_verify(credential); + } else if (rc != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Verifying attestation (type: Self attestation)"); + rc = fido_cred_verify_self(credential); + } else { + SSH_LOG(SSH_LOG_DEBUG, "No attestation data available"); + rc = FIDO_OK; + } + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to verify credential: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + /* Construct the enrollment response from the credential data */ + rc = fido_cred_export_sk_enroll_response(alg, credential, flags, &response); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to export public key from credential"); + goto out; + } + + *enroll_response = response; + response = NULL; + ret = SSH_OK; + +out: + + /* Clean up extracted values */ + if (option_values != NULL) { + for (j = 0; supported_options[j] != NULL; j++) { + SAFE_FREE(option_values[j]); + } + SAFE_FREE(option_values); + } + sk_enroll_response_free(response); + sk_device_close(device); + fido_cred_free(&credential); + + return ret; +} + +static int ssh_sk_usbhid_sign(uint32_t alg, + const uint8_t *data, + size_t data_len, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_sign_response **sign_response) +{ + int rc, ret = SSH_SK_ERR_GENERAL; + size_t i; + + const char *supported_options[] = {SSH_SK_OPTION_NAME_DEVICE_PATH, NULL}; + char **option_values = NULL; + const char *device_path = NULL; + + struct sk_device *device = NULL; + struct sk_sign_response *response = NULL; + + bool has_internal_uv = false, is_winhello = false; + fido_opt_t user_presence = FIDO_OPT_FALSE; + fido_assert_t *assert = NULL; + + if (sign_response == NULL) { + SSH_LOG(SSH_LOG_WARN, "sign_response cannot be NULL"); + goto out; + } + *sign_response = NULL; + + switch (alg) { + case SSH_SK_ECDSA: + case SSH_SK_ED25519: + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm: %u", alg); + ret = SSH_SK_ERR_UNSUPPORTED; + goto out; + } + + if (data == NULL || data_len == 0) { + SSH_LOG(SSH_LOG_WARN, "data to sign cannot be NULL or empty"); + goto out; + } + + if (application == NULL || application[0] == '\0') { + SSH_LOG(SSH_LOG_WARN, "application cannot be NULL or empty"); + goto out; + } + + if (key_handle == NULL || key_handle_len == 0) { + SSH_LOG(SSH_LOG_WARN, "key_handle cannot be NULL or empty"); + goto out; + } + + /* Extract device path from options if provided */ + rc = sk_options_validate_get((const struct sk_option **)options, + supported_options, + &option_values); + if (rc == SSH_OK && option_values != NULL) { + device_path = option_values[0]; + } + + sk_fido_init(); + + /* + * We directly open the device if path is given. + * + * Otherwise, If PIN supplied or UV required, we avoid credential probing + * across multiple devices (which could trigger multiple UV prompts). + * Instead, we select by user touch first. + * + * For presence-only (UP) cases, credential-based probing is silent (see the + * comment in the sk_device_check_key_handle function about pre-flight + * checking), so we keep it to reduce touches. + */ + if (device_path != NULL) { + device = sk_device_open(device_path); + } else if (pin != NULL || (flags & SSH_SK_USER_VERIFICATION_REQD)) { + /* Touch based selection */ + device = sk_device_probe(NULL, NULL, 0, false); + } else { + /* Credential based selection */ + device = + sk_device_probe(application, key_handle, key_handle_len, false); + } + + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open FIDO2/U2F device"); + ret = SSH_SK_ERR_DEVICE_NOT_FOUND; + goto out; + } + + assert = fido_assert_new(); + if (assert == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create new FIDO2/U2F assertion"); + goto out; + } + + rc = fido_assert_set_clientdata(assert, data, data_len); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to set client data: %s", fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + rc = fido_assert_set_rp(assert, application); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set relying party: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + rc = fido_assert_allow_cred(assert, key_handle, key_handle_len); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allow credential: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + user_presence = + (flags & SSH_SK_USER_PRESENCE_REQD) ? FIDO_OPT_TRUE : FIDO_OPT_FALSE; + rc = fido_assert_set_up(assert, user_presence); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user presence: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + /* + * WinHello always requests the pin, unless we explicitly specify that we + * don't expect user verification. + */ + is_winhello = fido_dev_is_winhello(device->fido_device); + if (pin == NULL && is_winhello) { + rc = fido_assert_set_uv(assert, FIDO_OPT_FALSE); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user verification: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + } + } + + /* + * pin can be NULL if device internally has user verification capabilities + * such as biometric. + */ + if (pin == NULL && (flags & SSH_SK_USER_VERIFICATION_REQD)) { + has_internal_uv = fido_dev_has_uv(device->fido_device); + if (!has_internal_uv) { + SSH_LOG(SSH_LOG_WARN, + "User Verification requirement cannot be satisfied as " + "device lacks internal user verification and PIN is also " + "not provided"); + ret = SSH_SK_ERR_PIN_REQUIRED; + goto out; + } + + rc = fido_assert_set_uv(assert, FIDO_OPT_TRUE); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to set user verification: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + } + + rc = fido_dev_get_assert(device->fido_device, assert, pin); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to get assertion: %s", fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + response = calloc(1, sizeof(struct sk_sign_response)); + if (response == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate sign response"); + goto out; + } + + response->flags = fido_assert_flags(assert, 0); + response->counter = fido_assert_sigcount(assert, 0); + + rc = export_signature(alg, assert, response); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to export signature"); + goto out; + } + + *sign_response = response; + response = NULL; + ret = SSH_OK; + +out: + if (option_values != NULL) { + for (i = 0; supported_options[i] != NULL; i++) { + SAFE_FREE(option_values[i]); + } + SAFE_FREE(option_values); + } + + fido_assert_free(&assert); + sk_device_close(device); + sk_sign_response_free(response); + + return ret; +} + +/** + * Export a single resident credential into an allocated sk_resident_key. + */ +static int fido_cred_export_sk_resident_key(const fido_cred_t *credential, + const char *relying_party_id, + bool has_internal_uv, + struct sk_resident_key **out_key) +{ + struct sk_resident_key *resident_key = NULL; + const uint8_t *ptr = NULL; + size_t len; + int algorithm; + int rc; + + resident_key = calloc(1, sizeof(struct sk_resident_key)); + if (resident_key == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for resident key"); + goto error; + } + + /* application */ + resident_key->application = strdup(relying_party_id); + if (resident_key->application == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for application"); + goto error; + } + + /* key handle */ + len = fido_cred_id_len(credential); + ptr = fido_cred_id_ptr(credential); + resident_key->key.key_handle_len = len; + resident_key->key.key_handle = calloc(len, sizeof(uint8_t)); + if (resident_key->key.key_handle == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for key handle"); + goto error; + } + memcpy(resident_key->key.key_handle, ptr, len); + + /* user id */ + len = fido_cred_user_id_len(credential); + ptr = fido_cred_user_id_ptr(credential); + resident_key->user_id_len = len; + resident_key->user_id = calloc(len, sizeof(uint8_t)); + if (resident_key->user_id == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to allocate memory for user ID"); + goto error; + } + memcpy(resident_key->user_id, ptr, len); + + /* algorithm */ + algorithm = fido_cred_type(credential); + switch (algorithm) { + case COSE_ES256: + resident_key->alg = SSH_SK_ECDSA; + break; + case COSE_EDDSA: + resident_key->alg = SSH_SK_ED25519; + break; + default: + SSH_LOG(SSH_LOG_WARN, "Unsupported algorithm %d", algorithm); + goto error; + } + + rc = fido_cred_prot(credential); + if (rc == FIDO_CRED_PROT_UV_REQUIRED && !has_internal_uv) { + resident_key->flags |= SSH_SK_USER_VERIFICATION_REQD; + } + + rc = export_public_key(resident_key->alg, credential, &resident_key->key); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to export public key for credential: %d", + rc); + goto error; + } + + *out_key = resident_key; + return SSH_OK; + +error: + SK_RESIDENT_KEY_FREE(resident_key); + return SSH_ERROR; +} + +/** + * Load resident keys from a specific security key device. + * + * @param device The security key device to load keys from + * @param pin The PIN for the device (required for loading resident keys) + * @param resident_keys_ptr Pointer to store the array of loaded resident keys + * @param num_keys_found_ptr Pointer to store the number of keys found + * + * @return SSH_SK_ERR_* error code (SSH_OK on success) + * + * @note This function only considers resident keys that belong to + * relying parties starting with "ssh:". + */ +static int +sk_device_load_resident_keys(struct sk_device *device, + const char *pin, + struct sk_resident_key ***resident_keys_ptr, + size_t *num_keys_found_ptr) +{ + int ret = SSH_SK_ERR_GENERAL, rc; + bool has_internal_uv = false; + size_t i, j, keys_count, num_relying_parties; + const char *relying_party_id = NULL; + + struct sk_resident_key *cur_resident_key = NULL, **temp_ptr = NULL; + fido_credman_metadata_t *metadata = NULL; + fido_credman_rp_t *relying_parties = NULL; + fido_credman_rk_t *resident_keys = NULL; + const fido_cred_t *credential = NULL; + + has_internal_uv = fido_dev_has_uv(device->fido_device); + + metadata = fido_credman_metadata_new(); + if (metadata == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create FIDO2/U2F metadata"); + goto out; + } + + rc = fido_credman_get_dev_metadata(device->fido_device, metadata, pin); + if (rc != FIDO_OK) { + if (rc == FIDO_ERR_INVALID_COMMAND) { + SSH_LOG(SSH_LOG_WARN, "Device does not support resident keys"); + ret = SSH_SK_ERR_UNSUPPORTED; + } else { + SSH_LOG(SSH_LOG_WARN, + "Failed to get device metadata: %s for device at %s", + fido_strerr(rc), + device->path); + ret = fido_err_to_ssh_sk_err(rc); + } + goto out; + } + + relying_parties = fido_credman_rp_new(); + if (relying_parties == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create relying parties list"); + goto out; + } + + rc = fido_credman_get_dev_rp(device->fido_device, relying_parties, pin); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_WARN, + "Failed to get relying party: %s", + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + goto out; + } + + num_relying_parties = fido_credman_rp_count(relying_parties); + + SSH_LOG(SSH_LOG_DEBUG, + "Device %s has key(s) for %zu relying party(ies).", + device->path, + num_relying_parties); + + /* + * Check all resident keys belonging to relying parties starting with "ssh:" + */ + for (i = 0; i < num_relying_parties; i++) { + relying_party_id = fido_credman_rp_id(relying_parties, i); + if (relying_party_id != NULL) { + rc = strncasecmp(relying_party_id, "ssh:", 4); + if (rc != 0) { + SSH_LOG(SSH_LOG_DEBUG, + "Skipping non-SSH relying party: %s", + relying_party_id); + continue; + } + } else { + SSH_LOG(SSH_LOG_DEBUG, + "Relying party ID is NULL, skipping RP %zu", + i); + continue; + } + + fido_credman_rk_free(&resident_keys); + resident_keys = fido_credman_rk_new(); + if (resident_keys == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to create FIDO2 resident key"); + goto out; + } + + rc = fido_credman_get_dev_rk(device->fido_device, + relying_party_id, + resident_keys, + pin); + if (rc != FIDO_OK) { + SSH_LOG(SSH_LOG_INFO, + "Failed to get resident key for RP %s: %s", + relying_party_id, + fido_strerr(rc)); + ret = fido_err_to_ssh_sk_err(rc); + continue; + } + + keys_count = fido_credman_rk_count(resident_keys); + if (keys_count == 0) { + SSH_LOG(SSH_LOG_INFO, + "No resident keys found for RP %s", + relying_party_id); + continue; + } + + SSH_LOG(SSH_LOG_DEBUG, + "Found %zu resident key(s) for RP %s", + keys_count, + relying_party_id); + + for (j = 0; j < keys_count; j++) { + credential = fido_credman_rk(resident_keys, j); + if (credential == NULL) { + SSH_LOG(SSH_LOG_INFO, "No resident key in slot %zu", j); + continue; + } + + rc = fido_cred_export_sk_resident_key(credential, + relying_party_id, + has_internal_uv, + &cur_resident_key); + if (rc != SSH_OK) { + goto out; + } + + temp_ptr = realloc(*resident_keys_ptr, + sizeof(struct sk_resident_key *) * + (*num_keys_found_ptr + 1)); + if (temp_ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, + "Failed to allocate memory for resident keys list"); + goto out; + } + + *resident_keys_ptr = temp_ptr; + (*resident_keys_ptr)[*num_keys_found_ptr] = cur_resident_key; + (*num_keys_found_ptr)++; + cur_resident_key = NULL; + } + } + + ret = SSH_OK; + +out: + SK_RESIDENT_KEY_FREE(cur_resident_key); + fido_credman_rp_free(&relying_parties); + fido_credman_rk_free(&resident_keys); + fido_credman_metadata_free(&metadata); + return ret; +} + +static int +ssh_sk_usbhid_load_resident_keys(const char *pin, + struct sk_option **options, + struct sk_resident_key ***resident_keys_ptr, + size_t *num_keys_found_ptr) +{ + int rc, ret = SSH_SK_ERR_GENERAL; + size_t i, j, keys_count = 0; + const char *supported_options[] = {SSH_SK_OPTION_NAME_DEVICE_PATH, NULL}; + char **option_values = NULL; + const char *device_path = NULL; + + struct sk_resident_key **resident_keys = NULL; + struct sk_device *device = NULL; + + if (resident_keys_ptr == NULL || num_keys_found_ptr == NULL) { + SSH_LOG(SSH_LOG_WARN, + "resident_keys_ptr and num_keys_found_ptr cannot be NULL"); + return SSH_SK_ERR_GENERAL; + } + + /* + * To load device metadata and resident keys, a valid pin must be provided + * regardless of internal uv support. + */ + if (pin == NULL) { + SSH_LOG(SSH_LOG_WARN, "PIN cannot be NULL for loading resident keys"); + return SSH_SK_ERR_PIN_REQUIRED; + } + + *resident_keys_ptr = NULL; + *num_keys_found_ptr = 0; + + sk_fido_init(); + + rc = sk_options_validate_get((const struct sk_option **)options, + supported_options, + &option_values); + if (rc == SSH_OK && option_values != NULL) { + device_path = option_values[0]; + } + + if (device_path != NULL) { + device = sk_device_open(device_path); + } else { + device = sk_device_probe(NULL, NULL, 0, 1); + } + + if (device == NULL) { + SSH_LOG(SSH_LOG_WARN, "Failed to open FIDO2 device"); + ret = SSH_SK_ERR_DEVICE_NOT_FOUND; + goto out; + } + + rc = sk_device_load_resident_keys(device, pin, &resident_keys, &keys_count); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "Failed to load resident keys: %d", rc); + ret = rc; + goto out; + } + + *resident_keys_ptr = resident_keys; + *num_keys_found_ptr = keys_count; + resident_keys = NULL; + keys_count = 0; + ret = SSH_OK; + +out: + if (option_values != NULL) { + for (j = 0; supported_options[j] != NULL; j++) { + SAFE_FREE(option_values[j]); + } + SAFE_FREE(option_values); + } + + sk_device_close(device); + + for (i = 0; i < keys_count; i++) { + SK_RESIDENT_KEY_FREE(resident_keys[i]); + } + SAFE_FREE(resident_keys); + return ret; +} + +static struct ssh_sk_callbacks_struct sk_usbhid_callbacks = { + .api_version = ssh_sk_usbhid_api_version, + .enroll = ssh_sk_usbhid_enroll, + .sign = ssh_sk_usbhid_sign, + .load_resident_keys = ssh_sk_usbhid_load_resident_keys, +}; + +const struct ssh_sk_callbacks_struct *ssh_sk_get_usbhid_callbacks(void) +{ + ssh_callbacks_init(&sk_usbhid_callbacks); + return &sk_usbhid_callbacks; +} diff --git a/src/libs/libssh-0.12.2/src/sntrup761.c b/src/libs/libssh-0.12.2/src/sntrup761.c new file mode 100644 index 000000000000..a67de4372231 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/sntrup761.c @@ -0,0 +1,524 @@ +/* + * sntrup761.c - SNTRUP761x25519 ECDH functions for key exchange + * sntrup761x25519-sha512@openssh.com - based on curve25519.c. + * + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Aris Adamantiadis + * Copyright (c) 2023 Simon Josefsson + * Copyright (c) 2025 Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, version 2.1 of the License. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "libssh/sntrup761.h" +#ifdef HAVE_SNTRUP761 + +#include "libssh/bignum.h" +#include "libssh/buffer.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" +#include "libssh/pki.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/ssh2.h" + +#ifndef HAVE_LIBGCRYPT +static void crypto_random(void *ctx, size_t length, uint8_t *dst) +{ + int *err = ctx; + *err = ssh_get_random(dst, length, 1); +} +#endif /* HAVE_LIBGCRYPT */ + +static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply); + +static ssh_packet_callback dh_client_callbacks[] = { + ssh_packet_client_sntrup761x25519_reply, +}; + +static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_client_callbacks = + { + .start = SSH2_MSG_KEX_ECDH_REPLY, + .n_callbacks = 1, + .callbacks = dh_client_callbacks, + .user = NULL, +}; + +static int ssh_sntrup761x25519_init(ssh_session session) +{ + int rc; + + rc = ssh_curve25519_init(session); + if (rc != SSH_OK) { + return rc; + } + + if (!session->server) { +#ifdef HAVE_LIBGCRYPT + gcry_error_t err; + + err = gcry_kem_keypair(GCRY_KEM_SNTRUP761, + session->next_crypto->sntrup761_client_pubkey, + SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_privkey, + SNTRUP761_SECRETKEY_SIZE); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate sntrup761 key: %s", + gpg_strerror(err)); + return SSH_ERROR; + } +#else + sntrup761_keypair(session->next_crypto->sntrup761_client_pubkey, + session->next_crypto->sntrup761_privkey, + &rc, + crypto_random); + if (rc != 1) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to generate sntrup761 key: PRNG failure"); + return SSH_ERROR; + } +#endif /* HAVE_LIBGCRYPT */ + } + + return SSH_OK; +} + +/** @internal + * @brief Starts sntrup761x25519-sha512@openssh.com key exchange + */ +int ssh_client_sntrup761x25519_init(ssh_session session) +{ + int rc; + + rc = ssh_sntrup761x25519_init(session); + if (rc != SSH_OK) { + return rc; + } + + rc = ssh_buffer_pack(session->out_buffer, + "bdPP", + SSH2_MSG_KEX_ECDH_INIT, + CURVE25519_PUBKEY_SIZE + SNTRUP761_PUBLICKEY_SIZE, + (size_t)SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_client_pubkey, + (size_t)CURVE25519_PUBKEY_SIZE, + session->next_crypto->curve25519_client_pubkey); + if (rc != SSH_OK) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_client_callbacks); + session->dh_handshake_state = DH_STATE_INIT_SENT; + rc = ssh_packet_send(session); + + return rc; +} + +void ssh_client_sntrup761x25519_remove_callbacks(ssh_session session) +{ + ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_client_callbacks); +} + +static int ssh_sntrup761x25519_build_k(ssh_session session) +{ + unsigned char ssk[SNTRUP761_SIZE + CURVE25519_PUBKEY_SIZE]; + unsigned char *k = ssk + SNTRUP761_SIZE; + unsigned char hss[SHA512_DIGEST_LEN]; + int rc; + + rc = ssh_curve25519_create_k(session, k); + if (rc != SSH_OK) { + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("Curve25519 shared secret", k, CURVE25519_PUBKEY_SIZE); +#endif + +#ifdef HAVE_LIBGCRYPT + if (session->server) { + gcry_error_t err; + err = gcry_kem_encap(GCRY_KEM_SNTRUP761, + session->next_crypto->sntrup761_client_pubkey, + SNTRUP761_PUBLICKEY_SIZE, + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE, + ssk, + SNTRUP761_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to encapsulate sntrup761 shared secret: %s", + gpg_strerror(err)); + rc = SSH_ERROR; + goto cleanup; + } + } else { + gcry_error_t err; + err = gcry_kem_decap(GCRY_KEM_SNTRUP761, + session->next_crypto->sntrup761_privkey, + SNTRUP761_SECRETKEY_SIZE, + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE, + ssk, + SNTRUP761_SIZE, + NULL, + 0); + if (err) { + SSH_LOG(SSH_LOG_TRACE, + "Failed to decapsulate sntrup761 shared secret: %s", + gpg_strerror(err)); + rc = SSH_ERROR; + goto cleanup; + } + } +#else + if (session->server) { + sntrup761_enc(session->next_crypto->sntrup761_ciphertext, + ssk, + session->next_crypto->sntrup761_client_pubkey, + &rc, + crypto_random); + if (rc != 1) { + rc = SSH_ERROR; + goto cleanup; + } + } else { + sntrup761_dec(ssk, + session->next_crypto->sntrup761_ciphertext, + session->next_crypto->sntrup761_privkey); + } +#endif /* HAVE_LIBGCRYPT */ + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("server cipher text", + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE); + ssh_log_hexdump("kem key", ssk, SNTRUP761_SIZE); +#endif + + sha512(ssk, sizeof ssk, hss); + + bignum_bin2bn(hss, sizeof hss, &session->next_crypto->shared_secret); + if (session->next_crypto->shared_secret == NULL) { + rc = SSH_ERROR; + goto cleanup; + } + +#ifdef DEBUG_CRYPTO + ssh_print_bignum("Shared secret key", session->next_crypto->shared_secret); +#endif + + return 0; +cleanup: + ssh_burn(ssk, sizeof ssk); + ssh_burn(hss, sizeof hss); + + return rc; +} + +/** @internal + * @brief parses a SSH_MSG_KEX_ECDH_REPLY packet and sends back + * a SSH_MSG_NEWKEYS + */ +static SSH_PACKET_CALLBACK(ssh_packet_client_sntrup761x25519_reply) +{ + ssh_string q_s_string = NULL; + ssh_string pubkey_blob = NULL; + ssh_string signature = NULL; + int rc; + (void)type; + (void)user; + + ssh_client_sntrup761x25519_remove_callbacks(session); + + pubkey_blob = ssh_buffer_get_ssh_string(packet); + if (pubkey_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "No public key in packet"); + goto error; + } + + rc = ssh_dh_import_next_pubkey_blob(session, pubkey_blob); + SSH_STRING_FREE(pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Failed to import next public key"); + goto error; + } + + q_s_string = ssh_buffer_get_ssh_string(packet); + if (q_s_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No sntrup761x25519 Q_S in packet"); + goto error; + } + if (ssh_string_len(q_s_string) != (SNTRUP761_CIPHERTEXT_SIZE + CURVE25519_PUBKEY_SIZE)) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server sntrup761x25519 ciphertext+key: %d", + (int)ssh_string_len(q_s_string)); + SSH_STRING_FREE(q_s_string); + goto error; + } + memcpy(session->next_crypto->sntrup761_ciphertext, + ssh_string_data(q_s_string), + SNTRUP761_CIPHERTEXT_SIZE); + memcpy(session->next_crypto->curve25519_server_pubkey, + (char *)ssh_string_data(q_s_string) + SNTRUP761_CIPHERTEXT_SIZE, + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_s_string); + + signature = ssh_buffer_get_ssh_string(packet); + if (signature == NULL) { + ssh_set_error(session, SSH_FATAL, "No signature in packet"); + goto error; + } + session->next_crypto->dh_server_signature = signature; + signature = NULL; /* ownership changed */ + /* TODO: verify signature now instead of waiting for NEWKEYS */ + if (ssh_sntrup761x25519_build_k(session) < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* Send the MSG_NEWKEYS */ + if (ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS) < 0) { + goto error; + } + + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + + return SSH_PACKET_USED; + +error: + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#ifdef WITH_SERVER + +static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init); + +static ssh_packet_callback dh_server_callbacks[] = { + ssh_packet_server_sntrup761x25519_init, +}; + +static struct ssh_packet_callbacks_struct ssh_sntrup761x25519_server_callbacks = + { + .start = SSH2_MSG_KEX_ECDH_INIT, + .n_callbacks = 1, + .callbacks = dh_server_callbacks, + .user = NULL, +}; + +/** @internal + * @brief sets up the sntrup761x25519-sha512@openssh.com kex callbacks + */ +void ssh_server_sntrup761x25519_init(ssh_session session) +{ + /* register the packet callbacks */ + ssh_packet_set_callbacks(session, &ssh_sntrup761x25519_server_callbacks); +} + +/** @brief Parse a SSH_MSG_KEXDH_INIT packet (server) and send a + * SSH_MSG_KEXDH_REPLY + */ +static SSH_PACKET_CALLBACK(ssh_packet_server_sntrup761x25519_init) +{ + /* ECDH/SNTRUP761 keys */ + ssh_string q_c_string = NULL; + ssh_string q_s_string = NULL; + ssh_string server_pubkey_blob = NULL; + + /* SSH host keys (rsa, ed25519 and ecdsa) */ + ssh_key privkey = NULL; + enum ssh_digest_e digest = SSH_DIGEST_AUTO; + ssh_string sig_blob = NULL; + int rc; + (void)type; + (void)user; + + ssh_packet_remove_callbacks(session, &ssh_sntrup761x25519_server_callbacks); + + /* Extract the client pubkey from the init packet */ + q_c_string = ssh_buffer_get_ssh_string(packet); + if (q_c_string == NULL) { + ssh_set_error(session, SSH_FATAL, "No sntrup761x25519 Q_C in packet"); + goto error; + } + if (ssh_string_len(q_c_string) != (SNTRUP761_PUBLICKEY_SIZE + CURVE25519_PUBKEY_SIZE)) { + ssh_set_error(session, + SSH_FATAL, + "Incorrect size for server sntrup761x25519 public key: %zu", + ssh_string_len(q_c_string)); + goto error; + } + + memcpy(session->next_crypto->sntrup761_client_pubkey, + ssh_string_data(q_c_string), + SNTRUP761_PUBLICKEY_SIZE); + memcpy(session->next_crypto->curve25519_client_pubkey, + ((char *)ssh_string_data(q_c_string)) + SNTRUP761_PUBLICKEY_SIZE, + CURVE25519_PUBKEY_SIZE); + SSH_STRING_FREE(q_c_string); + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("client public key sntrup761", + session->next_crypto->sntrup761_client_pubkey, + SNTRUP761_PUBLICKEY_SIZE); + ssh_log_hexdump("client public key c25519", + session->next_crypto->curve25519_client_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + + /* Build server's key pair */ + rc = ssh_sntrup761x25519_init(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Failed to generate sntrup761 keys"); + goto error; + } + + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_KEX_ECDH_REPLY); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* build k and session_id */ + rc = ssh_sntrup761x25519_build_k(session); + if (rc < 0) { + ssh_set_error(session, SSH_FATAL, "Cannot build k number"); + goto error; + } + + /* privkey is not allocated */ + rc = ssh_get_key_params(session, &privkey, &digest); + if (rc == SSH_ERROR) { + goto error; + } + + rc = ssh_make_sessionid(session); + if (rc != SSH_OK) { + ssh_set_error(session, SSH_FATAL, "Could not create a session id"); + goto error; + } + + rc = ssh_dh_get_next_server_publickey_blob(session, &server_pubkey_blob); + if (rc != 0) { + ssh_set_error(session, SSH_FATAL, "Could not export server public key"); + goto error; + } + + /* add host's public key */ + rc = ssh_buffer_add_ssh_string(session->out_buffer, server_pubkey_blob); + SSH_STRING_FREE(server_pubkey_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + /* add ecdh public key */ + rc = ssh_buffer_add_u32(session->out_buffer, + ntohl(SNTRUP761_CIPHERTEXT_SIZE + + CURVE25519_PUBKEY_SIZE)); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_add_data(session->out_buffer, + session->next_crypto->sntrup761_ciphertext, + SNTRUP761_CIPHERTEXT_SIZE); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + + rc = ssh_buffer_add_data(session->out_buffer, + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("server public key c25519", + session->next_crypto->curve25519_server_pubkey, + CURVE25519_PUBKEY_SIZE); +#endif + + /* add signature blob */ + sig_blob = ssh_srv_pki_do_sign_sessionid(session, privkey, digest); + if (sig_blob == NULL) { + ssh_set_error(session, SSH_FATAL, "Could not sign the session id"); + goto error; + } + + rc = ssh_buffer_add_ssh_string(session->out_buffer, sig_blob); + SSH_STRING_FREE(sig_blob); + if (rc < 0) { + ssh_set_error_oom(session); + goto error; + } + +#ifdef DEBUG_CRYPTO + ssh_log_hexdump("ECDH_REPLY:", + ssh_buffer_get(session->out_buffer), + ssh_buffer_get_len(session->out_buffer)); +#endif + + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_KEX_ECDH_REPLY sent"); + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + return SSH_ERROR; + } + + /* Send the MSG_NEWKEYS */ + rc = ssh_buffer_add_u8(session->out_buffer, SSH2_MSG_NEWKEYS); + if (rc < 0) { + goto error; + } + + session->dh_handshake_state = DH_STATE_NEWKEYS_SENT; + rc = ssh_packet_send(session); + if (rc == SSH_ERROR) { + goto error; + } + SSH_LOG(SSH_LOG_DEBUG, "SSH_MSG_NEWKEYS sent"); + + return SSH_PACKET_USED; +error: + SSH_STRING_FREE(q_c_string); + SSH_STRING_FREE(q_s_string); + ssh_buffer_reinit(session->out_buffer); + session->session_state = SSH_SESSION_STATE_ERROR; + return SSH_PACKET_USED; +} + +#endif /* WITH_SERVER */ + +#endif /* HAVE_SNTRUP761 */ diff --git a/src/libs/libssh-0.12.2/src/socket.c b/src/libs/libssh-0.12.2/src/socket.c new file mode 100644 index 000000000000..bebd98c50190 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/socket.c @@ -0,0 +1,1764 @@ +/* + * socket.c - socket functions for the library + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#ifdef _WIN32 +#include +#include +#ifndef UNIX_PATH_MAX + /* Inlining the key portions of afunix.h in Windows 10 SDK; + * that header isn't available in the mingw environment. */ +#define UNIX_PATH_MAX 108 +struct sockaddr_un { + ADDRESS_FAMILY sun_family; + char sun_path[UNIX_PATH_MAX]; +}; +#endif +#else /* _WIN32 */ +#include +#include +#include +#include +#include +#include +#ifdef HAVE_PTHREAD +#include +#endif +#endif /* _WIN32 */ + +#include "libssh/priv.h" +#include "libssh/callbacks.h" +#include "libssh/socket.h" +#include "libssh/buffer.h" +#include "libssh/poll.h" +#include "libssh/session.h" + +/** + * @defgroup libssh_socket The SSH socket functions. + * @ingroup libssh + * + * Functions for handling sockets. + * + * @{ + */ + +enum ssh_socket_states_e { + SSH_SOCKET_NONE, + SSH_SOCKET_CONNECTING, + SSH_SOCKET_CONNECTED, + SSH_SOCKET_EOF, + SSH_SOCKET_ERROR, + SSH_SOCKET_CLOSED +}; + +struct ssh_socket_struct { + socket_t fd; + int fd_is_socket; + int last_errno; + int read_wontblock; /* reading now on socket will + not block */ + int write_wontblock; + int data_except; + enum ssh_socket_states_e state; + ssh_buffer out_buffer; + ssh_buffer in_buffer; + ssh_session session; + ssh_socket_callbacks callbacks; + ssh_poll_handle poll_handle; +#ifndef _WIN32 + pid_t proxy_pid; +#endif +}; + +#ifdef HAVE_PTHREAD +struct jump_thread_data_struct { + ssh_session session; + socket_t fd; + char *next_hostname; + uint16_t next_port; + struct ssh_jump_info_struct *next_jump; + struct ssh_jump_callbacks_struct *next_cb; +}; + +int proxy_disconnect = 0; +#endif /* HAVE_PTHREAD */ + +static int sockets_initialized = 0; + +static ssize_t ssh_socket_unbuffered_read(ssh_socket s, + void *buffer, + uint32_t len); +static ssize_t ssh_socket_unbuffered_write(ssh_socket s, + const void *buffer, + uint32_t len); + +/** + * @internal + * + * @brief Initialize socket support for libssh. + * + * Initializes the socket subsystem, calling WSAStartup() on Windows and + * ssh_poll_init() on all platforms. Can be called multiple times. + * + * @return 0 on success; -1 on Windows socket initialization failure. + */ +int ssh_socket_init(void) +{ + if (sockets_initialized == 0) { +#ifdef _WIN32 + struct WSAData wsaData; + + /* Initiates use of the Winsock DLL by a process. */ + if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0) { + return -1; + } +#endif + ssh_poll_init(); + + sockets_initialized = 1; + } + + return 0; +} + +/** + * @internal + * + * @brief Cleanup socket support for libssh. + * + * Cleans up the socket subsystem, calling ssh_poll_cleanup() on all platforms + * and WSACleanup() on Windows. Can be called multiple times. + */ +void ssh_socket_cleanup(void) +{ + if (sockets_initialized == 1) { + ssh_poll_cleanup(); +#ifdef _WIN32 + WSACleanup(); +#endif + sockets_initialized = 0; + } +} + +/** + * @internal + * + * @brief Allocate and initialize a new SSH socket structure. + * + * Creates a new ssh_socket structure associated with the given session, + * initializes input/output buffers and sets default socket state. + * + * @param[in] session The SSH session to associate with the socket. + * + * @return A new ssh_socket on success; NULL on memory allocation failure. + */ +ssh_socket ssh_socket_new(ssh_session session) +{ + ssh_socket s; + + s = calloc(1, sizeof(struct ssh_socket_struct)); + if (s == NULL) { + ssh_set_error_oom(session); + return NULL; + } + s->fd = SSH_INVALID_SOCKET; + s->last_errno = -1; + s->fd_is_socket = 1; + s->session = session; + s->in_buffer = ssh_buffer_new(); + if (s->in_buffer == NULL) { + ssh_set_error_oom(session); + SAFE_FREE(s); + return NULL; + } + s->out_buffer=ssh_buffer_new(); + if (s->out_buffer == NULL) { + ssh_set_error_oom(session); + SSH_BUFFER_FREE(s->in_buffer); + SAFE_FREE(s); + return NULL; + } + s->read_wontblock = 0; + s->write_wontblock = 0; + s->data_except = 0; + s->poll_handle = NULL; + s->state=SSH_SOCKET_NONE; + return s; +} + +/** + * @internal + * + * @brief Reset the state of a socket, so it looks brand new. + * + * Clears the file descriptor, reinitializes input/output buffers, frees + * the poll handle if present, and resets all socket state flags. + * + * @param[in] s The SSH socket to reset. + */ +void ssh_socket_reset(ssh_socket s) +{ + s->fd = SSH_INVALID_SOCKET; + s->last_errno = -1; + s->fd_is_socket = 1; + ssh_buffer_reinit(s->in_buffer); + ssh_buffer_reinit(s->out_buffer); + s->read_wontblock = 0; + s->write_wontblock = 0; + s->data_except = 0; + if (s->poll_handle != NULL) { + ssh_poll_free(s->poll_handle); + s->poll_handle = NULL; + } + s->state=SSH_SOCKET_NONE; +#ifndef _WIN32 + s->proxy_pid = 0; +#endif +} + +/** + * @internal + * @brief the socket callbacks, i.e. callbacks to be called + * upon a socket event. + * @param s socket to set callbacks on. + * @param callbacks a ssh_socket_callback object reference. + */ +void ssh_socket_set_callbacks(ssh_socket s, ssh_socket_callbacks callbacks) +{ + s->callbacks = callbacks; +} + +/** + * @internal + * + * @brief Mark an SSH socket as connected. + * + * Sets the socket state to connected and configures the poll handle + * to wait for `POLLIN` and `POLLOUT` events (needed for non-blocking connect). + * + * @param[in] s The SSH socket. + * @param[in] p The poll handle to configure, or NULL. + */ +void ssh_socket_set_connected(ssh_socket s, struct ssh_poll_handle_struct *p) +{ + s->state = SSH_SOCKET_CONNECTED; + /* `POLLOUT` is the event to wait for in a non-blocking connect */ + if (p != NULL) { + ssh_poll_set_events(p, POLLIN | POLLOUT); + } +} + +/** + * @internal + * + * @brief SSH poll callback. This callback will be used when an + * event caught on the socket. + * + * @param p Poll object this callback belongs to. + * @param fd The raw socket. + * @param revents The current poll events on the socket. + * @param v_s Userdata to be passed to the callback function, + * in this case the socket object. + * + * @return 0 on success, < 0 when the poll object has been removed + * from its poll context. + */ +int ssh_socket_pollcallback(struct ssh_poll_handle_struct *p, + socket_t fd, + int revents, + void *v_s) +{ + ssh_socket s = (ssh_socket)v_s; + void *buffer = NULL; + ssize_t nread = 0; + int rc; + int err = 0; + socklen_t errlen = sizeof(err); + + /* Do not do anything if this socket was already closed */ + if (!ssh_socket_is_open(s)) { + return -1; + } + SSH_LOG(SSH_LOG_TRACE, + "Poll callback on socket %d (%s%s%s), out buffer %" PRIu32, fd, + (revents & POLLIN) ? "POLLIN ":"", + (revents & POLLOUT) ? "POLLOUT ":"", + (revents & POLLERR) ? "POLLERR":"", + ssh_buffer_get_len(s->out_buffer)); + if ((revents & POLLERR) || (revents & POLLHUP)) { + /* Check if we are in a connecting state */ + if (s->state == SSH_SOCKET_CONNECTING) { + s->state = SSH_SOCKET_ERROR; + rc = getsockopt(fd, SOL_SOCKET, SO_ERROR, (char *)&err, &errlen); + if (rc < 0) { + err = errno; + } + ssh_socket_close(s); + /* Overwrite ssh_socket_close() error with the real socket error */ + s->last_errno = err; + errno = err; + + if (s->callbacks != NULL && s->callbacks->connected != NULL) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_ERROR, + err, + s->callbacks->userdata); + } + + return -1; + } + /* Then we are in a more standard kind of error */ + /* force a read to get an explanation */ + revents |= POLLIN; + } + if ((revents & POLLIN) && s->state == SSH_SOCKET_CONNECTED) { + s->read_wontblock = 1; + buffer = ssh_buffer_allocate(s->in_buffer, MAX_BUF_SIZE); + if (buffer) { + nread = ssh_socket_unbuffered_read(s, buffer, MAX_BUF_SIZE); + } + if (nread < 0) { + ssh_buffer_pass_bytes_end(s->in_buffer, MAX_BUF_SIZE); + if (p != NULL) { + ssh_poll_remove_events(p, POLLIN); + } + + if (s->callbacks != NULL && s->callbacks->exception != NULL) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno, + s->callbacks->userdata); + } + return -2; + } + + /* Rollback the unused space */ + ssh_buffer_pass_bytes_end(s->in_buffer, + (uint32_t)(MAX_BUF_SIZE - nread)); + + if (nread == 0) { + if (p != NULL) { + ssh_poll_remove_events(p, POLLIN); + } + if (s->callbacks != NULL && s->callbacks->exception != NULL) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_EOF, + 0, + s->callbacks->userdata); + } + return -2; + } + + if (s->session->socket_counter != NULL) { + s->session->socket_counter->in_bytes += nread; + } + + /* Call the callback */ + if (s->callbacks != NULL && s->callbacks->data != NULL) { + size_t processed; + do { + processed = s->callbacks->data(ssh_buffer_get(s->in_buffer), + ssh_buffer_get_len(s->in_buffer), + s->callbacks->userdata); + ssh_buffer_pass_bytes(s->in_buffer, (uint32_t)processed); + } while ((processed > 0) && (s->state == SSH_SOCKET_CONNECTED)); + + /* p may have been freed, so don't use it + * anymore in this function */ + p = NULL; + } + } +#ifdef _WIN32 + if (revents & POLLOUT || revents & POLLWRNORM) { +#else + if (revents & POLLOUT) { +#endif + uint32_t len; + + /* First, POLLOUT is a sign we may be connected */ + if (s->state == SSH_SOCKET_CONNECTING) { + SSH_LOG(SSH_LOG_PACKET, "Received POLLOUT in connecting state"); + ssh_socket_set_connected(s, p); + + rc = ssh_socket_set_blocking(ssh_socket_get_fd(s)); + if (rc < 0) { + return -1; + } + + if (s->callbacks != NULL && s->callbacks->connected != NULL) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, + 0, + s->callbacks->userdata); + } + + return 0; + } + + /* So, we can write data */ + s->write_wontblock = 1; + if (p != NULL) { + ssh_poll_remove_events(p, POLLOUT); + } + + /* If buffered data is pending, write it */ + len = ssh_buffer_get_len(s->out_buffer); + if (len > 0) { + ssh_socket_nonblocking_flush(s); + } else if (s->callbacks != NULL && s->callbacks->controlflow != NULL) { + /* Otherwise advertise the upper level that write can be done */ + SSH_LOG(SSH_LOG_TRACE, "sending control flow event"); + s->callbacks->controlflow(SSH_SOCKET_FLOW_WRITEWONTBLOCK, + s->callbacks->userdata); + } + /* TODO: Find a way to put back POLLOUT when buffering occurs */ + } + + /* Return -1 if the poll handler disappeared */ + if (s->poll_handle == NULL) { + return -1; + } + + return 0; +} + +/** @internal + * @brief returns the poll handle corresponding to the socket, + * creates it if it does not exist. + * @returns allocated and initialized ssh_poll_handle object + */ +ssh_poll_handle ssh_socket_get_poll_handle(ssh_socket s) +{ + if (s->poll_handle) { + return s->poll_handle; + } + s->poll_handle = ssh_poll_new(s->fd, 0, ssh_socket_pollcallback, s); + return s->poll_handle; +} + +/** + * @internal + * + * @brief Deletes a socket object. + * + * Closes the socket connection, frees input/output buffers and + * releases the socket structure memory. + * + * @param[in] s The SSH socket to free, or NULL. + */ +void ssh_socket_free(ssh_socket s) +{ + if (s == NULL) { + return; + } + ssh_socket_close(s); + SSH_BUFFER_FREE(s->in_buffer); + SSH_BUFFER_FREE(s->out_buffer); + SAFE_FREE(s); +} + +/** + * @internal + * + * @brief Connect an SSH socket to a Unix domain socket. + * + * Creates a Unix domain socket connection to the given @p path and associates + * it with the SSH socket. + * + * @param[in] s The SSH socket to connect. + * @param[in] path Path to the Unix domain socket. + * + * @return `SSH_OK` on success; `SSH_ERROR` on socket creation, connect, or fd + * setup failure. + */ +int ssh_socket_unix(ssh_socket s, const char *path) +{ + struct sockaddr_un sunaddr; + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socket_t fd; + sunaddr.sun_family = AF_UNIX; + snprintf(sunaddr.sun_path, sizeof(sunaddr.sun_path), "%s", path); + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == SSH_INVALID_SOCKET) { + ssh_set_error(s->session, SSH_FATAL, + "Error from socket(AF_UNIX, SOCK_STREAM, 0): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + +#ifndef _WIN32 + if (fcntl(fd, F_SETFD, 1) == -1) { + ssh_set_error(s->session, SSH_FATAL, + "Error from fcntl(fd, F_SETFD, 1): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); + return SSH_ERROR; + } +#endif + + if (connect(fd, (struct sockaddr *) &sunaddr, sizeof(sunaddr)) < 0) { + ssh_set_error(s->session, SSH_FATAL, "Error from connect(%s): %s", + path, + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + CLOSE_SOCKET(fd); + return SSH_ERROR; + } + return ssh_socket_set_fd(s, fd); +} + +/** + * @internal + * + * @brief Close an SSH socket. + * + * Closes the socket file descriptor if open, saves the last error code, + * frees the poll handle if unlocked, and marks the socket state as closed. + * On Unix, attempts to terminate and wait for any running proxy command + * process. + * + * @param[in] s The SSH socket to close. + */ +void ssh_socket_close(ssh_socket s) +{ + if (ssh_socket_is_open(s)) { +#ifdef _WIN32 + CLOSE_SOCKET(s->fd); + s->last_errno = WSAGetLastError(); +#else + CLOSE_SOCKET(s->fd); + s->last_errno = errno; +#endif + } + + if (s->poll_handle != NULL && !ssh_poll_is_locked(s->poll_handle)) { + ssh_poll_free(s->poll_handle); + s->poll_handle = NULL; + } + + s->state = SSH_SOCKET_CLOSED; + +#ifndef _WIN32 + /* If the proxy command still runs try to kill it */ + if (s->proxy_pid != 0) { + int status; + pid_t pid = s->proxy_pid; + + s->proxy_pid = 0; + kill(pid, SIGTERM); + while (waitpid(pid, &status, 0) == -1) { + if (errno != EINTR) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + SSH_LOG(SSH_LOG_TRACE, "waitpid failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return; + } + } + if (!WIFEXITED(status)) { + SSH_LOG(SSH_LOG_TRACE, "Proxy command exited abnormally"); + return; + } + SSH_LOG(SSH_LOG_TRACE, "Proxy command returned %d", WEXITSTATUS(status)); + } +#endif +} + +/** + * @internal + * @brief sets the file descriptor of the socket. + * @param[out] s ssh_socket to update + * @param[in] fd file descriptor to set + * @warning this function updates both the input and output + * file descriptors + */ +int ssh_socket_set_fd(ssh_socket s, socket_t fd) +{ + ssh_poll_handle h = NULL; + + s->fd = fd; + + if (s->poll_handle) { + ssh_poll_set_fd(s->poll_handle,fd); + } else { + s->state = SSH_SOCKET_CONNECTING; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return SSH_ERROR; + } + + /* POLLOUT is the event to wait for in a nonblocking connect */ + ssh_poll_set_events(h, POLLOUT); +#ifdef _WIN32 + ssh_poll_add_events(h, POLLWRNORM); +#endif + } + return SSH_OK; +} + +/** + * @internal + * + * @brief Returns the input file descriptor of a socket. + * + * @param[in] s The SSH socket. + * + * @return The socket file descriptor (socket_t). + */ +socket_t ssh_socket_get_fd(ssh_socket s) +{ + return s->fd; +} + +/** + * @internal + * + * @brief Check if an SSH socket is open. + * + * @param[in] s The SSH socket. + * + * @return Non-zero if socket is open, 0 if closed or invalid. + */ +int ssh_socket_is_open(ssh_socket s) +{ + return s->fd != SSH_INVALID_SOCKET; +} + +/** + * @internal + * + * @brief Perform an unbuffered read from an SSH socket. + * + * Reads @p len bytes from the socket file descriptor directly into @p buffer, + * using `recv()` if the descriptor is a socket, or `read()` otherwise. + * Updates internal error and state flags based on the result. + * + * @param[in] s The SSH socket. + * @param[out] buffer Buffer to read data into. + * @param[in] len Maximum number of bytes to read. + * + * @return Number of bytes read on success, or -1 on error. + */ +static ssize_t ssh_socket_unbuffered_read(ssh_socket s, + void *buffer, + uint32_t len) +{ + ssize_t rc = -1; + + if (s->data_except) { + return -1; + } + if (s->fd_is_socket) { + rc = recv(s->fd, buffer, len, 0); + } else { + rc = read(s->fd, buffer, len); + } +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->read_wontblock = 0; + + if (rc < 0) { + s->data_except = 1; + } else { + SSH_LOG(SSH_LOG_TRACE, "read %zd", rc); + } + + return rc; +} + +/** + * @internal + * + * @brief Perform an unbuffered write to an SSH socket. + * + * Writes @p len bytes from @p buffer to the socket file descriptor, + * using `send()` if the descriptor is a socket or `write()` otherwise. + * Updates internal error and state flags, and re-enables POLLOUT + * polling if a poll handle exists. + * + * @param[in] s The SSH socket. + * @param[in] buffer Buffer containing data to write. + * @param[in] len Number of bytes to write. + * + * @return Number of bytes written on success, or -1 on error. + */ +static ssize_t ssh_socket_unbuffered_write(ssh_socket s, + const void *buffer, + uint32_t len) +{ + ssize_t w = -1; + int flags = 0; + +#ifdef MSG_NOSIGNAL + flags |= MSG_NOSIGNAL; +#endif + + if (s->data_except) { + return -1; + } + + if (s->fd_is_socket) { + w = send(s->fd, buffer, len, flags); + } else { + w = write(s->fd, buffer, len); + } +#ifdef _WIN32 + s->last_errno = WSAGetLastError(); +#else + s->last_errno = errno; +#endif + s->write_wontblock = 0; + /* Reactive the POLLOUT detector in the poll multiplexer system */ + if (s->poll_handle) { + SSH_LOG(SSH_LOG_PACKET, "Enabling POLLOUT for socket"); + ssh_poll_add_events(s->poll_handle, POLLOUT); + } + if (w < 0) { + s->data_except = 1; + } + + SSH_LOG(SSH_LOG_TRACE, "wrote %zd", w); + return w; +} + +/** + * @internal + * + * @brief Check if SSH socket file descriptor is set in an fd_set. + * + * Tests if the socket's file descriptor is present in the + * given @p set (fd_set) . Returns 0 if the socket has no valid file descriptor. + * + * @param[in] s The SSH socket. + * @param[in] set The fd_set to test against. + * + * @return Non-zero if the socket fd is set in the fd_set, 0 otherwise. + */ +int ssh_socket_fd_isset(ssh_socket s, fd_set *set) +{ + if(s->fd == SSH_INVALID_SOCKET) { + return 0; + } + return FD_ISSET(s->fd,set); +} + +/** + * @internal + * + * @brief Add SSH socket file descriptor to an fd_set. + * + * Adds the socket's file descriptor to the given @p set (fd_set) + * and updates @p max_fd if this socket has the highest file descriptor number. + * @param[in] s The SSH socket. + * @param[in,out] set The fd_set to add the socket to. + * @param[in,out] max_fd the maximum fd value. + */ +void ssh_socket_fd_set(ssh_socket s, fd_set *set, socket_t *max_fd) +{ + if (s->fd == SSH_INVALID_SOCKET) { + return; + } + + FD_SET(s->fd,set); + + if (s->fd >= 0 && + s->fd >= *max_fd && + s->fd != SSH_INVALID_SOCKET) { + *max_fd = s->fd + 1; + } +} + +/** + * @internal + * + * @brief Write data to an SSH socket output buffer. + * + * Adds the data to the socket's output @p buffer and calls a nonblocking + * flush attempt to send buffered data. + * + * @param[in] s The SSH socket. + * @param[in] buffer Data to write. + * @param[in] len Number of bytes to write. + * + * @return `SSH_OK` on success; `SSH_ERROR` on buffer allocation failure. + * + * @warning It has no effect on socket before a flush. + */ +int ssh_socket_write(ssh_socket s, const void *buffer, uint32_t len) +{ + if (len > 0) { + if (ssh_buffer_add_data(s->out_buffer, buffer, len) < 0) { + ssh_set_error_oom(s->session); + return SSH_ERROR; + } + ssh_socket_nonblocking_flush(s); + } + + return SSH_OK; +} + +/** + * @internal + * + * @brief Starts a nonblocking flush of the output buffer. + * + * Sends all buffered data from the socket's output buffer. + * If the socket is not open, marks the session as dead and calls an + * exception callback or sets a fatal error. If the socket cannot currently + * accept data, polls for writable events and returns `SSH_AGAIN`. + * On write errors, closes the socket and signals the error. Updates + * byte counters on successful writes. + * + * @param[in] s The SSH socket. + * + * @return `SSH_OK` if all data was sent; `SSH_AGAIN` if the operation should + * be retried later; `SSH_ERROR` on fatal socket error. + */ +int ssh_socket_nonblocking_flush(ssh_socket s) +{ + ssh_session session = s->session; + uint32_t len; + + if (!ssh_socket_is_open(s)) { + session->alive = 0; + if (s->callbacks && s->callbacks->exception) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno, + s->callbacks->userdata); + } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_FATAL, + "Writing packet: error on socket (or connection " + "closed): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } + + return SSH_ERROR; + } + + len = ssh_buffer_get_len(s->out_buffer); + if (!s->write_wontblock && s->poll_handle && len > 0) { + /* force the poll system to catch pollout events */ + ssh_poll_add_events(s->poll_handle, POLLOUT); + + return SSH_AGAIN; + } + + if (s->write_wontblock && len > 0) { + ssize_t bwritten; + + bwritten = ssh_socket_unbuffered_write(s, + ssh_buffer_get(s->out_buffer), + len); + if (bwritten < 0) { + session->alive = 0; + ssh_socket_close(s); + + if (s->callbacks && s->callbacks->exception) { + s->callbacks->exception(SSH_SOCKET_EXCEPTION_ERROR, + s->last_errno, + s->callbacks->userdata); + } else { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_set_error(session, + SSH_FATAL, + "Writing packet: error on socket (or connection " + "closed): %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } + + return SSH_ERROR; + } + + ssh_buffer_pass_bytes(s->out_buffer, (uint32_t)bwritten); + if (s->session->socket_counter != NULL) { + s->session->socket_counter->out_bytes += bwritten; + } + } + + /* Is there some data pending? */ + len = ssh_buffer_get_len(s->out_buffer); + if (s->poll_handle && len > 0) { + SSH_LOG(SSH_LOG_TRACE, + "did not send all the data, queuing pollout event"); + /* force the poll system to catch pollout events */ + ssh_poll_add_events(s->poll_handle, POLLOUT); + + return SSH_AGAIN; + } + + /* all data written */ + return SSH_OK; +} + +/** + * @internal + * + * @brief Set the SSH socket write_wontblock flag. + * + * Marks the socket as ready for nonblocking writes (`write_wontblock = 1`). + * Used by the poll system when POLLOUT becomes available. + * + * @param[in] s The SSH socket. + */ +void ssh_socket_set_write_wontblock(ssh_socket s) +{ + s->write_wontblock = 1; +} + +/** + * @internal + * + * @brief Set the SSH socket read_wontblock flag. + * + * Marks the socket as ready for nonblocking reads (`read_wontblock = 1`). + * Used by the poll system when POLLIN becomes available. + * + * @param[in] s The SSH socket. + */ +void ssh_socket_set_read_wontblock(ssh_socket s) +{ + s->read_wontblock = 1; +} + +/** + * @internal + * + * @brief Set the SSH socket exception flag. + * + * Marks the socket as having an exception condition (`data_except = 1`). + * + * @param[in] s The SSH socket. + */ +void ssh_socket_set_except(ssh_socket s) +{ + s->data_except = 1; +} + +/** + * @internal + * + * @brief Check if SSH socket data is available for reading. + * + * Returns true if the socket is ready for nonblocking reads + * (`read_wontblock` flag is set). + * + * @param[in] s The SSH socket. + * + * @return 1 if data is available, 0 otherwise. + */ +int ssh_socket_data_available(ssh_socket s) +{ + return s->read_wontblock; +} + +/** + * @internal + * + * @brief Check if SSH socket is writable. + * + * Returns true if the socket is ready for nonblocking writes + * (`write_wontblock` flag is set). + * + * @param[in] s The SSH socket. + * + * @return 1 if socket is writable, 0 otherwise. + */ +int ssh_socket_data_writable(ssh_socket s) +{ + return s->write_wontblock; +} + +/** @internal + * @brief returns the number of outgoing bytes currently buffered + * @param s the socket + * @returns numbers of bytes buffered, or 0 if the socket isn't connected + */ +int ssh_socket_buffered_write_bytes(ssh_socket s) +{ + if (s==NULL || s->out_buffer == NULL) { + return 0; + } + + return ssh_buffer_get_len(s->out_buffer); +} + +/** + * @internal + * + * @brief Get the current status of an SSH socket. + * + * Checks the input/output buffers and exception flag to determine socket + * status: `SSH_READ_PENDING` if input data available, `SSH_WRITE_PENDING` + * if output data pending, `SSH_CLOSED_ERROR` if exception occurred. + * + * @param[in] s The SSH socket. + * + * @return Socket status flags. + */ +int ssh_socket_get_status(ssh_socket s) +{ + int r = 0; + + if (ssh_buffer_get_len(s->in_buffer) > 0) { + r |= SSH_READ_PENDING; + } + + if (ssh_buffer_get_len(s->out_buffer) > 0) { + r |= SSH_WRITE_PENDING; + } + + if (s->data_except) { + r |= SSH_CLOSED_ERROR; + } + + return r; +} + +/** + * @internal + * + * @brief Get SSH socket poll flags from the poll handle. + * + * Checks the poll handle events and returns `SSH_READ_PENDING` if POLLIN + * is set, `SSH_WRITE_PENDING` if POLLOUT is set. + * + * @param[in] s The SSH socket. + * + * @return Socket status flags based on poll events. + */ +int ssh_socket_get_poll_flags(ssh_socket s) +{ + int r = 0; + if (s->poll_handle != NULL && (ssh_poll_get_events (s->poll_handle) & POLLIN) > 0) { + r |= SSH_READ_PENDING; + } + if (s->poll_handle != NULL && (ssh_poll_get_events (s->poll_handle) & POLLOUT) > 0) { + r |= SSH_WRITE_PENDING; + } + return r; +} + +#ifdef _WIN32 +int ssh_socket_set_nonblocking(socket_t fd) +{ + u_long nonblocking = 1; + return ioctlsocket(fd, FIONBIO, &nonblocking); +} + +int ssh_socket_set_blocking(socket_t fd) +{ + u_long nonblocking = 0; + return ioctlsocket(fd, FIONBIO, &nonblocking); +} + +#else /* _WIN32 */ +int ssh_socket_set_nonblocking(socket_t fd) +{ + return fcntl(fd, F_SETFL, O_NONBLOCK); +} + +int ssh_socket_set_blocking(socket_t fd) +{ + return fcntl(fd, F_SETFL, 0); +} +#endif /* _WIN32 */ + +/** + * @internal + * @brief Launches a socket connection + * If the socket connected callback has been defined and + * a poll object exists, this call will be non blocking. + * @param s socket to connect. + * @param host hostname or ip address to connect to. + * @param port port number to connect to. + * @param bind_addr address to bind to, or NULL for default. + * @returns `SSH_OK` socket is being connected. + * @returns `SSH_ERROR` error while connecting to remote host. + */ +int ssh_socket_connect(ssh_socket s, + const char *host, + uint16_t port, + const char *bind_addr) +{ + socket_t fd; + + if (s->state != SSH_SOCKET_NONE) { + ssh_set_error(s->session, SSH_FATAL, + "ssh_socket_connect called on socket not unconnected"); + return SSH_ERROR; + } + fd = ssh_connect_host_nonblocking(s->session, host, bind_addr, port); + SSH_LOG(SSH_LOG_DEBUG, "Nonblocking connection socket: %d", fd); + if (fd == SSH_INVALID_SOCKET) { + return SSH_ERROR; + } + return ssh_socket_set_fd(s, fd); +} + +#ifdef WITH_EXEC +/** + * @internal + * @brief executes a command and redirect input and outputs + * @param command command to execute + * @param in input file descriptor + * @param out output file descriptor + */ +void +ssh_execute_command(const char *command, socket_t in, socket_t out) +{ + const char *shell = NULL; + const char *args[] = {NULL/*shell*/, "-c", command, NULL}; + int devnull; + int rc; + + /* Prepare /dev/null socket for the stderr redirection */ + devnull = open("/dev/null", O_WRONLY); + if (devnull == -1) { + SSH_LOG(SSH_LOG_TRACE, "Failed to open /dev/null"); + exit(1); + } + + /* + * By default, use the current users shell. This could fail with some + * shells like zsh or dash ... + */ + shell = getenv("SHELL"); + if (shell == NULL || shell[0] == '\0') { + /* Fall back to the /bin/sh only if the bash is not available. But there are + * issues with dash or whatever people tend to link to /bin/sh */ + rc = access("/bin/bash", 0); + if (rc != 0) { + shell = "/bin/sh"; + } else { + shell = "/bin/bash"; + } + } + args[0] = shell; + + /* redirect in and out to stdin, stdout */ + dup2(in, 0); + dup2(out, 1); + /* Ignore anything on the stderr */ + dup2(devnull, STDERR_FILENO); + close(in); + close(out); + rc = execv(args[0], (char * const *)args); + if (rc < 0) { + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + + SSH_LOG(SSH_LOG_WARN, "Failed to execute command %s: %s", + command, ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + } + exit(1); +} + +/** + * @internal + * @brief Open a socket on a ProxyCommand + * This call will always be nonblocking. + * @param s socket to connect. + * @param command Command to execute. + * @returns `SSH_OK` socket is being connected. + * @returns `SSH_ERROR` error while executing the command. + */ +int +ssh_socket_connect_proxycommand(ssh_socket s, const char *command) +{ + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + socket_t pair[2]; + ssh_poll_handle h = NULL; + int pid; + int rc; + + if (s->state != SSH_SOCKET_NONE) { + return SSH_ERROR; + } + + rc = socketpair(PF_UNIX, SOCK_STREAM, 0, pair); + if (rc < 0) { + return SSH_ERROR; + } + + SSH_LOG(SSH_LOG_DEBUG, "Executing proxycommand '%s'", command); + pid = fork(); + if (pid == 0) { + ssh_execute_command(command, pair[0], pair[0]); + /* child: Does not return */ + } + /* parent */ + if (pid == -1) { + close(pair[0]); + close(pair[1]); + ssh_set_error(s->session, + SSH_FATAL, + "fork failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + return SSH_ERROR; + } + s->proxy_pid = pid; + close(pair[0]); + SSH_LOG(SSH_LOG_DEBUG, + "ProxyCommand connection pipe: [%d,%d]", + pair[0], + pair[1]); + + rc = ssh_socket_set_fd(s, pair[1]); + if (rc != SSH_OK) { + return rc; + } + + s->fd_is_socket = 0; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return SSH_ERROR; + } + ssh_socket_set_connected(s, h); + if (s->callbacks && s->callbacks->connected) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, 0, s->callbacks->userdata); + } + + return SSH_OK; +} +#endif /* WITH_EXEC */ + +#ifndef _WIN32 +#ifdef HAVE_PTHREAD +static int +verify_knownhost(ssh_session session) +{ + enum ssh_known_hosts_e state; + + state = ssh_session_is_known_server(session); + + switch (state) { + case SSH_KNOWN_HOSTS_OK: + break; /* ok */ + default: + SSH_LOG(SSH_LOG_WARN, "Couldn't verify knownhost during proxyjump."); + return SSH_ERROR; + } + + return SSH_OK; +} + +static void free_jump_thread_data(struct jump_thread_data_struct *data) +{ + if (data == NULL) { + return; + } + + ssh_free(data->session); + SAFE_FREE(data->next_hostname); + if (data->next_jump != NULL) { + SAFE_FREE(data->next_jump->hostname); + SAFE_FREE(data->next_jump->username); + } + SAFE_FREE(data->next_jump); + SAFE_FREE(data); +} + +static void * +jump_thread_func(void *arg) +{ + struct jump_thread_data_struct *jump_thread_data = NULL; + struct ssh_jump_info_struct *jis = NULL; + struct ssh_jump_callbacks_struct *cb = NULL; + ssh_session jump_session = NULL; + ssh_channel caa = NULL; + int rc; + ssh_event event = NULL; + ssh_connector connector_in = NULL, connector_out = NULL; + uint16_t next_port; + char *next_hostname = NULL; + + jump_thread_data = (struct jump_thread_data_struct *)arg; + jump_session = jump_thread_data->session; + + /* First thing we need to do is to set the right level as its kept in + * thread local variable, therefore reset to 0 after spawning new thread. + */ + ssh_set_log_level(jump_session->common.log_verbosity); + + cb = jump_thread_data->next_cb; + jis = jump_thread_data->next_jump; + + /* This is the calling thread target where we will eventually initialize + * forwarding */ + next_port = jump_thread_data->next_port; + next_hostname = jump_thread_data->next_hostname; + + ssh_options_set(jump_session, SSH_OPTIONS_HOST, jis->hostname); + + /* + * Only propagate the username and port that the ProxyJump specification + * actually provided. Setting them unconditionally would inject internal + * defaults (the local username and port 22) as if the application had + * chosen them, which then prevents the jump host's own configuration from + * supplying these values (issue #365). When they are omitted here, the jump + * host config and the connection internals fill them in. + */ + if (jis->username != NULL) { + ssh_options_set(jump_session, SSH_OPTIONS_USER, jis->username); + } + if (jis->port > 0) { + ssh_options_set(jump_session, SSH_OPTIONS_PORT, &jis->port); + } + + if (cb != NULL && cb->before_connection != NULL) { + rc = cb->before_connection(jump_session, cb->userdata); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); + goto exit; + } + } + + SSH_LOG(SSH_LOG_PACKET, + "Proxy connecting to host %s port %d user %s, callbacks=%p", + jis->hostname, + jis->port, + jis->username, + (void *)cb); + + /* If there are more jumps then this will make a new thread and call the + * current function again, until there are no jumps. When there are no jumps + * it connects normally. */ + rc = ssh_connect(jump_session); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); + goto exit; + } + + /* Use the callback or default implementation for verifying knownhost */ + if (cb != NULL && cb->verify_knownhost != NULL) { + rc = cb->verify_knownhost(jump_session, cb->userdata); + } else { + rc = verify_knownhost(jump_session); + } + if (rc != SSH_OK) { + goto exit; + } + + /* Use the callback or publickey method to authenticate */ + if (cb != NULL && cb->authenticate != NULL) { + rc = cb->authenticate(jump_session, cb->userdata); + } else { + rc = ssh_userauth_publickey_auto(jump_session, NULL, NULL); + } + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, "%s", ssh_get_error(jump_session)); + goto exit; + } + + caa = ssh_channel_new(jump_session); + if (caa == NULL) { + goto exit; + } + /* The origin hostname and port are set to match OpenSSH implementation + * they are only used for logging on the server */ + rc = ssh_channel_open_forward(caa, + next_hostname, + next_port, + "127.0.0.1", + 65535); + if (rc != SSH_OK) { + SSH_LOG(SSH_LOG_WARN, + "Error opening port forwarding channel: %s", + ssh_get_error(jump_session)); + goto exit; + } + + event = ssh_event_new(); + if (event == NULL) { + goto exit; + } + + connector_in = ssh_connector_new(jump_session); + if (connector_in == NULL) { + goto exit; + } + ssh_connector_set_out_channel(connector_in, caa, SSH_CONNECTOR_STDINOUT); + ssh_connector_set_in_fd(connector_in, jump_thread_data->fd); + ssh_event_add_connector(event, connector_in); + + connector_out = ssh_connector_new(jump_session); + if (connector_out == NULL) { + goto exit; + } + ssh_connector_set_out_fd(connector_out, jump_thread_data->fd); + ssh_connector_set_in_channel(connector_out, caa, SSH_CONNECTOR_STDINOUT); + ssh_event_add_connector(event, connector_out); + + while (ssh_channel_is_open(caa)) { + if (proxy_disconnect == 1) { + break; + } + rc = ssh_event_dopoll(event, 60000); + if (rc == SSH_ERROR) { + SSH_LOG(SSH_LOG_WARN, + "Error in ssh_event_dopoll() during proxy jump"); + break; + } + } + +exit: + if (connector_in != NULL) { + ssh_event_remove_connector(event, connector_in); + ssh_connector_free(connector_in); + } + if (connector_out != NULL) { + ssh_event_remove_connector(event, connector_out); + ssh_connector_free(connector_out); + } + + ssh_disconnect(jump_session); + ssh_event_free(event); + + shutdown(jump_thread_data->fd, SHUT_RDWR); + close(jump_thread_data->fd); + + free_jump_thread_data(jump_thread_data); + pthread_exit(NULL); +} + +int +ssh_socket_connect_proxyjump(ssh_socket s) +{ + char err_msg[SSH_ERRNO_MSG_MAX] = {0}; + ssh_poll_handle h = NULL; + int rc; + pthread_t jump_thread; + struct ssh_jump_info_struct *jis = NULL; + struct ssh_jump_callbacks_struct *cb = NULL; + struct jump_thread_data_struct *jump_thread_data = NULL; + ssh_session jump_session = NULL, session = NULL; + struct ssh_list *empty_list = NULL; + socket_t pair[2] = {SSH_INVALID_SOCKET, SSH_INVALID_SOCKET}; + + session = s->session; + + SSH_LOG(SSH_LOG_INFO, + "Connecting to host %s port %d user %s through ProxyJump", + session->opts.host, + session->opts.port, + session->opts.username); + + if (s->state != SSH_SOCKET_NONE) { + ssh_set_error( + session, + SSH_FATAL, + "ssh_socket_connect_proxyjump called on socket not unconnected"); + return SSH_ERROR; + } + + jump_thread_data = calloc(1, sizeof(struct jump_thread_data_struct)); + if (jump_thread_data == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + rc = socketpair(PF_UNIX, SOCK_STREAM, 0, pair); + if (rc == -1) { + ssh_set_error(session, + SSH_FATAL, + "Creating socket pair failed: %s", + ssh_strerror(errno, err_msg, SSH_ERRNO_MSG_MAX)); + goto fail; + } + + jump_session = ssh_new(); + if (jump_session == NULL) { + ssh_set_error_oom(session); + goto fail; + } + + jump_session->proxy_root = false; + /* Reset the global variable if it was previously 1 */ + if (session->proxy_root) { + proxy_disconnect = 0; + } + + /* Pop first jump that will be used by the following thread */ + jis = ssh_list_pop_head(struct ssh_jump_info_struct *, + session->opts.proxy_jumps); + if (jis == NULL) { + SSH_LOG(SSH_LOG_WARN, "Inconsistent list of proxy jumps received"); + ssh_free(jump_session); + goto fail; + } + jump_thread_data->next_jump = jis; + /* Move remaining to the jump session without reallocation. + * The list in the new jump_session is just allocated so empty */ + empty_list = jump_session->opts.proxy_jumps; + jump_session->opts.proxy_jumps = session->opts.proxy_jumps; + session->opts.proxy_jumps = empty_list; + + /* Pop the callbacks for the first jump */ + cb = ssh_list_pop_head(struct ssh_jump_callbacks_struct *, + session->opts.proxy_jumps_user_cb); + /* empty is ok */ + jump_thread_data->next_cb = cb; + /* Move remaining to the jump session without reallocation. + * The list in the new jump_session is just allocated so empty */ + empty_list = jump_session->opts.proxy_jumps_user_cb; + jump_session->opts.proxy_jumps_user_cb = session->opts.proxy_jumps_user_cb; + session->opts.proxy_jumps_user_cb = empty_list; + + ssh_options_set(jump_session, + SSH_OPTIONS_LOG_VERBOSITY, + &session->common.log_verbosity); + + jump_thread_data->next_port = session->opts.port; + jump_thread_data->next_hostname = strdup(session->opts.host); + + jump_thread_data->fd = pair[0]; + pair[0] = SSH_INVALID_SOCKET; + jump_thread_data->session = jump_session; + /* transferred to the jump_thread_data */ + jump_session = NULL; + + SSH_LOG(SSH_LOG_INFO, + "Starting proxy thread to host %s port %d user %s, callbacks=%p", + jump_thread_data->next_jump->hostname, + jump_thread_data->next_jump->port, + jump_thread_data->next_jump->username, + (void *)jump_thread_data->next_cb); + + rc = pthread_create(&jump_thread, NULL, jump_thread_func, jump_thread_data); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Creating new thread failed: %s", + ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX)); + goto fail; + } + /* ownership passed to the thread */ + jump_thread_data = NULL; + + rc = pthread_detach(jump_thread); + if (rc != 0) { + ssh_set_error(session, + SSH_FATAL, + "Failed to detach thread: %s", + ssh_strerror(rc, err_msg, SSH_ERRNO_MSG_MAX)); + goto fail; + } + + SSH_LOG(SSH_LOG_DEBUG, + "ProxyJump connection thread %lu started pipe: [%d,%d]", + (unsigned long)jump_thread, + pair[0], + pair[1]); + + rc = ssh_socket_set_fd(s, pair[1]); + if (rc != SSH_OK) { + return rc; + } + pair[1] = SSH_INVALID_SOCKET; + + s->fd_is_socket = 1; + h = ssh_socket_get_poll_handle(s); + if (h == NULL) { + return SSH_ERROR; + } + ssh_socket_set_connected(s, h); + if (s->callbacks && s->callbacks->connected) { + s->callbacks->connected(SSH_SOCKET_CONNECTED_OK, + 0, + s->callbacks->userdata); + } + + return SSH_OK; + +fail: + if (pair[0] != SSH_INVALID_SOCKET) { + close(pair[0]); + } + if (pair[1] != SSH_INVALID_SOCKET) { + close(pair[1]); + } + free_jump_thread_data(jump_thread_data); + return SSH_ERROR; +} + +#endif /* HAVE_PTHREAD */ + +#endif /* _WIN32 */ + +#ifndef _WIN32 +#define SOCKADDR struct sockaddr +#define SOCKADDR_IN struct sockaddr_in +#define closesocket close +#endif +/** + * @internal + * @brief Open a socket with VirtualBox-specific ProxyCommand. + * This call will always be nonblocking. + * @param s socket to connect. + * @param command Command to execute. + * @returns SSH_OK socket is being connected. + * @returns SSH_ERROR error while executing the command. + */ + +int +ssh_socket_connect_proxycommand_vbox(ssh_socket s, const char *host, + uint16_t port, const char *params) +{ + socket_t fd; + SOCKADDR_IN sa_proxy; + int rc; + char buffer[2048]; + + if (s->state != SSH_SOCKET_NONE) { + return SSH_ERROR; + } + + /* Use buffer to convert parameters */ + strncpy(buffer, params, sizeof(buffer)); + char *proxy_addr = strchr(buffer, ' '); + if (!proxy_addr) + { + SSH_LOG(SSH_LOG_WARNING, "Invalid proxy parameter string '%s'", params); + return SSH_ERROR; + } + else + *proxy_addr++ = 0; /* Terminate proxy type */ + + if (strcmp(buffer, "HTTP")) + { + SSH_LOG(SSH_LOG_WARNING, "Unsupported proxy type '%s'", buffer); + return SSH_ERROR; + } + + /* Continue to parse parameters */ + char *proxy_auth = NULL; + char *proxy_user = NULL; + char *proxy_password = NULL; + char *pszPort = strchr(proxy_addr, ' '); + if (pszPort) + { + *pszPort++ = 0; /* Terminate IP address */ + + proxy_user = strchr(pszPort, ' '); + if (proxy_user) + { + *proxy_user++ = 0; /* Terminate port */ + proxy_password = strchr(proxy_user, ' '); + if (proxy_password) + { + *proxy_password++ = ':'; /* Create user:password string */ + proxy_auth = bin_to_base64(proxy_user, strlen(proxy_user)); + } + } + + sa_proxy.sin_port = htons(atoi(pszPort)); + } + else + sa_proxy.sin_port = htons(80); /* Is this a reasonable default? */ + sa_proxy.sin_family = AF_INET; + if (!inet_pton(sa_proxy.sin_family, proxy_addr, &sa_proxy.sin_addr)) + { + SSH_LOG(SSH_LOG_WARNING, "Failed to convert '%s' to IPv4 address", proxy_addr); + if (proxy_auth) + free(proxy_auth); + return SSH_ERROR; + } + + /* Invalidate temporary pointers */ + proxy_addr = pszPort = proxy_user = proxy_password = NULL; + /* Use buffer to compose HTTP CONNECT request */ + if (proxy_auth) + { + sprintf(buffer, + "CONNECT %s:%u HTTP/1.1\r\n" + "Host: %s:%u\r\n" + "Proxy-Authorization: basic %s\r\n" + "\r\n", + host, port, + host, port, + proxy_auth); + free(proxy_auth); + } + else + { + sprintf(buffer, + "CONNECT %s:%u HTTP/1.1\r\n" + "Host: %s:%u\r\n" + "\r\n", + host, port, + host, port); + } + + SSH_LOG(SSH_LOG_PROTOCOL, "Connecting to '%s:%u' via proxy at '%s'", host, port, params); + fd = socket(sa_proxy.sin_family, SOCK_STREAM, IPPROTO_TCP); + if (fd < 0) { + return SSH_ERROR; + } + + rc = connect(fd, (SOCKADDR*)&sa_proxy, sizeof(sa_proxy)); + if (rc != 0) + { + SSH_LOG(SSH_LOG_WARNING, "Failed to connect to '%s:%u' via proxy at '%s'", host, port, params); + closesocket(fd); + return SSH_ERROR; + } + + /* Send HTTP CONNECT */ + ssize_t cb = send(fd, buffer, strlen(buffer), 0); + if (cb < 0) + { + SSH_LOG(SSH_LOG_WARNING, "Failed to send connect request to proxy at '%s'", params); + closesocket(fd); + return SSH_ERROR; + } + + /* Recieve reply */ + cb = recv(fd, buffer, sizeof(buffer) - 1, 0); /* Limit to have enough room for ensuring zero termination of the response. */ + if (cb <= 0) + { + SSH_LOG(SSH_LOG_WARNING, "Failed to receive connect response from proxy at '%s'", params); + closesocket(fd); + return SSH_ERROR; + } + + buffer[cb] = 0; /* Make sure it is zero-terminated */ + SSH_LOG(SSH_LOG_PACKET, "Received response from proxy:\n%s", buffer); + + /* Is this really necessary? */ + rc = ssh_socket_set_nonblocking(fd); + if (rc < 0) { + ssh_set_error(s->session, SSH_FATAL, + "Failed to set socket non-blocking for %s:%d", + host, port); + closesocket(fd); + return SSH_ERROR; + } + + ssh_socket_set_fd(s, fd); + + return SSH_OK; +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/string.c b/src/libs/libssh-0.12.2/src/string.c new file mode 100644 index 000000000000..ba9283b27d9e --- /dev/null +++ b/src/libs/libssh-0.12.2/src/string.c @@ -0,0 +1,375 @@ +/* + * string.c - ssh string functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#ifndef _WIN32 +#include +#include +#endif + +#include "libssh/priv.h" +#include "libssh/string.h" + +/* String maximum size is 256M */ +#define STRING_SIZE_MAX 0x10000000 + +/** + * @defgroup libssh_string The SSH string functions + * @ingroup libssh + * + * @brief String manipulations used in libssh. + * + * @{ + */ + +/** + * @brief Create a new SSH String object. + * + * @param[in] size The size of the string. + * + * @return The newly allocated string, NULL on error. + */ +struct ssh_string_struct *ssh_string_new(size_t size) +{ + struct ssh_string_struct *str = NULL; + + if (size > STRING_SIZE_MAX) { + errno = EINVAL; + return NULL; + } + + str = calloc(1, sizeof(struct ssh_string_struct) + size); + if (str == NULL) { + return NULL; + } + + str->size = htonl((uint32_t)size); + + return str; +} + +/** + * @brief Fill a string with given data. The string should be big enough. + * + * @param s An allocated string to fill with data. + * + * @param data The data to fill the string with. + * + * @param len Size of data. + * + * @return 0 on success, < 0 on error. + */ +int ssh_string_fill(struct ssh_string_struct *s, const void *data, size_t len) +{ + if ((s == NULL) || (data == NULL) || (len == 0) || + (len > ssh_string_len(s))) { + return -1; + } + + memcpy(s->data, data, len); + + return 0; +} + +/** + * @brief Create a ssh string using a C string + * + * @param[in] what The source 0-terminated C string. + * + * @return The newly allocated string, NULL on error with errno + * set. + * + * @note The null byte is not copied nor counted in the output string. + */ +struct ssh_string_struct *ssh_string_from_char(const char *what) +{ + struct ssh_string_struct *ptr = NULL; + size_t len; + + if (what == NULL) { + errno = EINVAL; + return NULL; + } + + len = strlen(what); + + ptr = ssh_string_new(len); + if (ptr == NULL) { + return NULL; + } + + memcpy(ptr->data, what, len); + + return ptr; +} + +/** + * @brief Create a ssh string from an arbitrary data buffer. + * + * Allocates a new SSH string of length `len` and copies the provided data + * into it. If len is 0, returns an empty SSH string. When len > 0, data + * must not be NULL. + * + * @param[in] data Pointer to the data buffer to copy from. May be NULL + * only when len == 0. + * @param[in] len Length of the data buffer to copy. + * + * @return The newly allocated string, NULL on error. + */ +struct ssh_string_struct *ssh_string_from_data(const void *data, size_t len) +{ + struct ssh_string_struct *s = NULL; + int rc; + + if (len > 0 && data == NULL) { + errno = EINVAL; + return NULL; + } + + s = ssh_string_new(len); + if (s == NULL) { + return NULL; + } + + if (len > 0) { + rc = ssh_string_fill(s, data, len); + if (rc != 0) { + ssh_string_free(s); + return NULL; + } + } + + return s; +} + +/** + * @brief Return the size of a SSH string. + * + * @param[in] s The input SSH string. + * + * @return The size of the content of the string, 0 on error. + */ +size_t ssh_string_len(struct ssh_string_struct *s) +{ + size_t size; + + if (s == NULL) { + return 0; + } + + size = ntohl(s->size); + if (size > 0 && size <= STRING_SIZE_MAX) { + return size; + } + + return 0; +} + +/** + * @brief Get the string as a C null-terminated string. + * + * This is only available as long as the SSH string exists. + * + * @param[in] s The SSH string to get the C string from. + * + * @return The char pointer, NULL on error. + */ +const char *ssh_string_get_char(struct ssh_string_struct *s) +{ + if (s == NULL) { + return NULL; + } + s->data[ssh_string_len(s)] = '\0'; + + return (const char *)s->data; +} + +/** + * @brief Convert a SSH string to a C null-terminated string. + * + * @param[in] s The SSH input string. + * + * @return An allocated string pointer, NULL on error with errno + * set. + * + * @note If the input SSH string contains zeroes, some parts of the output + * string may not be readable with regular libc functions. + */ +char *ssh_string_to_char(struct ssh_string_struct *s) +{ + size_t len; + char *new = NULL; + + if (s == NULL) { + return NULL; + } + + len = ssh_string_len(s); + if (len + 1 < len) { + return NULL; + } + + new = malloc(len + 1); + if (new == NULL) { + return NULL; + } + memcpy(new, s->data, len); + new[len] = '\0'; + + return new; +} + +/** + * @brief Deallocate a char string object. + * + * @param[in] s The string to delete. + */ +void ssh_string_free_char(char *s) +{ + SAFE_FREE(s); +} + +/** + * @brief Copy a string, return a newly allocated string. The caller has to + * free the string. + * + * @param[in] s String to copy. + * + * @return Newly allocated copy of the string, NULL on error. + */ +struct ssh_string_struct *ssh_string_copy(struct ssh_string_struct *s) +{ + struct ssh_string_struct *new = NULL; + size_t len; + + if (s == NULL) { + return NULL; + } + + len = ssh_string_len(s); + + new = ssh_string_new(len); + if (new == NULL) { + return NULL; + } + + memcpy(new->data, s->data, len); + + return new; +} + +/** + * @brief Compare two SSH strings. + * + * @param[in] s1 The first SSH string to compare. + * @param[in] s2 The second SSH string to compare. + * + * @return 0 if the strings are equal, + * < 0 if s1 is less than s2, + * > 0 if s1 is greater than s2. + */ +int ssh_string_cmp(struct ssh_string_struct *s1, struct ssh_string_struct *s2) +{ + size_t len1, len2, min_len; + int cmp; + + /* Both are NULL */ + if (s1 == NULL && s2 == NULL) { + return 0; + } + + /* Only one is NULL - NULL is considered "less than" non-NULL */ + if (s1 == NULL) { + return -1; + } else if (s2 == NULL) { + return 1; + } + + /* Get lengths */ + len1 = ssh_string_len(s1); + len2 = ssh_string_len(s2); + min_len = MIN(len1, len2); + + /* Compare data up to the shorter length */ + if (min_len > 0) { + cmp = memcmp(s1->data, s2->data, min_len); + if (cmp != 0) { + return cmp; + } + } + + /* If common prefix is equal, compare lengths */ + if (len1 < len2) { + return -1; + } else if (len1 > len2) { + return 1; + } + + return 0; +} + +/** + * @brief Destroy the data in a string so it couldn't appear in a core dump. + * + * @param[in] s The string to burn. + */ +void ssh_string_burn(struct ssh_string_struct *s) +{ + if (s == NULL || s->size == 0) { + return; + } + + ssh_burn(s->data, ssh_string_len(s)); +} + +/** + * @brief Get the payload of the string. + * + * @param s The string to get the data from. + * + * @return Return the data of the string or NULL on error. + */ +void *ssh_string_data(struct ssh_string_struct *s) +{ + if (s == NULL) { + return NULL; + } + + return s->data; +} + +/** + * @brief Deallocate a SSH string object. + * + * \param[in] s The SSH string to delete. + */ +void ssh_string_free(struct ssh_string_struct *s) +{ + SAFE_FREE(s); +} + +/** @} */ diff --git a/src/libs/libssh-0.12.2/src/threads.c b/src/libs/libssh-0.12.2/src/threads.c new file mode 100644 index 000000000000..7660b972d96f --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads.c @@ -0,0 +1,97 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/** + * @defgroup libssh_threads The SSH threading functions + * @ingroup libssh + * + * Threading with libssh + * @{ + */ + +#include "config.h" + +#include "libssh/priv.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" + +static struct ssh_threads_callbacks_struct *user_callbacks = NULL; + +/** @internal + * @brief inits the threading with the backend cryptographic libraries + */ + +int ssh_threads_init(void) +{ + static int threads_initialized = 0; + int rc; + + if (threads_initialized) { + return SSH_OK; + } + + /* first initialize the user_callbacks with our default handlers if not + * already the case + */ + if (user_callbacks == NULL){ + user_callbacks = ssh_threads_get_default(); + } + + /* Then initialize the crypto libraries threading callbacks */ + rc = crypto_thread_init(user_callbacks); + if (rc == SSH_OK) { + threads_initialized = 1; + } + return rc; +} + +void ssh_threads_finalize(void) +{ + crypto_thread_finalize(); +} + +int ssh_threads_set_callbacks(struct ssh_threads_callbacks_struct *cb) +{ + + int rc; + + if (user_callbacks != NULL) { + crypto_thread_finalize(); + } + + user_callbacks = cb; + + rc = crypto_thread_init(user_callbacks); + + return rc; +} + +const char *ssh_threads_get_type(void) +{ + if (user_callbacks != NULL) { + return user_callbacks->type; + } + return NULL; +} + +/** + * @} + */ diff --git a/src/libs/libssh-0.12.2/src/threads/libcrypto.c b/src/libs/libssh-0.12.2/src/threads/libcrypto.c new file mode 100644 index 000000000000..18951b6ad667 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/libcrypto.c @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" +#include + +int crypto_thread_init(struct ssh_threads_callbacks_struct *cb) +{ + (void) cb; + return SSH_OK; +} + +void crypto_thread_finalize(void) +{ + return; +} diff --git a/src/libs/libssh-0.12.2/src/threads/libgcrypt.c b/src/libs/libssh-0.12.2/src/threads/libgcrypt.c new file mode 100644 index 000000000000..3560dc55f532 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/libgcrypt.c @@ -0,0 +1,74 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" +#include + +#if (GCRYPT_VERSION_NUMBER >= 0x010600) +/* libgcrypt >= 1.6 does not support custom callbacks */ +GCRY_THREAD_OPTION_PTHREAD_IMPL; + +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) +{ + (void) user_callbacks; + + return SSH_OK; +} + +#else +/* Libgcrypt < 1.6 specific way of handling thread callbacks */ + +static struct gcry_thread_cbs gcrypt_threads_callbacks; + +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) +{ + int cmp; + + if (user_callbacks == NULL) { + return SSH_OK; + } + + cmp = strcmp(user_callbacks->type, "threads_noop"); + if (cmp == 0) { + gcrypt_threads_callbacks.option= GCRY_THREAD_OPTION_VERSION << 8 || + GCRY_THREAD_OPTION_DEFAULT; + } else { + gcrypt_threads_callbacks.option= GCRY_THREAD_OPTION_VERSION << 8 || + GCRY_THREAD_OPTION_USER; + } + + gcrypt_threads_callbacks.mutex_init = user_callbacks->mutex_init; + gcrypt_threads_callbacks.mutex_destroy = user_callbacks->mutex_destroy; + gcrypt_threads_callbacks.mutex_lock = user_callbacks->mutex_lock; + gcrypt_threads_callbacks.mutex_unlock = user_callbacks->mutex_unlock; + gcry_control(GCRYCTL_SET_THREAD_CBS, &gcrypt_threads_callbacks); + + return SSH_OK; +} + +#endif /* GCRYPT_VERSION_NUMBER */ + +void crypto_thread_finalize(void) +{ + return; +} diff --git a/src/libs/libssh-0.12.2/src/threads/mbedtls.c b/src/libs/libssh-0.12.2/src/threads/mbedtls.c new file mode 100644 index 000000000000..62e1db875665 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/mbedtls.c @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/crypto.h" +#include "libssh/threads.h" +#include + +#include + +int crypto_thread_init(struct ssh_threads_callbacks_struct *user_callbacks) +{ + int cmp; + + if (user_callbacks == NULL) { + return SSH_OK; + } + + cmp = strcmp(user_callbacks->type, "threads_noop"); + if (cmp == 0) { + return SSH_OK; + } +#ifdef MBEDTLS_THREADING_ALT + else { + if (user_callbacks != NULL) { + crypto_thread_finalize(); + } + + mbedtls_threading_set_alt(user_callbacks->mutex_init, + user_callbacks->mutex_destroy, + user_callbacks->mutex_lock, + user_callbacks->mutex_unlock); + } +#elif defined MBEDTLS_THREADING_PTHREAD + return SSH_OK; +#else + fprintf(stderr, + "MbedTLS needs to have threading enabled with " + "MBEDTLS_THREADING_PTHREAD or MBEDTLS_THREADING_ALT " + "in mbedtls_config.h\n"); +#warn "MbedTLS needs to have threading enabled with " \ + "MBEDTLS_THREADING_PTHREAD or MBEDTLS_THREADING_ALT in mbedtls_config.h" + return SSH_ERROR; +#endif +} + +void crypto_thread_finalize(void) +{ +#ifdef MBEDTLS_THREADING_ALT + mbedtls_threading_free_alt(); +#endif + return; +} diff --git a/src/libs/libssh-0.12.2/src/threads/noop.c b/src/libs/libssh-0.12.2/src/threads/noop.c new file mode 100644 index 000000000000..63aca1e7cbbe --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/noop.c @@ -0,0 +1,74 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/threads.h" +#include + +static int threads_noop(void **lock) +{ + (void)lock; + + return 0; +} + +static unsigned long threads_id_noop (void) +{ + return 1; +} + +static struct ssh_threads_callbacks_struct ssh_threads_noop = +{ + .type = "threads_noop", + .mutex_init = threads_noop, + .mutex_destroy = threads_noop, + .mutex_lock = threads_noop, + .mutex_unlock = threads_noop, + .thread_id = threads_id_noop +}; + +/* Threads interface implementation */ + +#if !(HAVE_PTHREAD) && !(defined _WIN32 || defined _WIN64) +void ssh_mutex_lock(SSH_MUTEX *mutex) +{ + (void) mutex; + + return; +} + +void ssh_mutex_unlock(SSH_MUTEX *mutex) +{ + (void) mutex; + + return; +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void) +{ + return &ssh_threads_noop; +} +#endif + +struct ssh_threads_callbacks_struct *ssh_threads_get_noop(void) +{ + return &ssh_threads_noop; +} diff --git a/src/libs/libssh-0.12.2/src/threads/pthread.c b/src/libs/libssh-0.12.2/src/threads/pthread.c new file mode 100644 index 000000000000..422dd8549b84 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/pthread.c @@ -0,0 +1,140 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/threads.h" +#include + +#include +#include +#include + +static int ssh_pthread_mutex_init (void **mutex) +{ + int rc = 0; + + if (mutex == NULL) { + return EINVAL; + } + + *mutex = malloc(sizeof(pthread_mutex_t)); + if (*mutex == NULL) { + return ENOMEM; + } + + rc = pthread_mutex_init ((pthread_mutex_t *)*mutex, NULL); + if (rc){ + free (*mutex); + *mutex = NULL; + } + + return rc; +} + +static int ssh_pthread_mutex_destroy (void **mutex) +{ + + int rc = 0; + + if (mutex == NULL) { + return EINVAL; + } + + rc = pthread_mutex_destroy ((pthread_mutex_t *)*mutex); + + free (*mutex); + *mutex = NULL; + + return rc; +} + +static int ssh_pthread_mutex_lock (void **mutex) +{ + return pthread_mutex_lock((pthread_mutex_t *)*mutex); +} + +static int ssh_pthread_mutex_unlock (void **mutex) +{ + return pthread_mutex_unlock((pthread_mutex_t *)*mutex); +} + +static unsigned long ssh_pthread_thread_id (void) +{ +#if defined(_WIN32) && !defined(__WINPTHREADS_VERSION) + return (unsigned long) pthread_self().p; +#else + return (unsigned long) pthread_self(); +#endif +} + +static struct ssh_threads_callbacks_struct ssh_threads_pthread = +{ + .type = "threads_pthread", + .mutex_init = ssh_pthread_mutex_init, + .mutex_destroy = ssh_pthread_mutex_destroy, + .mutex_lock = ssh_pthread_mutex_lock, + .mutex_unlock = ssh_pthread_mutex_unlock, + .thread_id = ssh_pthread_thread_id +}; + +/* Threads interface implementation */ + +#if (HAVE_PTHREAD) +void ssh_mutex_lock(SSH_MUTEX *mutex) +{ + int rc; + + if (mutex == NULL) { + exit(EINVAL); + } + + rc = pthread_mutex_lock(mutex); + + if (rc) { + exit(rc); + } +} + +void ssh_mutex_unlock(SSH_MUTEX *mutex) +{ + int rc; + + if (mutex == NULL) { + exit(EINVAL); + } + + rc = pthread_mutex_unlock(mutex); + + if (rc) { + exit(rc); + } +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void) +{ + return &ssh_threads_pthread; +} +#endif + +struct ssh_threads_callbacks_struct *ssh_threads_get_pthread(void) +{ + return &ssh_threads_pthread; +} diff --git a/src/libs/libssh-0.12.2/src/threads/winlocks.c b/src/libs/libssh-0.12.2/src/threads/winlocks.c new file mode 100644 index 000000000000..e63635e74728 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/threads/winlocks.c @@ -0,0 +1,124 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/threads.h" +#include + +#include +#include +#include +#include + +static int ssh_winlock_mutex_init (void **priv) +{ + CRITICAL_SECTION *lock = malloc(sizeof(CRITICAL_SECTION)); + + if (lock == NULL) { + return ENOMEM; + } + + InitializeCriticalSection(lock); + + *priv = lock; + + return 0; +} + +static int ssh_winlock_mutex_destroy (void **lock) +{ + DeleteCriticalSection((CRITICAL_SECTION *) *lock); + free(*lock); + + return 0; +} + +static int ssh_winlock_mutex_lock (void **lock) +{ + EnterCriticalSection((CRITICAL_SECTION *) *lock); + return 0; +} + +static int ssh_winlock_mutex_unlock (void **lock) +{ + LeaveCriticalSection((CRITICAL_SECTION *) *lock); + return 0; +} + +static unsigned long ssh_winlock_thread_id (void) +{ + return GetCurrentThreadId(); +} + +static struct ssh_threads_callbacks_struct ssh_threads_winlock = +{ + .type = "threads_winlock", + .mutex_init = ssh_winlock_mutex_init, + .mutex_destroy = ssh_winlock_mutex_destroy, + .mutex_lock = ssh_winlock_mutex_lock, + .mutex_unlock = ssh_winlock_mutex_unlock, + .thread_id = ssh_winlock_thread_id +}; + +/* Threads interface implementation */ + +void ssh_mutex_lock(SSH_MUTEX *mutex) +{ + void *rc = NULL; + + CRITICAL_SECTION *mutex_tmp = NULL; + + if (*mutex == NULL) { + mutex_tmp = malloc(sizeof(CRITICAL_SECTION)); + + if (mutex_tmp == NULL) { + exit(ENOMEM); + } + + InitializeCriticalSection(mutex_tmp); + + rc = InterlockedCompareExchangePointer((PVOID*)mutex, + (PVOID)mutex_tmp, + NULL); + if (rc != NULL) { + DeleteCriticalSection(mutex_tmp); + free(mutex_tmp); + exit(ENOMEM); + } + } + + EnterCriticalSection(*mutex); +} + +void ssh_mutex_unlock(SSH_MUTEX *mutex) +{ + LeaveCriticalSection(*mutex); +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_winlock(void) +{ + return &ssh_threads_winlock; +} + +struct ssh_threads_callbacks_struct *ssh_threads_get_default(void) +{ + return &ssh_threads_winlock; +} diff --git a/src/libs/libssh-0.12.2/src/token.c b/src/libs/libssh-0.12.2/src/token.c new file mode 100644 index 000000000000..3fe1745c6f08 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/token.c @@ -0,0 +1,545 @@ +/* + * token.c - Token list handling functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2008 by Aris Adamantiadis + * Copyright (c) 2019 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/token.h" + +/** + * @internal + * + * @brief Free the given tokens list structure. The used buffer is overwritten + * with zeroes before freed. + * + * @param[in] tokens The pointer to a structure to be freed; + */ +void ssh_tokens_free(struct ssh_tokens_st *tokens) +{ + int i; + if (tokens == NULL) { + return; + } + + if (tokens->tokens != NULL) { + for (i = 0; tokens->tokens[i] != NULL; i++) { + ssh_burn(tokens->tokens[i], strlen(tokens->tokens[i])); + } + } + + SAFE_FREE(tokens->buffer); + SAFE_FREE(tokens->tokens); + SAFE_FREE(tokens); +} + +/** + * @internal + * + * @brief Split a given string on the given separator character. The returned + * structure holds an array of pointers (tokens) pointing to the obtained + * parts and a buffer where all the content of the list is stored. The last + * element of the array will always be set as NULL. + * + * @param[in] chain The string to split + * @param[in] separator The character used to separate the tokens. + * + * @return A newly allocated tokens list structure; NULL in case of error. + */ +struct ssh_tokens_st *ssh_tokenize(const char *chain, char separator) +{ + + struct ssh_tokens_st *tokens = NULL; + size_t num_tokens = 1, i = 1; + + char *found, *c; + + if (chain == NULL) { + return NULL; + } + + tokens = calloc(1, sizeof(struct ssh_tokens_st)); + if (tokens == NULL) { + return NULL; + } + + tokens->buffer = strdup(chain); + if (tokens->buffer == NULL) { + goto error; + } + + c = tokens->buffer; + do { + found = strchr(c, separator); + if (found != NULL) { + c = found + 1; + num_tokens++; + } + } while(found != NULL); + + /* Allocate tokens list */ + tokens->tokens = calloc(num_tokens + 1, sizeof(char *)); + if (tokens->tokens == NULL) { + goto error; + } + + /* First token starts in the beginning of the chain */ + tokens->tokens[0] = tokens->buffer; + c = tokens->buffer; + + for (i = 1; i < num_tokens; i++) { + /* Find next separator */ + found = strchr(c, separator); + if (found == NULL) { + break; + } + + /* Replace it with a string terminator */ + *found = '\0'; + + /* The next token starts in the next byte */ + c = found + 1; + + /* If we did not reach the end of the chain yet, set the next token */ + if (*c != '\0') { + tokens->tokens[i] = c; + } else { + break; + } + } + + return tokens; + +error: + ssh_tokens_free(tokens); + return NULL; +} + +/** + * @internal + * + * @brief Given two strings, the first containing a list of available tokens and + * the second containing a list of tokens to be searched ordered by preference, + * returns a copy of the first preferred token present in the available list. + * + * @param[in] available_list The list of available tokens + * @param[in] preferred_list The list of tokens to search, ordered by + * preference + * + * @return A newly allocated copy of the token if found; NULL otherwise + */ +char *ssh_find_matching(const char *available_list, + const char *preferred_list) +{ + struct ssh_tokens_st *a_tok = NULL, *p_tok = NULL; + + int i, j; + char *ret = NULL; + + if ((available_list == NULL) || (preferred_list == NULL)) { + return NULL; + } + + a_tok = ssh_tokenize(available_list, ','); + if (a_tok == NULL) { + return NULL; + } + + p_tok = ssh_tokenize(preferred_list, ','); + if (p_tok == NULL) { + goto out; + } + + for (i = 0; p_tok->tokens[i]; i++) { + for (j = 0; a_tok->tokens[j]; j++) { + if (strcmp(a_tok->tokens[j], p_tok->tokens[i]) == 0) { + ret = strdup(a_tok->tokens[j]); + goto out; + } + } + } + +out: + ssh_tokens_free(a_tok); + ssh_tokens_free(p_tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings, the first containing a list of available tokens and + * the second containing a list of tokens to be searched ordered by preference, + * returns a list of all matching tokens ordered by preference. + * + * @param[in] available_list The list of available tokens + * @param[in] preferred_list The list of tokens to search, ordered by + * preference + * + * @return A newly allocated string containing the list of all matching tokens; + * NULL otherwise + */ +char *ssh_find_all_matching(const char *available_list, + const char *preferred_list) +{ + struct ssh_tokens_st *a_tok = NULL, *p_tok = NULL; + int i, j; + char *ret = NULL; + size_t max, len, pos = 0; + int match; + + if ((available_list == NULL) || (preferred_list == NULL)) { + return NULL; + } + + max = MAX(strlen(available_list), strlen(preferred_list)); + + ret = calloc(1, max + 1); + if (ret == NULL) { + return NULL; + } + + a_tok = ssh_tokenize(available_list, ','); + if (a_tok == NULL) { + SAFE_FREE(ret); + goto out; + } + + p_tok = ssh_tokenize(preferred_list, ','); + if (p_tok == NULL) { + SAFE_FREE(ret); + goto out; + } + + for (i = 0; p_tok->tokens[i] ; i++) { + for (j = 0; a_tok->tokens[j]; j++) { + match = !strcmp(a_tok->tokens[j], p_tok->tokens[i]); + if (match) { + if (pos != 0) { + ret[pos] = ','; + pos++; + } + + len = strlen(a_tok->tokens[j]); + memcpy(&ret[pos], a_tok->tokens[j], len); + pos += len; + ret[pos] = '\0'; + } + } + } + + if (ret[0] == '\0') { + SAFE_FREE(ret); + } + +out: + ssh_tokens_free(a_tok); + ssh_tokens_free(p_tok); + return ret; +} + +/** + * @internal + * + * @brief Given a string containing a list of elements, remove all duplicates + * and return in a newly allocated string. + * + * @param[in] list The list to be freed of duplicates + * + * @return A newly allocated copy of the string free of duplicates; NULL in + * case of error. + */ +char *ssh_remove_duplicates(const char *list) +{ + struct ssh_tokens_st *tok = NULL; + + size_t i, j, num_tokens, max_len; + char *ret = NULL; + bool *should_copy = NULL, need_comma = false; + + if (list == NULL) { + return NULL; + } + + /* The maximum number of tokens is the size of the list */ + max_len = strlen(list); + if (max_len == 0) { + return NULL; + } + + /* Add space for ending '\0' */ + max_len++; + + tok = ssh_tokenize(list, ','); + if ((tok == NULL) || (tok->tokens == NULL) || (tok->tokens[0] == NULL)) { + goto out; + } + + should_copy = calloc(1, max_len); + if (should_copy == NULL) { + goto out; + } + + if (strlen(tok->tokens[0]) > 0) { + should_copy[0] = true; + } + + for (i = 1; tok->tokens[i]; i++) { + for (j = 0; j < i; j++) { + if (strcmp(tok->tokens[i], tok->tokens[j]) == 0) { + /* Found a duplicate; do not copy */ + should_copy[i] = false; + break; + } + } + + /* No matching token before */ + if (j == i) { + /* Only copy if it is not an empty string */ + if (strlen(tok->tokens[i]) > 0) { + should_copy[i] = true; + } else { + should_copy[i] = false; + } + } + } + + num_tokens = i; + + ret = calloc(1, max_len); + if (ret == NULL) { + goto out; + } + + for (i = 0; i < num_tokens; i++) { + if (should_copy[i]) { + if (need_comma) { + strncat(ret, ",", (max_len - strlen(ret) - 1)); + } + strncat(ret, tok->tokens[i], (max_len - strlen(ret) - 1)); + need_comma = true; + } + } + + /* If no comma is needed, nothing was copied */ + if (!need_comma) { + SAFE_FREE(ret); + } + +out: + SAFE_FREE(should_copy); + ssh_tokens_free(tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing all the elements of the first list appended with + * all the elements of the second list, without duplicates. The order of the + * elements will be preserved. + * + * @param[in] list The first list + * @param[in] appended_list The list to be appended + * + * @return A newly allocated copy list containing all the elements of the + * kept_list appended with the elements of the appended_list without duplicates; + * NULL in case of error. + */ +char *ssh_append_without_duplicates(const char *list, + const char *appended_list) +{ + size_t concat_len = 0; + char *ret = NULL, *concat = NULL; + int rc = 0; + + if (list != NULL) { + concat_len = strlen(list); + } + + if (appended_list != NULL) { + concat_len += strlen(appended_list); + } + + if (concat_len == 0) { + return NULL; + } + + /* Add room for ending '\0' and for middle ',' */ + concat_len += 2; + concat = calloc(1, concat_len); + if (concat == NULL) { + return NULL; + } + + rc = snprintf(concat, concat_len, "%s%s%s", + list == NULL ? "" : list, + list == NULL ? "" : ",", + appended_list == NULL ? "" : appended_list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; + } + + ret = ssh_remove_duplicates(concat); + + SAFE_FREE(concat); + + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing the elements of the first list without the + * elements of the second list. The order of the elements will be preserved. + * + * @param[in] list The first list + * @param[in] remove_list The list to be removed + * + * @return A newly allocated copy list containing elements of the + * list without the elements of remove_list; NULL in case of error. + */ +char *ssh_remove_all_matching(const char *list, + const char *remove_list) +{ + struct ssh_tokens_st *l_tok = NULL, *r_tok = NULL; + int i, j, cmp; + char *ret = NULL; + size_t len, pos = 0; + bool exclude; + + if (list == NULL) { + return NULL; + } + if (remove_list == NULL) { + return strdup (list); + } + + l_tok = ssh_tokenize(list, ','); + if (l_tok == NULL) { + goto out; + } + + r_tok = ssh_tokenize(remove_list, ','); + if (r_tok == NULL) { + goto out; + } + + ret = calloc(1, strlen(list) + 1); + if (ret == NULL) { + goto out; + } + + for (i = 0; l_tok->tokens[i]; i++) { + exclude = false; + for (j = 0; r_tok->tokens[j]; j++) { + cmp = strcmp(l_tok->tokens[i], r_tok->tokens[j]); + if (cmp == 0) { + exclude = true; + break; + } + } + if (exclude == false) { + if (pos != 0) { + ret[pos] = ','; + pos++; + } + + len = strlen(l_tok->tokens[i]); + memcpy(&ret[pos], l_tok->tokens[i], len); + pos += len; + } + } + + if (ret[0] == '\0') { + SAFE_FREE(ret); + } + +out: + ssh_tokens_free(l_tok); + ssh_tokens_free(r_tok); + return ret; +} + +/** + * @internal + * + * @brief Given two strings containing lists of tokens, return a newly + * allocated string containing all the elements of the first list prefixed at + * the beginning of the second list, without duplicates. + * + * @param[in] list The first list + * @param[in] prefixed_list The list to use as a prefix + * + * @return A newly allocated list containing all the elements + * of the list prefixed with the elements of the prefixed_list without + * duplicates; NULL in case of error. + */ +char *ssh_prefix_without_duplicates(const char *list, + const char *prefixed_list) +{ + size_t concat_len = 0; + char *ret = NULL, *concat = NULL; + int rc = 0; + + if (list != NULL) { + concat_len = strlen(list); + } + + if (prefixed_list != NULL) { + concat_len += strlen(prefixed_list); + } + + if (concat_len == 0) { + return NULL; + } + + /* Add room for ending '\0' and for middle ',' */ + concat_len += 2; + concat = calloc(concat_len, 1); + if (concat == NULL) { + return NULL; + } + + rc = snprintf(concat, concat_len, "%s%s%s", + prefixed_list == NULL ? "" : prefixed_list, + prefixed_list == NULL ? "" : ",", + list == NULL ? "" : list); + if (rc < 0) { + SAFE_FREE(concat); + return NULL; + } + + ret = ssh_remove_duplicates(concat); + + SAFE_FREE(concat); + + return ret; +} diff --git a/src/libs/libssh-0.12.2/src/ttyopts.c b/src/libs/libssh-0.12.2/src/ttyopts.c new file mode 100644 index 000000000000..251a988bb758 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/ttyopts.c @@ -0,0 +1,476 @@ +/* + * ttyopts.c - encoding of TTY modes. + * + * This file is part of the SSH Library + * + * Copyright (c) 2023 by Utimaco TS GmbH + * Author: Daniel Evers + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include +#include + +#ifdef HAVE_TERMIOS_H +#include +#endif + +/** Terminal mode opcodes */ +enum { + TTY_OP_END = 0, + TTY_OP_VINTR = 1, + TTY_OP_VQUIT = 2, + TTY_OP_VERASE = 3, + TTY_OP_VKILL = 4, + TTY_OP_VEOF = 5, + TTY_OP_VEOL = 6, + TTY_OP_VEOL2 = 7, + TTY_OP_VSTART = 8, + TTY_OP_VSTOP = 9, + TTY_OP_VSUSP = 10, + TTY_OP_VDSUSP = 11, + TTY_OP_VREPRINT = 12, + TTY_OP_VWERASE = 13, + TTY_OP_VLNEXT = 14, + TTY_OP_VFLUSH = 15, + TTY_OP_VSWTC = 16, + TTY_OP_VSTATUS = 17, + TTY_OP_VDISCARD = 18, + TTY_OP_IGNPAR = 30, + TTY_OP_PARMRK = 31, + TTY_OP_INPCK = 32, + TTY_OP_ISTRIP = 33, + TTY_OP_INLCR = 34, + TTY_OP_IGNCR = 35, + TTY_OP_ICRNL = 36, + TTY_OP_IUCLC = 37, + TTY_OP_IXON = 38, + TTY_OP_IXANY = 39, + TTY_OP_IXOFF = 40, + TTY_OP_IMAXBEL = 41, + TTY_OP_IUTF8 = 42, + TTY_OP_ISIG = 50, + TTY_OP_ICANON = 51, + TTY_OP_XCASE = 52, + TTY_OP_ECHO = 53, + TTY_OP_ECHOE = 54, + TTY_OP_ECHOK = 55, + TTY_OP_ECHONL = 56, + TTY_OP_NOFLSH = 57, + TTY_OP_TOSTOP = 58, + TTY_OP_IEXTEN = 59, + TTY_OP_ECHOCTL = 60, + TTY_OP_ECHOKE = 61, + TTY_OP_PENDIN = 62, + TTY_OP_OPOST = 70, + TTY_OP_OLCUC = 71, + TTY_OP_ONLCR = 72, + TTY_OP_OCRNL = 73, + TTY_OP_ONOCR = 74, + TTY_OP_ONLRET = 75, + TTY_OP_CS7 = 90, + TTY_OP_CS8 = 91, + TTY_OP_PARENB = 92, + TTY_OP_PARODD = 93, + TTY_OP_ISPEED = 128, + TTY_OP_OSPEED = 129, +}; + +/** + * Encodes a single SSH terminal mode option into the buffer. + * + * @param[in] attr The mode's opcode value. + * + * @param[in] value The mode's value. + * + * @param[out] buf Destination buffer to encode into. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes written to the buffer on success, -1 on + * error. + */ +static int +encode_termios_opt(unsigned char opcode, + uint32_t value, + unsigned char *buf, + size_t buflen) +{ + int offset = 0; + + /* always need 5 bytes */ + if (buflen < 5) { + return -1; + } + + /* 1 byte opcode */ + buf[offset++] = opcode; + + /* 4 bytes value (big endian) */ + value = htonl(value); + memcpy(buf + offset, &value, sizeof(value)); + offset += sizeof(value); + + return offset; +} + +#ifdef HAVE_TERMIOS_H +/** Converts a baudrate constant (Bxxxx) to a numeric value. */ +static int +baud2speed(int baudrate) +{ + switch (baudrate) { + default: + case B0: + return 0; + case B50: + return 50; + case B75: + return 75; + case B110: + return 110; + case B134: + return 134; + case B150: + return 150; + case B200: + return 200; + case B300: + return 300; + case B600: + return 600; + case B1200: + return 1200; + case B1800: + return 1800; + case B2400: + return 2400; + case B4800: + return 4800; + case B9600: + return 9600; + case B19200: + return 19200; + case B38400: + return 38400; +#ifdef B57600 + case B57600: + return 57600; +#endif +#ifdef B115200 + case B115200: + return 115200; +#endif +#ifdef B230400 + case B230400: + return 230400; +#endif + } +} + +/** + * Encodes all terminal options from the given \c termios structure + * into the buffer. + * + * @param[in] attr The terminal options to encode. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_termios_opts(struct termios *attr, unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + +#define SSH_ENCODE_INPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_iflag & opt) ? 1 : 0) + SSH_ENCODE_INPUT_OPT(IGNPAR) + SSH_ENCODE_INPUT_OPT(PARMRK) + SSH_ENCODE_INPUT_OPT(INPCK) + SSH_ENCODE_INPUT_OPT(ISTRIP) + SSH_ENCODE_INPUT_OPT(INLCR) + SSH_ENCODE_INPUT_OPT(IGNCR) + SSH_ENCODE_INPUT_OPT(ICRNL) +#ifdef IUCLC + SSH_ENCODE_INPUT_OPT(IUCLC) +#endif + SSH_ENCODE_INPUT_OPT(IXON) + SSH_ENCODE_INPUT_OPT(IXANY) + SSH_ENCODE_INPUT_OPT(IXOFF) +#ifdef IMAXBEL + SSH_ENCODE_INPUT_OPT(IMAXBEL) +#endif +#ifdef IUTF8 + SSH_ENCODE_INPUT_OPT(IUTF8) +#endif +#undef SSH_ENCODE_INPUT_OPT + +#define SSH_ENCODE_OUTPUT_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_oflag & opt) ? 1 : 0) + SSH_ENCODE_OUTPUT_OPT(OPOST) +#ifdef OLCUC + SSH_ENCODE_OUTPUT_OPT(OLCUC) +#endif + SSH_ENCODE_OUTPUT_OPT(ONLCR) + SSH_ENCODE_OUTPUT_OPT(OCRNL) + SSH_ENCODE_OUTPUT_OPT(ONOCR) + SSH_ENCODE_OUTPUT_OPT(ONLRET) +#undef SSH_ENCODE_OUTPUT_OPT + +#define SSH_ENCODE_CONTROL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_cflag & opt) ? 1 : 0) + SSH_ENCODE_CONTROL_OPT(CS7) + SSH_ENCODE_CONTROL_OPT(CS8) + SSH_ENCODE_CONTROL_OPT(PARENB) + SSH_ENCODE_CONTROL_OPT(PARODD) +#undef SSH_ENCODE_CONTROL_OPT + +#define SSH_ENCODE_LOCAL_OPT(opt) \ + SSH_ENCODE_OPT(TTY_OP_##opt, (attr->c_lflag & opt) ? 1 : 0) + SSH_ENCODE_LOCAL_OPT(ISIG) + SSH_ENCODE_LOCAL_OPT(ICANON) +#ifdef XCASE + SSH_ENCODE_LOCAL_OPT(XCASE) +#endif + SSH_ENCODE_LOCAL_OPT(ECHO) + SSH_ENCODE_LOCAL_OPT(ECHOE) + SSH_ENCODE_LOCAL_OPT(ECHOK) + SSH_ENCODE_LOCAL_OPT(ECHONL) + SSH_ENCODE_LOCAL_OPT(NOFLSH) + SSH_ENCODE_LOCAL_OPT(TOSTOP) + SSH_ENCODE_LOCAL_OPT(IEXTEN) +#ifdef ECHOCTL + SSH_ENCODE_LOCAL_OPT(ECHOCTL) +#endif +#ifdef ECHOKE + SSH_ENCODE_LOCAL_OPT(ECHOKE) +#endif +#ifdef PENDIN + SSH_ENCODE_LOCAL_OPT(PENDIN) +#endif +#undef SSH_ENCODE_LOCAL_OPT + +#define SSH_ENCODE_CC_OPT(opt) SSH_ENCODE_OPT(TTY_OP_##opt, attr->c_cc[opt]) + SSH_ENCODE_CC_OPT(VINTR) + SSH_ENCODE_CC_OPT(VQUIT) + SSH_ENCODE_CC_OPT(VERASE) + SSH_ENCODE_CC_OPT(VKILL) + SSH_ENCODE_CC_OPT(VEOF) + SSH_ENCODE_CC_OPT(VEOL) +#ifdef VEOL2 + SSH_ENCODE_CC_OPT(VEOL2) +#endif + SSH_ENCODE_CC_OPT(VSTART) + SSH_ENCODE_CC_OPT(VSTOP) + SSH_ENCODE_CC_OPT(VSUSP) +#ifdef VDSUSP + SSH_ENCODE_CC_OPT(VDSUSP) +#endif +#ifdef VREPRINT + SSH_ENCODE_CC_OPT(VREPRINT) +#endif +#ifdef VWERASE + SSH_ENCODE_CC_OPT(VWERASE) +#endif +#ifdef VLNEXT + SSH_ENCODE_CC_OPT(VLNEXT) +#endif +#ifdef VFLUSH + SSH_ENCODE_CC_OPT(VFLUSH) +#endif +#ifdef VSWTC + SSH_ENCODE_CC_OPT(VSWTC) +#endif +#ifdef VSTATUS + SSH_ENCODE_CC_OPT(VSTATUS) +#endif +#ifdef VDISCARD + SSH_ENCODE_CC_OPT(VDISCARD) +#endif +#undef SSH_ENCODE_CC_OPT + + SSH_ENCODE_OPT(TTY_OP_ISPEED, baud2speed(cfgetispeed(attr))) + SSH_ENCODE_OPT(TTY_OP_OSPEED, baud2speed(cfgetospeed(attr))) +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} +#endif + +/** + * Encodes a set of default options to ensure "sane" PTY behavior. + * This function intentionally doesn't use the \c termios structure + * to allow it to work on Windows as well. + * + * The "sane" default set is derived from the `stty sane`, but iutf8 support is + * added on top of that. + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +static int +encode_default_opts(unsigned char *buf, size_t buflen) +{ + unsigned int offset = 0; + int rc; + +#define SSH_ENCODE_OPT(code, value) \ + rc = encode_termios_opt(code, value, buf + offset, buflen - offset); \ + if (rc < 0) { \ + return rc; \ + } else { \ + offset += rc; \ + } + + SSH_ENCODE_OPT(TTY_OP_VINTR, 003) + SSH_ENCODE_OPT(TTY_OP_VQUIT, 034) + SSH_ENCODE_OPT(TTY_OP_VERASE, 0177) + SSH_ENCODE_OPT(TTY_OP_VKILL, 025) + SSH_ENCODE_OPT(TTY_OP_VEOF, 004) + SSH_ENCODE_OPT(TTY_OP_VEOL, 0) + SSH_ENCODE_OPT(TTY_OP_VEOL2, 0) + SSH_ENCODE_OPT(TTY_OP_VSTART, 021) + SSH_ENCODE_OPT(TTY_OP_VSTOP, 023) + SSH_ENCODE_OPT(TTY_OP_VSUSP, 032) + SSH_ENCODE_OPT(TTY_OP_VDSUSP, 031) + SSH_ENCODE_OPT(TTY_OP_VREPRINT, 022) + SSH_ENCODE_OPT(TTY_OP_VWERASE, 027) + SSH_ENCODE_OPT(TTY_OP_VLNEXT, 026) + SSH_ENCODE_OPT(TTY_OP_VDISCARD, 017) + SSH_ENCODE_OPT(TTY_OP_IGNPAR, 0) + SSH_ENCODE_OPT(TTY_OP_PARMRK, 0) + SSH_ENCODE_OPT(TTY_OP_INPCK, 0) + SSH_ENCODE_OPT(TTY_OP_ISTRIP, 0) + SSH_ENCODE_OPT(TTY_OP_INLCR, 0) + SSH_ENCODE_OPT(TTY_OP_IGNCR, 0) + SSH_ENCODE_OPT(TTY_OP_ICRNL, 1) + SSH_ENCODE_OPT(TTY_OP_IUCLC, 0) + SSH_ENCODE_OPT(TTY_OP_IXON, 1) + SSH_ENCODE_OPT(TTY_OP_IXANY, 0) + SSH_ENCODE_OPT(TTY_OP_IXOFF, 0) + SSH_ENCODE_OPT(TTY_OP_IMAXBEL, 0) + SSH_ENCODE_OPT(TTY_OP_IUTF8, 1) + SSH_ENCODE_OPT(TTY_OP_ISIG, 1) + SSH_ENCODE_OPT(TTY_OP_ICANON, 1) + SSH_ENCODE_OPT(TTY_OP_XCASE, 0) + SSH_ENCODE_OPT(TTY_OP_ECHO, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOE, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOK, 1) + SSH_ENCODE_OPT(TTY_OP_ECHONL, 0) + SSH_ENCODE_OPT(TTY_OP_NOFLSH, 0) + SSH_ENCODE_OPT(TTY_OP_TOSTOP, 0) + SSH_ENCODE_OPT(TTY_OP_IEXTEN, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOCTL, 1) + SSH_ENCODE_OPT(TTY_OP_ECHOKE, 1) + SSH_ENCODE_OPT(TTY_OP_PENDIN, 0) + SSH_ENCODE_OPT(TTY_OP_OPOST, 1) + SSH_ENCODE_OPT(TTY_OP_OLCUC, 0) + SSH_ENCODE_OPT(TTY_OP_ONLCR, 1) + SSH_ENCODE_OPT(TTY_OP_OCRNL, 0) + SSH_ENCODE_OPT(TTY_OP_ONOCR, 0) + SSH_ENCODE_OPT(TTY_OP_ONLRET, 0) + SSH_ENCODE_OPT(TTY_OP_CS7, 1) + SSH_ENCODE_OPT(TTY_OP_CS8, 1) + SSH_ENCODE_OPT(TTY_OP_PARENB, 0) + SSH_ENCODE_OPT(TTY_OP_PARODD, 0) + SSH_ENCODE_OPT(TTY_OP_ISPEED, 38400); + SSH_ENCODE_OPT(TTY_OP_OSPEED, 38400); + +#undef SSH_ENCODE_OPT + + /* end of options */ + if (buflen > offset) { + buf[offset++] = TTY_OP_END; + } else { + return -1; + } + + return (int)offset; +} + +/** + * @ingroup libssh_misc + * + * @brief Encode the current TTY options as SSH modes. + * + * Call this function to determine the settings of the process' TTY and + * encode them as SSH Terminal Modes according to RFC 4254 section 8. + * + * If STDIN isn't connected to a TTY, this function fills the buffer with + * "sane" default modes. + * + * The encoded modes can be passed to \c ssh_channel_request_pty_size_modes . + * + * @code + * unsigned char modes_buf[SSH_TTY_MODES_MAX_BUFSIZE]; + * encode_current_tty_opts(modes_buf, sizeof(modes_buf)); + * @endcode + * + * + * @param[out] buf Modes will be encoded into this buffer. + * + * @param[in] buflen The length of the buffer. + * + * @return number of bytes in the buffer on success, -1 on error. + */ +int +encode_current_tty_opts(unsigned char *buf, size_t buflen) +{ +#ifdef HAVE_TERMIOS_H + struct termios attr; + ZERO_STRUCT(attr); + + if (isatty(STDIN_FILENO)) { + /* get local terminal attributes */ + if (tcgetattr(STDIN_FILENO, &attr) < 0) { + perror("tcgetattr"); + return -1; + } + return encode_termios_opts(&attr, buf, buflen); + } +#endif + + /* use "sane" default attributes */ + return encode_default_opts(buf, buflen); +} diff --git a/src/libs/libssh-0.12.2/src/wrapper.c b/src/libs/libssh-0.12.2/src/wrapper.c new file mode 100644 index 000000000000..a172f9014bb9 --- /dev/null +++ b/src/libs/libssh-0.12.2/src/wrapper.c @@ -0,0 +1,646 @@ +/* + * wrapper.c - wrapper for crypto functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2003-2013 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* + * Why a wrapper? + * + * Let's say you want to port libssh from libcrypto of openssl to libfoo + * you are going to spend hours removing every reference to SHA1_Update() + * to libfoo_sha1_update after the work is finished, you're going to have + * only this file to modify it's not needed to say that your modifications + * are welcome. + */ + +#include "config.h" + + +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/wrapper.h" +#include "libssh/pki.h" +#include "libssh/poly1305.h" +#include "libssh/dh.h" +#ifdef WITH_GEX +#include "libssh/dh-gex.h" +#endif /* WITH_GEX */ +#include "libssh/curve25519.h" +#include "libssh/kex-gss.h" +#include "libssh/ecdh.h" +#include "libssh/hybrid_mlkem.h" +#include "libssh/sntrup761.h" + +static struct ssh_hmac_struct ssh_hmac_tab[] = { + { "hmac-sha1", SSH_HMAC_SHA1, false }, + { "hmac-sha2-256", SSH_HMAC_SHA256, false }, + { "hmac-sha2-512", SSH_HMAC_SHA512, false }, + { "hmac-md5", SSH_HMAC_MD5, false }, + { "aead-poly1305", SSH_HMAC_AEAD_POLY1305, false }, + { "aead-gcm", SSH_HMAC_AEAD_GCM, false }, + { "hmac-sha1-etm@openssh.com", SSH_HMAC_SHA1, true }, + { "hmac-sha2-256-etm@openssh.com", SSH_HMAC_SHA256, true }, + { "hmac-sha2-512-etm@openssh.com", SSH_HMAC_SHA512, true }, + { "hmac-md5-etm@openssh.com", SSH_HMAC_MD5, true }, +#ifdef WITH_INSECURE_NONE + { "none", SSH_HMAC_NONE, false }, +#endif /* WITH_INSECURE_NONE */ + { NULL, 0, false } +}; + +struct ssh_hmac_struct *ssh_get_hmactab(void) { + return ssh_hmac_tab; +} + +size_t hmac_digest_len(enum ssh_hmac_e type) { + switch(type) { + case SSH_HMAC_SHA1: + return SHA_DIGEST_LEN; + case SSH_HMAC_SHA256: + return SHA256_DIGEST_LEN; + case SSH_HMAC_SHA512: + return SHA512_DIGEST_LEN; + case SSH_HMAC_MD5: + return MD5_DIGEST_LEN; + case SSH_HMAC_AEAD_POLY1305: + return POLY1305_TAGLEN; + case SSH_HMAC_AEAD_GCM: + return AES_GCM_TAGLEN; + default: + return 0; + } +} + +const char *ssh_hmac_type_to_string(enum ssh_hmac_e hmac_type, bool etm) +{ + int i = 0; + struct ssh_hmac_struct *ssh_hmactab = ssh_get_hmactab(); + while (ssh_hmactab[i].name && + ((ssh_hmactab[i].hmac_type != hmac_type) || + (ssh_hmactab[i].etm != etm))) { + i++; + } + return ssh_hmactab[i].name; +} + +/* it allocates a new cipher structure based on its offset into the global table */ +static struct ssh_cipher_struct *cipher_new(uint8_t offset) { + struct ssh_cipher_struct *cipher = NULL; + + cipher = malloc(sizeof(struct ssh_cipher_struct)); + if (cipher == NULL) { + return NULL; + } + + /* note the memcpy will copy the pointers : so, you shouldn't free them */ + memcpy(cipher, &ssh_get_ciphertab()[offset], sizeof(*cipher)); + + return cipher; +} + +void ssh_cipher_clear(struct ssh_cipher_struct *cipher){ +#ifdef HAVE_LIBGCRYPT + unsigned int i; +#endif + + if (cipher == NULL) { + return; + } + +#ifdef HAVE_LIBGCRYPT + if (cipher->key) { + for (i = 0; i < (cipher->keylen / sizeof(gcry_cipher_hd_t)); i++) { + gcry_cipher_close(cipher->key[i]); + } + SAFE_FREE(cipher->key); + } +#endif + + if (cipher->cleanup != NULL) { + cipher->cleanup(cipher); + } +} + +static void cipher_free(struct ssh_cipher_struct *cipher) { + ssh_cipher_clear(cipher); + SAFE_FREE(cipher); +} + +struct ssh_crypto_struct *crypto_new(void) +{ + struct ssh_crypto_struct *crypto = NULL; + + crypto = calloc(1, sizeof(struct ssh_crypto_struct)); + if (crypto == NULL) { + return NULL; + } + return crypto; +} + +void crypto_free(struct ssh_crypto_struct *crypto) +{ + size_t i; + + if (crypto == NULL) { + return; + } + + ssh_key_free(crypto->server_pubkey); + + ssh_dh_cleanup(crypto); + bignum_safe_free(crypto->shared_secret); +#ifdef HAVE_ECDH + SAFE_FREE(crypto->ecdh_client_pubkey); + SAFE_FREE(crypto->ecdh_server_pubkey); + if (crypto->ecdh_privkey != NULL) { +#ifdef HAVE_OPENSSL_ECC +#if OPENSSL_VERSION_NUMBER < 0x30000000L + EC_KEY_free(crypto->ecdh_privkey); +#else + EVP_PKEY_free(crypto->ecdh_privkey); +#endif /* OPENSSL_VERSION_NUMBER */ +#elif defined HAVE_GCRYPT_ECC + gcry_sexp_release(crypto->ecdh_privkey); +#elif defined HAVE_LIBMBEDCRYPTO + mbedtls_ecp_keypair_free(crypto->ecdh_privkey); + SAFE_FREE(crypto->ecdh_privkey); +#endif /* HAVE_LIBGCRYPT */ + crypto->ecdh_privkey = NULL; + } +#endif +#ifdef HAVE_LIBCRYPTO + EVP_PKEY_free(crypto->curve25519_privkey); +#elif defined(HAVE_GCRYPT_CURVE25519) + gcry_sexp_release(crypto->curve25519_privkey); +#endif + SAFE_FREE(crypto->dh_server_signature); + if (crypto->session_id != NULL) { + ssh_burn(crypto->session_id, crypto->session_id_len); + SAFE_FREE(crypto->session_id); + } + if (crypto->secret_hash != NULL) { + ssh_burn(crypto->secret_hash, crypto->digest_len); + SAFE_FREE(crypto->secret_hash); + } + compress_cleanup(crypto); + SAFE_FREE(crypto->encryptIV); + SAFE_FREE(crypto->decryptIV); + SAFE_FREE(crypto->encryptMAC); + SAFE_FREE(crypto->decryptMAC); + if (crypto->encryptkey != NULL) { + ssh_burn(crypto->encryptkey, crypto->out_cipher->keysize / 8); + SAFE_FREE(crypto->encryptkey); + } + if (crypto->decryptkey != NULL) { + ssh_burn(crypto->decryptkey, crypto->in_cipher->keysize / 8); + SAFE_FREE(crypto->decryptkey); + } + + cipher_free(crypto->in_cipher); + cipher_free(crypto->out_cipher); + + for (i = 0; i < SSH_KEX_METHODS; i++) { + SAFE_FREE(crypto->client_kex.methods[i]); + SAFE_FREE(crypto->server_kex.methods[i]); + SAFE_FREE(crypto->kex_methods[i]); + } + +#ifdef HAVE_OPENSSL_MLKEM + EVP_PKEY_free(crypto->mlkem_privkey); +#else + if (crypto->mlkem_privkey != NULL) { + ssh_burn(crypto->mlkem_privkey, crypto->mlkem_privkey_len); + SAFE_FREE(crypto->mlkem_privkey); + crypto->mlkem_privkey_len = 0; + } +#endif + ssh_string_burn(crypto->hybrid_shared_secret); + ssh_string_free(crypto->mlkem_client_pubkey); + ssh_string_free(crypto->mlkem_ciphertext); + ssh_string_free(crypto->hybrid_client_init); + ssh_string_free(crypto->hybrid_server_reply); + ssh_string_free(crypto->hybrid_shared_secret); + + ssh_burn(crypto, sizeof(struct ssh_crypto_struct)); + + SAFE_FREE(crypto); +} + +static void +compression_enable(ssh_session session, + enum ssh_crypto_direction_e direction, + bool delayed) +{ + /* The delayed compression is turned on AFTER authentication. This means + * that we need to turn it on immediately in case of rekeying */ + if (delayed && !(session->flags & SSH_SESSION_FLAG_AUTHENTICATED)) { + if (direction == SSH_DIRECTION_IN) { + session->next_crypto->delayed_compress_in = 1; + } else { /* SSH_DIRECTION_OUT */ + session->next_crypto->delayed_compress_out = 1; + } + } else { + if (direction == SSH_DIRECTION_IN) { + session->next_crypto->do_compress_in = 1; + } else { /* SSH_DIRECTION_OUT */ + session->next_crypto->do_compress_out = 1; + } + } +} + +static int crypt_set_algorithms2(ssh_session session) +{ + const char *wanted = NULL; + const char *method = NULL; + struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); + struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); + uint8_t i = 0; + int cmp; + + /* + * We must scan the kex entries to find crypto algorithms and set their + * appropriate structure. + */ + + /* out */ + wanted = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + for (i = 0; i < 64 && ssh_ciphertab[i].name != NULL; ++i) { + cmp = strcmp(wanted, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no crypto algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set output algorithm to %s", wanted); + + session->next_crypto->out_cipher = cipher_new(i); + if (session->next_crypto->out_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->out_cipher->aead_encrypt != NULL) { + /* this cipher has integrated MAC */ + if (session->next_crypto->out_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + wanted = "aead-poly1305"; + } else { + wanted = "aead-gcm"; + } + } else { + /* + * We must scan the kex entries to find hmac algorithms and set their + * appropriate structure. + */ + + /* out */ + wanted = session->next_crypto->kex_methods[SSH_MAC_C_S]; + } + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(wanted, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no hmac algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", wanted); + + session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->out_hmac_etm = ssh_hmactab[i].etm; + + /* in */ + wanted = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + cmp = strcmp(wanted, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "Crypt_set_algorithms: no crypto algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set input algorithm to %s", wanted); + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->in_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + if (session->next_crypto->in_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + wanted = "aead-poly1305"; + } else { + wanted = "aead-gcm"; + } + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + wanted = session->next_crypto->kex_methods[SSH_MAC_S_C]; + } + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(wanted, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms2: no hmac algorithm function found for %s", + wanted); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC input algorithm to %s", wanted); + + session->next_crypto->in_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->in_hmac_etm = ssh_hmactab[i].etm; + + /* compression: client */ + method = session->next_crypto->kex_methods[SSH_COMP_C_S]; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S compression"); + compression_enable(session, SSH_DIRECTION_OUT, false); + } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S delayed compression"); + compression_enable(session, SSH_DIRECTION_OUT, true); + } + + method = session->next_crypto->kex_methods[SSH_COMP_S_C]; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C compression"); + compression_enable(session, SSH_DIRECTION_IN, false); + } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C delayed compression"); + compression_enable(session, SSH_DIRECTION_IN, true); + } + + return SSH_OK; +} + +int crypt_set_algorithms_client(ssh_session session) +{ + return crypt_set_algorithms2(session); +} + +#ifdef WITH_SERVER +int crypt_set_algorithms_server(ssh_session session){ + const char *method = NULL; + uint8_t i = 0; + struct ssh_cipher_struct *ssh_ciphertab=ssh_get_ciphertab(); + struct ssh_hmac_struct *ssh_hmactab=ssh_get_hmactab(); + int cmp; + + if (session == NULL) { + return SSH_ERROR; + } + + /* + * We must scan the kex entries to find crypto algorithms and set their + * appropriate structure + */ + /* out */ + method = session->next_crypto->kex_methods[SSH_CRYPT_S_C]; + + for (i = 0; ssh_ciphertab[i].name != NULL; i++) { + cmp = strcmp(method, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session,SSH_FATAL,"crypt_set_algorithms_server : " + "no crypto algorithm function found for %s",method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET,"Set output algorithm %s",method); + + session->next_crypto->out_cipher = cipher_new(i); + if (session->next_crypto->out_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->out_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + if (session->next_crypto->out_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + method = "aead-poly1305"; + } else { + method = "aead-gcm"; + } + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + /* out */ + method = session->next_crypto->kex_methods[SSH_MAC_S_C]; + } + /* HMAC algorithm selection */ + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(method, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms_server: no hmac algorithm function found for %s", + method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC output algorithm to %s", method); + + session->next_crypto->out_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->out_hmac_etm = ssh_hmactab[i].etm; + + /* in */ + method = session->next_crypto->kex_methods[SSH_CRYPT_C_S]; + + for (i = 0; ssh_ciphertab[i].name; i++) { + cmp = strcmp(method, ssh_ciphertab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_ciphertab[i].name == NULL) { + ssh_set_error(session,SSH_FATAL,"Crypt_set_algorithms_server :" + "no crypto algorithm function found for %s",method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET,"Set input algorithm %s",method); + + session->next_crypto->in_cipher = cipher_new(i); + if (session->next_crypto->in_cipher == NULL) { + ssh_set_error_oom(session); + return SSH_ERROR; + } + + if (session->next_crypto->in_cipher->aead_encrypt != NULL){ + /* this cipher has integrated MAC */ + if (session->next_crypto->in_cipher->ciphertype == SSH_AEAD_CHACHA20_POLY1305) { + method = "aead-poly1305"; + } else { + method = "aead-gcm"; + } + } else { + /* we must scan the kex entries to find hmac algorithms and set their appropriate structure */ + method = session->next_crypto->kex_methods[SSH_MAC_C_S]; + } + + for (i = 0; ssh_hmactab[i].name != NULL; i++) { + cmp = strcmp(method, ssh_hmactab[i].name); + if (cmp == 0) { + break; + } + } + + if (ssh_hmactab[i].name == NULL) { + ssh_set_error(session, SSH_FATAL, + "crypt_set_algorithms_server: no hmac algorithm function found for %s", + method); + return SSH_ERROR; + } + SSH_LOG(SSH_LOG_PACKET, "Set HMAC input algorithm to %s", method); + + session->next_crypto->in_hmac = ssh_hmactab[i].hmac_type; + session->next_crypto->in_hmac_etm = ssh_hmactab[i].etm; + + /* compression */ + method = session->next_crypto->kex_methods[SSH_COMP_C_S]; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S compression"); + compression_enable(session, SSH_DIRECTION_IN, false); + } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling C->S delayed compression"); + compression_enable(session, SSH_DIRECTION_IN, true); + } + + method = session->next_crypto->kex_methods[SSH_COMP_S_C]; + cmp = strcmp(method, "zlib"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C compression"); + compression_enable(session, SSH_DIRECTION_OUT, false); + } + cmp = strcmp(method, "zlib@openssh.com"); + if (cmp == 0) { + SSH_LOG(SSH_LOG_PACKET, "enabling S->C delayed compression"); + compression_enable(session, SSH_DIRECTION_OUT, true); + } + + method = session->next_crypto->kex_methods[SSH_HOSTKEYS]; + session->srv.hostkey = ssh_key_type_from_signature_name(method); + session->srv.hostkey_digest = ssh_key_hash_from_name(method); + + /* setup DH key exchange type */ + switch (session->next_crypto->kex_type) { + case SSH_KEX_DH_GROUP1_SHA1: + case SSH_KEX_DH_GROUP14_SHA1: + case SSH_KEX_DH_GROUP14_SHA256: + case SSH_KEX_DH_GROUP16_SHA512: + case SSH_KEX_DH_GROUP18_SHA512: + ssh_server_dh_init(session); + break; +#ifdef WITH_GSSAPI + case SSH_GSS_KEX_DH_GROUP14_SHA256: + case SSH_GSS_KEX_DH_GROUP16_SHA512: + case SSH_GSS_KEX_ECDH_NISTP256_SHA256: + case SSH_GSS_KEX_CURVE25519_SHA256: + ssh_server_gss_kex_init(session); + break; +#endif /* WITH_GSSAPI */ +#ifdef WITH_GEX + case SSH_KEX_DH_GEX_SHA1: + case SSH_KEX_DH_GEX_SHA256: + ssh_server_dhgex_init(session); + break; +#endif /* WITH_GEX */ +#ifdef HAVE_ECDH + case SSH_KEX_ECDH_SHA2_NISTP256: + case SSH_KEX_ECDH_SHA2_NISTP384: + case SSH_KEX_ECDH_SHA2_NISTP521: + ssh_server_ecdh_init(session); + break; +#endif +#ifdef HAVE_CURVE25519 + case SSH_KEX_CURVE25519_SHA256: + case SSH_KEX_CURVE25519_SHA256_LIBSSH_ORG: + ssh_server_curve25519_init(session); + break; +#endif +#ifdef HAVE_SNTRUP761 + case SSH_KEX_SNTRUP761X25519_SHA512: + case SSH_KEX_SNTRUP761X25519_SHA512_OPENSSH_COM: + ssh_server_sntrup761x25519_init(session); + break; +#endif + case SSH_KEX_MLKEM768X25519_SHA256: + case SSH_KEX_MLKEM768NISTP256_SHA256: +#ifdef HAVE_MLKEM1024 + case SSH_KEX_MLKEM1024NISTP384_SHA384: +#endif + ssh_server_hybrid_mlkem_init(session); + break; + default: + ssh_set_error(session, + SSH_FATAL, + "crypt_set_algorithms_server: could not find init " + "handler for kex type %d", + session->next_crypto->kex_type); + return SSH_ERROR; + } + return SSH_OK; +} + +#endif /* WITH_SERVER */ diff --git a/src/libs/libssh-0.12.2/tests/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/CMakeLists.txt new file mode 100644 index 000000000000..cf1adf567327 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/CMakeLists.txt @@ -0,0 +1,473 @@ +project(libssh-tests C) + +if (BSD OR SOLARIS OR OSX) + find_package(Argp) +endif (BSD OR SOLARIS OR OSX) + +set(TORTURE_LIBRARY torture) + +include_directories(${CMOCKA_INCLUDE_DIR} + ${libssh_BINARY_DIR}/include + ${libssh_BINARY_DIR} + ${libssh_SOURCE_DIR}/src + ${CMAKE_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR} + ${CMAKE_BINARY_DIR}/tests) + +set(TORTURE_LINK_LIBRARIES + ${CMOCKA_LIBRARY} + ssh::static) + +if (NOT WIN32) + set(TORTURE_LINK_LIBRARIES + ${TORTURE_LINK_LIBRARIES} + pthread) +endif(NOT WIN32) +if (WITH_GSSAPI AND GSSAPI_FOUND) + find_package(OpenSSL 1.1.1 REQUIRED) + set(TORTURE_LINK_LIBRARIES + ${TORTURE_LINK_LIBRARIES} + OpenSSL::Crypto) +endif (WITH_GSSAPI AND GSSAPI_FOUND) + +# Check for sk-dummy library if FIDO2 support is enabled +if (WITH_FIDO2) + find_file(SK_DUMMY_LIBRARY + NAMES sk-dummy.so + PATHS /usr/lib64/sshtest /usr/lib/sshtest + NO_DEFAULT_PATH + ) +endif (WITH_FIDO2) + +# create test library +add_library(${TORTURE_LIBRARY} + STATIC + cmdline.c + torture.c + torture_key.c + torture_pki.c + torture_sk.c + torture_cmocka.c) +target_link_libraries(${TORTURE_LIBRARY} PRIVATE ${TORTURE_LINK_LIBRARIES}) +target_compile_options(${TORTURE_LIBRARY} PRIVATE + -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" +) + +# Check for sk-dummy and add HAVE_SK_DUMMY definition if available +if (SK_DUMMY_LIBRARY) + add_library(sk-dummy SHARED IMPORTED) + set_target_properties(sk-dummy PROPERTIES IMPORTED_LOCATION "${SK_DUMMY_LIBRARY}") + target_link_libraries(${TORTURE_LIBRARY} PRIVATE sk-dummy) + set(HAVE_SK_DUMMY 1) + set(SK_DUMMY_LIBRARY_PATH ${SK_DUMMY_LIBRARY}) +endif() + +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(${TORTURE_LIBRARY}) +endif (WITH_COVERAGE) + +# The shared version of the library is only useful when client testing is +# enabled +if (CLIENT_TESTING) + # create shared test library + set(TORTURE_SHARED_LIBRARY torture_shared) + + # Create a list of symbols that should be wrapped for override test + set(WRAP_SYMBOLS "") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=chacha_keysetup" + "-Wl,--wrap=chacha_ivsetup" + "-Wl,--wrap=chacha_encrypt_bytes") + list(APPEND WRAP_SYMBOLS "-Wl,--wrap=poly1305_auth") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=crypto_sign_ed25519_keypair" + "-Wl,--wrap=crypto_sign_ed25519" + "-Wl,--wrap=crypto_sign_ed25519_open") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=crypto_scalarmult_base" + "-Wl,--wrap=crypto_scalarmult") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=sntrup761_keypair" + "-Wl,--wrap=sntrup761_enc" + "-Wl,--wrap=sntrup761_dec") + list(APPEND WRAP_SYMBOLS + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_generate_key_pair" + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_validate_public_key" + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_encapsulate" + "-Wl,--wrap=libcrux_ml_kem_mlkem768_portable_decapsulate") + + add_library(${TORTURE_SHARED_LIBRARY} + SHARED + cmdline.c + torture.c + torture_key.c + torture_pki.c + torture_sk.c + torture_cmocka.c + ) + target_link_libraries(${TORTURE_SHARED_LIBRARY} PUBLIC + ${CMOCKA_LIBRARY} + ssh::static + ${WRAP_SYMBOLS} + ) + + # Link sk-dummy to torture_shared library if available + if (SK_DUMMY_LIBRARY) + target_link_libraries(${TORTURE_SHARED_LIBRARY} PRIVATE sk-dummy) + endif (SK_DUMMY_LIBRARY) + + target_compile_options(${TORTURE_SHARED_LIBRARY} PRIVATE + -DSSH_PING_EXECUTABLE="${CMAKE_CURRENT_BINARY_DIR}/ssh_ping" + -DTORTURE_SHARED + ) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(${TORTURE_SHARED_LIBRARY}) + endif (WITH_COVERAGE) +endif () + +if (ARGP_LIBRARIES) + target_link_libraries(${TORTURE_LIBRARY} + PUBLIC ${ARGP_LIBRARIES} + ) +endif() + +set(TEST_TARGET_LIBRARIES + ${TORTURE_LIBRARY} + ${TORTURE_LINK_LIBRARIES} +) + +add_subdirectory(unittests) + +# OpenSSH Capabilities are required for all unit tests +find_program(SSH_EXECUTABLE NAMES ssh) +find_program(SSH_KEYGEN_EXECUTABLE NAMES ssh-keygen) +if (SSH_EXECUTABLE) + file(SIZE ${SSH_EXECUTABLE} SSH_EXECUTABLE_SIZE) + execute_process(COMMAND ${SSH_EXECUTABLE} -V ERROR_VARIABLE OPENSSH_VERSION_STR) + string(REGEX REPLACE "^.*OpenSSH_([0-9]+).[0-9].*$" "\\1" OPENSSH_VERSION_MAJOR "${OPENSSH_VERSION_STR}") + string(REGEX REPLACE "^.*OpenSSH_[0-9]+.([0-9]).*$" "\\1" OPENSSH_VERSION_MINOR "${OPENSSH_VERSION_STR}") + set(OPENSSH_VERSION "${OPENSSH_VERSION_MAJOR}.${OPENSSH_VERSION_MINOR}") + add_definitions(-DOPENSSH_VERSION_MAJOR=${OPENSSH_VERSION_MAJOR} -DOPENSSH_VERSION_MINOR=${OPENSSH_VERSION_MINOR}) + if("${OPENSSH_VERSION}" VERSION_GREATER_EQUAL "8.1" AND SSH_KEYGEN_EXECUTABLE) + set(OPENSSH_SUPPORTS_SSHSIG 1) + message(STATUS "OpenSSH ${OPENSSH_VERSION} supports SSH signatures") + else() + set(OPENSSH_SUPPORTS_SSHSIG 0) + message(STATUS "OpenSSH ${OPENSSH_VERSION} does not support SSH signatures (requires 8.1+)") + endif() + if("${OPENSSH_VERSION}" VERSION_LESS "6.3") + # ssh - Q was introduced in 6.3 + message("Version less than 6.3, hardcoding cipher list") + set(OPENSSH_CIPHERS "aes128-ctr\naes192-ctr\naes256-ctr\narcfour256\narcfour128\naes128-gcm@openssh.com\naes256-gcm@openssh.com\naes128-cbc\n3des-cbc\nblowfish-cbc\ncast128-cbc\naes192-cbc\naes256-cbc\narcfour\nrijndael-cbc@lysator.liu.se\n") + set(OPENSSH_MACS "hmac-md5-etm@openssh.com\nhmac-sha1-etm@openssh.com\numac-64-etm@openssh.com\numac-128-etm@openssh.com\nhmac-sha2-256-etm@openssh.com\nhmac-sha2-512-etm@openssh.com\nhmac-ripemd160-etm@openssh.com\nhmac-sha1-96-etm@openssh.com\nhmac-md5-96-etm@openssh.com\nhmac-md5\nhmac-sha1\numac-64@openssh.com\numac-128@openssh.com\nhmac-sha2-256\nhmac-sha2-512\nhmac-ripemd160\nhmac-ripemd160@openssh.com\nhmac-sha1-96\nhmac-md5-96\n") + set(OPENSSH_KEX "ecdh-sha2-nistp256\necdh-sha2-nistp384\necdh-sha2-nistp521\ndiffie-hellman-group-exchange-sha256\ndiffie-hellman-group-exchange-sha1\ndiffie-hellman-group14-sha1\ndiffie-hellman-group1-sha1\n") + set(OPENSSH_KEYS "ssh-rsa\necdsa-sha2-nistp256\n") + else() + execute_process(COMMAND ${SSH_EXECUTABLE} -Q cipher OUTPUT_VARIABLE OPENSSH_CIPHERS) + execute_process(COMMAND ${SSH_EXECUTABLE} -Q mac OUTPUT_VARIABLE OPENSSH_MACS) + execute_process(COMMAND ${SSH_EXECUTABLE} -Q kex OUTPUT_VARIABLE OPENSSH_KEX) + execute_process(COMMAND ${SSH_EXECUTABLE} -Q key OUTPUT_VARIABLE OPENSSH_KEYS) + execute_process(COMMAND ${SSH_EXECUTABLE} -Q sig OUTPUT_VARIABLE OPENSSH_SIGS ERROR_QUIET) + + # We need both of them, but lets get rid of duplicate items presented in both lists + # to avoid processing too long arguments in pkd + set(OPENSSH_KEYS "${OPENSSH_KEYS}${OPENSSH_SIGS}") + string(REPLACE "\n" ";" OPENSSH_KEYS "${OPENSSH_KEYS}") + list(REMOVE_DUPLICATES OPENSSH_KEYS) + string(REPLACE ";" "\n" OPENSSH_KEYS "${OPENSSH_KEYS}") + endif() + + set(SSH_ALGORITHMS + 3des-cbc aes128-cbc aes192-cbc aes256-cbc rijndael-cbc@lysator.liu.se aes128-ctr aes192-ctr + aes256-ctr aes128-gcm@openssh.com aes256-gcm@openssh.com chacha20-poly1305@openssh.com + hmac-sha1 hmac-sha1-96 hmac-sha2-256 hmac-sha2-512 hmac-md5 hmac-md5-96 umac-64@openssh.com + umac-128@openssh.com hmac-sha1-etm@openssh.com hmac-sha1-96-etm@openssh.com + hmac-sha2-256-etm@openssh.com hmac-sha2-512-etm@openssh.com hmac-md5-etm@openssh.com + hmac-md5-96-etm@openssh.com umac-64-etm@openssh.com umac-128-etm@openssh.com + diffie-hellman-group1-sha1 diffie-hellman-group14-sha1 diffie-hellman-group14-sha256 + diffie-hellman-group16-sha512 diffie-hellman-group18-sha512 diffie-hellman-group-exchange-sha1 + diffie-hellman-group-exchange-sha256 ecdh-sha2-nistp256 ecdh-sha2-nistp384 ecdh-sha2-nistp521 + sntrup761x25519-sha512@openssh.com sntrup761x25519-sha512 + mlkem768x25519-sha256 mlkem768nistp256-sha256 mlkem1024nistp384-sha384 + curve25519-sha256 curve25519-sha256@libssh.org + ssh-ed25519 ssh-ed25519-cert-v01@openssh.com ssh-rsa + ecdsa-sha2-nistp256 ecdsa-sha2-nistp384 ecdsa-sha2-nistp521 + ssh-rsa-cert-v01@openssh.com + ecdsa-sha2-nistp256-cert-v01@openssh.com ecdsa-sha2-nistp384-cert-v01@openssh.com + ecdsa-sha2-nistp521-cert-v01@openssh.com + sk-ssh-ed25519@openssh.com sk-ecdsa-sha2-nistp256@openssh.com + sk-ssh-ed25519-cert-v01@openssh.com sk-ecdsa-sha2-nistp256-cert-v01@openssh.com + ) + foreach(ALGORITHM ${SSH_ALGORITHMS}) + string(TOUPPER ${ALGORITHM} VARNAME) + string(REGEX REPLACE "[-@.]" "_" VARNAME "OPENSSH_${VARNAME}") + + # Match the current algorithm into the complete list of OpenSSH supported algorithms. + # If matching, create an OPENSSH_CIPHER_NAME variable. + string(REGEX MATCH ".*${ALGORITHM}\n" "${VARNAME}" "${OPENSSH_CIPHERS}${OPENSSH_MACS}${OPENSSH_KEX}${OPENSSH_KEYS}") + endforeach(ALGORITHM) + + string(STRIP "${OPENSSH_CIPHERS}" OPENSSH_CIPHERS) + string(STRIP "${OPENSSH_MACS}" OPENSSH_MACS) + string(STRIP "${OPENSSH_KEX}" OPENSSH_KEX) + string(STRIP "${OPENSSH_KEYS}" OPENSSH_KEYS) + string(REPLACE "\n" "," OPENSSH_CIPHERS "${OPENSSH_CIPHERS}") + string(REPLACE "\n" "," OPENSSH_MACS "${OPENSSH_MACS}") + string(REPLACE "\n" "," OPENSSH_KEX "${OPENSSH_KEX}") + string(REPLACE "\n" "," OPENSSH_KEYS "${OPENSSH_KEYS}") + +endif() + +find_program(DROPBEAR_EXECUTABLE NAMES dbclient) +if (DROPBEAR_EXECUTABLE) + execute_process(COMMAND ${DROPBEAR_EXECUTABLE} -V ERROR_VARIABLE DROPBEAR_VERSION_STR) + string(REGEX REPLACE "^.*Dropbear v([0-9]+)\\.([0-9]+).*$" "\\1.\\2" DROPBEAR_VERSION "${DROPBEAR_VERSION_STR}") + set(DROPBEAR_VERSION "${DROPBEAR_VERSION}") + + # HMAC-SHA1 support was removed in version 2025.87 + if("${DROPBEAR_VERSION}" VERSION_LESS "2025.87") + message("Dropbear Version less than 2025.87, enabling dropbear HMAC-SHA1 tests") + add_definitions(-DDROPBEAR_SUPPORTS_HMAC_SHA1) + endif() +else() + message(STATUS "Could NOT find Dropbear (missing: dbclient executable)") + set(DROPBEAR_EXECUTABLE "/bin/false") +endif() + +find_program(PUTTY_EXECUTABLE + NAMES + plink + plink.exe + putty # Fallback for systems where plink isn't separate + DOC "Path to PuTTY plink executable for automated tests") + +if (PUTTY_EXECUTABLE) + message(STATUS "Found PuTTY client: ${PUTTY_EXECUTABLE}") +else() + set(PUTTY_EXECUTABLE "/bin/putty-not-found") +endif() + +find_program(PUTTYGEN_EXECUTABLE + NAMES + puttygen + puttygen.exe + DOC "Path to PuTTYgen executable for key conversion") + +if (PUTTYGEN_EXECUTABLE) + message(STATUS "Found PuTTY keygen: ${PUTTYGEN_EXECUTABLE}") +else() + set(PUTTYGEN_EXECUTABLE "/bin/puttygen-not-found") +endif() + +find_program(SSHD_EXECUTABLE + NAME + sshd + PATHS + /sbin + /usr/sbin + /usr/local/sbin) + +if (WITH_PKCS11_URI) + find_package(softhsm) + if (NOT SOFTHSM_FOUND) + message(SEND_ERROR "Could not find softhsm module!") + endif (NOT SOFTHSM_FOUND) + find_library(PKCS11SPY + NAMES + pkcs11-spy.so + ) + + #Copy the script to setup PKCS11 tokens + file(COPY pkcs11/setup-softhsm-tokens.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/pkcs11 FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) +endif (WITH_PKCS11_URI) + +if (CLIENT_TESTING OR SERVER_TESTING) + find_package(socket_wrapper 1.1.5 REQUIRED) + find_package(nss_wrapper 1.1.2 REQUIRED) + find_package(uid_wrapper 1.2.0 REQUIRED) + find_package(pam_wrapper 1.0.1 REQUIRED) + find_package(priv_wrapper 1.0.0) + + if (NOT SSHD_EXECUTABLE) + message(SEND_ERROR "Could not find sshd which is required for client testing") + endif() + find_program(NCAT_EXECUTABLE + NAME + ncat + PATHS + /bin + /usr/bin + /usr/local/bin) + + set(LOCAL_USER "nobody") + set(LOCAL_UID "65533") + find_program(ID_EXECUTABLE NAMES id) + find_program(WHO_EXECUTABLE NAMES whoami) + if (ID_EXECUTABLE AND WHO_EXECUTABLE) + execute_process(COMMAND ${WHO_EXECUTABLE} OUTPUT_VARIABLE LOCAL_USER OUTPUT_STRIP_TRAILING_WHITESPACE) + execute_process(COMMAND ${ID_EXECUTABLE} -u OUTPUT_VARIABLE LOCAL_UID OUTPUT_STRIP_TRAILING_WHITESPACE) + endif() + + find_program(TIMEOUT_EXECUTABLE + NAME + timeout + PATHS + /bin + /usr/bin + /usr/local/bin) + if (TIMEOUT_EXECUTABLE) + set(WITH_TIMEOUT "1") + endif() + + # For chroot() use priv_wrapper package if found, or internal chroot_wrapper + if (priv_wrapper_FOUND) + set(CHROOT_WRAPPER "${PRIV_WRAPPER_LIBRARY}") + else() + add_library(chroot_wrapper SHARED chroot_wrapper.c) + set(CHROOT_WRAPPER_LIBRARY ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chroot_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(TEST_TARGET_LIBRARIES + ${TEST_TARGET_LIBRARIES} + chroot_wrapper + ) + set(CHROOT_WRAPPER "${CHROOT_WRAPPER_LIBRARY}") + endif() + + # fs wrapper + add_library(fs_wrapper SHARED fs_wrapper.c) + set(FS_WRAPPER_LIBRARY + ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}fs_wrapper${CMAKE_SHARED_LIBRARY_SUFFIX}) + set(TEST_TARGET_LIBRARIES + ${TEST_TARGET_LIBRARIES} + fs_wrapper + ) + set(FS_WRAPPER "${FS_WRAPPER_LIBRARY}") + + # ssh_ping + add_executable(ssh_ping ssh_ping.c) + target_compile_options(ssh_ping PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(ssh_ping ssh::static pthread) + + # homedir will be used in passwd + set(HOMEDIR ${CMAKE_CURRENT_BINARY_DIR}/home) + + ### Setup nss_wrapper + configure_file(etc/passwd.in ${CMAKE_CURRENT_BINARY_DIR}/etc/passwd @ONLY) + configure_file(etc/shadow.in ${CMAKE_CURRENT_BINARY_DIR}/etc/shadow @ONLY) + configure_file(etc/group.in ${CMAKE_CURRENT_BINARY_DIR}/etc/group @ONLY) + configure_file(etc/hosts.in ${CMAKE_CURRENT_BINARY_DIR}/etc/hosts @ONLY) + + ### Setup pam_wrapper + configure_file(etc/pam_matrix_passdb.in ${CMAKE_CURRENT_BINARY_DIR}/etc/pam_matrix_passdb @ONLY) + configure_file(etc/pam.d/sshd.in ${CMAKE_CURRENT_BINARY_DIR}/etc/pam.d/sshd @ONLY) + + + set(TORTURE_ENVIRONMENT + "LD_PRELOAD=${FS_WRAPPER}:${SOCKET_WRAPPER_LIBRARY}:${NSS_WRAPPER_LIBRARY}:${UID_WRAPPER_LIBRARY}:${PAM_WRAPPER_LIBRARY}:${CHROOT_WRAPPER}") + if (priv_wrapper_FOUND) + list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER=1 PRIV_WRAPPER_CHROOT_DISABLE=1) + list(APPEND TORTURE_ENVIRONMENT PRIV_WRAPPER_PRCTL_DISABLE="ALL" PRIV_WRAPPER_SETRLIMIT_DISABLE="ALL") + endif() + list(APPEND TORTURE_ENVIRONMENT UID_WRAPPER=1 UID_WRAPPER_ROOT=1) + list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_PASSWD=${CMAKE_CURRENT_BINARY_DIR}/etc/passwd) + list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_SHADOW=${CMAKE_CURRENT_BINARY_DIR}/etc/shadow) + list(APPEND TORTURE_ENVIRONMENT NSS_WRAPPER_GROUP=${CMAKE_CURRENT_BINARY_DIR}/etc/group) + list(APPEND TORTURE_ENVIRONMENT PAM_WRAPPER_SERVICE_DIR=${CMAKE_CURRENT_BINARY_DIR}/etc/pam.d) + list(APPEND TORTURE_ENVIRONMENT LSAN_OPTIONS=suppressions=${CMAKE_CURRENT_SOURCE_DIR}/suppressions/lsan.supp) + list(APPEND TORTURE_ENVIRONMENT OPENSSL_ENABLE_SHA1_SIGNATURES=1) + + # Give bob some keys + file(COPY keys/id_rsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_rsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + # Same as id_rsa, protected with passphrase "secret" + file(COPY keys/id_rsa_protected DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_rsa_protected.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ecdsa DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ecdsa.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519 DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + # Security key support + file(COPY keys/id_ecdsa_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ecdsa_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519_sk DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/id_ed25519_sk.pub DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/home/bob/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + + # Allow to auth with bob's public keys on alice and doe account + configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys @ONLY) + configure_file(keys/id_rsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/authorized_keys @ONLY) + configure_file(keys/id_ecdsa.pub ${CMAKE_CURRENT_BINARY_DIR}/home/frank/.ssh/authorized_keys @ONLY) + + # append ECDSA public key + file(READ keys/id_ecdsa.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # append ed25519 public key + file(READ keys/id_ed25519.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # append sk-ecdsa public key + file(READ keys/id_ecdsa_sk.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # append sk-ed25519 public key + file(READ keys/id_ed25519_sk.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/alice/.ssh/authorized_keys "${CONTENTS}") + + # Allow to auth with bob his public keys on charlie account + configure_file(keys/pkcs11/id_pkcs11_rsa_openssh.pub ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys @ONLY) + + # append ECDSA public key + file(READ keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + file(READ keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + file(READ keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + file(READ keys/pkcs11/id_pkcs11_ed25519_openssh.pub CONTENTS) + file(APPEND ${CMAKE_CURRENT_BINARY_DIR}/home/charlie/.ssh/authorized_keys "${CONTENTS}") + + # Copy the signed key to an doe's homedir. + file(COPY keys/certauth/id_rsa DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa.pub DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) + file(COPY keys/certauth/id_rsa-cert.pub DESTINATION + ${CMAKE_CURRENT_BINARY_DIR}/home/doe/.ssh/ FILE_PERMISSIONS OWNER_READ OWNER_WRITE) +endif () + +file(COPY gss/kdcsetup.sh DESTINATION ${CMAKE_CURRENT_BINARY_DIR}/gss FILE_PERMISSIONS OWNER_READ OWNER_WRITE OWNER_EXECUTE) + +message(STATUS "TORTURE_ENVIRONMENT=${TORTURE_ENVIRONMENT}") + +configure_file(tests_config.h.cmake ${CMAKE_CURRENT_BINARY_DIR}/tests_config.h) + +if (WITH_BENCHMARKS) + add_subdirectory(benchmarks) +endif () + +if (CLIENT_TESTING) + add_subdirectory(client) + + # Only add override testing if testing the client + add_subdirectory(external_override) +endif () + +if (WITH_SERVER AND SERVER_TESTING) + add_subdirectory(pkd) + add_subdirectory(server) +endif () + +if (FUZZ_TESTING) + add_subdirectory(fuzz) +endif() + +add_custom_target(test_memcheck + # FIXME: The pkd_hello_i1 test is skipped under valgrind as it times out + # Passing suppression file is also stupid so lets go with override here: + # https://stackoverflow.com/a/56116311 + COMMAND ${CMAKE_CTEST_COMMAND} -E pkd_hello_i1 + --output-on-failure --force-new-ctest-process --test-action memcheck + --overwrite MemoryCheckSuppressionFile=${CMAKE_SOURCE_DIR}/tests/valgrind.supp + WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/benchmarks/CMakeLists.txt new file mode 100644 index 000000000000..c7b245a5e924 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/CMakeLists.txt @@ -0,0 +1,17 @@ +project(libssh-benchmarks C) + +set(benchmarks_SRCS + bench_scp.c bench_raw.c benchmarks.c latency.c +) +if (WITH_SFTP) + set(benchmarks_SRCS + ${benchmarks_SRCS} + bench_sftp.c + ) +endif (WITH_SFTP) + +include_directories(${libssh_BINARY_DIR}) + +add_executable(benchmarks ${benchmarks_SRCS}) + +target_link_libraries(benchmarks ssh::static pthread) diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench1.sh b/src/libs/libssh-0.12.2/tests/benchmarks/bench1.sh new file mode 100755 index 000000000000..6b3b20f32190 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench1.sh @@ -0,0 +1,14 @@ +#!/bin/bash +export CIPHER=aes128-cbc +export DEST=localhost + +echo "Upload raw SSH statistics" +echo "local machine: $(uname -a)" +echo "Cipher : $CIPHER ; Destination : $DEST ($(ssh $DEST uname -a))" +echo "Local ssh version: $(ssh -V 2>&1)" +echo "Ping latency to $DEST": +ping -q -c 1 -n $DEST +echo "Destination $DEST SSHD version : $(echo | nc $DEST 22 | head -n1)" +echo "ssh login latency :$( (command time -f user:%U ssh $DEST 'id > /dev/null') 2>&1)" +./generate.py | dd bs=4096 count=100000 | time ssh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 + diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench2.sh b/src/libs/libssh-0.12.2/tests/benchmarks/bench2.sh new file mode 100755 index 000000000000..cf240fda3566 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench2.sh @@ -0,0 +1,14 @@ +#!/bin/bash +export CIPHER=aes128-cbc +export DEST=localhost + +echo "Upload raw SSH statistics" +echo "local machine: $(uname -a)" +echo "Cipher : $CIPHER ; Destination : $DEST ($(ssh $DEST uname -a))" +echo "Local ssh version: $(samplessh -V 2>&1)" +echo "Ping latency to $DEST": +ping -q -c 1 -n $DEST +echo "Destination $DEST SSHD version : $(echo | nc $DEST 22 | head -n1)" +echo "ssh login latency :$( (command time -f user:%U samplessh $DEST 'id > /dev/null') 2>&1)" +./generate.py | dd bs=4096 count=100000 | strace samplessh -c $CIPHER $DEST "dd bs=4096 of=/dev/null" 2>&1 + diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench_raw.c b/src/libs/libssh-0.12.2/tests/benchmarks/bench_raw.c new file mode 100644 index 000000000000..05a6ceb06a5d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench_raw.c @@ -0,0 +1,310 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include +#include +#include + +#define PYTHON_PATH "/usr/bin/python" + +const char python_eater[]= +"#!/usr/bin/python\n" +"import sys\n" +"print 'go'\n" +"sys.stdout.flush()\n" +"toread=XXXXXXXXXX\n" +"read=0\n" +"while(read < toread):\n" +" buffersize=toread-read\n" +" if(buffersize > 4096):\n" +" buffersize=4096\n" +" r=len(sys.stdin.read(buffersize))\n" +" read+=r\n" +" if(r<=0):\n" +" print 'error'\n" +" exit()\n" +"print 'done'\n"; + +static char *get_python_eater(unsigned long bytes){ + char *eater=malloc(sizeof(python_eater)); + char *ptr; + char buf[12]; + + if (eater == NULL) { + return NULL; + } + memcpy(eater,python_eater,sizeof(python_eater)); + ptr=strstr(eater,"XXXXXXXXXX"); + if(!ptr){ + free(eater); + return NULL; + } + sprintf(buf,"0x%.8lx",bytes); + memcpy(ptr,buf,10); + return eater; +} + +/** @internal + * @brief uploads a script (python or other) at a specific path on the + * remote host + * @param[in] session an active SSH session + * @param[in] path to copy the file + * @param[in] content of the file to copy + * @return 0 on success, -1 on error + */ +static int upload_script(ssh_session session, const char *path, + const char *script){ + ssh_channel channel; + char cmd[128]; + int err; + + channel=ssh_channel_new(session); + if(!channel) + goto error; + if(ssh_channel_open_session(channel) == SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"cat > %s",path); + if(ssh_channel_request_exec(channel,cmd) == SSH_ERROR) + goto error; + err=ssh_channel_write(channel,script,strlen(script)); + if(err == SSH_ERROR) + goto error; + if(ssh_channel_send_eof(channel) == SSH_ERROR) + goto error; + if(ssh_channel_close(channel) == SSH_ERROR) + goto error; + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error while copying script : %s\n",ssh_get_error(session)); + return -1; +} + +/** @internal + * @brief benchmarks a raw upload (simple upload in a SSH channel) using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_raw_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + char *script; + char cmd[128]; + int err; + ssh_channel channel; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + + bytes = args->datasize * 1024 * 1024; + script = get_python_eater(bytes); + if (script == NULL) { + return -1; + } + err=upload_script(session,"/tmp/eater.py",script); + free(script); + if(err<0) + return err; + channel=ssh_channel_new(session); + if(channel == NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"%s /tmp/eater.py", PYTHON_PATH); + if(ssh_channel_request_exec(channel,cmd)==SSH_ERROR) + goto error; + err = ssh_channel_read(channel, buffer, sizeof(buffer) - 1, 0); + if (err == SSH_ERROR) + goto error; + if (err == SSH_AGAIN) { + fprintf(stderr, "ssh_channel_read timeout"); + goto error; + } + buffer[err]=0; + if(!strstr(buffer,"go")){ + fprintf(stderr,"parse error : %s\n",buffer); + ssh_channel_close(channel); + ssh_channel_free(channel); + return -1; + } + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=ssh_channel_write(channel,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += w; + } + + if(args->verbose>0) + fprintf(stdout,"Finished upload, now waiting the ack\n"); + err = ssh_channel_read(channel, buffer, 5, 0); + if (err == SSH_ERROR) + goto error; + if (err == SSH_AGAIN) { + fprintf(stderr, "ssh_channel_read timeout"); + goto error; + } + buffer[err]=0; + if(!strstr(buffer,"done")){ + fprintf(stderr,"parse error : %s\n",buffer); + ssh_channel_close(channel); + ssh_channel_free(channel); + return -1; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_channel_close(channel); + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error during raw upload : %s\n",ssh_get_error(session)); + if(channel){ + ssh_channel_close(channel); + ssh_channel_free(channel); + } + return -1; +} + +const char python_giver[] = +"#!/usr/bin/python\n" +"import sys\n" +"r=sys.stdin.read(2)\n" +"towrite=XXXXXXXXXX\n" +"wrote=0\n" +"mtu = 32786\n" +"buf = 'A'*mtu\n" +"while(wrote < towrite):\n" +" buffersize=towrite-wrote\n" +" if(buffersize > mtu):\n" +" buffersize=mtu\n" +" if(buffersize == mtu):\n" +" sys.stdout.write(buf)\n" +" else:\n" +" sys.stdout.write('A'*buffersize)\n" +" wrote+=buffersize\n" +"sys.stdout.flush()\n"; + +static char *get_python_giver(unsigned long bytes){ + char *giver=malloc(sizeof(python_giver)); + char *ptr; + char buf[12]; + + if (giver == NULL) { + return NULL; + } + memcpy(giver,python_giver,sizeof(python_giver)); + ptr=strstr(giver,"XXXXXXXXXX"); + if(!ptr){ + free(giver); + return NULL; + } + sprintf(buf,"0x%.8lx",bytes); + memcpy(ptr,buf,10); + return giver; +} + +/** @internal + * @brief benchmarks a raw download (simple upload in a SSH channel) using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_raw_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + char *script; + char cmd[128]; + int err; + ssh_channel channel; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + + bytes = args->datasize * 1024 * 1024; + script = get_python_giver(bytes); + if (script == NULL) { + return -1; + } + err=upload_script(session,"/tmp/giver.py",script); + free(script); + if(err<0) + return err; + channel=ssh_channel_new(session); + if(channel == NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + snprintf(cmd,sizeof(cmd),"%s /tmp/giver.py", PYTHON_PATH); + if(ssh_channel_request_exec(channel,cmd)==SSH_ERROR) + goto error; + if((err=ssh_channel_write(channel,"go",2))==SSH_ERROR) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long toread = bytes - total; + int r; + if(toread > args->chunksize) + toread = args->chunksize; + r=ssh_channel_read(channel,buffer,toread,0); + if (r == SSH_ERROR) + goto error; + if (r == SSH_AGAIN) { + fprintf(stderr, "ssh_channel_read timeout"); + goto error; + } + total += r; + } + + if(args->verbose>0) + fprintf(stdout,"Finished download\n"); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_channel_close(channel); + ssh_channel_free(channel); + return 0; +error: + fprintf(stderr,"Error during raw upload : %s\n",ssh_get_error(session)); + if(channel){ + ssh_channel_close(channel); + ssh_channel_free(channel); + } + return -1; +} diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench_scp.c b/src/libs/libssh-0.12.2/tests/benchmarks/bench_scp.c new file mode 100644 index 000000000000..71aba2854020 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench_scp.c @@ -0,0 +1,150 @@ +/* bench_scp.c + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include +#include + +#define SCPDIR "/tmp/" +#define SCPFILE "scpbenchmark" + +/** @internal + * @brief benchmarks a scp upload using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_scp_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + ssh_scp scp; + + bytes = args->datasize * 1024 * 1024; + scp = ssh_scp_new(session,SSH_SCP_WRITE,SCPDIR); + if(scp == NULL) + goto error; + if(ssh_scp_init(scp)==SSH_ERROR) + goto error; + if(ssh_scp_push_file(scp,SCPFILE,bytes,0777) != SSH_OK) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=ssh_scp_write(scp,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += towrite; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +error: + fprintf(stderr,"Error during scp upload : %s\n",ssh_get_error(session)); + if(scp){ + ssh_scp_close(scp); + ssh_scp_free(scp); + } + return -1; +} + +/** @internal + * @brief benchmarks a scp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_scp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + ssh_scp scp; + int r; + size_t size; + + bytes = args->datasize * 1024 * 1024; + scp = ssh_scp_new(session,SSH_SCP_READ,SCPDIR SCPFILE); + if(scp == NULL) + goto error; + if(ssh_scp_init(scp)==SSH_ERROR) + goto error; + r=ssh_scp_pull_request(scp); + if(r == SSH_SCP_REQUEST_NEWFILE){ + size=ssh_scp_request_get_size(scp); + if(bytes > size){ + printf("Only %zd bytes available (on %lu requested).\n",size,bytes); + bytes = size; + } + if(size > bytes){ + printf("File is %zd bytes (on %lu requested). Will cut the end\n",size,bytes); + } + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + ssh_scp_accept_request(scp); + while(total < bytes){ + unsigned long toread = bytes - total; + if(toread > args->chunksize) + toread = args->chunksize; + r=ssh_scp_read(scp,buffer,toread); + if(r == SSH_ERROR || r == 0) + goto error; + total += r; + } + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + } else { + fprintf(stderr,"Expected SSH_SCP_REQUEST_NEWFILE, got %d\n",r); + goto error; + } + ssh_scp_close(scp); + ssh_scp_free(scp); + return 0; +error: + fprintf(stderr,"Error during scp download : %s\n",ssh_get_error(session)); + if(scp){ + ssh_scp_close(scp); + ssh_scp_free(scp); + } + return -1; +} diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/bench_sftp.c b/src/libs/libssh-0.12.2/tests/benchmarks/bench_sftp.c new file mode 100644 index 000000000000..e6c6824847f9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/bench_sftp.c @@ -0,0 +1,636 @@ +/* bench_sftp.c + * + * This file is part of the SSH Library + * + * Copyright (c) 2011 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include +#include +#include +#include +#include +#include + +#define SFTPDIR "/tmp/" +#define SFTPFILE "scpbenchmark" + +/** @internal + * @brief benchmarks a synchronous sftp upload using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_sync_sftp_up (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file = NULL; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDWR | O_CREAT | O_TRUNC, 0777); + if(!file) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting upload of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long towrite = bytes - total; + int w; + if(towrite > args->chunksize) + towrite = args->chunksize; + w=sftp_write(file,buffer,towrite); + if(w == SSH_ERROR) + goto error; + total += w; + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"Upload took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + return 0; +error: + fprintf(stderr,"Error during scp upload : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + return -1; +} + +/** @internal + * @brief benchmarks a synchronous sftp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_sync_sftp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file = NULL; + int r; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDONLY,0); + if(!file) + goto error; + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now\n",bytes); + timestamp_init(&ts); + while(total < bytes){ + unsigned long toread = bytes - total; + if(toread > args->chunksize) + toread = args->chunksize; + r=sftp_read(file,buffer,toread); + if(r == SSH_ERROR) + goto error; + total += r; + /* we had a smaller file */ + if(r==0){ + fprintf(stdout,"File smaller than expected : %lu (expected %lu).\n",total,bytes); + bytes = total; + break; + } + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + return 0; +error: + fprintf(stderr,"Error during sftp download : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + return -1; +} + +/** @internal + * @brief benchmarks an asynchronous sftp download using an + * existing SSH session. + * @param[in] session Open SSH session + * @param[in] args Parsed command line arguments + * @param[out] bps The calculated bytes per second obtained via benchmark. + * @return 0 on success, -1 on error. + */ +int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, + float *bps){ + unsigned long bytes; + struct timestamp_struct ts; + float ms=0.0; + unsigned long total=0; + sftp_session sftp; + sftp_file file = NULL; + int r,i; + int warned = 0; + unsigned long toread; + int *ids=NULL; + int concurrent_downloads = args->concurrent_requests; + + bytes = args->datasize * 1024 * 1024; + sftp = sftp_new(session); + if(sftp == NULL) + goto error; + if(sftp_init(sftp)==SSH_ERROR) + goto error; + file = sftp_open(sftp,SFTPDIR SFTPFILE,O_RDONLY,0); + if(!file) + goto error; + ids = malloc(concurrent_downloads * sizeof(int)); + if (ids == NULL) { + return -1; + } + if(args->verbose>0) + fprintf(stdout,"Starting download of %lu bytes now, using %d concurrent downloads\n",bytes, + concurrent_downloads); + timestamp_init(&ts); + for (i=0;ichunksize); + if(ids[i]==SSH_ERROR) + goto error; + } + i=0; + while(total < bytes){ + r = sftp_async_read(file, buffer, args->chunksize, ids[i]); + if(r == SSH_ERROR) + goto error; + total += r; + if(r != (int)args->chunksize && total != bytes && !warned){ + fprintf(stderr,"async_sftp_download : receiving short reads (%d, requested %d) " + "the received file will be corrupted and shorted. Adapt chunksize to %d\n", + r, args->chunksize,r); + warned = 1; + } + /* we had a smaller file */ + if(r==0){ + fprintf(stdout,"File smaller than expected : %lu (expected %lu).\n",total,bytes); + bytes = total; + break; + } + toread = bytes - total; + if(toread < args->chunksize * concurrent_downloads){ + /* we've got enough launched downloads */ + ids[i]=-1; + } + if(toread > args->chunksize) + toread = args->chunksize; + ids[i]=sftp_async_read_begin(file,toread); + if(ids[i] == SSH_ERROR) + goto error; + i = (i+1) % concurrent_downloads; + } + sftp_close(file); + ms=elapsed_time(&ts); + *bps=8000 * (float)bytes / ms; + if(args->verbose > 0) + fprintf(stdout,"download took %f ms for %lu bytes, at %f bps\n",ms, + bytes,*bps); + sftp_free(sftp); + free(ids); + return 0; +error: + fprintf(stderr,"Error during sftp download : %s\n",ssh_get_error(session)); + if(file) + sftp_close(file); + if(sftp) + sftp_free(sftp); + free(ids); + return -1; +} + +int benchmarks_async_sftp_aio_down(ssh_session session, + struct argument_s *args, + float *bps) +{ + sftp_session sftp = NULL; + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + + struct ssh_list *aio_queue = NULL; + + int concurrent_downloads = args->concurrent_requests; + size_t chunksize; + struct timestamp_struct ts = {0}; + float ms = 0.0f; + + size_t total_bytes = args->datasize * 1024 * 1024; + size_t total_bytes_requested = 0, total_bytes_read = 0; + size_t bufsize = args->chunksize; + size_t to_read; + ssize_t bytes_read, bytes_requested; + int warned = 0, i, rc; + + sftp = sftp_new(session); + if (sftp == NULL) { + fprintf(stderr, "Error during sftp aio download: %s\n", + ssh_get_error(session)); + return -1; + } + + /* + * Errors which are logged in the ssh session are reported after + * jumping to the goto label, errors which aren't logged inside the + * ssh session are reported before jumping to that label + */ + + rc = sftp_init(sftp); + if (rc == SSH_ERROR) { + goto error; + } + + li = sftp_limits(sftp); + if (li == NULL) { + goto error; + } + + if (args->chunksize > li->max_read_length) { + chunksize = li->max_read_length; + if (args->verbose > 0) { + fprintf(stdout, + "Using the chunk size %zu (not the set size %u), " + "to respect the max data limit for read packet\n", + chunksize, args->chunksize); + } + } else { + chunksize = args->chunksize; + } + + file = sftp_open(sftp, SFTPDIR SFTPFILE, O_RDONLY, 0); + if (file == NULL) { + goto error; + } + + aio_queue = ssh_list_new(); + if (aio_queue == NULL) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory\n"); + goto error; + } + + if (args->verbose > 0) { + fprintf(stdout, + "Starting download of %zu bytes now, " + "using %d concurrent downloads.\n", + total_bytes, concurrent_downloads); + } + + timestamp_init(&ts); + + for (i = 0; + i < concurrent_downloads && total_bytes_requested < total_bytes; + ++i) { + to_read = total_bytes - total_bytes_requested; + if (to_read > chunksize) { + to_read = chunksize; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_read) { + fprintf(stderr, + "Error during sftp aio download: sftp_aio_begin_read() " + "requesting less bytes even when the number of bytes " + "asked to read are within the max limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory"); + sftp_aio_free(aio); + goto error; + } + } + + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + bytes_read = sftp_aio_wait_read(&aio, buffer, bufsize); + if (bytes_read == -1) { + goto error; + } + + total_bytes_read += (size_t)bytes_read; + if (bytes_read == 0) { + fprintf(stdout , + "File smaller than expected: %zu bytes (expected %zu).\n", + total_bytes_read, total_bytes); + break; + } + + if (total_bytes_read != total_bytes && + (size_t)bytes_read != chunksize && + warned != 1) { + fprintf(stderr, + "async_sftp_aio_download: Receiving short reads " + "(%zu, expected %zu) before encountering eof, " + "the received file will be corrupted and shorted. " + "Adapt chunksize to %zu.\n", + bytes_read, chunksize, bytes_read); + warned = 1; + } + + if (total_bytes_requested == total_bytes) { + /* No need to issue more requests */ + continue; + } + + /* else issue a request */ + to_read = total_bytes - total_bytes_requested; + if (to_read > chunksize) { + to_read = chunksize; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_read) { + fprintf(stderr, + "Error during sftp aio download: sftp_aio_begin_read() " + "requesting less bytes even when the number of bytes " + "asked to read are within the max limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio download: Insufficient memory\n"); + sftp_aio_free(aio); + goto error; + } + } + + sftp_close(file); + ms = elapsed_time(&ts); + *bps = (float)(8000 * total_bytes_read) / ms; + if (args->verbose > 0) { + fprintf(stdout, "Download took %f ms for %zu bytes at %f bps.\n", + ms, total_bytes_read, *bps); + } + + ssh_list_free(aio_queue); + sftp_limits_free(li); + sftp_free(sftp); + return 0; + +error: + rc = ssh_get_error_code(session); + if (rc != SSH_NO_ERROR) { + fprintf(stderr, "Error during sftp aio download: %s\n", + ssh_get_error(session)); + } + + /* Release aio structures corresponding to outstanding requests */ + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + sftp_aio_free(aio); + } + + ssh_list_free(aio_queue); + sftp_close(file); + sftp_limits_free(li); + sftp_free(sftp); + return -1; +} + +int benchmarks_async_sftp_aio_up(ssh_session session, + struct argument_s *args, + float *bps) +{ + sftp_session sftp = NULL; + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; + + int concurrent_uploads = args->concurrent_requests; + size_t chunksize; + struct timestamp_struct ts = {0}; + float ms = 0.0f; + + size_t total_bytes = args->datasize * 1024 * 1024; + size_t to_write, total_bytes_requested = 0; + ssize_t bytes_written, bytes_requested; + int i, rc; + + sftp = sftp_new(session); + if (sftp == NULL) { + fprintf(stderr, "Error during sftp aio upload: %s\n", + ssh_get_error(session)); + return -1; + } + + /* + * Errors which are logged in the ssh session are reported after + * jumping to the goto label, errors which aren't logged inside the + * ssh session are reported before jumping to that label + */ + + rc = sftp_init(sftp); + if (rc == SSH_ERROR) { + goto error; + } + + li = sftp_limits(sftp); + if (li == NULL) { + goto error; + } + + if (args->chunksize > li->max_write_length) { + chunksize = li->max_write_length; + if (args->verbose > 0) { + fprintf(stdout, + "Using the chunk size %zu (not the set size %u), " + "to respect the max data limit for write packet\n", + chunksize, args->chunksize); + } + } else { + chunksize = args->chunksize; + } + + file = sftp_open(sftp, SFTPDIR SFTPFILE, + O_WRONLY | O_CREAT | O_TRUNC, 0777); + if (file == NULL) { + goto error; + } + + aio_queue = ssh_list_new(); + if (aio_queue == NULL) { + fprintf(stderr, "Error during sftp aio upload: Insufficient memory\n"); + goto error; + } + + if (args->verbose > 0) { + fprintf(stdout, + "Starting upload of %zu bytes now, " + "using %d concurrent uploads.\n", + total_bytes, concurrent_uploads); + } + + timestamp_init(&ts); + + for (i = 0; + i < concurrent_uploads && total_bytes_requested < total_bytes; + ++i) { + to_write = total_bytes - total_bytes_requested; + if (to_write > chunksize) { + to_write = chunksize; + } + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_write) { + fprintf(stderr, + "Error during sftp aio upload: sftp_aio_begin_write() " + "requesting less bytes even when the number of bytes " + "asked to write are within the max write limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += (size_t)bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio upload: Insufficient memory\n"); + sftp_aio_free(aio); + goto error; + } + } + + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + bytes_written = sftp_aio_wait_write(&aio); + if (bytes_written == SSH_ERROR) { + goto error; + } + + if (total_bytes_requested == total_bytes) { + /* No need to issue more requests */ + continue; + } + + /* else issue a request */ + to_write = total_bytes - total_bytes_requested; + if (to_write > chunksize) { + to_write = chunksize; + } + + bytes_requested = sftp_aio_begin_write(file, buffer, to_write, &aio); + if (bytes_requested == SSH_ERROR) { + goto error; + } + + if ((size_t)bytes_requested != to_write) { + fprintf(stderr, + "Error during sftp aio upload: sftp_aio_begin_write() " + "requesting less bytes even when the number of bytes " + "asked to write are within the max write limit"); + sftp_aio_free(aio); + goto error; + } + + total_bytes_requested += bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + if (rc == SSH_ERROR) { + fprintf(stderr, + "Error during sftp aio upload: Insufficient memory\n"); + sftp_aio_free(aio); + goto error; + } + } + + sftp_close(file); + ms = elapsed_time(&ts); + *bps = (float)(8000 * total_bytes) / ms; + if (args->verbose > 0) { + fprintf(stdout, "Upload took %f ms for %zu bytes at %f bps.\n", + ms, total_bytes, *bps); + } + + ssh_list_free(aio_queue); + sftp_limits_free(li); + sftp_free(sftp); + return 0; + +error: + rc = ssh_get_error_code(session); + if (rc != SSH_NO_ERROR) { + fprintf(stderr, "Error during sftp aio upload: %s\n", + ssh_get_error(session)); + } + + /* Release aio structures corresponding to outstanding requests */ + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + sftp_aio_free(aio); + } + + ssh_list_free(aio_queue); + sftp_close(file); + sftp_limits_free(li); + sftp_free(sftp); + return -1; +} diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.c b/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.c new file mode 100644 index 000000000000..4229d5a5bde0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.c @@ -0,0 +1,494 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define LIBSSH_STATIC + +#include "config.h" +#include "benchmarks.h" +#include + +#include +#include +#include + +struct benchmark benchmarks[] = { + { + .name = "benchmark_raw_upload", + .fct = benchmarks_raw_up, + .enabled = 0 + }, + { + .name = "benchmark_raw_download", + .fct = benchmarks_raw_down, + .enabled = 0 + }, + { + .name = "benchmark_scp_upload", + .fct = benchmarks_scp_up, + .enabled = 0 + }, + { + .name = "benchmark_scp_download", + .fct = benchmarks_scp_down, + .enabled = 0 + }, +#ifdef WITH_SFTP + { + .name = "benchmark_sync_sftp_upload", + .fct = benchmarks_sync_sftp_up, + .enabled = 0 + }, + { + .name = "benchmark_sync_sftp_download", + .fct = benchmarks_sync_sftp_down, + .enabled = 0 + }, + { + .name="benchmark_async_sftp_download", + .fct=benchmarks_async_sftp_down, + .enabled=0 + }, + { + .name = "benchmark_async_sftp_aio_download", + .fct = benchmarks_async_sftp_aio_down, + .enabled = 0 + }, + { + .name = "benchmark_async_sftp_aio_upload", + .fct = benchmarks_async_sftp_aio_up, + .enabled = 0 + } +#endif /* WITH_SFTP */ +}; + +#ifdef HAVE_ARGP_H +#include + +const char *argp_program_version = "libssh benchmarks 2011-08-28"; +const char *argp_program_bug_address = "Aris Adamantiadis "; + +static char **cmdline = NULL; + +/* Program documentation. */ +static char doc[] = "libssh benchmarks"; + + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make libssh benchmark more verbose", + .group = 0 + }, + { + .name = "raw-upload", + .key = '1', + .arg = NULL, + .flags = 0, + .doc = "Upload raw data using channel", + .group = 0 + }, + { + .name = "raw-download", + .key = '2', + .arg = NULL, + .flags = 0, + .doc = "Download raw data using channel", + .group = 0 + }, + { + .name = "scp-upload", + .key = '3', + .arg = NULL, + .flags = 0, + .doc = "Upload data using SCP", + .group = 0 + }, + { + .name = "scp-download", + .key = '4', + .arg = NULL, + .flags = 0, + .doc = "Download data using SCP", + .group = 0 + }, + { + .name = "sync-sftp-upload", + .key = '5', + .arg = NULL, + .flags = 0, + .doc = "Upload data using synchronous SFTP", + .group = 0 + }, + { + .name = "sync-sftp-download", + .key = '6', + .arg = NULL, + .flags = 0, + .doc = "Download data using synchronous SFTP (slow)", + .group = 0 + }, + { + .name = "async-sftp-download", + .key = '7', + .arg = NULL, + .flags = 0, + .doc = "Download data using asynchronous SFTP (fast)", + .group = 0 + }, + { + .name = "async-sftp-aio-download", + .key = '8', + .arg = NULL, + .flags = 0, + .doc = "Download data using asynchronous SFTP AIO api (fast)", + .group = 0 + }, + { + .name = "async-sftp-aio-upload", + .key = '9', + .arg = NULL, + .flags = 0, + .doc = "Upload data using asynchronous SFTP AIO api (fast)", + .group = 0 + }, + { + .name = "host", + .key = 'h', + .arg = "HOST", + .flags = 0, + .doc = "Add a host to connect for benchmark (format user@hostname)", + .group = 0 + }, + { + .name = "size", + .key = 's', + .arg = "MBYTES", + .flags = 0, + .doc = "MBytes of data to send/receive per test", + .group = 0 + }, + { + .name = "chunk", + .key = 'c', + .arg = "bytes", + .flags = 0, + .doc = "size of data chunks to send/receive", + .group = 0 + }, + { + .name = "prequests", + .key = 'p', + .arg = "number [20]", + .flags = 0, + .doc = "[async SFTP] number of concurrent requests", + .group = 0 + }, + { + .name = "cipher", + .key = 'C', + .arg = "cipher", + .flags = 0, + .doc = "Cryptographic cipher to be used", + .group = 0 + }, + + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; + + /* arg is currently not used */ + (void) arg; + + switch (key) { + case '1': + case '2': + case '3': + case '4': + case '5': + case '6': + case '7': + case '8': + case '9': + benchmarks[key - '1'].enabled = 1; + arguments->ntests++; + break; + case 'v': + arguments->verbose++; + break; + case 's': + arguments->datasize = atoi(arg); + break; + case 'p': + arguments->concurrent_requests = atoi(arg); + break; + case 'c': + arguments->chunksize = atoi(arg); + break; + case 'C': + arguments->cipher = arg; + break; + case 'h': + if (arguments->nhosts >= MAX_HOSTS_CONNECT) { + fprintf(stderr, "Too much hosts\n"); + return ARGP_ERR_UNKNOWN; + } + + arguments->hosts[arguments->nhosts] = arg; + arguments->nhosts++; + break; + case ARGP_KEY_ARG: + /* End processing here. */ + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; + +#endif /* HAVE_ARGP_H */ + +static void cmdline_parse(int argc, char **argv, struct argument_s *arguments) +{ + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, arguments); +#else /* HAVE_ARGP_H */ + (void) argc; + (void) argv; + arguments->hosts[0] = "localhost"; + arguments->nhosts = 1; +#endif /* HAVE_ARGP_H */ +} + +static void arguments_init(struct argument_s *arguments) +{ + memset(arguments, 0, sizeof(*arguments)); + arguments->chunksize = 32758; + arguments->concurrent_requests = 20; + arguments->datasize = 10; +} + +static ssh_session connect_host(const char *host, int verbose, char *cipher) +{ + ssh_session session = NULL; + int rc; + + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Error connecting to \"%s\": %s\n", + host, "Unable to create a new ssh session"); + return NULL; + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, host); + if (rc < 0) + goto error; + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + if (rc < 0) + goto error; + + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + if (rc < 0) + goto error; + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + if (rc < 0) + goto error; + } + + rc = ssh_options_parse_config(session, NULL); + if (rc < 0) + goto error; + + rc = ssh_connect(session); + if (rc == SSH_ERROR) + goto error; + + rc = ssh_userauth_autopubkey(session, NULL); + if (rc != SSH_AUTH_SUCCESS) + goto error; + + return session; + +error: + fprintf(stderr, "Error connecting to \"%s\": %s\n", + host, ssh_get_error(session)); + ssh_free(session); + return NULL; +} + +static char *network_speed(float bps) +{ + static char buf[128]; + if (bps > 1000 * 1000 * 1000) { + /* Gbps */ + snprintf(buf, sizeof(buf), "%f Gbps", bps / (1000 * 1000 * 1000)); + } else if (bps > 1000 * 1000) { + /* Mbps */ + snprintf(buf, sizeof(buf), "%f Mbps", bps / (1000 * 1000)); + } else if (bps > 1000) { + snprintf(buf, sizeof(buf), "%f Kbps", bps / 1000); + } else { + snprintf(buf, sizeof(buf), "%f bps", bps); + } + + return buf; +} + +static void do_benchmarks(ssh_session session, struct argument_s *arguments, + const char *hostname) +{ + float ping_rtt = 0.0; + float ssh_rtt = 0.0; + float bps = 0.0; + int i; + int err; + struct benchmark *b = NULL; + + if (arguments->verbose > 0) + fprintf(stdout, "Testing ICMP RTT\n"); + + err = benchmarks_ping_latency(hostname, &ping_rtt); + if (err == 0) { + fprintf(stdout, "ping RTT : %f ms\n", ping_rtt); + } + + err = benchmarks_ssh_latency(session, &ssh_rtt); + if (err == 0) { + fprintf(stdout, + "SSH RTT : %f ms. Theoretical max BW (win=128K) : %s\n", + ssh_rtt, network_speed(128000.0 / (ssh_rtt / 1000.0))); + } + + for (i=0; i < BENCHMARK_NUMBER; ++i){ + b = &benchmarks[i]; + if (b->enabled) { + err=b->fct(session, arguments, &bps); + + if (err == 0) { + fprintf(stdout, + "%s : %s : %s\n", + hostname, b->name, network_speed(bps)); + } + } + } +} + +char *buffer = NULL; + +int main(int argc, char **argv) +{ + struct argument_s arguments; + ssh_session session = NULL; + int i, r; + + arguments_init(&arguments); + cmdline_parse(argc, argv, &arguments); + if (arguments.nhosts == 0) { + fprintf(stderr, "At least one host (-h) must be specified\n"); + return EXIT_FAILURE; + } + + if (arguments.ntests == 0) { + for (i=0; i < BENCHMARK_NUMBER; ++i) { + benchmarks[i].enabled = 1; + } + arguments.ntests = BENCHMARK_NUMBER; + } + + buffer = malloc(arguments.chunksize > 1024 ? arguments.chunksize : 1024); + if (buffer == NULL) { + fprintf(stderr, "Allocation of chunk buffer failed\n"); + return EXIT_FAILURE; + } + + if (arguments.verbose > 0) { + fprintf(stdout, "Will try hosts "); + for (i=0; i < arguments.nhosts; ++i) { + fprintf(stdout, "\"%s\" ", arguments.hosts[i]); + } + + fprintf(stdout, "with benchmarks "); + for (i = 0; i < BENCHMARK_NUMBER; ++i) { + if (benchmarks[i].enabled) + fprintf(stdout, "\"%s\" ", benchmarks[i].name); + } + + fprintf(stdout,"\n"); + } + + r = ssh_init(); + if (r == SSH_ERROR) { + fprintf(stderr, "Failed to initialize libssh\n"); + return EXIT_FAILURE; + } + + for (i = 0; i < arguments.nhosts; ++i) { + if (arguments.verbose > 0) + fprintf(stdout, "Connecting to \"%s\"...\n", arguments.hosts[i]); + + session = connect_host(arguments.hosts[i], + arguments.verbose, + arguments.cipher); + if (session != NULL && arguments.verbose > 0) + fprintf(stdout, "Success\n"); + + if (session == NULL) { + fprintf(stderr, "Errors occurred, stopping\n"); + return EXIT_FAILURE; + } + + do_benchmarks(session, &arguments, arguments.hosts[i]); + ssh_disconnect(session); + ssh_free(session); + } + + r = ssh_finalize(); + if (r == SSH_ERROR) { + fprintf(stderr, "Failed to finalize libssh\n"); + return EXIT_FAILURE; + } + + return EXIT_SUCCESS; +} + diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.h b/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.h new file mode 100644 index 000000000000..5f54a950d90f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/benchmarks.h @@ -0,0 +1,105 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef BENCHMARKS_H_ +#define BENCHMARKS_H_ + +#include + +/* benchmarks.c */ + +/* maximum number of parallel hosts that may be checked */ +#define MAX_HOSTS_CONNECT 20 + +enum libssh_benchmarks { + BENCHMARK_RAW_UPLOAD=0, + BENCHMARK_RAW_DOWNLOAD, + BENCHMARK_SCP_UPLOAD, + BENCHMARK_SCP_DOWNLOAD, + BENCHMARK_SYNC_SFTP_UPLOAD, + BENCHMARK_SYNC_SFTP_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_AIO_DOWNLOAD, + BENCHMARK_ASYNC_SFTP_AIO_UPLOAD, + BENCHMARK_NUMBER +}; + +struct argument_s { + const char *hosts[MAX_HOSTS_CONNECT]; + int verbose; + int nhosts; + int ntests; + unsigned int datasize; + unsigned int chunksize; + int concurrent_requests; + char *cipher; +}; + +extern char *buffer; + +typedef int (*bench_fct)(ssh_session session, struct argument_s *args, + float *bps); + +struct benchmark { + const char *name; + bench_fct fct; + int enabled; +}; + +/* latency.c */ + +struct timestamp_struct { + struct timeval timestamp; +}; + +int benchmarks_ping_latency (const char *host, float *average); +int benchmarks_ssh_latency (ssh_session session, float *average); + +void timestamp_init(struct timestamp_struct *ts); +float elapsed_time(struct timestamp_struct *ts); + +/* bench_raw.c */ + +int benchmarks_raw_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_raw_down (ssh_session session, struct argument_s *args, + float *bps); + +/* bench_scp.c */ + +int benchmarks_scp_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_scp_down (ssh_session session, struct argument_s *args, + float *bps); + +/* bench_sftp.c */ + +int benchmarks_sync_sftp_up (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_sync_sftp_down (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_async_sftp_down (ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_async_sftp_aio_down(ssh_session session, struct argument_s *args, + float *bps); +int benchmarks_async_sftp_aio_up(ssh_session session, struct argument_s *args, + float *bps); +#endif /* BENCHMARKS_H_ */ diff --git a/src/libs/libssh-0.12.2/tests/benchmarks/latency.c b/src/libs/libssh-0.12.2/tests/benchmarks/latency.c new file mode 100644 index 000000000000..09c50a0474a5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/benchmarks/latency.c @@ -0,0 +1,148 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "benchmarks.h" +#include + +#include +#include +#include +#include +#include + +#define PING_PROGRAM "/bin/ping" + +/** @internal + * @brief Calculates the RTT of the host with ICMP ping, and returns the + * average of the calculated RTT. + * @param[in] host hostname to ping. + * @param[out] average average RTT in milliseconds. + * @returns 0 on success, -1 if there is an error. + * @warning relies on an external ping program which may not exist on + * certain OS. + */ +int benchmarks_ping_latency (const char *host, float *average){ + const char *ptr; + char cmd[256]; + char line[1024]; + FILE *fd; + int found=0; + + /* strip out the hostname */ + ptr=strchr(host,'@'); + if(ptr) + ptr++; + else + ptr=host; + + snprintf(cmd,sizeof(cmd),"%s -n -q -c3 %s",PING_PROGRAM, ptr); + fd=popen(cmd,"r"); + if(fd==NULL){ + fprintf(stderr,"Error executing command : %s\n", strerror(errno)); + return -1; + } + + while(!found && fgets(line,sizeof(line),fd)!=NULL){ + if(strstr(line,"rtt")){ + ptr=strchr(line,'='); + if(ptr==NULL) + goto parseerror; + ptr=strchr(ptr,'/'); + if(ptr==NULL) + goto parseerror; + *average=strtof(ptr+1,NULL); + found=1; + break; + } + } + if(!found) + goto parseerror; + pclose(fd); + return 0; + +parseerror: + fprintf(stderr,"Parse error : couldn't locate average in %s",line); + pclose(fd); + return -1; +} + +/** @internal + * @brief initialize a timestamp to the current time. + * @param[out] ts A timestamp_struct pointer. + */ +void timestamp_init(struct timestamp_struct *ts){ + gettimeofday(&ts->timestamp,NULL); +} + +/** @internal + * @brief return the elapsed time since now and the moment ts was initialized. + * @param[in] ts An initialized timestamp_struct pointer. + * @return Elapsed time in milliseconds. + */ +float elapsed_time(struct timestamp_struct *ts){ + struct timeval now; + time_t secdiff; + long usecdiff; /* may be negative */ + + gettimeofday(&now,NULL); + secdiff=now.tv_sec - ts->timestamp.tv_sec; + usecdiff=now.tv_usec - ts->timestamp.tv_usec; + //printf("%d sec diff, %d usec diff\n",secdiff, usecdiff); + return (float) (secdiff*1000) + ((float)usecdiff)/1000; +} + +/** @internal + * @brief Calculates the RTT of the host with SSH channel operations, and + * returns the average of the calculated RTT. + * @param[in] session active SSH session to test. + * @param[out] average average RTT in milliseconds. + * @returns 0 on success, -1 if there is an error. + */ +int benchmarks_ssh_latency(ssh_session session, float *average){ + float times[3]; + struct timestamp_struct ts; + int i; + ssh_channel channel; + channel=ssh_channel_new(session); + if(channel==NULL) + goto error; + if(ssh_channel_open_session(channel)==SSH_ERROR) + goto error; + + for(i=0;i<3;++i){ + timestamp_init(&ts); + if(ssh_channel_request_env(channel,"TEST","test")==SSH_ERROR && + ssh_get_error_code(session)==SSH_FATAL) + goto error; + times[i]=elapsed_time(&ts); + } + ssh_channel_close(channel); + ssh_channel_free(channel); + channel=NULL; + printf("SSH request times : %f ms ; %f ms ; %f ms\n", times[0], times[1], times[2]); + *average=(times[0]+times[1]+times[2])/3; + return 0; +error: + fprintf(stderr,"Error calculating SSH latency : %s\n",ssh_get_error(session)); + if(channel) + ssh_channel_free(channel); + return -1; +} diff --git a/src/libs/libssh-0.12.2/tests/chmodtest.c b/src/libs/libssh-0.12.2/tests/chmodtest.c new file mode 100644 index 000000000000..60ac6a5bfb5f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/chmodtest.c @@ -0,0 +1,33 @@ +#include + +#include +#include "examples_common.h" +#include + +int main(void) { + ssh_session session; + sftp_session sftp; + char buffer[1024*1024]; + int rc; + + session = connect_ssh("localhost", NULL, NULL, 0); + if (session == NULL) { + return 1; + } + + sftp=sftp_new(session); + sftp_init(sftp); + rc=sftp_rename(sftp,"/tmp/test","/tmp/test"); + rc=sftp_rename(sftp,"/tmp/test","/tmp/test"); + rc=sftp_chmod(sftp,"/tmp/test",0644); + if (rc < 0) { + printf("error : %s\n",ssh_get_error(sftp)); + + ssh_disconnect(session); + return 1; + } + + ssh_disconnect(session); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/chroot_wrapper.c b/src/libs/libssh-0.12.2/tests/chroot_wrapper.c new file mode 100644 index 000000000000..3545f3975640 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/chroot_wrapper.c @@ -0,0 +1,8 @@ +/* silent gcc */ +int chroot(const char *); + +int chroot(const char *path) +{ + (void)path; + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/client/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/client/CMakeLists.txt new file mode 100644 index 000000000000..b14b91ffc38d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/CMakeLists.txt @@ -0,0 +1,109 @@ +project(clienttests C) + +find_package(socket_wrapper) + +set(LIBSSH_CLIENT_TESTS + torture_algorithms + torture_auth + torture_auth_cert + torture_auth_agent_forwarding + torture_client_callbacks + torture_client_config + torture_connect + torture_hostkey + torture_rekey + torture_forward + torture_knownhosts + torture_knownhosts_verify + torture_proxycommand + torture_session + torture_request_env + torture_request_pty_modes + torture_client_global_requests + torture_get_kex_algo + ) + +find_program(SCP_EXECUTABLE NAMES scp) +if (SCP_EXECUTABLE) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_scp) +endif() + +if (WITH_PKCS11_URI) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_auth_pkcs11) +endif() + +if (HAVE_PTHREAD) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_proxyjump) +endif() + +if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_gssapi_auth + torture_gssapi_key_exchange + torture_gssapi_key_exchange_null) +endif() + +if (DEFAULT_C_NO_DEPRECATION_FLAGS) + set_source_files_properties(torture_knownhosts.c + PROPERTIES + COMPILE_FLAGS ${DEFAULT_C_NO_DEPRECATION_FLAGS}) +endif() + +if (WITH_SFTP) + if (WITH_BENCHMARKS) + set(SFTP_BENCHMARK_TESTS + torture_sftp_benchmark) + endif() + set(LIBSSH_CLIENT_TESTS + ${LIBSSH_CLIENT_TESTS} + torture_sftp_init + torture_sftp_ext + torture_sftp_canonicalize_path + torture_sftp_dir + torture_sftp_read + torture_sftp_fsync + torture_sftp_hardlink + torture_sftp_limits + torture_sftp_rename + torture_sftp_expand_path + torture_sftp_aio + torture_sftp_home_directory + torture_sftp_setstat + torture_sftp_packet_read + torture_sftp_recv_response_msg + torture_sftp_get_users_groups_by_id + torture_sftp_request_id + ${SFTP_BENCHMARK_TESTS}) +endif (WITH_SFTP) + +set(TORTURE_CLIENT_ENVIRONMENT ${TORTURE_ENVIRONMENT}) +list(APPEND TORTURE_CLIENT_ENVIRONMENT NSS_WRAPPER_HOSTS=${CMAKE_BINARY_DIR}/tests/etc/hosts) + +foreach(_CLI_TEST ${LIBSSH_CLIENT_TESTS}) + add_cmocka_test(${_CLI_TEST} + SOURCES ${_CLI_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TORTURE_LIBRARY} util + ) + + if (OSX) + set_property( + TEST + ${_CLI_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${SOCKET_WRAPPER_LIBRARY}) + else () + set_property( + TEST + ${_CLI_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_CLIENT_ENVIRONMENT}) + endif() +endforeach() diff --git a/src/libs/libssh-0.12.2/tests/client/torture_algorithms.c b/src/libs/libssh-0.12.2/tests/client/torture_algorithms.c new file mode 100644 index 000000000000..f0f8ac056237 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_algorithms.c @@ -0,0 +1,1128 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int sshd_setup_hmac(void **state) +{ + torture_setup_sshd_server(state, false); + /* Set MAC to be something other than what the client will offer */ + torture_update_sshd_config(state, "MACs hmac-sha2-512"); + + return 0; +} + + +static int session_setup(void **state) { + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool false_v = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Prevent parsing configuration files that can introduce different + * algorithms then we want to test */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &false_v); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void test_algorithm(ssh_session session, + const char *kex, + const char *cipher, + const char *hmac) { + int rc; + char data[256]; + size_t len_to_test[] = { + 1, 2, 3, 4, 5, 6, 7, 8, 10, + 12, 15, 16, 20, + 31, 32, 33, + 63, 64, 65, + 100, 127, 128 + }; + unsigned int i; + + if (kex != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + assert_ssh_return_code(session, rc); + } + + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + assert_ssh_return_code(session, rc); + } + + if (hmac != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, hmac); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, hmac); + assert_ssh_return_code(session, rc); + } + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* send ignore packets of all sizes */ + memset(data, 0, sizeof(data)); + for (i = 0; i < (sizeof(len_to_test) / sizeof(size_t)); i++) { + memset(data, 'A', len_to_test[i]); + ssh_send_ignore(session, data); + ssh_handle_packets(session, 50); + } + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +static void torture_algorithms_aes128_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha1"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes128_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha1"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes192_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha1"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes256_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha1"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-256"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-512"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes128_ctr_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-ctr", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha1"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-256"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-512"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes192_ctr_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes192-ctr", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha1(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha1"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-256"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-512"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_aes256_ctr_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-ctr", "hmac-sha2-512-etm@openssh.com"); +} + +static void torture_algorithms_aes128_gcm(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-gcm@openssh.com", NULL); +} + +static void torture_algorithms_aes256_gcm(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-gcm@openssh.com", NULL); +} + +static void torture_algorithms_aes128_gcm_mac(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes128-gcm@openssh.com", "hmac-sha1"); +} + +static void torture_algorithms_aes256_gcm_mac(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, NULL/*kex*/, "aes256-gcm@openssh.com", "hmac-sha1"); +} + +static void torture_algorithms_3des_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha1"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_3des_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_3des_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "3des-cbc", "hmac-sha2-512-etm@openssh.com"); +} + +#if defined(HAVE_BLOWFISH) && defined(OPENSSH_BLOWFISH_CBC) +static void torture_algorithms_blowfish_cbc_hmac_sha1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha1"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-256"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_512(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-512"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha1_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha1-etm@openssh.com"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_256_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-256-etm@openssh.com"); +} + +static void torture_algorithms_blowfish_cbc_hmac_sha2_512_etm(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, NULL/*kex*/, "blowfish-cbc", "hmac-sha2-512-etm@openssh.com"); +} +#endif /* HAVE_BLOWFISH && defined(OPENSSH_BLOWFISH_CBC) */ + +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM +static void torture_algorithms_chacha20_poly1305(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /*kex*/ + "chacha20-poly1305@openssh.com", + NULL); +} +static void torture_algorithms_chacha20_poly1305_mac(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /*kex*/ + "chacha20-poly1305@openssh.com", + "hmac-sha1"); /* different from the server */ +} +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ + +static void torture_algorithms_zlib(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib"); +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, SSH_OK); + } +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib"); +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, SSH_OK); + } +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_connect(session); +#ifdef WITH_ZLIB + if (!ssh_fips_mode()) { + if (ssh_get_openssh_version(session)) { + assert_false(rc == SSH_OK); + ssh_disconnect(session); + return; + } + } +#endif + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +static void torture_algorithms_zlib_openssh(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_C_S, "zlib@openssh.com"); +#ifdef WITH_ZLIB + assert_int_equal(rc, SSH_OK); +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "zlib@openssh.com"); +#ifdef WITH_ZLIB + assert_int_equal(rc, SSH_OK); +#else + assert_int_equal(rc, SSH_ERROR); +#endif + + rc = ssh_connect(session); +#ifdef WITH_ZLIB + if (ssh_get_openssh_version(session)) { + assert_true(rc==SSH_OK); + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + ssh_disconnect(session); + return; + } + assert_false(rc == SSH_OK); +#else + assert_int_equal(rc, SSH_OK); +#endif + + ssh_disconnect(session); +} + +#if defined(HAVE_ECC) +static void torture_algorithms_ecdh_sha2_nistp256(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "ecdh-sha2-nistp256", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_ecdh_sha2_nistp384(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "ecdh-sha2-nistp384", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_ecdh_sha2_nistp521(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "ecdh-sha2-nistp521", NULL/*cipher*/, NULL/*hmac*/); +} +#endif + +#ifdef OPENSSH_CURVE25519_SHA256 +static void torture_algorithms_ecdh_curve25519_sha256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "curve25519-sha256", NULL/*cipher*/, NULL/*hmac*/); +} +#endif /* OPENSSH_CURVE25519_SHA256 */ + +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG +static void torture_algorithms_ecdh_curve25519_sha256_libssh_org(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "curve25519-sha256@libssh.org", NULL/*cipher*/, NULL/*hmac*/); +} +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM +static void +torture_algorithms_ecdh_sntrup761x25519_sha512_openssh_com(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512@openssh.com", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512 +static void +torture_algorithms_ecdh_sntrup761x25519_sha512(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ + +#if defined(OPENSSH_MLKEM768X25519_SHA256) +static void torture_algorithms_ecdh_mlkem768x25519_sha256(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "mlkem768x25519-sha256", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* defined(OPENSSH_MLKEM768X25519_SHA256) */ + +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) +static void torture_algorithms_ecdh_mlkem768nistp256_sha256(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, + "mlkem768nistp256-sha256", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* defined(OPENSSH_MLKEM768NISTP256_SHA256) */ + +#if defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) +static void torture_algorithms_ecdh_mlkem1024nistp384_sha384(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, + "mlkem1024nistp384-sha384", + NULL /*cipher*/, + NULL /*hmac*/); +} +#endif /* HAVE_MLKEM1024 && defined(OPENSSH_MLKEM1024NISTP384_SHA384) */ + +static void torture_algorithms_dh_group1(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "diffie-hellman-group1-sha1", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group14(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "diffie-hellman-group14-sha1", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group14_sha256(void **state) { + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, "diffie-hellman-group14-sha256", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group16(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "diffie-hellman-group16-sha512", NULL/*cipher*/, NULL/*hmac*/); +} + +static void torture_algorithms_dh_group18(void **state) { + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, "diffie-hellman-group18-sha512", NULL/*cipher*/, NULL/*hmac*/); +} + +#ifdef WITH_GEX +static void torture_algorithms_dh_gex_sha1(void **state) +{ + struct torture_state *s = *state; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "diffie-hellman-group-exchange-sha1", + NULL, /* cipher */ + NULL); /* hmac */ +} + +static void torture_algorithms_dh_gex_sha256(void **state) +{ + struct torture_state *s = *state; + + test_algorithm(s->ssh.session, + "diffie-hellman-group-exchange-sha256", + NULL, /* cipher */ + NULL); /* hmac */ +} +#endif /* WITH_GEX */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_ctr_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes192_ctr_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_ctr_hmac_sha2_512_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_gcm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_gcm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_3des_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), +#if defined(HAVE_BLOWFISH) && defined(OPENSSH_BLOWFISH_CBC) + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha1_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_256_etm, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_blowfish_cbc_hmac_sha2_512_etm, + session_setup, + session_teardown), +#endif /* HAVE_BLOWFISH_CIPHER && defined(OPENSSH_BLOWFISH_CBC) */ +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_algorithms_chacha20_poly1305, + session_setup, + session_teardown), +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ + cmocka_unit_test_setup_teardown(torture_algorithms_zlib, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_zlib_openssh, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group14, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group16, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_group18, + session_setup, + session_teardown), +#ifdef WITH_GEX + cmocka_unit_test_setup_teardown(torture_algorithms_dh_gex_sha1, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_dh_gex_sha256, + session_setup, + session_teardown), +#endif /* WITH_GEX */ +#ifdef OPENSSH_CURVE25519_SHA256 + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_curve25519_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256 */ +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_curve25519_sha256_libssh_org, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sntrup761x25519_sha512_openssh_com, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512 + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sntrup761x25519_sha512, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ +#if defined(OPENSSH_MLKEM768X25519_SHA256) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem768x25519_sha256, + session_setup, + session_teardown), +#endif /* defined(OPENSSH_MLKEM768X25519_SHA256) */ +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem768nistp256_sha256, + session_setup, + session_teardown), +#endif /* defined(OPENSSH_MLKEM768NISTP256_SHA256) */ +#if defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_mlkem1024nistp384_sha384, + session_setup, + session_teardown), +#endif /* defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) */ +#if defined(HAVE_ECC) + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp384, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_ecdh_sha2_nistp521, + session_setup, + session_teardown), +#endif + }; + + struct CMUnitTest tests_hmac[] = { + cmocka_unit_test_setup_teardown(torture_algorithms_aes128_gcm_mac, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_algorithms_aes256_gcm_mac, + session_setup, + session_teardown), +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_algorithms_chacha20_poly1305_mac, + session_setup, + session_teardown), +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + if (rc != 0) { + return rc; + } + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests_hmac, sshd_setup_hmac, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth.c b/src/libs/libssh-0.12.2/tests/client/torture_auth.c new file mode 100644 index 000000000000..99d59b94b124 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth.c @@ -0,0 +1,1473 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#include "torture_auth_common.c" + +#ifdef WITH_FIDO2 +#include "torture_sk.h" +#endif + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, true); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + const char *all_keytypes = NULL; + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Enable all hostkeys */ + all_keytypes = ssh_get_supported_methods(SSH_HOSTKEYS); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, all_keytypes); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int pubkey_setup(void **state) +{ + int rc; + + rc = session_setup(state); + if (rc != 0) { + return rc; + } + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int agent_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + char ssh_key_path[1024]; + int rc; + + rc = pubkey_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + /* Use the common function to set up the SSH agent with Bob's key */ + snprintf(ssh_key_path, sizeof(ssh_key_path), "%s/.ssh/id_rsa", pwd->pw_dir); + rc = torture_setup_ssh_agent(s, ssh_key_path); + if (rc != 0) { + return rc; + } + + return 0; +} + +static int agent_teardown(void **state) +{ + int rc; + + rc = session_teardown(state); + if (rc != 0) { + return rc; + } + + /* Use the common function to clean up the SSH agent */ + rc = torture_cleanup_ssh_agent(); + if (rc != 0) { + return rc; + } + + return 0; +} + +static void torture_auth_none(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } +} + +static void torture_auth_none_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + ssh_set_blocking(session,0); + + do { + rc = ssh_userauth_none(session,NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + +} + +/* Setting MaxAuthTries 0 makes libssh hang. The option is not practical, + * but simulates setting low value and requiring multiple authentication + * methods to succeed (T233) + */ +static void torture_auth_none_max_tries(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + const char *sshd_config = "MaxAuthTries 0"; + + torture_update_sshd_config(state, sshd_config); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + /* Reset config back to defaults */ + torture_update_sshd_config(state, ""); +} + + +static void torture_auth_pubkey(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* negative tests */ + rc = ssh_userauth_try_publickey(NULL, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_try_publickey(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + + rc = ssh_userauth_try_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* negative tests */ + rc = ssh_userauth_publickey(NULL, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_ERROR); + rc = ssh_userauth_publickey(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_none(session,NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_try_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_autopubkey(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +struct torture_auth_autopubkey_protected_data { + ssh_session session; + int n_calls; +}; + +static int +torture_auth_autopubkey_protected_auth_function (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata) +{ + int rc; + char *id, *expected_id; + struct torture_auth_autopubkey_protected_data *data = userdata; + + assert_true(prompt != NULL); + assert_int_equal(echo, 0); + assert_int_equal(verify, 0); + + expected_id = ssh_path_expand_escape(data->session, "%d/.ssh/id_rsa_protected"); + assert_true(expected_id != NULL); + + rc = ssh_userauth_publickey_auto_get_current_identity(data->session, &id); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(expected_id, id); + + ssh_string_free_char(id); + ssh_string_free_char(expected_id); + + data->n_calls += 1; + strncpy(buf, "secret", len); + return 0; +} + +static void torture_auth_autopubkey_protected(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char *id; + int rc; + + struct torture_auth_autopubkey_protected_data data = { + .session = session, + .n_calls = 0 + }; + + struct ssh_callbacks_struct callbacks = { + .userdata = &data, + .auth_function = torture_auth_autopubkey_protected_auth_function + }; + + /* no session pointer */ + rc = ssh_userauth_publickey_auto_get_current_identity(NULL, &id); + assert_int_equal(rc, SSH_ERROR); + + /* no result pointer */ + rc = ssh_userauth_publickey_auto_get_current_identity(session, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* no auto auth going on */ + rc = ssh_userauth_publickey_auto_get_current_identity(session, &id); + assert_int_equal(rc, SSH_ERROR); + + ssh_callbacks_init(&callbacks); + ssh_set_callbacks(session, &callbacks); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Try id_rsa_protected first. + */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "%d/.ssh/id_rsa_protected"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + assert_int_equal (data.n_calls, 1); +} + +static void torture_auth_autopubkey_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session,0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void +torture_auth_kbdint(void **state, + const char *password, + enum ssh_auth_e res) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + + rc = ssh_userauth_kbdint_setanswer(session, 0, password); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + /* Sometimes, SSH server send an empty query at the end of exchange */ + if (rc == SSH_AUTH_INFO) { + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + assert_int_equal(rc, res); +} + +static void +torture_auth_kbdint_good(void **state) +{ + torture_auth_kbdint(state, TORTURE_SSH_USER_BOB_PASSWORD, SSH_AUTH_SUCCESS); +} + +static void +torture_auth_kbdint_bad(void **state) +{ + torture_auth_kbdint(state, "bad password stample", SSH_AUTH_DENIED); +} + +static void +torture_auth_kbdint_nonblocking(void **state, + const char *password, + enum ssh_auth_e res) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 1); + rc = ssh_userauth_kbdint_setanswer(session, 0, password); + assert_false(rc < 0); + + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* Sometimes, SSH server send an empty query at the end of exchange */ + if (rc == SSH_AUTH_INFO) { + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 0); + do { + rc = ssh_userauth_kbdint(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + } + assert_int_equal(rc, res); +} + +static void +torture_auth_kbdint_nonblocking_good(void **state) +{ + torture_auth_kbdint_nonblocking(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); +} + +static void +torture_auth_kbdint_nonblocking_bad(void **state) +{ + torture_auth_kbdint_nonblocking(state, + "bad password stample", + SSH_AUTH_DENIED); +} + +static void +torture_auth_password(void **state, const char *password, enum ssh_auth_e res) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + rc = ssh_userauth_password(session, NULL, password); + assert_int_equal(rc, res); +} + +static void +torture_auth_password_good(void **state) +{ + torture_auth_password(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); +} + +static void +torture_auth_password_bad(void **state) +{ + torture_auth_password(state, "bad password stample", SSH_AUTH_DENIED); +} + +static void +torture_auth_password_nonblocking(void **state, + const char *password, + enum ssh_auth_e res) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session,0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + do { + rc = ssh_userauth_password(session, NULL, password); + } while (rc == SSH_AUTH_AGAIN); + + assert_int_equal(rc, res); +} + +static void +torture_auth_password_nonblocking_good(void **state) +{ + torture_auth_password_nonblocking(state, + TORTURE_SSH_USER_BOB_PASSWORD, + SSH_AUTH_SUCCESS); +} + +static void +torture_auth_password_nonblocking_bad(void **state) +{ + torture_auth_password_nonblocking(state, + "bad password stample", + SSH_AUTH_DENIED); +} + +/* TODO cover the case: + * * when there is accompanying certificate (identities only + agent) + * * export private key to public key during _auto() authentication. + * this needs to be a encrypted private key in PEM format without + * accompanying public key. + */ +static void torture_auth_agent_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + struct passwd *pwd = NULL; + int rc; + bool identities_only = true; + char *id = NULL; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key */ + rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_agent_identities_only_protected(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + struct passwd *pwd; + int rc; + bool identities_only = true; + char *id = NULL; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa_protected", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key */ + rc = ssh_list_append(session->opts.identity, strdup(bob_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_pubkey_types(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Disable RSA key types for authentication */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp384"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Now enable it and retry */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "rsa-sha2-512,ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_pubkey_types_ecdsa(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* We have only the 256b key -- allowlisting only larger should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp384"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ECDSA keys with their various names */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp256"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +static void torture_auth_pubkey_types_ed25519(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_ed25519", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Import the ED25519 private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* Enable only RSA keys -- authentication should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ed25519 keys */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_types_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Disable RSA key types for authentication */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Now enable it and retry */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "rsa-sha2-512,ssh-rsa"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +static void torture_auth_pubkey_types_ecdsa_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* We have only the 256b key -- allowlisting only larger should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp384"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ECDSA key to authenticate */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ecdsa-sha2-nistp256"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + +} + +static void torture_auth_pubkey_types_ed25519_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_ed25519", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Import the ED25519 private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* Enable only RSA keys -- authentication should fail */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use also ED25519 key to authenticate */ + rc = ssh_options_set(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519"); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +#ifdef WITH_FIDO2 + +static void torture_auth_pubkey_types_sk_key(void **state, + enum ssh_keytypes_e key_type) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *privkey_file_name = NULL; + const char *key_type_str = NULL; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + const struct ssh_sk_callbacks_struct *sk_dummy_callbacks = NULL; + ssh_pki_ctx pki_context = NULL; + int rc; + + /* Conditions to skip the test */ + sk_dummy_callbacks = torture_get_sk_dummy_callbacks(); + if (sk_dummy_callbacks == NULL) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Key type specific setup */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + privkey_file_name = "id_ecdsa_sk"; + key_type_str = "sk-ecdsa-sha2-nistp256@openssh.com"; + break; + + case SSH_KEYTYPE_SK_ED25519: + privkey_file_name = "id_ed25519_sk"; + key_type_str = "sk-ssh-ed25519@openssh.com"; + break; + + default: + /* should never reach here */ + assert_true(0); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/%s", + pwd->pw_dir, + privkey_file_name); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + pki_context = ssh_pki_ctx_new(); + assert_non_null(pki_context); + + rc = ssh_pki_ctx_options_set(pki_context, + SSH_PKI_OPTION_SK_CALLBACKS, + sk_dummy_callbacks); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_PKI_CONTEXT, pki_context); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Import the private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + /* Enable only RSA keys -- authentication should fail */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* Verify we can use the SK key */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + key_type_str); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); + SSH_PKI_CTX_FREE(pki_context); +} + +static void torture_auth_pubkey_types_sk_ecdsa(void **state) +{ + torture_auth_pubkey_types_sk_key(state, SSH_KEYTYPE_SK_ECDSA); +} + +static void torture_auth_pubkey_types_sk_ed25519(void **state) +{ + torture_auth_pubkey_types_sk_key(state, SSH_KEYTYPE_SK_ED25519); +} + +#endif /* WITH_FIDO2 */ + +static void torture_auth_pubkey_rsa_key_size(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_rsa_key_size_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd; + int rc; + unsigned int limit = 4096; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, + sizeof(bob_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* set unreasonable large minimum key size to trigger the condition */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); /* larger than the test key */ + assert_ssh_return_code(session, rc); + + /* Import the RSA private key */ + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); + + /* revert to default values which should work also in FIPS mode */ + limit = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &limit); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +static void torture_auth_pubkey_skip_none(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char bob_ssh_key[1024]; + ssh_key privkey = NULL; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + /* Authenticate as alice with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Skip the ssh_userauth_none() here */ + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_none, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_none_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_none_max_tries, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_password_nonblocking_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking_good, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_kbdint_nonblocking_bad, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_protected, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_nonblocking, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_identities_only_protected, + agent_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ecdsa_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_ed25519_nonblocking, + pubkey_setup, + session_teardown), +#ifdef WITH_FIDO2 + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_sk_ecdsa, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_types_sk_ed25519, + pubkey_setup, + session_teardown), +#endif /* WITH_FIDO2 */ + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_rsa_key_size_nonblocking, + pubkey_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_pubkey_skip_none, + pubkey_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth_agent_forwarding.c b/src/libs/libssh-0.12.2/tests/client/torture_auth_agent_forwarding.c new file mode 100644 index 000000000000..cdde532849cb --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth_agent_forwarding.c @@ -0,0 +1,369 @@ +#include "config.h" + +#if !defined(_WIN32) || (defined(WITH_SERVER) && defined(HAVE_PTHREAD)) + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include /* For calloc/free */ + +#include "libssh/callbacks.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" + +#include +#include +#include +#include +#include /* usleep */ + +#ifndef UNIX_PATH_MAX +#define UNIX_PATH_MAX 108 +#endif + +/* struct to store the state of the test */ +struct agent_callback_state { + int called; + ssh_session expected_session; + ssh_channel created_channel; +}; + +/* Agent callback function that will be triggered when a channel open request is + * received */ +static ssh_channel agent_callback(ssh_session session, void *userdata) +{ + struct agent_callback_state *state = + (struct agent_callback_state *)userdata; + ssh_channel channel = NULL; /* Initialize to NULL */ + + /* Increment call counter */ + state->called++; + + /* Verify session matches what we expect */ + assert_ptr_equal(session, state->expected_session); + + /* Create a new channel for agent forwarding */ + channel = ssh_channel_new(session); + if (channel == NULL) { + return NULL; + } + + /* Make the channel non-blocking */ + ssh_channel_set_blocking(channel, 0); + + /* Store the channel for verification and later cleanup */ + state->created_channel = channel; + + return channel; +} + +static int sshd_setup_agent_forwarding(void **state) +{ + int rc; + + /* Use the standard server setup function */ + torture_setup_sshd_server(state, false); + + /* Override the default configuration with our own, adding agent forwarding + * support */ + rc = torture_update_sshd_config(state, "AllowAgentForwarding yes\n"); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +/* Only free the session - nothing else */ +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + if (s != NULL && s->ssh.ssh.session != NULL) { + /* Clean up callback resources first */ + if (s->ssh.ssh.cb_state != NULL) { + struct agent_callback_state *cb_state = s->ssh.ssh.cb_state; + + /* Close and free any open channel from the callback */ + if (cb_state->created_channel != NULL) { + ssh_channel_close(cb_state->created_channel); + ssh_channel_free(cb_state->created_channel); + } + + free(cb_state); + s->ssh.ssh.cb_state = NULL; + } + + if (s->ssh.ssh.callbacks != NULL) { + free(s->ssh.ssh.callbacks); + s->ssh.ssh.callbacks = NULL; + } + + /* Disconnect and free the session */ + ssh_disconnect(s->ssh.ssh.session); + ssh_free(s->ssh.ssh.session); + s->ssh.ssh.session = NULL; + } + + return 0; +} + +static int torture_teardown_ssh_agent(void **state) +{ + struct torture_state *s = *state; + int rc; + + if (s == NULL) { + return 0; + } + + /* Kill the SSH agent */ + rc = torture_cleanup_ssh_agent(); + assert_return_code(rc, errno); + + /* Use the standard teardown function which will properly clean up */ + torture_teardown_sshd_server(state); + + return 0; +} + +/* Test function to verify if agent forwarding callback works */ +static void torture_auth_agent_forwarding(void **state) +{ + struct torture_state *s = *state; + struct agent_callback_state *cb_state; + ssh_session session = NULL; + ssh_channel channel = NULL; /* Initialize to NULL */ + int rc; + int port = torture_server_port(); + char buffer[4096] = {0}; + int nbytes; + int max_read_attempts = 10; /* Limit the number of read attempts */ + int read_count = 0; + bool agent_available = false; + bool agent_not_available_found = false; + size_t exp_socket_len; + + /* The forwarded agent socket is created under the home directory, which + * might easily extend the maximum unix domain socket path length. + * If we see this, just skip the test as it will not work */ + exp_socket_len = strlen(BINARYDIR) + + strlen("/home/bob/.ssh/agent.1234567890.sshd.XXXXXXXXXX"); + if (exp_socket_len > UNIX_PATH_MAX) { + SSH_LOG(SSH_LOG_WARNING, + "The working directory is too long for agent forwarding to work" + ": Skipping the test"); + skip(); + } + + assert_non_null(s); + session = s->ssh.ssh.session; + assert_non_null(session); + + /* Get our callback state */ + cb_state = (struct agent_callback_state *)s->ssh.ssh.cb_state; + assert_non_null(cb_state); + + /* Set username */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + /* Set server address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + /* Set port */ + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &port); + assert_ssh_return_code(session, rc); + + /* Connect to server */ + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Authenticate */ + rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Create a single channel that we'll use for all tests */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Request agent forwarding */ + rc = ssh_channel_request_auth_agent(channel); + assert_ssh_return_code(session, rc); + + /* Running a command that will try to use the SSH agent */ + rc = ssh_channel_request_exec( + channel, + "echo 'Simple command'; " + "echo 'ENV SSH_AUTH_SOCK=>['$SSH_AUTH_SOCK']<'; " /* Use boundary + markers */ + "ssh-add -l || echo 'Agent not available'; " + "echo 'Done'"); /* Marker for command completion */ + assert_ssh_return_code(session, rc); + + /* Set to non-blocking mode with manual timeout implementation + * This prevents the test from hanging indefinitely if there's an issue with + * the channel communication. We implement our own timeout logic using a + * counter and sleep, which gives the server time to process our request + * while still ensuring the test will eventually terminate even if no EOF is + * received. + */ + ssh_channel_set_blocking(channel, 0); + + /* Read with safety counter to prevent infinite loops */ + while (!ssh_channel_is_eof(channel) && read_count < max_read_attempts) { + nbytes = ssh_channel_read_nonblocking(channel, + buffer, + sizeof(buffer) - 1, + 0); + + if (nbytes > 0) { + buffer[nbytes] = 0; + ssh_log_hexdump("Read bytes:", (unsigned char *)buffer, nbytes); + + /* Process the command output to check for three key conditions: + * 1. If SSH_AUTH_SOCK is properly set (meaning agent forwarding + * works) + * 2. If "Agent not available" message appears (indicating failure) + * 3. If we've seen the "Done" marker (to know when to stop reading) + */ + /* Check if SSH_AUTH_SOCK has a non-empty value by looking for + * boundary markers with content between them */ + if (strstr(buffer, "ENV SSH_AUTH_SOCK=>[") != NULL && + strstr(buffer, "]<") != NULL && + strstr(buffer, "ENV SSH_AUTH_SOCK=>[]<") == NULL) { + agent_available = true; + } + + if (strstr(buffer, "Agent not available") != NULL) { + agent_not_available_found = true; + } + + if (strstr(buffer, "Done") != NULL) { + break; + } + } else if (nbytes == SSH_ERROR) { + break; + } else if (nbytes == SSH_EOF) { + break; + } + + /* Short sleep between reads to avoid spinning */ + usleep(100000); /* 100ms */ + read_count++; + } + + /* Trying to read from stderr as well */ + ssh_channel_read_nonblocking(channel, buffer, sizeof(buffer) - 1, 1); + + /* Close the channel */ + ssh_channel_send_eof(channel); + ssh_channel_close(channel); + ssh_channel_free(channel); + + /* Verify agent forwarding worked correctly */ + + /* Verify callback was called exactly once */ + assert_int_equal(cb_state->called, 1); + + /* Verify "Agent not available" was not found + * The agent should be available - we should never see "Agent not available" + * output + */ + assert_false(agent_not_available_found); + + /* Verify SSH_AUTH_SOCK is set */ + assert_true(agent_available); + + /* Any channel created in the callback is freed */ + if (cb_state->created_channel) { + ssh_channel_close(cb_state->created_channel); + ssh_channel_free(cb_state->created_channel); + cb_state->created_channel = NULL; + } +} + +/* Session setup function that configures SSH agent */ +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct agent_callback_state *cb_state = NULL; + struct ssh_callbacks_struct *callbacks = NULL; + char key_path[1024]; + struct passwd *pw = NULL; + int rc; + + /* Create a new session */ + s->ssh.ssh.session = ssh_new(); + assert_non_null(s->ssh.ssh.session); + + rc = ssh_options_set(s->ssh.ssh.session, + SSH_OPTIONS_LOG_VERBOSITY, + &verbosity); + assert_int_equal(rc, SSH_OK); + + /* Create and initialize the callback state */ + cb_state = calloc(1, sizeof(struct agent_callback_state)); + assert_non_null(cb_state); + + cb_state->expected_session = s->ssh.ssh.session; + cb_state->created_channel = NULL; + + /* Set up the callbacks */ + callbacks = calloc(1, sizeof(struct ssh_callbacks_struct)); + assert_non_null(callbacks); + + callbacks->userdata = cb_state; + callbacks->channel_open_request_auth_agent_function = agent_callback; + + ssh_callbacks_init(callbacks); + rc = ssh_set_callbacks(s->ssh.ssh.session, callbacks); + assert_int_equal(rc, SSH_OK); + + /* Store callback state and callbacks */ + s->ssh.ssh.cb_state = cb_state; + s->ssh.ssh.callbacks = callbacks; + + /* Set up SSH agent with Bob's key */ + pw = getpwnam("bob"); + assert_non_null(pw); + snprintf(key_path, sizeof(key_path), "%s/.ssh/id_rsa", pw->pw_dir); + rc = torture_setup_ssh_agent(s, key_path); + assert_return_code(rc, errno); + + return 0; +} + +/* Main test function */ +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_agent_forwarding, + session_setup, + session_teardown), + }; + + ssh_init(); + + /* Simplify the CMocka test filter handling */ +#if defined HAVE_CMOCKA_SET_TEST_FILTER + cmocka_set_message_output(CM_OUTPUT_STDOUT); +#endif + + torture_filter_tests(tests); + + rc = cmocka_run_group_tests(tests, + sshd_setup_agent_forwarding, + torture_teardown_ssh_agent); + + ssh_finalize(); + + return rc; +} + +#endif diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth_cert.c b/src/libs/libssh-0.12.2/tests/client/torture_auth_cert.c new file mode 100644 index 000000000000..9a6990def697 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth_cert.c @@ -0,0 +1,1125 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * Copyright (c) 2023 by Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "torture.h" + +#include +#include +#include +#include + +#include "torture_auth_common.c" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, true); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + const char *all_keytypes = NULL; + struct passwd *pwd = NULL; + bool b = false; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Enable all hostkeys */ + all_keytypes = ssh_get_supported_methods(SSH_HOSTKEYS); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + all_keytypes); + assert_ssh_return_code(s->ssh.session, rc); + + /* certs have been signed for login as alice */ + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options or from agent explicitly. */ +static int session_setup_ssh_dir(void **state) +{ + struct torture_state *s = *state; + + session_setup(state); + + s->ssh.session->opts.homedir = strdup("~/.no_ssh"); + + return 0; +} + +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options. It also changes the target user to one that does + * not accept authentication using this certificate and moves private key to + * different location so the default matching does not work */ +static int session_setup_bob_cert(void **state) +{ + struct torture_state *s = *state; + char doe_ssh_key[1024]; + char new_ssh_key[1024]; + char doe_ssh_cert[2048]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd; + int rc; + + session_setup(state); + + s->ssh.session->opts.homedir = strdup("~/.no_ssh"); + + /* certs won't log in for bob */ + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(new_ssh_key, sizeof(new_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(new_ssh_key, keydata); + + /* Explicit private key and cert */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_IDENTITY, new_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +/* This sets up the ssh session in the directory without the default + * certificates that are used for authentication, requiring them to be provided + * as configuration options. It also changes the target user to one that does + * not accept authentication using this certificate and sets non-existing + * certificate path to trigger the right code path */ +static int session_setup_bob_cert_bad(void **state) +{ + struct torture_state *s = *state; + char doe_ssh_key[1024]; + char new_ssh_key[1024]; + char doe_ssh_cert[2048]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd; + int rc; + + session_setup(state); + + s->ssh.session->opts.homedir = strdup("~/.no_ssh"); + + /* certs won't log in for bob */ + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(new_ssh_key, sizeof(new_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert1.pub", doe_ssh_key); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(new_ssh_key, keydata); + + /* Explicit private key and cert */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_IDENTITY, new_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int agent_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + char key_path[1024]; + int rc; + + rc = session_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(key_path, sizeof(key_path), "%s/.ssh/id_rsa", pwd->pw_dir); + + /* run ssh-agent and add the key */ + rc = torture_setup_ssh_agent(s, key_path); + assert_int_equal(rc, 0); + + return 0; +} + +static int agent_cert_setup(void **state) +{ + char ssh_key_cmd[1024]; + struct passwd *pwd; + int rc; + + rc = agent_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + /* remove all keys, load alternative key + cert */ + snprintf(ssh_key_cmd, + sizeof(ssh_key_cmd), + "ssh-add -D && ssh-add %s/.ssh/id_rsa", + pwd->pw_dir); + + rc = system(ssh_key_cmd); + assert_return_code(rc, errno); + + return 0; +} + +static int agent_cert_setup_explicit(void **state) +{ + char orig_doe_ssh_key[1024]; + char doe_ssh_key[1024]; + char keydata[2048]; + struct passwd *pwd = NULL; + int fd; + int rc; + + rc = agent_cert_setup(state); + if (rc != 0) { + return rc; + } + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(orig_doe_ssh_key, + sizeof(orig_doe_ssh_key), + "%s/.ssh/id_rsa", + pwd->pw_dir); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + + /* move the private key away from the default location the certificate can + * not be loaded automatically */ + fd = open(orig_doe_ssh_key, O_RDONLY); + assert_true(fd > 0); + rc = read(fd, keydata, sizeof(keydata)); + assert_true(rc > 0); + keydata[rc] = '\0'; + close(fd); + torture_write_file(doe_ssh_key, keydata); + + return 0; +} + +static int agent_teardown(void **state) +{ + int rc; + + rc = session_teardown(state); + if (rc != 0) { + return rc; + } + + rc = torture_cleanup_ssh_agent(); + assert_int_equal(rc, 0); + + return 0; +} + +static void torture_auth_cert(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_key privkey = NULL; + ssh_key cert = NULL; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_privkey_file(doe_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_cert_file(doe_ssh_cert, &cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_try_publickey(session, NULL, cert); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_publickey(session, NULL, privkey); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); +} + +static void torture_auth_cert_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_key privkey = NULL; + ssh_key cert = NULL; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + rc = ssh_pki_import_privkey_file(doe_ssh_key, NULL, NULL, NULL, &privkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_import_cert_file(doe_ssh_cert, &cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_OK); + + do { + rc = ssh_userauth_try_publickey(session, NULL, cert); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_userauth_publickey(session, NULL, privkey); + } while (rc == SSH_AUTH_AGAIN); + + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); +} + +/* Same as torture_auth_cert, but without explicitly loading certificate to the + * private key file, keeping libssh to use default cert path when done with + * _auto(). */ +static void torture_auth_cert_default_non_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* the cert is in the default location (~/.ssh/id_rsa-cert.pub) */ + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert_nonblocking, but without explicitly loading + * certificate to the private key file, keeping libssh to use default cert path + * when done with _auto(). + * Non-blocking version */ +static void torture_auth_cert_default_non_explicit_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* the cert is in the default location (~/.ssh/id_rsa-cert.pub) */ + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Sanity test that there are no default identities available and the automatic + * pubkey authentication fails without any explicit identities */ +static void torture_auth_auto_fail(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); +} + +/* Sanity test that there are no default identities available and the automatic + * pubkey authentication fails without any explicit identities + * Non-blocking version */ +static void torture_auth_auto_fail_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_DENIED); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. */ +static void torture_auth_cert_options_private(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + /* the cert has default naming relative to the private key (*-cert.pub) */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. + * Non-blocking version */ +static void torture_auth_cert_options_private_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + /* the cert has default naming relative to the private key (*-cert.pub) */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, also the certificate file + */ +static void torture_auth_cert_options_cert(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + /* Explicit private key and cert */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +/* Same as torture_auth_cert, but the home SSH dir does not have any default + * identities and they are loaded through the options, only through the private + * key path. + * Non-blocking version */ +static void torture_auth_cert_options_cert_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[2048]; + struct passwd *pwd; + int rc; + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, sizeof(doe_ssh_cert), "%s-cert.pub", doe_ssh_key); + + /* Explicit private key and cert */ + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, doe_ssh_key); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_CERTIFICATE, doe_ssh_cert); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void workaround_old_openssh_bug(void **state) +{ +#if OPENSSH_VERSION_MAJOR < 8 || \ + (OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR == 0) + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + /* Skip this test if in FIPS mode. + * + * OpenSSH agent has a bug which makes it to not use SHA2 in signatures when + * using certificates. It always uses SHA1. + * + * This should be removed as soon as OpenSSH agent bug is fixed. + * (see https://gitlab.com/libssh/libssh-mirror/merge_requests/34) */ + if (ssh_fips_mode()) { + skip(); + } else { + /* After the bug is solved, this also should be removed */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-rsa-cert-v01@openssh.com"); + assert_int_equal(rc, SSH_OK); + } +#else + (void)state; +#endif /* OPENSSH_VERSION_MAJOR < 8.1 */ +} + +static void torture_auth_agent_cert(void **state) +{ + workaround_old_openssh_bug(state); + + /* Setup loads a different key, tests are exactly the same. */ + torture_auth_agent(state); +} + +static void torture_auth_agent_cert_nonblocking(void **state) +{ + workaround_old_openssh_bug(state); + + torture_auth_agent_nonblocking(state); +} + +static void torture_auth_agent_cert_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + return; + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key -- the cert in default location should be loaded + * automatically */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_agent_cert_identities_only_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + return; + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key -- the cert in default location should be loaded + * automatically */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_agent_cert_identities_only_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key and cert */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_identities_only_nonblocking_explicit(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_key[1024]; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_key, sizeof(doe_ssh_key), "%s/.ssh/my_rsa", pwd->pw_dir); + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a key and cert */ + rc = ssh_list_append(session->opts.identity, strdup(doe_ssh_key)); + assert_int_equal(rc, SSH_OK); + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +static void torture_auth_agent_cert_only_identities_only(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a cert: key is in the agent */ + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +static void +torture_auth_agent_cert_only_identities_only_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char doe_ssh_cert[1024]; + struct passwd *pwd = NULL; + bool identities_only = true; + char *id = NULL; + int rc; + + workaround_old_openssh_bug(state); + + pwd = getpwnam("doe"); + assert_non_null(pwd); + + snprintf(doe_ssh_cert, + sizeof(doe_ssh_cert), + "%s/.ssh/id_rsa-cert.pub", + pwd->pw_dir); + + if (!ssh_agent_is_running(session)) { + print_message("*** Agent not running. Test ignored\n"); + skip(); + } + + rc = + ssh_options_set(session, SSH_OPTIONS_IDENTITIES_ONLY, &identities_only); + assert_int_equal(rc, SSH_OK); + + /* Remove the default identities */ + while ((id = ssh_list_pop_head(char *, session->opts.identity_non_exp)) != + NULL) { + SAFE_FREE(id); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + ssh_set_blocking(session, 0); + + do { + rc = ssh_userauth_none(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* Should fail as key is not in config */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code_equal(session, rc, SSH_AUTH_DENIED); + + /* Re-add a cert: key is in the agent */ + rc = ssh_list_append(session->opts.certificate, strdup(doe_ssh_cert)); + assert_int_equal(rc, SSH_OK); + + /* Should succeed as key now in config/options */ + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} + +#define GROUP_TEST(TEST_NAME, SETUP) \ + { \ + #TEST_NAME "_" #SETUP, \ + TEST_NAME, \ + session_setup##_##SETUP, \ + session_teardown, \ + NULL, \ + } + + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_cert, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_default_non_explicit, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_cert_default_non_explicit_nonblocking, + session_setup, + session_teardown), + GROUP_TEST(torture_auth_auto_fail, ssh_dir), + GROUP_TEST(torture_auth_auto_fail_nonblocking, ssh_dir), + GROUP_TEST(torture_auth_auto_fail, bob_cert), + GROUP_TEST(torture_auth_auto_fail_nonblocking, bob_cert), + GROUP_TEST(torture_auth_auto_fail, bob_cert_bad), + GROUP_TEST(torture_auth_auto_fail_nonblocking, bob_cert_bad), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_private, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_cert_options_private_nonblocking, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_cert_options_cert, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_cert_options_cert_nonblocking, + session_setup_ssh_dir, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_nonblocking, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown(torture_auth_agent_cert_identities_only, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_identities_only_nonblocking, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_identities_only_explicit, + agent_cert_setup_explicit, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_identities_only_nonblocking_explicit, + agent_cert_setup_explicit, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_only_identities_only, + agent_cert_setup, + agent_teardown), + cmocka_unit_test_setup_teardown( + torture_auth_agent_cert_only_identities_only_nonblocking, + agent_cert_setup, + agent_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth_common.c b/src/libs/libssh-0.12.2/tests/client/torture_auth_common.c new file mode 100644 index 000000000000..8a4f2854488c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth_common.c @@ -0,0 +1,94 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include "torture.h" +#include "libssh/libssh.h" + +/* agent_is_running */ +#include "agent.c" + +void torture_auth_agent(void **state); +void torture_auth_agent(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + /* negative test case */ + rc = ssh_userauth_agent(NULL, NULL); + assert_int_equal(rc, SSH_AUTH_ERROR); + + rc = ssh_userauth_agent(session, NULL); + assert_ssh_return_code(session, rc); +} + +void torture_auth_agent_nonblocking(void **state); +void torture_auth_agent_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + if (!ssh_agent_is_running(session)){ + print_message("*** Agent not running. Test ignored\n"); + return; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + ssh_set_blocking(session,0); + + do { + rc = ssh_userauth_agent(session, NULL); + } while (rc == SSH_AUTH_AGAIN); + assert_ssh_return_code(session, rc); +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_auth_pkcs11.c b/src/libs/libssh-0.12.2/tests/client/torture_auth_pkcs11.c new file mode 100644 index 000000000000..d33a400be5a2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_auth_pkcs11.c @@ -0,0 +1,294 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +/* agent_is_running */ +#include "agent.c" + +#define LIBSSH_RSA_TESTKEY "id_pkcs11_rsa" +#define LIBSSH_ECDSA_256_TESTKEY "id_pkcs11_ecdsa_256" +#define LIBSSH_ECDSA_384_TESTKEY "id_pkcs11_ecdsa_384" +#define LIBSSH_ECDSA_521_TESTKEY "id_pkcs11_ecdsa_521" +#define LIBSSH_ED25519_TESTKEY "id_pkcs11_ed25519" + +const char template[] = "/tmp/temp_dir_XXXXXX"; + +struct pki_st { + char *temp_dir; + char *orig_dir; + char *keys_dir; +}; + +static int setup_tokens(void **state, const char *type, const char *obj_name) +{ + struct torture_state *s = *state; + struct pki_st *test_state = s->private_data; + char priv_filename[1024]; + char *cwd = NULL; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + snprintf(priv_filename, + sizeof(priv_filename), + "%s%s", + test_state->keys_dir, + type); + + torture_setup_tokens(cwd, priv_filename, obj_name, "1"); + + return 0; +} + +static int session_setup(void **state) +{ + int verbosity = torture_libssh_verbosity(); + struct torture_state *s = *state; + struct passwd *pwd = NULL; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int setup_pkcs11(void **state) +{ + struct torture_state *s = *state; + struct pki_st *test_state = NULL; + int rc; + char keys_dir[1024] = {0}; + char *temp_dir = NULL; + + test_state = malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + s->private_data = test_state; + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + snprintf(keys_dir, sizeof(keys_dir), "%s/tests/keys/pkcs11/", SOURCEDIR); + + test_state->keys_dir = strdup(keys_dir); + + setup_tokens(state, LIBSSH_RSA_TESTKEY, "rsa"); + setup_tokens(state, LIBSSH_ECDSA_256_TESTKEY, "ecdsa256"); + setup_tokens(state, LIBSSH_ECDSA_384_TESTKEY, "ecdsa384"); + setup_tokens(state, LIBSSH_ECDSA_521_TESTKEY, "ecdsa521"); + if (!ssh_fips_mode()) { + setup_tokens(state, LIBSSH_ED25519_TESTKEY, "ed25519"); + } + + return 0; +} + +static int sshd_setup(void **state) +{ + + torture_setup_sshd_server(state, true); + setup_pkcs11(state); + + return 0; +} + +static int sshd_teardown(void **state) +{ + struct torture_state *s = *state; + struct pki_st *test_state = s->private_data; + int rc; + + if (test_state != NULL) { + torture_cleanup_tokens(test_state->temp_dir); + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state->keys_dir); + SAFE_FREE(test_state); + } + + torture_teardown_sshd_server(state); + + return 0; +} + +static void +torture_auth_autopubkey(void **state, const char *obj_name, const char *pin) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + char priv_uri[1042]; + + /* Authenticate as charlie with bob his pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_CHARLIE); + assert_int_equal(rc, SSH_OK); + + snprintf(priv_uri, + sizeof(priv_uri), + "pkcs11:token=%s;object=%s;type=private?pin-value=%s", + obj_name, + obj_name, + pin); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, priv_uri); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->opts.identity_non_exp->root->data, priv_uri); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_auth_autopubkey_rsa(void **state) +{ + torture_auth_autopubkey(state, "rsa", "1234"); +} + +static void torture_auth_autopubkey_ecdsa_key_256(void **state) +{ + torture_auth_autopubkey(state, "ecdsa256", "1234"); +} + +static void torture_auth_autopubkey_ecdsa_key_384(void **state) +{ + torture_auth_autopubkey(state, "ecdsa384", "1234"); +} + +static void torture_auth_autopubkey_ecdsa_key_521(void **state) +{ + torture_auth_autopubkey(state, "ecdsa521", "1234"); +} + +#ifdef WITH_PKCS11_PROVIDER +static void torture_auth_autopubkey_ed25519(void **state) +{ + /* The Ed25519 keys are not supported in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + torture_auth_autopubkey(state, "ed25519", "1234"); +} +#endif /* WITH_PKCS11_PROVIDER */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_rsa, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_384, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ecdsa_key_521, + session_setup, + session_teardown), +#ifdef WITH_PKCS11_PROVIDER + cmocka_unit_test_setup_teardown(torture_auth_autopubkey_ed25519, + session_setup, + session_teardown), +#endif /* WITH_PKCS11_PROVIDER */ + }; + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_client_callbacks.c b/src/libs/libssh-0.12.2/tests/client/torture_client_callbacks.c new file mode 100644 index 000000000000..498783e3aad0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_client_callbacks.c @@ -0,0 +1,261 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2012 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/callbacks.h" + +#include +#include +#include + +#define STATE_SUCCESS (1) +#define STATE_FAILURE (2) + +struct callback_state +{ + int open_response; + int request_response; + ssh_session expected_session; + ssh_channel expected_channel; + struct ssh_channel_callbacks_struct *callback; +}; + +static void on_open_response(ssh_session session, ssh_channel channel, bool is_success, void *userdata) +{ + struct callback_state *state = (struct callback_state*)userdata; + assert_ptr_equal(state->expected_session, session); + assert_ptr_equal(state->expected_channel, channel); + state->open_response = is_success ? STATE_SUCCESS : STATE_FAILURE; +} + +static void on_request_response(ssh_session session, ssh_channel channel, void *userdata) +{ + struct callback_state *state = (struct callback_state*)userdata; + assert_ptr_equal(state->expected_session, session); + assert_ptr_equal(state->expected_channel, channel); + state->request_response = STATE_SUCCESS; +} + +static struct callback_state *set_callbacks(ssh_session session, ssh_channel channel) +{ + int rc; + struct ssh_channel_callbacks_struct *cb; + struct callback_state *cb_state = NULL; + + cb_state = (struct callback_state *)calloc(1, + sizeof(struct callback_state)); + assert_non_null(cb_state); + cb_state->expected_session = session; + cb_state->expected_channel = channel; + + cb = (struct ssh_channel_callbacks_struct *)calloc(1, + sizeof(struct ssh_channel_callbacks_struct)); + assert_non_null(cb); + ssh_callbacks_init(cb); + cb->userdata = cb_state; + cb->channel_open_response_function = on_open_response; + cb->channel_request_response_function = on_request_response; + rc = ssh_set_channel_callbacks(channel, cb); + assert_ssh_return_code(session, rc); + + /* Keep the reference so it can be cleaned up later */ + cb_state->callback = cb; + return cb_state; +} + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_open_success(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + assert_int_equal(STATE_SUCCESS, cb_state->open_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +static void torture_open_failure(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_forward(channel, "0.0.0.0", 0, "0.0.0.0", 0); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + assert_int_equal(STATE_FAILURE, cb_state->open_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +static void torture_request_success(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(channel, "echo -n ABCD"); + assert_ssh_return_code(session, rc); + + assert_int_equal(STATE_SUCCESS, cb_state->request_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +static void torture_request_failure(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + struct callback_state *cb_state = NULL; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + cb_state = set_callbacks(session, channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_env(channel, "NOT_ACCEPTED", "VALUE"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + assert_int_equal(STATE_SUCCESS, cb_state->request_response); + + ssh_channel_free(channel); + free(cb_state->callback); + free(cb_state); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_open_success, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_open_failure, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_success, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_failure, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_client_config.c b/src/libs/libssh-0.12.2/tests/client/torture_client_config.c new file mode 100644 index 000000000000..61b4bedbf0af --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_client_config.c @@ -0,0 +1,487 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include "torture.h" +#include "libssh/session.h" +#include "libssh/options.h" +#include "libssh/misc.h" + +#define LIBSSH_SSH_CONFIG "libssh_config" + +#define TORTURE_CONFIG_USER "test-user" + +#define CIPHERS "aes256-gcm@openssh.com,chacha20-poly1305@openssh.com" +#define CIPHERS2 "aes256-cbc,aes128-ctr" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int setup_config_files(void **state) +{ + struct torture_state *s = *state; + int verbosity; + struct passwd *pwd; + char *filename = NULL; + int rc; + + /* Work under the bob's UID to be able to load his configuration file */ + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + filename = ssh_path_expand_tilde("~/.ssh/config"); + torture_write_file(filename, "Ciphers "CIPHERS"\nTestBogus1\nUser "TORTURE_CONFIG_USER); + free(filename); + + torture_write_file(LIBSSH_SSH_CONFIG, "Ciphers "CIPHERS2"\nTestBogus2\n"); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + return 0; +} + +static int setup_session(void **state) +{ + struct torture_state *s = *state; + int verbosity; + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + setenv("NSS_WRAPPER_HOSTNAME", "client.libssh.site", 1); + + return 0; +} + +static int teardown(void **state) +{ + struct torture_state *s = *state; + char *filename; + + filename = ssh_path_expand_tilde("~/.ssh/config"); + if (filename != NULL) { + if (strlen(filename) > 0) { + unlink(filename); + } + SAFE_FREE(filename); + } + + unlink(LIBSSH_SSH_CONFIG); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int teardown_session(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* This tests makes sure that parsing both system-wide and per-user + * configuration files retains OpenSSH semantics (the per-user overrides + * the system-wide values). + * This function ssh_options_parse_config() has hardcoded path to the + * system-wide configuration file so this might not test anything at all + * if this system-wide file does not overwrite this option. + */ +static void torture_client_config_system(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + char *fips_ciphers = NULL; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + } + + /* The first tests assumes there is system-wide configuration file + * setting Ciphers to some non-default value. We do not have any control + * of that in this test case. + */ + ret = ssh_options_parse_config(s->ssh.session, NULL); + assert_ssh_return_code(s->ssh.session, ret); + + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + fips_ciphers); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + fips_ciphers); + } else { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + CIPHERS); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + CIPHERS); + } + + /* Make sure the configuration was processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, TORTURE_CONFIG_USER); + + SAFE_FREE(fips_ciphers); +} + +/* This tests makes sure that parsing both system-wide and per-user + * configuration files retains OpenSSH semantics (the per-user overrides + * the system-wide values). + * The function ssh_options_parse_config() has hardcoded path to the + * system-wide configuration file so we try to emulate the behavior by parsing + * the files separately in the same order. + */ +static void torture_client_config_emulate(void **state) +{ + struct torture_state *s = *state; + char *filename = NULL; + int ret = 0; + + char *fips_ciphers = NULL; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + } + + /* The first tests assumes there is system-wide configuration file + * setting Ciphers to some non-default value. We do not have any control + * of that in this test case + */ + filename = ssh_path_expand_tilde("~/.ssh/config"); + ret = ssh_options_parse_config(s->ssh.session, filename); + free(filename); + assert_ssh_return_code(s->ssh.session, ret); + + ret = ssh_options_parse_config(s->ssh.session, LIBSSH_SSH_CONFIG); + assert_ssh_return_code(s->ssh.session, ret); + + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + fips_ciphers); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + fips_ciphers); + } else { + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_C_S], + CIPHERS); + assert_string_equal(s->ssh.session->opts.wanted_methods[SSH_CRYPT_S_C], + CIPHERS); + } + /* Make sure the configuration was processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, TORTURE_CONFIG_USER); + + SAFE_FREE(fips_ciphers); +} + +/* This verifies that configuration files are parsed by default. + */ +static void torture_client_config_autoparse(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + ret = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* Make sure the configuration was processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, TORTURE_CONFIG_USER); +} + +/* This verifies that we are able to suppress parsing of the configuration files + * on connect using an option. + */ +static void torture_client_config_suppress(void **state) +{ + struct torture_state *s = *state; + bool b = false; + int ret = 0; + + ret = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, ret); + + ret = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* Make sure the configuration was not processed and user modified */ + assert_string_equal(s->ssh.session->opts.username, "bob"); +} + +static void torture_client_config_expand_bad(void **state) +{ + ssh_session session = ssh_new(); + int ret = 0; + + (void)state; + + assert_non_null(session); + + /* The hash without host fails, but does not crash */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(session); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + /* The hash without host fails, but does not crash */ + ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(session); + assert_ssh_return_code_equal(session, ret, SSH_OK); + + ssh_free(session); +} + +static void torture_client_config_expand(void **state) +{ + struct torture_state *s = *state; + int ret = 0; + + /* TEST: user home directory */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%d"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, + BINARYDIR "/tests/home"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: target host name */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%h"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, TORTURE_SSH_SERVER); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: local username */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%u"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "root"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: local hostname */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "client.libssh.site"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: remote username */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, "alice"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%r"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "alice"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: remote port */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT_STR, "2222"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%p"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, "2222"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: empty proxyjump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%j"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + /* No proxyjump string should not explode */ + assert_string_equal(s->ssh.session->opts.knownhosts, ""); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: proxyjump string present */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%j"); + ssh_options_set(s->ssh.session, + SSH_OPTIONS_PROXYJUMP, + "user@" TORTURE_SSH_SERVER ":22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + assert_string_equal(s->ssh.session->opts.knownhosts, + "user@" TORTURE_SSH_SERVER ":22"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: separate list %l-%h-%p-%r-%j with empty ProxyJump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l-%h-%p-%r-%j"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYJUMP, "none"); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PORT_STR, "22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%l-%h-%p-%r-%j\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "client.libssh.site-127.0.0.10-22-alice-"); + + + /* TEST: hash of %l%h%p%r%j with empty ProxyJump */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%C\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "133e3957ff9d01fdcf1f6c7f83325a8ce49bf850"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: separate list %l-%h-%p-%r-%j */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%l-%h-%p-%r-%j"); + ssh_options_set(s->ssh.session, + SSH_OPTIONS_PROXYJUMP, + "user@" TORTURE_SSH_SERVER ":22"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -oProxyJump=user@" TORTURE_SSH_SERVER ":22" + // " -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%l-%h-%p-%r-%j\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "client.libssh.site-127.0.0.10-22-alice-user@" + TORTURE_SSH_SERVER ":22"); + + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + + + /* TEST: hash of %l%h%p%r%j */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_KNOWNHOSTS, "%C"); + + ret = ssh_options_apply(s->ssh.session); + assert_ssh_return_code(s->ssh.session, ret); + + // Tested by + // ret = system(SSH_EXECUTABLE + // " -p 22 -oProxyJump=user@" TORTURE_SSH_SERVER ":22" + // " -o UserKnownHostsFile=/dev/null" + // " -o KnownHostsCommand='/bin/touch \"/tmp/%C\"'" + // " alice@" TORTURE_SSH_SERVER); + // assert_return_code(ret, errno); + assert_string_equal(s->ssh.session->opts.knownhosts, + "adf0b7c4e71a0fee85fd97506507ba8591f3663b"); + + /* Reset the flag so we can repeat the test */ + s->ssh.session->opts.exp_flags &= ~SSH_OPT_EXP_FLAG_KNOWNHOSTS; + +} + + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + /* Keep these first -- following setup is changing user to bob, which we + * do not want */ + cmocka_unit_test(torture_client_config_expand_bad), + cmocka_unit_test_setup_teardown(torture_client_config_expand, + setup_session, + teardown_session), + cmocka_unit_test_setup_teardown(torture_client_config_system, + setup_config_files, + teardown), + cmocka_unit_test_setup_teardown(torture_client_config_emulate, + setup_config_files, + teardown), + cmocka_unit_test_setup_teardown(torture_client_config_autoparse, + setup_config_files, + teardown), + cmocka_unit_test_setup_teardown(torture_client_config_suppress, + setup_config_files, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_client_global_requests.c b/src/libs/libssh-0.12.2/tests/client/torture_client_global_requests.c new file mode 100644 index 000000000000..1320ea0051c4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_client_global_requests.c @@ -0,0 +1,152 @@ +/* + * torture_client_global_requests.c - Tests for client global requests + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/channels.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, true); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int authenticate(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_password(session, NULL, TORTURE_SSH_USER_BOB_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + return rc; +} + +static void torture_unknown_request(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + + rc = authenticate(state); + assert_ssh_return_code(session, rc); + + /* Request asking for reply */ + rc = ssh_global_request(session, "unknown-request-00@test.com", NULL, 1); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* Request and don't ask for reply */ + rc = ssh_global_request(session, "another-bad-req-00@test.com", NULL, 0); + assert_ssh_return_code(session, rc); + + /* Open channel to make sure the session is still working */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + ssh_channel_close(channel); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_unknown_request, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_connect.c b/src/libs/libssh-0.12.2/tests/client/torture_connect.c new file mode 100644 index 000000000000..b34e5bfe6f6b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_connect.c @@ -0,0 +1,401 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "torture_cmocka.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include +#include +#include +#include +#include + +/* Should work until Apnic decides to assign it :) */ +#define BLACKHOLE "1.1.1.1" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, BLACKHOLE); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_connect_peer_discon_msg(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_service_request(session, "wrong-service"); + assert_int_not_equal(rc, SSH_OK); + + ssh_disconnect(session); + assert_non_null(session->peer_discon_msg); + assert_non_null(ssh_get_disconnect_message(session)); +} + +static void torture_connect_nonblocking(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + ssh_set_blocking(session,0); + + do { + rc = ssh_connect(session); + assert_ssh_return_code_not_equal(session, rc, SSH_ERROR); + } while(rc == SSH_AGAIN); + + assert_ssh_return_code(session, rc); +} + +static void torture_connect_ipv6(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "testing"); + assert_ssh_return_code(session, rc); + /* set non-blocking mode */ + ssh_set_blocking(session, 0); + + do { + rc = ssh_connect(session); + } while (rc == SSH_AGAIN); + + assert_ssh_return_code(session, rc); + + /* should work for blocking mode too */ + ssh_disconnect(session); + ssh_set_blocking(session, 1); + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_connect_addrfamily(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + struct aftest { + enum ssh_address_family_options_e family; + char const *host; + int return_code; + }; + static struct aftest aftests[] = { + {SSH_ADDRESS_FAMILY_ANY, "afboth", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET, "afboth", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET6, "afboth", SSH_OK}, + {SSH_ADDRESS_FAMILY_ANY, "afinet", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET, "afinet", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET6, "afinet", SSH_ERROR}, + {SSH_ADDRESS_FAMILY_ANY, "afinet6", SSH_OK}, + {SSH_ADDRESS_FAMILY_INET, "afinet6", SSH_ERROR}, + {SSH_ADDRESS_FAMILY_INET6, "afinet6", SSH_OK}, + }; + + int aftest_count = sizeof(aftests) / sizeof(aftests[0]); + for (int i = 0; i < aftest_count; ++i) { + struct aftest const *t = &aftests[i]; + + rc = ssh_options_set(session, SSH_OPTIONS_ADDRESS_FAMILY, &t->family); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, t->host); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_connect(session); + } while (rc == SSH_AGAIN); + + assert_ssh_return_code_equal(session, rc, t->return_code); + ssh_disconnect(session); + } +} + +#if 0 /* This does not work with socket_wrapper */ +static void torture_connect_timeout(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + struct timeval before, after; + int rc; + long timeout = 2; + time_t sec; + suseconds_t usec; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, BLACKHOLE); + assert_true(rc == SSH_OK); + rc = ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &timeout); + assert_true(rc == SSH_OK); + + rc = gettimeofday(&before, NULL); + assert_true(rc == 0); + rc = ssh_connect(session); + assert_true(rc == SSH_ERROR); + rc = gettimeofday(&after, NULL); + assert_true(rc == 0); + sec = after.tv_sec - before.tv_sec; + usec = after.tv_usec - before.tv_usec; + /* Borrow a second for the missing usecs, but don't bother calculating */ + if (usec < 0) + sec--; + assert_in_range(sec, 1, 3); +} +#endif + +static void torture_connect_double(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_connect_failure(void **state) { + /* + * The intent of this test is to check that a fresh + * ssh_new/ssh_disconnect/ssh_free sequence doesn't crash/leak + * and the behavior of a double ssh_disconnect + */ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + ssh_disconnect(session); +} + +static void torture_connect_socket(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int rc; + int sock_fd = 0; + struct sockaddr_in server_addr = { + .sin_family = AF_INET, + .sin_port = htons(22), + .sin_addr.s_addr = inet_addr(TORTURE_SSH_SERVER), + }; + + sock_fd = socket(AF_INET, SOCK_STREAM, 0); + assert_true(sock_fd > 2); + + rc = connect(sock_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); + assert_return_code(rc, errno); + + ssh_options_set(session, SSH_OPTIONS_FD, &sock_fd); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_connect_uninitialized(UNUSED_PARAM(void **state)) +{ + int rc; + ssh_session session; + struct passwd *pwd; + + /* Make sure the library is uninitialized */ + while (is_ssh_initialized()) { + rc = ssh_finalize(); + assert_return_code(rc, errno); + } + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + session = ssh_new(); + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + /* Expect error from ssh_connect */ + rc = ssh_connect(session); + assert_false(rc == SSH_OK); + assert_string_equal(ssh_get_error(session), "Library not initialized."); + + ssh_free(session); +} + +static void +internal_log(ssh_session session, + int priority, + const char *message, + void *userdata) +{ + (void)session; + (void)priority; + (void)message; + (void)userdata; + + return; +} + +static void +torture_legacy_callback(void **state) +{ + struct ssh_callbacks_struct cb[2] = {0}; + int rc, verbosity = SSH_LOG_WARNING; + ssh_session session = NULL; + + /* unused. */ + (void)state; + + /* + * Legacy code in 'ssh_set_callbacks' used to + * create the conditions for a use-after-free + * issue, in multi-session programs, by failing + * to update a pointer with the new session. + * + * To verify it won't happen again, this test + * creates two consecutive sessions and frees + * them; if any fault occurs then the pointer + * remained at the previous session, failing + * to be updated. + */ + for (int i = 0; i < 2; i++) { + session = ssh_new(); + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(session, rc); + + cb[i].log_function = internal_log; + + ssh_callbacks_init(&cb[i]); + ssh_set_callbacks(session, &cb[i]); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + ssh_disconnect(session); + + ssh_free(session); + } +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_connect_peer_discon_msg, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_ipv6, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_addrfamily, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_double, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_connect_failure, + session_setup, + session_teardown), +#if 0 + cmocka_unit_test_setup_teardown(torture_connect_timeout, session_setup, session_teardown), +#endif + cmocka_unit_test_setup_teardown(torture_connect_socket, + session_setup, + session_teardown), + cmocka_unit_test(torture_legacy_callback), + cmocka_unit_test(torture_connect_uninitialized), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_forward.c b/src/libs/libssh-0.12.2/tests/client/torture_forward.c new file mode 100644 index 000000000000..18b8c85feb00 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_forward.c @@ -0,0 +1,119 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_ssh_forward(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int dport; + int bound_port; + char *originator_host = NULL; + int originator_port; + int rc; + int verbosity = SSH_LOG_TRACE; + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + rc = ssh_channel_listen_forward(session, "127.0.0.21", 8080, &bound_port); + assert_ssh_return_code(session, rc); + + c = ssh_channel_open_forward_port(session, 10, &dport, &originator_host, &originator_port); + /* We do not get a listener and run into the timeout here */ + assert_null(c); + + ssh_channel_send_eof(c); + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_ssh_forward, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_get_kex_algo.c b/src/libs/libssh-0.12.2/tests/client/torture_get_kex_algo.c new file mode 100644 index 000000000000..f25e65553ff2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_get_kex_algo.c @@ -0,0 +1,253 @@ +#include "config.h" +#include "libssh/libcrypto.h" +#include +#define LIBSSH_STATIC +#include "torture.h" +#include +#include + +#define ECDH_SHA2_NISTP256 "ecdh-sha2-nistp256" +#define CURVE25519_SHA256 "curve25519-sha256" +#define DIFFIE_HELLMAN_GROUP_14_SHA_1 "diffie-hellman-group14-sha1" +#define KEX_DH_GEX_SHA1 "diffie-hellman-group-exchange-sha1" +#define KEX_DH_GEX_SHA256 "diffie-hellman-group-exchange-sha256" +#define SNTRUP761X25519 "sntrup761x25519-sha512" +#define SNTRUP761X25519_OPENSSH "sntrup761x25519-sha512@openssh.com" +#define MLKEM768X25519 "mlkem768x25519-sha256" +#define MLKEM768NISTP256 "mlkem768nistp256-sha256" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + bool false_v = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &false_v); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_kex_basic_functionality(void **state) +{ + struct torture_state *s = *state; + ssh_session session = NULL; + const char *kex_algo = NULL; + const char *valid_algorithms[] = { + SNTRUP761X25519, + SNTRUP761X25519_OPENSSH, + MLKEM768X25519, + MLKEM768NISTP256, + CURVE25519_SHA256, + ECDH_SHA2_NISTP256, + DIFFIE_HELLMAN_GROUP_14_SHA_1, + }; + size_t valid_algorithms_count, i; + int rc; + bool is_valid_algo; + + session = s->ssh.session; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + kex_algo = ssh_get_kex_algo(session); + assert_non_null(kex_algo); + + is_valid_algo = false; + valid_algorithms_count = + sizeof(valid_algorithms) / sizeof(valid_algorithms[0]); + for (i = 0; i < valid_algorithms_count; i++) { + if (strcmp(kex_algo, valid_algorithms[i]) == 0) { + is_valid_algo = true; + break; + } + } + assert_true(is_valid_algo); +} + +static void torture_kex_algo_preference(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *expected_kex = NULL; + const char *actual_kex = NULL; + int rc; + + if (ssh_fips_mode()) { + expected_kex = ECDH_SHA2_NISTP256; + } else { + expected_kex = CURVE25519_SHA256; + } + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, expected_kex); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + actual_kex = ssh_get_kex_algo(session); + assert_non_null(actual_kex); + assert_string_equal(actual_kex, expected_kex); +} + +static void torture_kex_algo_negotiation(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *kex_list = + "non-existent-algo,not-supported-kex," CURVE25519_SHA256 + "," ECDH_SHA2_NISTP256 "," DIFFIE_HELLMAN_GROUP_14_SHA_1; + int rc, cmp; + const char *negotiated_kex = NULL; + bool found; + char *temp_list = NULL; + char *token = NULL; + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex_list); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + negotiated_kex = ssh_get_kex_algo(session); + assert_non_null(negotiated_kex); + + assert_string_not_equal(negotiated_kex, "non-existent-algo"); + assert_string_not_equal(negotiated_kex, "not-supported-kex"); + + found = false; + temp_list = strdup(kex_list); + + for (token = strtok(temp_list, ","); token != NULL; + token = strtok(NULL, ",")) { + cmp = strcmp(token, negotiated_kex); + if (cmp == 0) { + found = true; + break; + } + } + + free(temp_list); + assert_true(found); +} + +static void torture_kex_algo_before_connect(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *kex_algo = NULL; + + kex_algo = ssh_get_kex_algo(session); + assert_null(kex_algo); +} + +#ifdef WITH_GEX +static void torture_dgex_algo(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *kex_list = KEX_DH_GEX_SHA1 "," KEX_DH_GEX_SHA256; + int rc, cmp; + const char *negotiated_kex = NULL; + bool found; + char *temp_list = NULL; + char *token = NULL; + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex_list); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + negotiated_kex = ssh_get_kex_algo(session); + assert_non_null(negotiated_kex); + + found = false; + temp_list = strdup(kex_list); + + for (token = strtok(temp_list, ","); token != NULL; + token = strtok(NULL, ",")) { + cmp = strcmp(token, negotiated_kex); + if (cmp == 0) { + found = true; + break; + } + } + + free(temp_list); + assert_true(found); +} +#endif /* WITH_GEX */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_kex_basic_functionality, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_kex_algo_preference, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_kex_algo_negotiation, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_kex_algo_before_connect, + session_setup, + session_teardown), +#ifdef WITH_GEX + cmocka_unit_test_setup_teardown(torture_dgex_algo, + session_setup, + session_teardown), +#endif /* WITH_GEX */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_gssapi_auth.c b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_auth.c new file mode 100644 index 000000000000..d73cad64725d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_auth.c @@ -0,0 +1,305 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPICleanupCredentials yes\n" + "GSSAPIStrictAcceptorCheck yes\n"); + + return 0; +} + +static int +sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_gssapi_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* No client credential */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + /* Invalid host principal */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_client_identity(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid client identity option */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, "bob"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); + + /* Valid client identity option*/ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, SSH_OPTIONS_GSSAPI_CLIENT_IDENTITY, "alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_server_identity(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid server identity option */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "invalid.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server(state); + + /* Valid server identity option*/ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "server.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server(state); +} + +static void +torture_gssapi_auth_delegate_creds(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + OM_uint32 maj_stat, min_stat; + gss_cred_id_t client_creds = GSS_C_NO_CREDENTIAL; + gss_OID_set no_mechs = GSS_C_NO_OID_SET; + int t = 1; + + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &t); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + maj_stat = gss_acquire_cred(&min_stat, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + GSS_C_INITIATE, + &client_creds, + &no_mechs, + NULL); + assert_int_equal(GSS_ERROR(maj_stat), 0); + + ssh_gssapi_set_creds(session, client_creds); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + gss_release_cred(&min_stat, &client_creds); + gss_release_oid_set(&min_stat, &no_mechs); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_auth_bad_user(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server(state); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_client_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_server_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_delegate_creds, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_bad_user, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange.c b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange.c new file mode 100644 index 000000000000..b61acc40cb61 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange.c @@ -0,0 +1,348 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/crypto.h" +#include "torture.h" +#include + +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPIKeyExchange yes\n"); + + return 0; +} + +static int sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_gssapi_key_exchange(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_true(ssh_session_kex_is_gss(session)); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_no_tgt(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Don't run kinit */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_false(ssh_session_kex_is_gss(session)); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_alg(void **state, + const char *kex_string, + enum ssh_key_exchange_e kex_type) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + kex_string); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_int_equal(session->current_crypto->kex_type, kex_type); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_gss_group14_sha256(void **state) +{ + torture_gssapi_key_exchange_alg(state, + "gss-group14-sha256-", + SSH_GSS_KEX_DH_GROUP14_SHA256); +} + +static void torture_gssapi_key_exchange_gss_group16_sha512(void **state) +{ + torture_gssapi_key_exchange_alg(state, + "gss-group16-sha512-", + SSH_GSS_KEX_DH_GROUP16_SHA512); +} + +static void torture_gssapi_key_exchange_gss_nistp256_sha256(void **state) +{ + torture_gssapi_key_exchange_alg(state, + "gss-nistp256-sha256-", + SSH_GSS_KEX_ECDH_NISTP256_SHA256); +} + +static void torture_gssapi_key_exchange_gss_curve25519_sha256(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + torture_gssapi_key_exchange_alg(state, + "gss-curve25519-sha256-", + SSH_GSS_KEX_CURVE25519_SHA256); +} + +static void torture_gssapi_key_exchange_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_auth_bad_user(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_no_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + bool f = false; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + /* Don't do GSSAPI Key Exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &f); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_false(ssh_session_kex_is_gss(session)); + + /* Still try to do "gssapi-keyex" auth */ + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + + torture_teardown_kdc_server(state); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_no_tgt, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_group16_sha512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_nistp256_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_gss_curve25519_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_key_exchange_auth_bad_user, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_no_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange_null.c b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange_null.c new file mode 100644 index 000000000000..125fa39e0022 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_gssapi_key_exchange_null.c @@ -0,0 +1,181 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + struct torture_state *s = NULL; + torture_setup_sshd_server(state, false); + + s = *state; + s->disable_hostkeys = true; + + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + torture_update_sshd_config(state, + "GSSAPIAuthentication yes\n" + "GSSAPIKeyExchange yes\n"); + + torture_teardown_kdc_server(state); + + return 0; +} + +static int sshd_teardown(void **state) +{ + assert_non_null(state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_gssapi_key_exchange_null(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(s->ssh.session, rc); + + assert_string_equal(session->current_crypto->kex_methods[SSH_HOSTKEYS], + "null"); + + assert_true(ssh_session_kex_is_gss(session)); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server(state); +} + +static void torture_gssapi_key_exchange_null_pubkey_auth(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + /* Valid */ + torture_setup_kdc_server( + state, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(s->ssh.session, rc); + + assert_string_equal(session->current_crypto->kex_methods[SSH_HOSTKEYS], + "null"); + + assert_true(ssh_session_kex_is_gss(session)); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_kdc_server(state); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_null, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_key_exchange_null_pubkey_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_hostkey.c b/src/libs/libssh-0.12.2/tests/client/torture_hostkey.c new file mode 100644 index 000000000000..88b657b60f97 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_hostkey.c @@ -0,0 +1,214 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#ifdef HAVE_SYS_TIME_H +#include +#endif /* HAVE_SYS_TIME_H */ +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_hostkey_rsa(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "ssh-rsa"; + + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_hostkey_ed25519(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char ed[] = "ssh-ed25519"; + + int rc; + + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &ed); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +#ifdef HAVE_ECC +static void torture_hostkey_ecdsa(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char ecdsa[] = "ecdsa-sha2-nistp521"; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &ecdsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} +#endif + +static void torture_hostkey_rsa_sha256(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "rsa-sha2-256"; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +static void torture_hostkey_rsa_sha512(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char rsa[] = "rsa-sha2-512"; + + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, &rsa); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_hostkey_rsa, session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_hostkey_ed25519, session_setup, + session_teardown), +#ifdef HAVE_ECC + cmocka_unit_test_setup_teardown(torture_hostkey_ecdsa, session_setup, + session_teardown), +#endif + /* the client is able to handle SHA2 extension (if negotiated) */ + cmocka_unit_test_setup_teardown(torture_hostkey_rsa_sha256, + session_setup, session_teardown), + cmocka_unit_test_setup_teardown(torture_hostkey_rsa_sha512, + session_setup, session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_knownhosts.c b/src/libs/libssh-0.12.2/tests/client/torture_knownhosts.c new file mode 100644 index 000000000000..55aee217905e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_knownhosts.c @@ -0,0 +1,513 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2010 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" + +#include +#include +#include + +#include "session.c" +#include "known_hosts.c" + +#define TMP_FILE_TEMPLATE "known_hosts_XXXXXX" + +#define BADRSA "AAAAB3NzaC1yc2EAAAADAQABAAABAQChm5" \ + "a6Av65O8cKtx5YXOnui3wJnYE6A6J/I4kZSAibbn14Jcl+34VJQwv96f25AxNmo" \ + "NwoiZV93IzdypQmiuieh6s6wB9WhYjU9K/6CkIpNhpCxswA90b3ePjS7LnR9B9J" \ + "slPSbG1H0KC1c5lb7G3utXteXtM+4YvCvpN5VdC4CpghT+p0cwN2Na8Md5vRItz" \ + "YgIytryNn7LLiwYfoSxvWigFrTTZsrVtCOYyNgklmffpGdzuC43wdANvTewfI9G" \ + "o71r8EXmEc228CrYPmb8Scv3mpXFK/BosohSGkPlEHu9lf3YjnknBicDaVtJOYp" \ + "wnXJPjZo2EhG79HxDRpjJHH" +#define BADED25519 "AAAAC3NzaC1lZDI1NTE5AAAAIE74wHmKKkrxpW/dZ69pKPlMoWG9VvWfrNnUkWRQqaDa" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool process_config = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, + &process_config); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + + +static void torture_knownhosts_port(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + char buffer[200]; + char *p; + FILE *file; + int rc; + bool process_config = false; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + session->opts.port = 1234; + rc = ssh_write_knownhost(session); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "r"); + assert_non_null(file); + p = fgets(buffer, sizeof(buffer), file); + assert_non_null(p); + fclose(file); + buffer[sizeof(buffer) - 1] = '\0'; + assert_non_null(strstr(buffer,"[127.0.0.10]:1234 ")); + + ssh_disconnect(session); + ssh_free(session); + + /* Now, connect back to the ssh server and verify the known host line */ + s->ssh.session = session = ssh_new(); + + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + session->opts.port = 1234; + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); +} + +static void torture_knownhosts_wildcard(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + const char *key = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + key = torture_get_testkey_pub(SSH_KEYTYPE_RSA); + fprintf(file, "[127.0.0.10]:* %s\n", key); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); +} + +static void torture_knownhosts_standard_port(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + const char *key = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + key = torture_get_testkey_pub(SSH_KEYTYPE_RSA); + fprintf(file, "[127.0.0.10]:22 %s\n", key); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); +} + +static void torture_knownhosts_fail(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + free(known_hosts_file); + + fprintf(file, "127.0.0.10 ssh-rsa %s\n", BADRSA); + fclose(file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_CHANGED); +} + +static void torture_knownhosts_other(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + free(known_hosts_file); + + fprintf(file, "127.0.0.10 ssh-rsa %s\n", BADRSA); + fclose(file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_FOUND_OTHER); +} + +static void torture_knownhosts_other_auto(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + int rc; + bool process_config = false; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_NOT_KNOWN); + + rc = ssh_write_knownhost(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* ssh-rsa is the default but libssh should try ssh-ed25519 instead */ + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_conflict(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + bool process_config = false; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, "127.0.0.10 ssh-rsa %s\n", BADRSA); + fprintf(file, "127.0.0.10 ssh-ed25519 %s\n", BADED25519); + fclose(file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_CHANGED); + + rc = ssh_write_knownhost(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + assert_ssh_return_code(session, rc); + + ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_is_server_known(session); + assert_int_equal(rc, SSH_SERVER_KNOWN_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_no_hostkeychecking(void **state) +{ + + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + int strict_host_key_checking = 0; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + rc = ssh_options_set(session, SSH_OPTIONS_STRICTHOSTKEYCHECK, &strict_host_key_checking); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_knownhosts_wildcard, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_standard_port, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_port, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_fail, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_other, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_other_auto, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_conflict, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_no_hostkeychecking, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_knownhosts_verify.c b/src/libs/libssh-0.12.2/tests/client/torture_knownhosts_verify.c new file mode 100644 index 000000000000..85963345614e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_knownhosts_verify.c @@ -0,0 +1,519 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" + +#include +#include + +#include "knownhosts.c" + +#define TMP_FILE_TEMPLATE "known_hosts_XXXXXX" + +#define BAD_RSA "AAAAB3NzaC1yc2EAAAADAQABAAABAQDXvXuawzaArEwkLIXTz/EWywLOC" \ + "tqQL3P9yKkrhz6AplXP2PhOh5pyxa1VfGKe453jNeYBJ0ROto3BshXgZX" \ + "bo86oLXTkbe0gO5xi3r5WjXxjOFvRRTLot5fPLNDOv9+TnsPmkNn0iIey" \ + "PnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpbJvTXSDipukpUy41d7jg4uWGu" \ + "onMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdcARyhix4RMWZZqVY20" \ + "H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1TjzkrBZ6B6" \ + "thmZtyeb9IsiB0tg2g0JN2VTAGkxqp" + +const char template[] = "temp_dir_XXXXXX"; + +static int sshd_group_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_group_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + bool process_config = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, + &process_config); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +#define KNOWN_HOST_ENTRY_ECDSA "127.0.0.10 ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd18g==\n" +#define KNOWN_HOST_ENTRY_ED25519 "127.0.0.10 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqX\n" +static void torture_knownhosts_export(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char *entry = NULL; + char *p = NULL; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_session_export_known_hosts_entry(session, &entry); + assert_ssh_return_code(session, rc); + + p = strstr(entry, "ssh-ed25519"); + if (p != NULL) { + assert_string_equal(entry, KNOWN_HOST_ENTRY_ED25519); + } else { + assert_string_equal(entry, KNOWN_HOST_ENTRY_ECDSA); + } + SAFE_FREE(entry); +} + +static void torture_knownhosts_write_and_verify(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e found; + int rc; + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + +static void torture_knownhosts_precheck(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + struct ssh_list *algo_list = NULL; + struct ssh_iterator *it = NULL; + size_t algo_count; + const char *algo = NULL; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_ECDSA_P521)); + + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + algo_list = ssh_known_hosts_get_algorithms(session); + assert_non_null(algo_list); + + algo_count = ssh_list_count(algo_list); + assert_int_equal(algo_count, 3); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ssh-rsa"); + + ssh_list_remove(algo_list, it); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ssh-ed25519"); + + ssh_list_remove(algo_list, it); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ecdsa-sha2-nistp521"); + + ssh_list_free(algo_list); +} + +static void torture_knownhosts_duplicate(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + struct ssh_list *algo_list = NULL; + struct ssh_iterator *it = NULL; + size_t algo_count; + const char *algo = NULL; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + FILE *file; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + free(known_hosts_file); + + algo_list = ssh_known_hosts_get_algorithms(session); + assert_non_null(algo_list); + + algo_count = ssh_list_count(algo_list); + assert_int_equal(algo_count, 1); + + it = ssh_list_get_iterator(algo_list); + assert_non_null(it); + algo = ssh_iterator_value(const char *, it); + assert_string_equal(algo, "ssh-rsa"); + + ssh_list_free(algo_list); +} + +static void torture_knownhosts_other(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + FILE *file = NULL; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + fclose(file); + free(known_hosts_file); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OTHER); +} + +static void torture_knownhosts_unknown(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "ecdsa-sha2-nistp521"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* ssh-rsa is the default but libssh should try ssh-ed25519 instead */ + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_conflict(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char tmp_file[1024] = {0}; + char *known_hosts_file = NULL; + enum ssh_known_hosts_e found; + FILE *file = NULL; + int rc; + + snprintf(tmp_file, + sizeof(tmp_file), + "%s/%s", + s->socket_dir, + TMP_FILE_TEMPLATE); + + known_hosts_file = torture_create_temp_file(tmp_file); + assert_non_null(known_hosts_file); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s %s\n", + "ssh-rsa", + BAD_RSA); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_CHANGED); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + ssh_disconnect(session); + ssh_free(session); + + /* connect again and check host key */ + session = ssh_new(); + assert_non_null(session); + + s->ssh.session = session; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* session will be freed by session_teardown() */ + free(known_hosts_file); +} + +static void torture_knownhosts_new_file(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + enum ssh_known_hosts_e found; + int rc; + + char new_known_hosts[256]; + char *tmp_dir = NULL; + ssize_t count = 0; + + /* Create a disposable directory */ + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + count = snprintf(new_known_hosts, sizeof(new_known_hosts), + "%s/a/b/c/d/known_hosts", tmp_dir); + assert_return_code(count, errno); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, new_known_hosts); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_session_update_known_hosts(session); + assert_ssh_return_code(session, rc); + + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* Cleanup */ + torture_rmdirs(tmp_dir); + + SAFE_FREE(tmp_dir); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_knownhosts_export, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_write_and_verify, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_precheck, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_other, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_unknown, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_conflict, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_duplicate, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_knownhosts_new_file, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_group_setup, sshd_group_teardown); + + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_proxycommand.c b/src/libs/libssh-0.12.2/tests/client/torture_proxycommand.c new file mode 100644 index 000000000000..232080eb0d47 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_proxycommand.c @@ -0,0 +1,265 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include "libssh/priv.h" + +#include +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +#ifdef NCAT_EXECUTABLE +static void torture_options_set_proxycommand(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *address = torture_server_address(AF_INET); + int port = torture_server_port(); + char command[255] = {0}; + struct stat sb; + int rc; +#ifdef WITH_EXEC + socket_t fd; +#endif + + rc = stat(NCAT_EXECUTABLE, &sb); + if (rc != 0 || (sb.st_mode & S_IXOTH) == 0) { + SSH_LOG(SSH_LOG_WARNING, + "Could not find " NCAT_EXECUTABLE ": Skipping the test"); + skip(); + } + + rc = snprintf(command, + sizeof(command), + NCAT_EXECUTABLE " %s %d", + address, + port); + assert_true((size_t)rc < sizeof(command)); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(session); +#ifdef WITH_EXEC + assert_ssh_return_code(session, rc); + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); +#else + assert_int_equal(rc, SSH_ERROR); +#endif /* WITH_EXEC */ +} + +#else /* NCAT_EXECUTABLE */ + +static void torture_options_set_proxycommand(void **state) +{ + (void) state; + skip(); +} + +#endif /* NCAT_EXECUTABLE */ + +static void torture_options_set_proxycommand_notexist(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "this_command_does_not_exist"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +static void torture_options_set_proxycommand_ssh(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *address = torture_server_address(AF_INET); + char command[255] = {0}; + int rc; +#ifdef WITH_EXEC + socket_t fd; +#endif + + rc = snprintf(command, sizeof(command), + "ssh -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -W [%%h]:%%p alice@%s", + address); + assert_true((size_t)rc < sizeof(command)); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(session); +#ifdef WITH_EXEC + assert_ssh_return_code(session, rc); + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); +#else + assert_int_equal(rc, SSH_ERROR); +#endif /* WITH_EXEC */ +} + +static void torture_options_set_proxycommand_ssh_stderr(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + const char *address = torture_server_address(AF_INET); + char command[255] = {0}; + int rc; +#ifdef WITH_EXEC + socket_t fd; +#endif + + /* The -vvv switches produce the desired output on the standard error */ + rc = snprintf(command, sizeof(command), + "ssh -vvv -oStrictHostKeyChecking=no -oUserKnownHostsFile=/dev/null -W [%%h]:%%p alice@%s", + address); + assert_true((size_t)rc < sizeof(command)); + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(session); +#ifdef WITH_EXEC + assert_ssh_return_code(session, rc); + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); +#else + assert_int_equal(rc, SSH_ERROR); +#endif /* WITH_EXEC */ +} + +static void torture_options_proxycommand_injection(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + const char *malicious_host = "`echo foo > mfile`"; + const char *command = "nc %h %p"; + char *current_dir = NULL; + char *malicious_file_path = NULL; + int mfp_len; + int verbosity = torture_libssh_verbosity(); + struct stat sb; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + // if we would be checking the rc, this should fail + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, malicious_host); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROXYCOMMAND, command); + assert_int_equal(rc, 0); + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code_equal(s->ssh.session, rc, SSH_ERROR); + + current_dir = torture_get_current_working_dir(); + assert_non_null(current_dir); + mfp_len = strlen(current_dir) + 6; + malicious_file_path = malloc(mfp_len); + assert_non_null(malicious_file_path); + rc = snprintf(malicious_file_path, mfp_len, + "%s/mfile", current_dir); + assert_int_equal(rc, mfp_len); + free(current_dir); + rc = stat(malicious_file_path, &sb); + assert_int_not_equal(rc, 0); + + // cleanup + remove(malicious_file_path); + free(malicious_file_path); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_notexist, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_set_proxycommand_ssh_stderr, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_options_proxycommand_injection, + NULL, + session_teardown), + }; + + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_proxyjump.c b/src/libs/libssh-0.12.2/tests/client/torture_proxyjump.c new file mode 100644 index 000000000000..cc2d8d64801f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_proxyjump.c @@ -0,0 +1,362 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd = NULL; + int rc; + bool b = false; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(s->ssh.session, rc); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_proxyjump_single_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + int rc; + socket_t fd; + + rc = snprintf(proxyjump_buf, sizeof(proxyjump_buf), "alice@%s:22", address); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static int before_connection(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int verify_knownhost(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int authenticate(ssh_session jump_session, void *user) +{ + (void)user; + + return ssh_userauth_publickey_auto(jump_session, NULL, NULL); +} + +static int authenticate_doe(ssh_session jump_session, void *user) +{ + ssh_key pkey = NULL; + char bob_ssh_key[1024]; + struct passwd *pwd = NULL; + int rc; + + (void)user; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_rsa", pwd->pw_dir); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &pkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(jump_session, NULL, pkey); + ssh_key_free(pkey); + return rc; +} + +static int authenticate_frank(ssh_session jump_session, void *user) +{ + ssh_key pkey = NULL; + char bob_ssh_key[1024]; + struct passwd *pwd = NULL; + int rc; + + (void)user; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + snprintf(bob_ssh_key, sizeof(bob_ssh_key), "%s/.ssh/id_ecdsa", pwd->pw_dir); + + rc = ssh_pki_import_privkey_file(bob_ssh_key, NULL, NULL, NULL, &pkey); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey(jump_session, NULL, pkey); + ssh_key_free(pkey); + return rc; +} + +static void torture_proxyjump_multiple_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + int rc; + socket_t fd; + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate, + }; + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "alice@%s:22,alice@%s:22", + address, + address); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_proxyjump_multiple_sshd_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + const char *address1 = torture_server1_address(AF_INET6); + int rc; + socket_t fd; + + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate_doe, + }; + + torture_setup_sshd_servers(state, false); + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "doe@%s:22,doe@%s:22", + address, + address1); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_sshd_server1(state); +} + +static void torture_proxyjump_multiple_sshd_users_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET6); + const char *address1 = torture_server1_address(AF_INET); + int rc; + socket_t fd; + + struct ssh_jump_callbacks_struct c1 = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate_doe, + }; + struct ssh_jump_callbacks_struct c2 = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate_frank, + }; + + torture_setup_sshd_servers(state, false); + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "doe@%s:22,frank@%s:22", + address, + address1); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c1); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c2); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_int_not_equal(fd, SSH_INVALID_SOCKET); + + rc = fcntl(fd, F_GETFL); + assert_int_equal(rc & O_RDWR, O_RDWR); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_sshd_server1(state); +} + +static void torture_proxyjump_invalid_jump(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char proxyjump_buf[500] = {0}; + const char *address = torture_server_address(AF_INET); + int rc; + + rc = snprintf(proxyjump_buf, + sizeof(proxyjump_buf), + "doesnotexist@%s:54", + address); + if (rc < 0 || rc >= (int)sizeof(proxyjump_buf)) { + fail_msg("snprintf failed"); + } + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, proxyjump_buf); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_proxyjump_single_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_proxyjump_multiple_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_proxyjump_multiple_sshd_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_proxyjump_multiple_sshd_users_jump, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_proxyjump_invalid_jump, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_rekey.c b/src/libs/libssh-0.12.2/tests/client/torture_rekey.c new file mode 100644 index 000000000000..d458975cff43 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_rekey.c @@ -0,0 +1,1013 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Authors: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/sftp.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/token.h" + +#include +#include +#include +#include +#include + +#define KEX_RETRY 32 + +static uint64_t bytes = 2048; /* 2KB (more than the authentication phase) */ + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + + /* Authenticate as alice with bob's pubkey */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + /* Make sure we do not interfere with another ssh-agent */ + unsetenv("SSH_AUTH_SOCK"); + unsetenv("SSH_AGENT_PID"); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_free(s->ssh.session); + s->ssh.session = NULL; + + return 0; +} + +/* Check that the default limits for rekeying are enforced. + * the limits are too high for testsuite to verify so + * we should be fine with checking the values in internal + * structures + */ +static void torture_rekey_default(void **state) +{ + struct torture_state *s = *state; + int rc; + struct ssh_crypto_struct *c = NULL; + + /* Define preferred ciphers: */ + if (ssh_fips_mode()) { + /* We do not have any FIPS allowed cipher with different block size */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_C_S, + "aes128-gcm@openssh.com"); + } else { + /* (out) C->S has 8B block */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_C_S, + "chacha20-poly1305@openssh.com"); + } + assert_ssh_return_code(s->ssh.session, rc); + /* (in) S->C has 16B block */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_CIPHERS_S_C, + "aes128-cbc"); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + c = s->ssh.session->current_crypto; + /* The blocks limit is set correctly */ + /* For S->C (in) we have 16B block => 2**(L/4) blocks */ + assert_int_equal(c->in_cipher->max_blocks, + (uint64_t)1 << (2 * c->in_cipher->blocksize)); + if (ssh_fips_mode()) { + /* We do not have any FIPS allowed cipher with different block size */ + assert_int_equal(c->in_cipher->max_blocks, + (uint64_t)1 << (2 * c->in_cipher->blocksize)); + } else { + /* The C->S (out) we have 8B block => 1 GB limit */ + assert_int_equal(c->out_cipher->max_blocks, + ((uint64_t)1 << 30) / c->out_cipher->blocksize); + } + + ssh_disconnect(s->ssh.session); +} + +static void sanity_check_session_size(void **state, uint64_t rekey_limit) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + + c = s->ssh.session->current_crypto; + assert_non_null(c); + assert_int_equal(c->in_cipher->max_blocks, + rekey_limit / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, + rekey_limit / c->out_cipher->blocksize); + /* when strict kex is used, the newkeys reset the sequence number */ + if ((s->ssh.session->flags & SSH_SESSION_FLAG_KEX_STRICT) != 0) { + assert_int_equal(c->out_cipher->packets, s->ssh.session->send_seq); + assert_int_equal(c->in_cipher->packets, s->ssh.session->recv_seq); + } else { + /* Otherwise we have less encrypted packets than transferred + * (first are not encrypted) */ + assert_true(c->out_cipher->packets < s->ssh.session->send_seq); + assert_true(c->in_cipher->packets < s->ssh.session->recv_seq); + } +} +static void sanity_check_session(void **state) +{ + sanity_check_session_size(state, bytes); +} + +/* We lower the rekey limits manually and check that the rekey + * really happens when sending data + */ +static void torture_rekey_send(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + sanity_check_session(state); + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* send ignore packets of up to 1KB to trigger rekey. Send little bit more + * to make sure it completes with all different ciphers */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* The rekey limit was restored in the new crypto to the same value */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); + /* Check that the secret hash is different than initially */ + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + +#ifdef WITH_SFTP +static void session_setup_sftp(void **state) +{ + struct torture_state *s = *state; + int rc; + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Initialize SFTP session */ + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); +} + +static int session_setup_sftp_client(void **state) +{ + struct torture_state *s = *state; + int rc; + + session_setup(state); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + session_setup_sftp(state); + + return 0; +} + +#define MAX_XFER_BUF_SIZE 16384 + +/* To trigger rekey by receiving data, the easiest thing is probably to + * use sftp + */ +static void torture_rekey_recv_size(void **state, uint64_t rekey_limit) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + int rc; + + sanity_check_session_size(state, rekey_limit); + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* Download a file */ + file = sftp_open(s->ssh.tsftp->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + unlink(libssh_tmp_file); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + if (bytesread == 0) { + break; /* EOF */ + } + assert_false(bytesread < 0); + + byteswritten = write(fd, buf, bytesread); + assert_int_equal(byteswritten, bytesread); + } + + rc = sftp_close(file); + assert_int_equal(rc, SSH_NO_ERROR); + close(fd); + + /* The rekey limit was restored in the new crypto to the same value */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, + rekey_limit / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, + rekey_limit / c->out_cipher->blocksize); + /* Check that the secret hash is different than initially */ + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); +} + +static void torture_rekey_recv(void **state) +{ + torture_rekey_recv_size(state, bytes); +} +#endif /* WITH_SFTP */ + +/* Rekey time requires rekey after specified time and is off by default. + * Setting the time to small enough value and waiting, we should trigger + * rekey on the first sent packet afterward. + */ +static void torture_rekey_time(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + uint32_t time = 3; /* 3 seconds */ + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_TIME, &time); + assert_ssh_return_code(s->ssh.session, rc); + /* The time is internally stored in microseconds */ + assert_int_equal(time * 1000, s->ssh.session->opts.rekey_time); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Send some data. This should not trigger rekey yet */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 8); + for (i = 0; i < 3; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* Check that the secret hash is the same */ + c = s->ssh.session->current_crypto; + assert_memory_equal(secret_hash, c->secret_hash, c->digest_len); + + /* Wait some more time */ + sleep(3); + + /* send some more data to trigger rekey and handle the + * key exchange "in background" */ + for (i = 0; i < 8; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + +/* We lower the rekey limits manually and check that the rekey + * really happens when sending data + */ +static void torture_rekey_server_send(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + const char *sshd_config = "RekeyLimit 2K none"; + + torture_update_sshd_config(state, sshd_config); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* send ignore packets of up to 1KB to trigger rekey */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < 20; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 50); + } + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + +static void torture_rekey_different_kex(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + size_t secret_hash_len = 0; + const char *kex1 = "diffie-hellman-group14-sha256,curve25519-sha256,ecdh-sha2-nistp256"; + const char *kex2 = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,ecdh-sha2-nistp521"; + + /* Use short digest for initial key exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex1); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* The blocks limit is set correctly */ + sanity_check_session(state); + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + assert_non_null(c); + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + secret_hash_len = c->digest_len; + assert_int_equal(secret_hash_len, 32); /* SHA256 len */ + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Now try to change preference of key exchange algorithm to something with larger digest */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex2); + assert_ssh_return_code(s->ssh.session, rc); + + /* send ignore packets of up to 1KB to trigger rekey. Send little bit more + * to make sure the rekey it completes with all different ciphers (paddings */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 1000); + + c = s->ssh.session->current_crypto; + /* SHA256 len */ + if (c->digest_len != 32) { + break; + } + } + + /* The rekey limit was restored in the new crypto to the same value */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->in_cipher->max_blocks, bytes / c->in_cipher->blocksize); + assert_int_equal(c->out_cipher->max_blocks, bytes / c->out_cipher->blocksize); + /* Check that the secret hash is different than initially */ + assert_int_equal(c->digest_len, 64); /* SHA512 len */ + assert_memory_not_equal(secret_hash, c->secret_hash, secret_hash_len); + /* Session ID stays same after one rekey */ + assert_memory_equal(secret_hash, c->session_id, secret_hash_len); + free(secret_hash); + + assert_int_equal(ssh_is_connected(s->ssh.session), 1); + assert_int_equal(s->ssh.session->session_state, SSH_SESSION_STATE_AUTHENTICATED); + + ssh_disconnect(s->ssh.session); +} + +static void torture_rekey_server_different_kex(void **state) +{ + struct torture_state *s = *state; + int rc; + char data[256]; + unsigned int i; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + size_t secret_hash_len = 0; + const char *sshd_config = "RekeyLimit 2K none"; + const char *kex1 = "diffie-hellman-group14-sha256,curve25519-sha256,ecdh-sha2-nistp256"; + const char *kex2 = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512"; + + /* Use short digest for initial key exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex1); + assert_ssh_return_code(s->ssh.session, rc); + + torture_update_sshd_config(state, sshd_config); + + rc = ssh_connect(s->ssh.session); + assert_ssh_return_code(s->ssh.session, rc); + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + secret_hash_len = c->digest_len; + assert_int_equal(secret_hash_len, 32); /* SHA256 len */ + + /* OpenSSH can not rekey before authentication so authenticate here */ + rc = ssh_userauth_none(s->ssh.session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(s->ssh.session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(s->ssh.session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(s->ssh.session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Now try to change preference of key exchange algorithm to something with larger digest */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_KEY_EXCHANGE, kex2); + assert_ssh_return_code(s->ssh.session, rc); + + /* send ignore packets of up to 1KB to trigger rekey. Send little bit more + * to make sure the rekey it completes with all different ciphers (paddings */ + memset(data, 0, sizeof(data)); + memset(data, 'A', 128); + for (i = 0; i < KEX_RETRY; i++) { + ssh_send_ignore(s->ssh.session, data); + ssh_handle_packets(s->ssh.session, 1000); + + c = s->ssh.session->current_crypto; + /* SHA256 len */ + if (c->digest_len != 32) { + break; + } + } + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_int_equal(c->digest_len, 64); /* SHA512 len */ + assert_memory_not_equal(secret_hash, c->secret_hash, secret_hash_len); + /* Session ID stays same after one rekey */ + assert_memory_equal(secret_hash, c->session_id, secret_hash_len); + free(secret_hash); + + ssh_disconnect(s->ssh.session); +} + + +#ifdef WITH_SFTP +static int session_setup_sftp_server(void **state) +{ + const char *sshd_config = "RekeyLimit 2K none"; + + session_setup(state); + + torture_update_sshd_config(state, sshd_config); + + session_setup_sftp(state); + + return 0; +} + +static void torture_rekey_server_recv(void **state) +{ + struct torture_state *s = *state; + struct ssh_crypto_struct *c = NULL; + unsigned char *secret_hash = NULL; + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + int rc; + + /* Copy the initial secret hash = session_id so we know we changed keys later */ + c = s->ssh.session->current_crypto; + secret_hash = malloc(c->digest_len); + assert_non_null(secret_hash); + memcpy(secret_hash, c->secret_hash, c->digest_len); + + /* Download a file */ + file = sftp_open(s->ssh.tsftp->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + unlink(libssh_tmp_file); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + if (bytesread == 0) { + break; /* EOF */ + } + assert_false(bytesread < 0); + + byteswritten = write(fd, buf, bytesread); + assert_int_equal(byteswritten, bytesread); + } + + rc = sftp_close(file); + assert_int_equal(rc, SSH_NO_ERROR); + close(fd); + + /* Check that the secret hash is different than initially */ + c = s->ssh.session->current_crypto; + assert_memory_not_equal(secret_hash, c->secret_hash, c->digest_len); + free(secret_hash); + + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); +} +#endif /* WITH_SFTP */ + +#ifdef WITH_ZLIB +/* This is disabled by OpenSSH since OpenSSH 7.4p1 */ +#if (OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR < 4) || OPENSSH_VERSION_MAJOR < 7 +/* Compression can be funky to get right after rekey + */ +static void torture_rekey_send_compression(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_send(state); +} + +#ifdef WITH_SFTP +static void torture_rekey_recv_compression(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_recv(state); +} +#endif /* WITH_SFTP */ +#endif + +/* Especially the delayed compression by openssh. + */ +static void torture_rekey_send_compression_delayed(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib@openssh.com"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_send(state); +} + +#ifdef WITH_SFTP +static void torture_rekey_recv_compression_delayed(void **state) +{ + struct torture_state *s = *state; + const char *comp = "zlib@openssh.com"; + int rc; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_C_S, comp); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_COMPRESSION_S_C, comp); + assert_ssh_return_code(s->ssh.session, rc); + + torture_rekey_recv(state); +} +#endif /* WITH_SFTP */ +#endif /* WITH_ZLIB */ + +static void setup_server_for_good_guess(void *state) +{ + const char *default_sshd_config = "KexAlgorithms curve25519-sha256"; + const char *fips_sshd_config = "KexAlgorithms ecdh-sha2-nistp256"; + const char *sshd_config = default_sshd_config; + + if (ssh_fips_mode()) { + sshd_config = fips_sshd_config; + } + /* This sets an only supported kex algorithm that we do not have as a first + * option */ + torture_update_sshd_config(state, sshd_config); +} + +static void torture_rekey_guess_send(void **state) +{ + struct torture_state *s = *state; + + setup_server_for_good_guess(state); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + torture_rekey_send(state); +} + +static void torture_rekey_guess_wrong_send(void **state) +{ + struct torture_state *s = *state; + const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; + + /* This sets an only supported kex algorithm that we do not have as a first + * option */ + torture_update_sshd_config(state, sshd_config); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + torture_rekey_send(state); +} + +#ifdef WITH_SFTP +static void torture_rekey_guess_recv(void **state) +{ + struct torture_state *s = *state; + int rc; + + setup_server_for_good_guess(state); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + session_setup_sftp(state); + + torture_rekey_recv(state); +} + +static void torture_rekey_guess_wrong_recv(void **state) +{ + struct torture_state *s = *state; + const char *sshd_config = "KexAlgorithms diffie-hellman-group14-sha256"; + int rc; + + /* This sets an only supported kex algorithm that we do not have as a first + * option */ + torture_update_sshd_config(state, sshd_config); + + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_REKEY_DATA, &bytes); + assert_ssh_return_code(s->ssh.session, rc); + + session_setup_sftp(state); + + torture_rekey_recv(state); +} + +static void torture_rekey_guess_all_combinations(void **state) +{ + struct torture_state *s = *state; + char sshd_config[256] = ""; + char client_kex[256] = ""; + const char *supported = NULL; + struct ssh_tokens_st *s_tok = NULL; + uint64_t rekey_limit = 0; + const char *p = NULL; + int rc, i, j; + + /* The rekey limit is 1/2 of the transferred file size so we will likely get + * 2 rekeys per test, which still runs for acceptable time */ + rekey_limit = atoll(SSH_EXECUTABLE_SIZE); + rekey_limit /= 2; + + if (ssh_fips_mode()) { + supported = ssh_kex_get_fips_methods(SSH_KEX); + } else { + supported = ssh_kex_get_supported_method(SSH_KEX); + } + assert_non_null(supported); + + s_tok = ssh_tokenize(supported, ','); + assert_non_null(s_tok); + for (i = 0; s_tok->tokens[i]; i++) { + /* Skip algorithms not supported by the OpenSSH server. + * Check also for prefix matches to distinguish vendor-specific names + * such as sntrup761x25519-sha512 and the @openssh.com alias */ + if ((p = strstr(OPENSSH_KEX, s_tok->tokens[i])) == NULL || + (*(p + strlen(s_tok->tokens[i])) != ',' && + *(p + strlen(s_tok->tokens[i])) != '\0')) { + SSH_LOG(SSH_LOG_INFO, "Server: %s [skipping]", s_tok->tokens[i]); + continue; + } + SSH_LOG(SSH_LOG_INFO, "Server: %s", s_tok->tokens[i]); + snprintf(sshd_config, + sizeof(sshd_config), + "KexAlgorithms %s", + s_tok->tokens[i]); + /* This sets an only supported kex algorithm that we do not have as + * a first option in the client */ + torture_update_sshd_config(state, sshd_config); + + for (j = 0; s_tok->tokens[j]; j++) { + if (i == j) { + continue; + } + + session_setup(state); + /* Make the client send the first_kex_packet_follows flag during key + * exchange as well as during the rekey */ + s->ssh.session->send_first_kex_follows = true; + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_REKEY_DATA, + &rekey_limit); + assert_ssh_return_code(s->ssh.session, rc); + + /* Client kex preference will have the second of the pair and the + * server one as a second to negotiate on the second attempt */ + snprintf(client_kex, + sizeof(client_kex), + "%s,%s", + s_tok->tokens[j], + s_tok->tokens[i]); + SSH_LOG(SSH_LOG_INFO, "Client: %s", client_kex); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_KEY_EXCHANGE, + client_kex); + assert_ssh_return_code(s->ssh.session, rc); + session_setup_sftp(state); + torture_rekey_recv_size(state, rekey_limit); + session_teardown(state); + } + } + + ssh_tokens_free(s_tok); +} +#endif /* WITH_SFTP */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_rekey_default, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_time, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_recv, + session_setup_sftp_client, + session_teardown), +#endif /* WITH_SFTP */ + cmocka_unit_test_setup_teardown(torture_rekey_send, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_different_kex, + session_setup, + session_teardown), +#ifdef WITH_ZLIB +#if (OPENSSH_VERSION_MAJOR == 7 && OPENSSH_VERSION_MINOR < 4) || OPENSSH_VERSION_MAJOR < 7 + cmocka_unit_test_setup_teardown(torture_rekey_send_compression, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_recv_compression, + session_setup_sftp_client, + session_teardown), +#endif /* WITH_SFTP */ +#endif + cmocka_unit_test_setup_teardown(torture_rekey_send_compression_delayed, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_recv_compression_delayed, + session_setup_sftp_client, + session_teardown), +#endif /* WITH_SFTP */ +#endif /* WITH_ZLIB */ + /* TODO verify the two rekey are possible and the states are not broken after rekey */ + + cmocka_unit_test_setup_teardown(torture_rekey_server_different_kex, + session_setup, + session_teardown), + /* Note, that these tests modify the sshd_config so follow-up tests + * might get unexpected behavior if they do not update the server with + * torture_update_sshd_config() too */ + cmocka_unit_test_setup_teardown(torture_rekey_server_send, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_send, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_send, + session_setup, + session_teardown), +#ifdef WITH_SFTP + cmocka_unit_test_setup_teardown(torture_rekey_server_recv, + session_setup_sftp_server, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_recv, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_rekey_guess_wrong_recv, + session_setup, + session_teardown), + cmocka_unit_test(torture_rekey_guess_all_combinations), +#endif /* WITH_SFTP */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_request_env.c b/src/libs/libssh-0.12.2/tests/client/torture_request_env.c new file mode 100644 index 000000000000..21806dfb6430 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_request_env.c @@ -0,0 +1,137 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_request_env(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + char buffer[4096] = {0}; + int nbytes; + int rc; + int lang_found = 0; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_env(c, "LC_LIBSSH", "LIBSSH_EXPORTED_VARIABLE"); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "echo $LC_LIBSSH"); + assert_ssh_return_code(session, rc); + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + printf("nbytes=%d\n", nbytes); + while (nbytes > 0) { +#if 1 + rc = fwrite(buffer, 1, nbytes, stdout); + assert_int_equal(rc, nbytes); +#endif + buffer[nbytes]='\0'; + if (strstr(buffer, "LIBSSH_EXPORTED_VARIABLE")) { + lang_found = 1; + break; + } + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer), 0); + } + assert_int_equal(lang_found, 1); + + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_request_env, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/client/torture_request_pty_modes.c b/src/libs/libssh-0.12.2/tests/client/torture_request_pty_modes.c new file mode 100644 index 000000000000..16d9b19cfdcc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_request_pty_modes.c @@ -0,0 +1,264 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2013 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* reads from the channel, expecting the given output */ +static int check_channel_output(ssh_channel c, const char *expected) +{ + char buffer[4096] = {0}; + int nbytes, offset = 0; + + nbytes = ssh_channel_read(c, buffer, sizeof(buffer) - 1, 0); + while (nbytes > 0) { + buffer[offset + nbytes] = '\0'; + ssh_log_hexdump("Read bytes:", + (unsigned char *)buffer, + offset + nbytes); + if (strstr(buffer, expected) != NULL) + { + return 1; + } + /* read on */ + offset = nbytes; + nbytes = ssh_channel_read(c, + buffer + offset, + sizeof(buffer) - offset - 1, + 0); + } + return 0; +} + +/* set explicit TTY modes and validate that the server uses them */ +static void torture_request_pty_modes_translate_ocrnl(void **state) +{ + const unsigned char modes[] = { + /* enable OCRNL */ + 73, 0, 0, 0, 1, + /* disable all other CR/NL handling */ + 34, 0, 0, 0, 0, + 35, 0, 0, 0, 0, + 36, 0, 0, 0, 0, + 72, 0, 0, 0, 0, + 74, 0, 0, 0, 0, + 75, 0, 0, 0, 0, + 0, /* TTY_OP_END */ + }; + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_pty_size_modes(c, "xterm", 80, 25, modes, sizeof(modes)); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect 2 newline characters */ + string_found = check_channel_output(c, ">TEST\n\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +/* if stdin is a TTY, its modes are passed to the server */ +static void torture_request_pty_modes_use_stdin_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + struct termios modes; + int stdin_backup_fd = -1; + int master_fd, slave_fd; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must be a TTY, so open one and replace the FD */ + stdin_backup_fd = dup(STDIN_FILENO); + rc = openpty(&master_fd, &slave_fd, NULL, NULL, NULL); + assert_int_equal(rc, 0); + dup2(master_fd, STDIN_FILENO); + assert_true(isatty(STDIN_FILENO)); + /* translate NL to CRNL on output to see a noticeable effect */ + memset(&modes, 0, sizeof(modes)); + tcgetattr(STDIN_FILENO, &modes); + modes.c_oflag |= ONLCR; + modes.c_iflag &= ~(ICRNL | INLCR | IGNCR); + tcsetattr(STDIN_FILENO, TCSANOW, &modes); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + close(master_fd); + close(slave_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect 2 carriage return characters + newline */ + string_found = check_channel_output(c, ">TEST\r\r\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +/* if stdin is NOT a TTY, default modes are passed to the server */ +static void torture_request_pty_modes_use_default_modes(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel c; + int rc; + int string_found = 0; + int stdin_backup_fd = -1; + + c = ssh_channel_new(session); + assert_non_null(c); + + rc = ssh_channel_open_session(c); + assert_ssh_return_code(session, rc); + + /* stdin must not a TTY - change the FD to something else */ + stdin_backup_fd = dup(STDIN_FILENO); + close(STDIN_FILENO); + rc = open("/dev/null", O_RDONLY); // reuses FD 0 now + assert_int_equal(rc, STDIN_FILENO); + assert_false(isatty(STDIN_FILENO)); + + rc = ssh_channel_request_pty_size(c, "xterm", 80, 25); + + /* revert the changes to STDIN first! */ + dup2(stdin_backup_fd, STDIN_FILENO); + close(stdin_backup_fd); + + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(c, "/bin/echo -e '>TEST\\r\\n<'"); + assert_ssh_return_code(session, rc); + + /* expect the CRLF translated to newline */ + string_found = check_channel_output(c, ">TEST\r\r\n<"); + assert_int_equal(string_found, 1); + + ssh_channel_close(c); +} + +int torture_run_tests(void) { + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_request_pty_modes_translate_ocrnl, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_stdin_modes, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_request_pty_modes_use_default_modes, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/client/torture_scp.c b/src/libs/libssh-0.12.2/tests/client/torture_scp.c new file mode 100644 index 000000000000..fe3f239b34a1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_scp.c @@ -0,0 +1,664 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "libssh/scp.h" + +#include +#include +#include +#include +#include + +#define BUF_SIZE 1024 + +#define TEMPLATE BINARYDIR "/tests/home/alice/temp_dir_XXXXXX" +#define ALICE_HOME BINARYDIR "/tests/home/alice" + +/* store the original umask */ +mode_t old; + +struct scp_st { + struct torture_state *s; + char *tmp_dir; + char *tmp_dir_basename; +}; + +static int sshd_setup(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ts = (struct scp_st *)calloc(1, sizeof(struct scp_st)); + assert_non_null(ts); + + torture_setup_sshd_server((void **)&s, false); + assert_non_null(s); + + ts->s = s; + + *state = ts; + + return 0; +} + +static int sshd_teardown(void **state) +{ + struct scp_st *ts = NULL; + + ts = *((struct scp_st **)state); + assert_non_null(ts); + assert_non_null(ts->s); + + torture_teardown_sshd_server((void **)&(ts->s)); + + SAFE_FREE(ts); + + return 0; +} + +static int session_setup(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + char *tmp_dir = NULL; + char *tmp_dir_basename = NULL; + + struct passwd *pwd; + + int rc; + + assert_non_null(state); + + ts = *state; + + assert_non_null(ts); + assert_non_null(ts->s); + + s = ts->s; + + /* store the original umask and set a new one */ + old = umask(0022); + + /* Create temporary directory for alice */ + tmp_dir = torture_make_temp_dir(TEMPLATE); + assert_non_null(tmp_dir); + ts->tmp_dir = tmp_dir; + + tmp_dir_basename = ssh_basename(tmp_dir); + assert_non_null(tmp_dir_basename); + ts->tmp_dir_basename = tmp_dir_basename; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + assert_non_null(state); + ts = *((struct scp_st **)state); + + assert_non_null(ts->s); + s = ts->s; + + /* restore the umask */ + umask(old); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + assert_non_null(ts->tmp_dir); + torture_rmdirs(ts->tmp_dir); + + SAFE_FREE(ts->tmp_dir); + SAFE_FREE(ts->tmp_dir_basename); + + return 0; +} + +static void torture_scp_upload(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_a[BUF_SIZE]; + char buf[BUF_SIZE]; + FILE *file = NULL; + size_t len = 0; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload file "a" to alice's temp dir */ + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE, ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Init buffer content to be written */ + memset(expected_a, 'A', BUF_SIZE); + + /* For ssh_scp_push_file(), the file_name is the name of the file without + * path */ + rc = ssh_scp_push_file(scp, "a", BUF_SIZE, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, expected_a, BUF_SIZE); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Open file and check content */ + snprintf(buf, BUF_SIZE, "%s/a", ts->tmp_dir); + + file = fopen(buf, "r"); + assert_non_null(file); + + len = fread(buf, BUF_SIZE, 1, file); + assert_int_equal(len, 1); + assert_memory_equal(buf, expected_a, BUF_SIZE); + + fclose(file); +} + +static void torture_scp_upload_recursive(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_b[BUF_SIZE]; + char buf[BUF_SIZE]; + FILE *file = NULL; + size_t len = 0; + + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload directory "test_dir" containing file "b" to alice's temp dir */ + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, + ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Push directory where the new file will be copied */ + rc = ssh_scp_push_directory(scp, "test_dir", 0755); + assert_ssh_return_code(session, rc); + + memset(expected_b, 'B', BUF_SIZE); + + /* For ssh_scp_push_file(), the file_name is the name of the file without + * path */ + rc = ssh_scp_push_file(scp, "b", BUF_SIZE, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, expected_b, BUF_SIZE); + assert_ssh_return_code(session, rc); + + /* Leave the directory */ + rc = ssh_scp_leave_directory(scp); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Open file and check content */ + snprintf(buf, BUF_SIZE, "%s/test_dir/b", ts->tmp_dir); + + file = fopen(buf, "r"); + assert_non_null(file); + + len = fread(buf, BUF_SIZE, 1, file); + assert_int_equal(len, 1); + assert_memory_equal(buf, expected_b, BUF_SIZE); + + fclose(file); +} + +static void torture_scp_download(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_a[BUF_SIZE]; + char buf[BUF_SIZE]; + const char *remote_file = NULL; + + FILE *file = NULL; + int fd = 0; + + size_t size; + + int mode; + int rc; + + assert_non_null(state); + + ts = *state; + + assert_non_null(ts); + assert_non_null(ts->s); + + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Create file "a" for alice */ + memset(expected_a, 'A', BUF_SIZE); + + snprintf(buf, BUF_SIZE, "%s/a", ts->tmp_dir); + + fd = open(buf, O_WRONLY | O_CREAT, 0644); + assert_true(fd > 0); + + file = fdopen(fd, "w"); + assert_non_null(file); + + size = fwrite(expected_a, 1, BUF_SIZE, file); + assert_int_equal(size, BUF_SIZE); + fclose(file); + + /* Construct the file path */ + snprintf(buf, BUF_SIZE, "%s/a", ts->tmp_dir_basename); + + /* When reading, the location is the file path */ + scp = ssh_scp_new(session, SSH_SCP_READ, buf); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_NEWFILE); + + size = ssh_scp_request_get_size(scp); + assert_int_equal(size, BUF_SIZE); + + mode = ssh_scp_request_get_permissions(scp); + assert_int_equal(mode, 0644); + + remote_file = ssh_scp_request_get_filename(scp); + assert_non_null(remote_file); + assert_string_equal(remote_file, "a"); + + rc = ssh_scp_accept_request(scp); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_read(scp, buf, BUF_SIZE); + assert_int_equal(rc, size); + + assert_memory_equal(expected_a, buf, BUF_SIZE); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); +} + +static void torture_scp_download_recursive(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + char expected_b[BUF_SIZE]; + char buf[BUF_SIZE]; + const char *remote_file = NULL; + FILE *file = NULL; + int fd = 0; + + size_t size; + + int mode; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Create file "b" for alice */ + memset(expected_b, 'B', BUF_SIZE); + + snprintf(buf, BUF_SIZE, "%s/b", ts->tmp_dir); + + fd = open(buf, O_WRONLY | O_CREAT, 0644); + assert_true(fd > 0); + + file = fdopen(fd, "w"); + assert_non_null(file); + + size = fwrite(expected_b, 1, BUF_SIZE, file); + assert_int_equal(size, BUF_SIZE); + fclose(file); + + /* Copy the directory containing the file "b" */ + scp = ssh_scp_new(session, SSH_SCP_READ | SSH_SCP_RECURSIVE, + ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Receive the directory */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_NEWDIR); + + mode = ssh_scp_request_get_permissions(scp); + assert_int_equal(mode, 0700); + + remote_file = ssh_scp_request_get_filename(scp); + assert_non_null(remote_file); + assert_string_equal(remote_file, ts->tmp_dir_basename); + + rc = ssh_scp_accept_request(scp); + assert_ssh_return_code(session, rc); + + /* Receive the file "b" */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_NEWFILE); + + size = ssh_scp_request_get_size(scp); + assert_int_equal(size, BUF_SIZE); + + mode = ssh_scp_request_get_permissions(scp); + assert_int_equal(mode, 0644); + + remote_file = ssh_scp_request_get_filename(scp); + assert_non_null(remote_file); + assert_string_equal(remote_file, "b"); + + rc = ssh_scp_accept_request(scp); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_read(scp, buf, BUF_SIZE); + assert_int_equal(rc, size); + + /* Check if the content was the expected */ + assert_memory_equal(expected_b, buf, BUF_SIZE); + + /* Receive end of directory */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_ENDDIR); + + /* Receive end of communication */ + rc = ssh_scp_pull_request(scp); + assert_int_equal(rc, SSH_SCP_REQUEST_EOF); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); +} + +static void torture_scp_upload_newline(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + FILE *file = NULL; + + char buf[1024]; + char *rs = NULL; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload recursively trying to inject protocol messages */ + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, + ts->tmp_dir_basename); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Push directory where the new file will be copied */ + rc = ssh_scp_push_directory(scp, "test_inject", 0755); + assert_ssh_return_code(session, rc); + + /* Try to push file with injected protocol messages */ + rc = ssh_scp_push_file(scp, "original\nreplacedC0777 8 injected", 8, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, "original", 8); + assert_ssh_return_code(session, rc); + + /* Leave the directory */ + rc = ssh_scp_leave_directory(scp); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Open the file and check content */ + snprintf(buf, BUF_SIZE, "%s/test_inject/" + "original\\nreplacedC0777 8 injected", + ts->tmp_dir); + file = fopen(buf, "r"); + assert_non_null(file); + + rs = fgets(buf, 1024, file); + assert_non_null(rs); + assert_string_equal(buf, "original"); + + fclose(file); +} + +static void torture_scp_upload_appended_command(void **state) +{ + struct scp_st *ts = NULL; + struct torture_state *s = NULL; + + ssh_session session = NULL; + ssh_scp scp = NULL; + + FILE *file = NULL; + + char buf[1024]; + char *rs = NULL; + int rc; + + assert_non_null(state); + ts = *state; + + assert_non_null(ts->s); + s = ts->s; + + session = s->ssh.session; + assert_non_null(session); + + assert_non_null(ts->tmp_dir_basename); + assert_non_null(ts->tmp_dir); + + /* Upload a file path with a command appended */ + + /* Append a command to the file path */ + snprintf(buf, BUF_SIZE, "%s" + "/;touch hack", + ts->tmp_dir); + + /* When writing the file_name must be the directory name */ + scp = ssh_scp_new(session, SSH_SCP_WRITE | SSH_SCP_RECURSIVE, + buf); + assert_non_null(scp); + + rc = ssh_scp_init(scp); + assert_ssh_return_code(session, rc); + + /* Push directory where the new file will be copied */ + rc = ssh_scp_push_directory(scp, ";touch hack", 0755); + assert_ssh_return_code(session, rc); + + /* Try to push file */ + rc = ssh_scp_push_file(scp, "original", 8, 0644); + assert_ssh_return_code(session, rc); + + rc = ssh_scp_write(scp, "original", 8); + assert_ssh_return_code(session, rc); + + /* Leave the directory */ + rc = ssh_scp_leave_directory(scp); + assert_ssh_return_code(session, rc); + + /* Cleanup */ + ssh_scp_close(scp); + ssh_scp_free(scp); + + /* Make sure the command was not executed */ + snprintf(buf, BUF_SIZE, ALICE_HOME "/hack"); + file = fopen(buf, "r"); + assert_null(file); + + /* Open the file and check content */ + snprintf(buf, BUF_SIZE, "%s" + "/;touch hack/original", + ts->tmp_dir); + + file = fopen(buf, "r"); + assert_non_null(file); + + rs = fgets(buf, 1024, file); + assert_non_null(rs); + assert_string_equal(buf, "original"); + + fclose(file); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_scp_upload, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_upload_recursive, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_download, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_download_recursive, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_upload_newline, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_scp_upload_appended_command, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_session.c b/src/libs/libssh-0.12.2/tests/client/torture_session.c new file mode 100644 index 000000000000..f3d3ead260d9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_session.c @@ -0,0 +1,551 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2012 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#define BUFLEN 4096 +static char buffer[BUFLEN]; + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_channel_read_error(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + int fd; + int i; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(channel, "hexdump -C /dev/urandom"); + assert_ssh_return_code(session, rc); + + /* send crap and wait for server to send us a disconnect */ + fd = ssh_get_fd(session); + assert_true(fd > 2); + rc = write(fd, "AAAA", 4); + assert_int_equal(rc, 4); + + for (i=0;i<20;++i){ + rc = ssh_channel_read(channel,buffer,sizeof(buffer),0); + if (rc == SSH_ERROR) + break; + } +#if OPENSSH_VERSION_MAJOR == 6 && OPENSSH_VERSION_MINOR >= 7 + /* With openssh 6.7 this doesn't produce and error anymore */ + assert_ssh_return_code(session, rc); +#else + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +#endif + + ssh_channel_free(channel); +} + +static void torture_channel_poll_timeout_valid(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_request_exec(channel, "echo -n ABCD"); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, strlen("ABCD")); +} + +static void torture_channel_poll_timeout(void **state) { + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + int fd; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_true(fd > 2); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_OK); + + /* send crap and for server to send us a disconnect */ + rc = write(fd, "AAAA", 4); + assert_int_equal(rc, 4); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_ERROR); + + ssh_channel_free(channel); +} + +/* + * Check that the client can properly handle the error returned from the server + * when the maximum number of sessions is exceeded. + * + * Related: T75, T239 + * + */ +static void torture_max_sessions(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char max_session_config[32] = {0}; +#define MAX_CHANNELS 10 + ssh_channel channels[MAX_CHANNELS + 1]; + size_t i; + int rc; + + snprintf(max_session_config, + sizeof(max_session_config), + "MaxSessions %u", + MAX_CHANNELS); + + /* Update server configuration to limit number of sessions */ + torture_update_sshd_config(state, max_session_config); + + /* Open the maximum number of channel sessions */ + for (i = 0; i < MAX_CHANNELS; i++) { + channels[i] = ssh_channel_new(session); + assert_non_null(channels[i]); + + rc = ssh_channel_open_session(channels[i]); + assert_ssh_return_code(session, rc); + } + + /* Try to open an extra session and expect failure */ + channels[i] = ssh_channel_new(session); + assert_non_null(channels[i]); + + rc = ssh_channel_open_session(channels[i]); + assert_int_equal(rc, SSH_ERROR); + + /* Free the unused channel */ + ssh_channel_free(channels[i]); + + /* Close and free channels */ + for (i = 0; i < MAX_CHANNELS; i++) { + ssh_channel_close(channels[i]); + ssh_channel_free(channels[i]); + } +#undef MAX_CHANNELS +} + +static void torture_no_more_sessions(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channels[2]; + int rc; + + /* Open a channel session */ + channels[0] = ssh_channel_new(session); + assert_non_null(channels[0]); + + rc = ssh_channel_open_session(channels[0]); + assert_ssh_return_code(session, rc); + + /* Send no-more-sessions@openssh.com global request */ + rc = ssh_request_no_more_sessions(session); + assert_ssh_return_code(session, rc); + + /* Try to open an extra session and expect failure */ + channels[1] = ssh_channel_new(session); + assert_non_null(channels[1]); + + rc = ssh_channel_open_session(channels[1]); + assert_int_equal(rc, SSH_ERROR); + + /* Free the unused channel */ + ssh_channel_free(channels[1]); + + /* Close and free open channel */ + ssh_channel_close(channels[0]); + ssh_channel_free(channels[0]); +} + +static void torture_channel_delayed_close(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + char buff[256] = {0}; + + int rc; + int fd; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + fd = ssh_get_fd(session); + assert_true(fd > 2); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + do { + rc = ssh_channel_read(channel, buff, 256, 0); + } while(rc > 0); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_poll_timeout(channel, 500, 0); + assert_int_equal(rc, SSH_EOF); + + ssh_channel_free(channel); + +} + +/* Ensure that calling 'ssh_channel_poll' on a freed channel does not lead to + * segmentation faults. */ +static void torture_freed_channel_poll(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_poll(channel, 0); + assert_int_equal(rc, SSH_ERROR); +} + +/* Ensure that calling 'ssh_channel_read_nonblocking' on a freed channel does + * not lead to segmentation faults. */ +static void torture_freed_channel_read_nonblocking(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + + char request[256]; + char buff[256] = {0}; + int rc; + + snprintf(request, 256, + "dd if=/dev/urandom of=/tmp/file bs=64000 count=2; hexdump -C /tmp/file"); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + ssh_channel_free(channel); + + rc = ssh_channel_read_nonblocking(channel, buff, 256, 0); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +static void torture_channel_exit_status(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel = NULL; + char request[256]; + uint32_t exit_status = (uint32_t)-1; + int rc; + + rc = snprintf(request, sizeof(request), "true"); + assert_return_code(rc, errno); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + + exit_status = ssh_channel_get_exit_state(channel, &exit_status, NULL, NULL); + assert_ssh_return_code(session, rc); + assert_int_equal(exit_status, 0); +} + +static void torture_channel_exit_signal(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel = NULL; + char request[256]; + uint32_t exit_status = (uint32_t)-1; + char *exit_signal = NULL; + int core_dumped = false; + int rc; + + rc = snprintf(request, sizeof(request), "cat"); + assert_return_code(rc, errno); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Make the request, read parts with close */ + rc = ssh_channel_request_exec(channel, request); + assert_ssh_return_code(session, rc); + rc = ssh_channel_request_send_signal(channel, "TERM"); + assert_ssh_return_code(session, rc); + + rc = ssh_channel_get_exit_state(channel, + &exit_status, + &exit_signal, + &core_dumped); + + assert_ssh_return_code(session, rc); + assert_int_equal(exit_status, (uint32_t)-1); + assert_string_equal(exit_signal, "TERM"); + SAFE_FREE(exit_signal); +} + +static void +torture_channel_read_stderr(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + ssh_channel channel; + int rc; + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* This writes to standard error "pipe" */ + rc = ssh_channel_request_exec(channel, "echo -n ABCD >&2"); + assert_ssh_return_code(session, rc); + + /* No data in stdout */ + rc = ssh_channel_read(channel, buffer, sizeof(buffer), 0); + assert_int_equal(rc, 0); + + /* poll should say how much we can read */ + rc = ssh_channel_poll(channel, 1); + assert_int_equal(rc, strlen("ABCD")); + + /* Everything in stderr */ + rc = ssh_channel_read(channel, buffer, sizeof(buffer), 1); + assert_int_equal(rc, strlen("ABCD")); + + buffer[rc] = '\0'; + assert_string_equal("ABCD", buffer); + + ssh_channel_free(channel); +} + +static void torture_pubkey_hash(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + char *hash = NULL; + char *hexa = NULL; + int rc = 0; + + /* bad arguments */ + rc = ssh_get_pubkey_hash(session, NULL); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_get_pubkey_hash(NULL, (unsigned char **)&hash); + assert_int_equal(rc, SSH_ERROR); + + /* deprecated, but should be covered by tests! */ + rc = ssh_get_pubkey_hash(session, (unsigned char **)&hash); + if (ssh_fips_mode()) { + /* When in FIPS mode, expect the call to fail */ + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, MD5_DIGEST_LEN); + + hexa = ssh_get_hexa((unsigned char *)hash, rc); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(hexa, + "ee:80:7f:61:f9:d5:be:f1:96:86:cc:96:7a:db:7a:7b"); + + SSH_STRING_FREE_CHAR(hexa); + } +} + +static void torture_openssh_banner_version(void **state) +{ + struct torture_state *s = *state; + ssh_session session = s->ssh.session; + + int openssh_version = ssh_get_openssh_version(session); + int cmake_openssh_version = SSH_VERSION_INT(OPENSSH_VERSION_MAJOR, OPENSSH_VERSION_MINOR, 0); + + assert_int_equal(openssh_version, cmake_openssh_version); +} + + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_channel_read_error, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_poll_timeout_valid, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_poll_timeout, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_max_sessions, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_no_more_sessions, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_delayed_close, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_poll, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_freed_channel_read_nonblocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_exit_status, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_exit_signal, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_channel_read_stderr, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_pubkey_hash, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_openssh_banner_version, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_aio.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_aio.c new file mode 100644 index 000000000000..9bd5ff4e2795 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_aio.c @@ -0,0 +1,778 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +#define DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(TEST_NAME) \ + { \ + #TEST_NAME, \ + TEST_NAME, \ + session_setup, \ + session_teardown, \ + NULL \ + }, \ + { \ + #TEST_NAME"_proxyjump", \ + TEST_NAME, \ + session_proxyjump_setup, \ + session_teardown, \ + NULL \ + } + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + torture_setup_sshd_servers(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + /* this will take care of the server1 teardown too */ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup_helper(void **state, bool with_proxyjump) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + if (with_proxyjump) { + s->ssh.session = torture_ssh_session_proxyjump(); + } else { + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + } + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_setup(void **state) +{ + return session_setup_helper(state, false); +} + +static int session_proxyjump_setup(void **state) +{ + return session_setup_helper(state, true); +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_aio_read_file(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + struct { + char *buf; + ssize_t bytes_read; + } a = {0}, b = {0}; + + sftp_file file = NULL; + sftp_attributes file_attr = NULL; + int fd; + + size_t chunk_size; + int in_flight_requests = 20; + + sftp_aio aio = NULL; + struct ssh_list *aio_queue = NULL; + sftp_limits_t li = NULL; + + size_t file_size; + size_t total_bytes_requested; + size_t to_read, total_bytes_read; + ssize_t bytes_requested; + + int i, rc; + + /* Get the max limit for reading, use it as the chunk size */ + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_read_length; + + a.buf = calloc(chunk_size, 1); + assert_non_null(a.buf); + + b.buf = calloc(chunk_size, 1); + assert_non_null(b.buf); + + aio_queue = ssh_list_new(); + assert_non_null(aio_queue); + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + fd = open(SSH_EXECUTABLE, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + /* Get the file size */ + file_attr = sftp_stat(t->sftp, SSH_EXECUTABLE); + assert_non_null(file_attr); + file_size = file_attr->size; + + total_bytes_requested = 0; + for (i = 0; + i < in_flight_requests && total_bytes_requested < file_size; + ++i) { + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(bytes_requested, to_read); + total_bytes_requested += bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + assert_int_equal(rc, SSH_OK); + } + + total_bytes_read = 0; + while ((aio = ssh_list_pop_head(sftp_aio, aio_queue)) != NULL) { + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, chunk_size); + assert_int_not_equal(a.bytes_read, SSH_ERROR); + + total_bytes_read += (size_t)a.bytes_read; + if (total_bytes_read != file_size) { + assert_int_equal((size_t)a.bytes_read, chunk_size); + /* + * Failure of this assertion means that a short + * read is encountered but we have not reached + * the end of file yet. A short read before reaching + * the end of file should not occur for our test where + * the chunk size respects the max limit for reading. + */ + } + + /* + * Check whether the bytes read above are bytes + * present in the file or some garbage was stored + * in the buffer supplied to sftp_aio_wait_read(). + */ + b.bytes_read = read(fd, b.buf, a.bytes_read); + assert_int_equal(a.bytes_read, b.bytes_read); + + rc = memcmp(a.buf, b.buf, (size_t)a.bytes_read); + assert_int_equal(rc, 0); + + /* Issue more read requests if needed */ + if (total_bytes_requested == file_size) { + continue; + } + + /* else issue more requests */ + to_read = file_size - total_bytes_requested; + if (to_read > chunk_size) { + to_read = chunk_size; + } + + bytes_requested = sftp_aio_begin_read(file, to_read, &aio); + assert_int_equal(bytes_requested, to_read); + total_bytes_requested += bytes_requested; + + /* enqueue */ + rc = ssh_list_append(aio_queue, aio); + assert_int_equal(rc, SSH_OK); + } + + /* + * Check whether sftp server responds with an + * eof for more requests. + */ + bytes_requested = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes_requested, chunk_size); + + a.bytes_read = sftp_aio_wait_read(&aio, a.buf, chunk_size); + assert_int_equal(a.bytes_read, 0); + + /* Clean up */ + sftp_attributes_free(file_attr); + close(fd); + sftp_close(file); + ssh_list_free(aio_queue); + free(b.buf); + free(a.buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_read_more_than_cap(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_limits_t li = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + + char *buf = NULL; + ssize_t bytes; + + /* Get the max limit for reading */ + li = sftp_limits(t->sftp); + assert_non_null(li); + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Try reading more than the max limit */ + bytes = sftp_aio_begin_read(file, + li->max_read_length * 2, + &aio); + assert_int_equal(bytes, li->max_read_length); + + buf = calloc(li->max_read_length, 1); + assert_non_null(buf); + + bytes = sftp_aio_wait_read(&aio, buf, li->max_read_length); + assert_int_not_equal(bytes, SSH_ERROR); + + free(buf); + sftp_close(file); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_file(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char file_path[128] = {0}; + sftp_file file = NULL; + int fd; + + struct { + char *buf; + ssize_t bytes; + } wr = {0}, rd = {0}; + + size_t chunk_size; + ssize_t bytes_requested; + int in_flight_requests = 2; + + sftp_limits_t li = NULL; + sftp_aio *aio_queue = NULL; + int rc, i; + + /* Get the max limit for writing, use it as the chunk size */ + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_write_length; + + rd.buf = calloc(chunk_size, 1); + assert_non_null(rd.buf); + + wr.buf = calloc(chunk_size, 1); + assert_non_null(wr.buf); + + aio_queue = malloc(sizeof(sftp_aio) * in_flight_requests); + assert_non_null(aio_queue); + + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + fd = open(file_path, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + for (i = 0; i < in_flight_requests; ++i) { + bytes_requested = sftp_aio_begin_write(file, + wr.buf, + chunk_size, + &aio_queue[i]); + assert_int_equal(bytes_requested, chunk_size); + } + + for (i = 0; i < in_flight_requests; ++i) { + wr.bytes = sftp_aio_wait_write(&aio_queue[i]); + assert_int_equal(wr.bytes, chunk_size); + + /* + * Check whether the bytes written to the file + * by SFTP AIO write api were the bytes present + * in the buffer to write or some garbage was + * written to the file. + */ + rd.bytes = read(fd, rd.buf, wr.bytes); + assert_int_equal(rd.bytes, wr.bytes); + + rc = memcmp(rd.buf, wr.buf, wr.bytes); + assert_int_equal(rc, 0); + } + + /* Clean up */ + close(fd); + sftp_close(file); + free(aio_queue); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(wr.buf); + free(rd.buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_more_than_cap(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_limits_t li = NULL; + char *buf = NULL; + size_t buf_size; + + char file_path[128] = {0}; + sftp_file file = NULL; + + sftp_aio aio = NULL; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + + buf_size = li->max_write_length * 2; + buf = calloc(buf_size, 1); + assert_non_null(buf); + + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test_cap", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* Try writing more than the max limit for writing */ + bytes = sftp_aio_begin_write(file, buf, buf_size, &aio); + assert_int_equal(bytes, li->max_write_length); + + bytes = sftp_aio_wait_write(&aio); + assert_int_equal(bytes, li->max_write_length); + + /* Clean up */ + sftp_close(file); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_read_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char *buf = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_limits_t li = NULL; + + size_t chunk_size; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_read_length; + + buf = calloc(chunk_size, 1); + assert_non_null(buf); + + /* Open a file for reading */ + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Passing NULL as the sftp file handle */ + bytes = sftp_aio_begin_read(NULL, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing 0 as the number of bytes to read */ + bytes = sftp_aio_begin_read(file, 0, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* + * Passing NULL instead of a pointer to a location to + * store an aio handle. + */ + bytes = sftp_aio_begin_read(file, chunk_size, NULL); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL instead of a pointer to an aio handle */ + bytes = sftp_aio_wait_read(NULL, buf, sizeof(buf)); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL as the buffer's address */ + bytes = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes, chunk_size); + + bytes = sftp_aio_wait_read(&aio, NULL, sizeof(buf)); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing 0 as the buffer size */ + bytes = sftp_aio_begin_read(file, chunk_size, &aio); + assert_int_equal(bytes, chunk_size); + + bytes = sftp_aio_wait_read(&aio, buf, 0); + assert_int_equal(bytes, SSH_ERROR); + + /* + * Test for the scenario when the number + * of bytes read exceed the buffer size. + */ + rc = sftp_seek(file, 0); /* Seek to the start of file */ + assert_int_equal(rc, 0); + + bytes = sftp_aio_begin_read(file, 2, &aio); + assert_int_equal(bytes, 2); + + bytes = sftp_aio_wait_read(&aio, buf, 1); + assert_int_equal(bytes, SSH_ERROR); + + sftp_close(file); + free(buf); + sftp_limits_free(li); +} + +static void torture_sftp_aio_write_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char *buf = NULL; + + char file_path[128] = {0}; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_limits_t li = NULL; + + size_t chunk_size; + ssize_t bytes; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + chunk_size = li->max_write_length; + + buf = calloc(chunk_size, 1); + assert_non_null(buf); + + /* Open a file for writing */ + snprintf(file_path, sizeof(file_path), + "%s/libssh_sftp_aio_write_test_negative", t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* Passing NULL as the sftp file handle */ + bytes = sftp_aio_begin_write(NULL, buf, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL as the buffer's address */ + bytes = sftp_aio_begin_write(file, NULL, chunk_size, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing 0 as the size of buffer */ + bytes = sftp_aio_begin_write(file, buf, 0, &aio); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL instead of a pointer to a location to store an aio handle */ + bytes = sftp_aio_begin_write(file, buf, chunk_size, NULL); + assert_int_equal(bytes, SSH_ERROR); + + /* Passing NULL instead of a pointer to an aio handle */ + bytes = sftp_aio_wait_write(NULL); + assert_int_equal(bytes, SSH_ERROR); + + sftp_close(file); + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(buf); + sftp_limits_free(li); +} + +/* + * Test that waiting for read responses in an order different from the + * sending order of corresponding read requests works properly. + * + * (For example, if Requests Rq1 and Rq2 have responses Rs1 and Rs2 + * respectively, and Rq1 is sent first followed by Rq2. Then waiting for + * response Rs2 first and then Rs1 should work properly) + */ +static void torture_sftp_aio_read_unordered_wait(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + sftp_file file = NULL; + sftp_aio aio_1 = NULL, aio_2 = NULL; + ssize_t bytes_requested, bytes_read; + + struct { + /* buffer to store read data */ + char *buf; + + /* buffer to store data expected to be read */ + char *expected; + + /* + * length of the data to read. Keep this length small enough so that we + * don't get short reads due to the sftp limits. + */ + size_t len; + } r1 = {0}, r2 = {0}; + + int fd, rc; + + /* Initialize r1 */ + r1.len = 10; + + r1.buf = calloc(r1.len, 1); + assert_non_null(r1.buf); + + r1.expected = calloc(r1.len, 1); + assert_non_null(r1.expected); + + /* Initialize r2 */ + r2.len = 20; + + r2.buf = calloc(r2.len, 1); + assert_non_null(r2.buf); + + r2.expected = calloc(r2.len, 1); + assert_non_null(r2.expected); + + /* Get data that is expected to be read from the file */ + fd = open(SSH_EXECUTABLE, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + bytes_read = read(fd, r1.expected, r1.len); + assert_int_equal(bytes_read, r1.len); + + bytes_read = read(fd, r2.expected, r2.len); + assert_int_equal(bytes_read, r2.len); + + /* Open an sftp file for reading */ + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* + * Issue 2 consecutive read requests (send the second request immediately + * after sending the first without waiting for the first's response) + */ + bytes_requested = sftp_aio_begin_read(file, r1.len, &aio_1); + assert_int_equal(bytes_requested, r1.len); + + bytes_requested = sftp_aio_begin_read(file, r2.len, &aio_2); + assert_int_equal(bytes_requested, r2.len); + + /* + * Wait for the responses in opposite order (Instead of waiting for response + * 1 first and then response 2, wait for response 2 first and then wait for + * response 1) + */ + bytes_read = sftp_aio_wait_read(&aio_2, r2.buf, r2.len); + assert_int_equal(bytes_read, r2.len); + assert_memory_equal(r2.buf, r2.expected, r2.len); + + bytes_read = sftp_aio_wait_read(&aio_1, r1.buf, r1.len); + assert_int_equal(bytes_read, r1.len); + assert_memory_equal(r1.buf, r1.expected, r1.len); + + /* Clean up */ + sftp_close(file); + + rc = close(fd); + assert_int_equal(rc, 0); + + free(r2.expected); + free(r2.buf); + + free(r1.expected); + free(r1.buf); +} + +/* + * Test that waiting for write responses in an order different from the + * sending order of corresponding write requests works properly. + * + * (For example, if Requests Rq1 and Rq2 have responses Rs1 and Rs2 + * respectively, and Rq1 is sent first followed by Rq2. Then waiting for + * response Rs2 first and then Rs1 should work properly) + */ +static void torture_sftp_aio_write_unordered_wait(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char file_path[128] = {0}; + sftp_file file = NULL; + sftp_aio aio_1 = NULL, aio_2 = NULL; + ssize_t bytes_requested, bytes_written, bytes_read; + size_t i; + int rc, fd; + + struct { + /* + * length of the data to write. Keep this length small enough so that we + * don't get short writes due to the sftp limits + */ + size_t len; + + /* data to write */ + char *data; + + /* buffer used to validate the written data */ + char *buf; + } r1 = {0}, r2 = {0}; + + /* Initialize r1 */ + r1.len = 10; + + r1.data = calloc(r1.len, 1); + assert_non_null(r1.data); + + for (i = 0; i < r1.len; ++i) { + r1.data[i] = (char)rand(); + } + + r1.buf = calloc(r1.len, 1); + assert_non_null(r1.buf); + + /* Initialize r2 */ + r2.len = 20; + + r2.data = calloc(r2.len, 1); + assert_non_null(r2.data); + + for (i = 0; i < r2.len; ++i) { + r2.data[i] = (char)rand(); + } + + r2.buf = calloc(r2.len, 1); + assert_non_null(r2.buf); + + /* Open an sftp file for writing */ + snprintf(file_path, + sizeof(file_path), + "%s/libssh_sftp_aio_write_unordered_wait", + t->testdir); + file = sftp_open(t->sftp, file_path, O_CREAT | O_WRONLY, 0777); + assert_non_null(file); + + /* + * Issue two consecutive write requests (send the second request immediately + * after sending the first without waiting for the first's response) + */ + bytes_requested = sftp_aio_begin_write(file, r1.data, r1.len, &aio_1); + assert_int_equal(bytes_requested, r1.len); + + bytes_requested = sftp_aio_begin_write(file, r2.data, r2.len, &aio_2); + assert_int_equal(bytes_requested, r2.len); + + /* + * Wait for the responses in opposite order (Instead of waiting for response + * 1 first and then response 2, wait for response 2 first and then wait for + * response 1) + */ + bytes_written = sftp_aio_wait_write(&aio_2); + assert_int_equal(bytes_written, r2.len); + + bytes_written = sftp_aio_wait_write(&aio_1); + assert_int_equal(bytes_written, r1.len); + + /* + * Validate that the data has been written to the file correctly by reading + * from the file. + */ + fd = open(file_path, O_RDONLY, 0); + assert_int_not_equal(fd, -1); + + /* Validate that write request 1's data has been written to file */ + bytes_read = read(fd, r1.buf, r1.len); + assert_int_equal(bytes_read, r1.len); + assert_memory_equal(r1.data, r1.buf, r1.len); + + /* Validate that write request 2's data has been written to file */ + bytes_read = read(fd, r2.buf, r2.len); + assert_int_equal(bytes_read, r2.len); + assert_memory_equal(r2.data, r2.buf, r2.len); + + /* Clean up */ + rc = close(fd); + assert_int_equal(rc, 0); + + sftp_close(file); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(r2.buf); + free(r2.data); + + free(r1.buf); + free(r1.data); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_read_file), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_read_more_than_cap), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_write_file), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_write_more_than_cap), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_read_negative), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN(torture_sftp_aio_write_negative), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_read_unordered_wait), + DIRECT_AND_PROXYJUMP_SETUP_TEARDOWN( + torture_sftp_aio_write_unordered_wait), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_benchmark.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_benchmark.c new file mode 100644 index 000000000000..6b2bea5b3ee5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_benchmark.c @@ -0,0 +1,133 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_benchmark_write_read(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + ssh_session session = s->ssh.session; + sftp_file file = NULL; + struct stat sb = { + .st_size = 0, + }; + uint8_t buf_16k[MAX_XFER_BUF_SIZE]; + char local_path[1024] = {0}; + ssize_t bwritten, nread; + size_t i; + int rc; + + memset(buf_16k, 'X', sizeof(buf_16k)); + + snprintf(local_path, sizeof(local_path), "%s/128M.dat", t->testdir); + + file = sftp_open(sftp, local_path, O_CREAT|O_WRONLY|O_TRUNC, 0644); + assert_non_null(file); + + /* Write 128M */ + for (i = 0; i < 0x2000; i++) { + bwritten = sftp_write(file, buf_16k, sizeof(buf_16k)); + assert_int_equal(bwritten, sizeof(buf_16k)); + } + + rc = sftp_close(file); + assert_ssh_return_code(session, rc); + + /* Check that 128M has been written */ + rc = stat(local_path, &sb); + assert_int_equal(sb.st_size, 0x8000000); + + file = sftp_open(sftp, local_path, O_RDONLY, 0); + assert_non_null(file); + + for (;;) { + nread = sftp_read(file, buf_16k, sizeof(buf_16k)); + if (nread == 0) { + break; /* EOF */ + } + assert_int_equal(nread, sizeof(buf_16k)); + } + + rc = sftp_close(file); + assert_ssh_return_code(session, rc); + + unlink(local_path); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_benchmark_write_read, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_canonicalize_path.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_canonicalize_path.c new file mode 100644 index 000000000000..8d5f90744e94 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_canonicalize_path.c @@ -0,0 +1,97 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_canonicalize_path(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *canonicalized_path = NULL; + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + canonicalized_path = sftp_canonicalize_path(t->sftp, "."); + assert_non_null(canonicalized_path); + + assert_string_equal(canonicalized_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(canonicalized_path); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_canonicalize_path, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_dir.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_dir.c new file mode 100644 index 000000000000..1c4f8391b7e2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_dir.c @@ -0,0 +1,104 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_mkdir(void **state) { + struct torture_state *s = *state; + + struct torture_sftp *t = s->ssh.tsftp; + char tmpdir[128] = {0}; + int rc; + + assert_non_null(t); + + snprintf(tmpdir, sizeof(tmpdir) - 1, "%s/mkdir_test", t->testdir); + + rc = sftp_mkdir(t->sftp, tmpdir, 0755); + if(rc != SSH_OK) + fprintf(stderr,"error:%s\n",ssh_get_error(t->sftp->session)); + assert_true(rc == 0); + + /* check if it really has been created */ + assert_true(torture_isdir(tmpdir)); + + rc = sftp_rmdir(t->sftp, tmpdir); + assert_true(rc == 0); + + /* check if it has been deleted */ + assert_false(torture_isdir(tmpdir)); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_mkdir, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_expand_path.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_expand_path.c new file mode 100644 index 000000000000..85ef01084bf9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_expand_path.c @@ -0,0 +1,125 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_expand_path(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *expanded_path = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, "expand-path@openssh.com", "1"); + if (rc == 0) { + skip(); + } + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + /* testing for a absolute path */ + expanded_path = sftp_expand_path(t->sftp, "~/."); + assert_non_null(expanded_path); + + assert_string_equal(expanded_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(expanded_path); + + /* testing for a relative path */ + expanded_path = sftp_expand_path(t->sftp, "."); + assert_non_null(expanded_path); + + assert_string_equal(expanded_path, pwd->pw_dir); + + SSH_STRING_FREE_CHAR(expanded_path); + + /* passing a NULL sftp session */ + expanded_path = sftp_expand_path(NULL, "~/."); + assert_null(expanded_path); + + /* passing an invalid path */ + expanded_path = sftp_expand_path(t->sftp, "/...//"); + assert_null(expanded_path); + + /* passing null path */ + expanded_path = sftp_expand_path(t->sftp, NULL); + assert_null(expanded_path); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_expand_path, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_ext.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_ext.c new file mode 100644 index 000000000000..ad4bbaa1c255 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_ext.c @@ -0,0 +1,35 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +static void torture_sftp_ext_new(void **state) { + sftp_ext x; + + (void) state; + + x = sftp_ext_new(); + assert_non_null(x); + assert_int_equal(x->count, 0); + assert_null(x->name); + assert_null(x->data); + + sftp_ext_free(x); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_sftp_ext_new), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_fsync.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_fsync.c new file mode 100644 index 000000000000..ac685b94cdac --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_fsync.c @@ -0,0 +1,137 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_fsync(void **state) { + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE] = {0}; + char buf_verify[MAX_XFER_BUF_SIZE] = {0}; + size_t count; + size_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + int rc; + FILE *fp; + struct stat sb; + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + assert_return_code(fd, errno); + close(fd); + unlink(libssh_tmp_file); + + file = sftp_open(t->sftp, libssh_tmp_file, O_WRONLY | O_CREAT, 0600); + assert_non_null(file); + + rc = lstat(libssh_tmp_file, &sb); + assert_return_code(rc, errno); + + snprintf(buf, sizeof(buf), "libssh fsync test\n"); + count = strlen(buf) + 1; + + byteswritten = sftp_write(file, buf, count); + assert_int_equal(byteswritten, count); + + rc = sftp_fsync(file); + assert_return_code(rc, errno); + + fp = fopen(libssh_tmp_file, "r"); + assert_non_null(fp); + + rc = fstat(fileno(fp), &sb); + assert_return_code(rc, errno); + + bytesread = fread(buf_verify, sizeof(buf_verify), 1, fp); + if (bytesread == 0) { + if (!feof(fp)) { + assert_int_equal(bytesread, count); + } + } + assert_string_equal(buf, buf_verify); + + sftp_close(file); + fclose(fp); + unlink(libssh_tmp_file); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_fsync, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_get_users_groups_by_id.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_get_users_groups_by_id.c new file mode 100644 index 000000000000..ed85dcbb654a --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_get_users_groups_by_id.c @@ -0,0 +1,263 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + /* + * The SFTP server used for testing is executed as a separate binary, which + * is making the uid_wrapper lose information about what user is used, and + * therefore, pwd is initialized to some bad value. + * If the embedded version using internal-sftp is used in sshd, it works ok. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + unsetenv("TORTURE_SFTP_SERVER"); + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_get_users_by_id(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *alice_pwd = NULL; + struct passwd *bob_pwd = NULL; + struct passwd *root_pwd = NULL; + sftp_name_id_map users_map = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, + "users-groups-by-id@openssh.com", + "1"); + if (rc == 0) { + skip(); + } + + alice_pwd = getpwnam("alice"); + assert_non_null(alice_pwd); + + bob_pwd = getpwnam("bob"); + assert_non_null(bob_pwd); + + root_pwd = getpwnam("root"); + assert_non_null(root_pwd); + + /* test for null */ + rc = sftp_get_users_groups_by_id(t->sftp, NULL, NULL); + assert_int_equal(rc, -1); + + /* test for 0 users */ + users_map = sftp_name_id_map_new(0); + + rc = sftp_get_users_groups_by_id(t->sftp, users_map, NULL); + assert_int_equal(rc, 0); + + sftp_name_id_map_free(users_map); + + /* test for 3 users */ + users_map = sftp_name_id_map_new(3); + + users_map->ids[0] = alice_pwd->pw_uid; + users_map->ids[1] = bob_pwd->pw_uid; + users_map->ids[2] = root_pwd->pw_uid; + + rc = sftp_get_users_groups_by_id(t->sftp, users_map, NULL); + assert_int_equal(rc, 0); + assert_string_equal(users_map->names[0], "alice"); + assert_string_equal(users_map->names[1], "bob"); + assert_string_equal(users_map->names[2], "root"); + + sftp_name_id_map_free(users_map); + + /* test for invalid uids */ + users_map = sftp_name_id_map_new(2); + + users_map->ids[0] = alice_pwd->pw_uid; + users_map->ids[1] = 42; /* invalid uid */ + rc = sftp_get_users_groups_by_id(t->sftp, users_map, NULL); + + assert_int_equal(rc, 0); + assert_string_equal(users_map->names[0], "alice"); + assert_string_equal(users_map->names[1], ""); + + sftp_name_id_map_free(users_map); +} + +static void torture_sftp_get_groups_by_id(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *alice_pwd = NULL; + struct passwd *root_pwd = NULL; + sftp_name_id_map groups_map = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, + "users-groups-by-id@openssh.com", + "1"); + + if (rc == 0) { + skip(); + } + + alice_pwd = getpwnam("alice"); + assert_non_null(alice_pwd); + + root_pwd = getpwnam("root"); + assert_non_null(root_pwd); + + /* test for 2 groups */ + groups_map = sftp_name_id_map_new(2); + + groups_map->ids[0] = alice_pwd->pw_gid; + groups_map->ids[1] = root_pwd->pw_gid; + + rc = sftp_get_users_groups_by_id(t->sftp, NULL, groups_map); + assert_int_equal(rc, 0); + assert_string_equal(groups_map->names[0], "users"); + assert_string_equal(groups_map->names[1], "root"); + + sftp_name_id_map_free(groups_map); + + /* test for invalid gids */ + groups_map = sftp_name_id_map_new(2); + + groups_map->ids[0] = alice_pwd->pw_gid; + groups_map->ids[1] = 42; /* invalid gid */ + + rc = sftp_get_users_groups_by_id(t->sftp, NULL, groups_map); + assert_int_equal(rc, 0); + assert_string_equal(groups_map->names[0], "users"); + assert_string_equal(groups_map->names[1], ""); + + sftp_name_id_map_free(groups_map); +} + +static void torture_sftp_get_users_groups_by_id(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *alice_pwd = NULL; + struct passwd *bob_pwd = NULL; + struct passwd *root_pwd = NULL; + sftp_name_id_map users_map = NULL; + sftp_name_id_map groups_map = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, + "users-groups-by-id@openssh.com", + "1"); + + if (rc == 0) { + skip(); + } + + alice_pwd = getpwnam("alice"); + assert_non_null(alice_pwd); + + bob_pwd = getpwnam("bob"); + assert_non_null(bob_pwd); + + root_pwd = getpwnam("root"); + assert_non_null(root_pwd); + + users_map = sftp_name_id_map_new(4); + groups_map = sftp_name_id_map_new(3); + + users_map->ids[0] = alice_pwd->pw_uid; + users_map->ids[1] = bob_pwd->pw_uid; + users_map->ids[2] = root_pwd->pw_uid; + users_map->ids[3] = 42; /* invalid uid */ + + groups_map->ids[0] = alice_pwd->pw_gid; + groups_map->ids[1] = root_pwd->pw_gid; + groups_map->ids[2] = 42; /* invalid gid */ + + rc = sftp_get_users_groups_by_id(t->sftp, users_map, groups_map); + + assert_int_equal(rc, 0); + assert_string_equal(users_map->names[0], "alice"); + assert_string_equal(users_map->names[1], "bob"); + assert_string_equal(users_map->names[2], "root"); + assert_string_equal(users_map->names[3], ""); + assert_string_equal(groups_map->names[0], "users"); + assert_string_equal(groups_map->names[1], "root"); + assert_string_equal(groups_map->names[2], ""); + + sftp_name_id_map_free(users_map); + sftp_name_id_map_free(groups_map); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_get_users_by_id, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_get_groups_by_id, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_get_users_groups_by_id, + session_setup, + session_teardown)}; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_hardlink.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_hardlink.c new file mode 100644 index 000000000000..379636368d1e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_hardlink.c @@ -0,0 +1,114 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_hardlink(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char link_1[128] = {0}; + char link_2[128] = {0}; + int fd; + int rc; + + snprintf(link_1, sizeof(link_1), + "%s/libssh_sftp_hardlink_test_1", t->testdir); + snprintf(link_2, sizeof(link_2), + "%s/libssh_sftp_hardlink_test_2", t->testdir); + + fd = open(link_1, O_CREAT, S_IRWXU); + assert_return_code(fd, errno); + close(fd); + + rc = sftp_hardlink(t->sftp, link_1, link_2); + assert_int_equal(rc, SSH_OK); + + /* check whether the file got associated with link_2 */ + rc = access(link_2, F_OK); + assert_int_equal(rc, 0); + + unlink(link_1); + unlink(link_2); + + /* + * try to create a hardlink for a file that does not + * exist, this should fail + */ + rc = sftp_hardlink(t->sftp, link_1, link_2); + assert_int_not_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_hardlink, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_home_directory.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_home_directory.c new file mode 100644 index 000000000000..a2399f02f898 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_home_directory.c @@ -0,0 +1,142 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + /* + The SFTP server used for testing is executed as a separate binary, which + is making the uid_wrapper lose information about what user is used, and + therefore, pwd is initialized to some bad value. + If the embedded version using internal-sftp is used in sshd, it works ok. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + torture_setup_sshd_server(state, false); + return 0; +} + +static int +sshd_teardown(void **state) +{ + unsetenv("TORTURE_SFTP_SERVER"); + torture_teardown_sshd_server(state); + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_sftp_home_directory(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + char *home_path = NULL; + int rc; + + rc = sftp_extension_supported(t->sftp, "home-directory", "1"); + if (!rc) { + skip(); + } + + pwd = getpwnam(TORTURE_SSH_USER_ALICE); + assert_non_null(pwd); + + /* testing for NULL sftp session */ + home_path = sftp_home_directory(NULL, NULL); + assert_null(home_path); + + /* testing for ~ */ + /* + home_path = sftp_home_directory(t->sftp, NULL); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); + + home_path = sftp_home_directory(t->sftp, ""); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); + */ + + /* + OpenSSH code handling this extension does not handle empty string for + username. getpwnam() also does not handle empty string. + PR in OpenSSH for fix: + https://github.com/openssh/openssh-portable/pull/477/ + */ + + /* testing for ~user */ + home_path = sftp_home_directory(t->sftp, pwd->pw_name); + fprintf(stderr, + "sftp error: %d, ssh error: %s\n", + sftp_get_error(t->sftp), + ssh_get_error(t->sftp->session)); + assert_non_null(home_path); + assert_string_equal(home_path, pwd->pw_dir); + SSH_STRING_FREE_CHAR(home_path); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_home_directory, + session_setup, + session_teardown)}; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_init.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_init.c new file mode 100644 index 000000000000..cdc2442639cb --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_init.c @@ -0,0 +1,166 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static void session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); +} + +static void session_setup_channel(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + ssh_channel c = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + c = ssh_channel_new(s->ssh.session); + assert_non_null(c); + + s->ssh.tsftp = torture_sftp_session_channel(s->ssh.session, c); + assert_non_null(s->ssh.tsftp); +} + +static void session_setup_extensions(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc, count; + const char *name = NULL, *data = NULL; + sftp_session sftp = NULL; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + sftp = s->ssh.tsftp->sftp; + + /* null parameter */ + count = sftp_extensions_get_count(NULL); + assert_int_equal(count, 0); + + count = sftp_extensions_get_count(sftp); + assert_int_not_equal(count, 0); + + /* first null parameter */ + name = sftp_extensions_get_name(NULL, 0); + assert_null(name); + data = sftp_extensions_get_data(NULL, 0); + assert_null(data); + + /* First extension */ + name = sftp_extensions_get_name(sftp, 0); + assert_non_null(name); + data = sftp_extensions_get_data(sftp, 0); + assert_non_null(data); + + /* Last extension */ + name = sftp_extensions_get_name(sftp, count - 1); + assert_non_null(name); + data = sftp_extensions_get_data(sftp, count - 1); + assert_non_null(data); + + /* Overrun */ + name = sftp_extensions_get_name(sftp, count); + assert_null(name); + data = sftp_extensions_get_data(sftp, count); + assert_null(data); +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(session_setup, + NULL, + session_teardown), + cmocka_unit_test_setup_teardown(session_setup_channel, + NULL, + session_teardown), + cmocka_unit_test_setup_teardown(session_setup_extensions, + NULL, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_limits.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_limits.c new file mode 100644 index 000000000000..07ef9928cf3b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_limits.c @@ -0,0 +1,177 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include +#include + +#if HAVE_VALGRIND_VALGRIND_H + #include +#endif + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_limits(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_limits_t li = NULL; + int rc; + + li = sftp_limits(t->sftp); + assert_non_null(li); + + rc = sftp_extension_supported(t->sftp, "limits@openssh.com", "1"); + if (rc == 1) { + /* + * Tests are run against the OpenSSH server, hence we check for the + * specific limits used by OpenSSH. + */ + uint64_t openssh_max_packet_length = 256 * 1024; + uint64_t openssh_max_read_length = openssh_max_packet_length - 1024; + uint64_t openssh_max_write_length = openssh_max_packet_length - 1024; + size_t vg = 0; + + assert_int_equal(li->max_packet_length, openssh_max_packet_length); + assert_int_equal(li->max_read_length, openssh_max_read_length); + assert_int_equal(li->max_write_length, openssh_max_write_length); + + /* + * fds - File descriptors, w.r.to - With respect to + * + * Valgrind reserves some fds for itself and changes the rlimits + * w.r.to fds for the process its inspecting. Due to this reservation + * the rlimits w.r.to fds for our test may not be the same as the + * rlimits w.r.to fds seen by OpenSSH server (which Valgrind isn't + * inspecting). + * + * Valgrind changes the limits in such a way that after seeing the + * changed limits, the test cannot predict the original unchanged + * limits (which OpenSSH would be using). Hence, the test cannot + * determine the correct value of "max_open_handles" that the OpenSSH + * server should've sent. + * + * So if Valgrind is running our test, we don't provide any kind of + * check for max_open_handles. Check for >= 0 is also not provided in + * this case since that's always true for an uint64_t (an unsigned type) + */ +#if HAVE_VALGRIND_VALGRIND_H + vg = RUNNING_ON_VALGRIND; +#endif + + if (vg == 0) { + struct rlimit rlim = {0}; + uint64_t openssh_max_open_handles = 0; + + /* + * Get the resource limit for max file descriptors that a process + * can open. Since the client and the server run on the same machine + * in case of tests, this limit should be same for both (except the + * case when Valgrind runs the test) + */ + rc = getrlimit(RLIMIT_NOFILE, &rlim); + assert_int_equal(rc, 0); + if (rlim.rlim_cur > 5) { + /* + * Leaving file handles for stdout, stdin, stderr, syslog and + * a spare file handle, OpenSSH server allows the client to open + * at max (rlim.rlim_cur - 5) handles. + */ + openssh_max_open_handles = rlim.rlim_cur - 5; + } + + assert_int_equal(li->max_open_handles, openssh_max_open_handles); + } + } else { + /* Check for the default limits */ + assert_int_equal(li->max_packet_length, 34000); + assert_int_equal(li->max_read_length, 32768); + assert_int_equal(li->max_write_length, 32768); + assert_int_equal(li->max_open_handles, 0); + } + + sftp_limits_free(li); +} + +static void torture_sftp_limits_negative(void **state) +{ + sftp_limits_t li = NULL; + + (void)state; + li = sftp_limits(NULL); + assert_null(li); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_limits, + session_setup, + session_teardown), + + cmocka_unit_test_setup_teardown(torture_sftp_limits_negative, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_packet_read.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_packet_read.c new file mode 100644 index 000000000000..4eaba301253e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_packet_read.c @@ -0,0 +1,118 @@ +/* + * This is a regression test to make sure that sftp_read_packet times out + * properly in blocking mode + */ + +#define LIBSSH_STATIC + +#include "config.h" + +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int +sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int +session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void +torture_sftp_packet_read(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_packet packet = NULL; + + int fds[2]; + int rc; + + /* creating blocking fd is the default pipe behaviour */ + rc = pipe(fds); + assert_return_code(rc, errno); + + t->ssh->opts.timeout = 1; + ssh_socket_set_fd(t->ssh->socket, fds[0]); + + /* + * Making sure that the sftp_packet_read function times out and returns + * NULL. + */ + packet = sftp_packet_read(t->sftp); + assert_null(packet); + + close(fds[0]); + close(fds[1]); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_packet_read, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_read.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_read.c new file mode 100644 index 000000000000..c6ec4b9122b5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_read.c @@ -0,0 +1,121 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +#define MAX_XFER_BUF_SIZE 16384 + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) { + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_read_blocking(void **state) { + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char libssh_tmp_file[] = "/tmp/libssh_sftp_test_XXXXXX"; + char buf[MAX_XFER_BUF_SIZE]; + ssize_t bytesread; + ssize_t byteswritten; + int fd; + sftp_file file; + mode_t mask; + + file = sftp_open(t->sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(libssh_tmp_file); + umask(mask); + unlink(libssh_tmp_file); + + for (;;) { + bytesread = sftp_read(file, buf, MAX_XFER_BUF_SIZE); + if (bytesread == 0) { + break; /* EOF */ + } + assert_false(bytesread < 0); + + byteswritten = write(fd, buf, bytesread); + assert_int_equal(byteswritten, bytesread); + } + + close(fd); + sftp_close(file); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + /* This test is intentionally running twice to trigger a bug in OpenSSH + * or in pam_wrapper, causing the second invocation to fail. + * See: https://gitlab.com/libssh/libssh-mirror/-/issues/23 + */ + cmocka_unit_test_setup_teardown(torture_sftp_read_blocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_read_blocking, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_recv_response_msg.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_recv_response_msg.c new file mode 100644 index 000000000000..c7bda318bc82 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_recv_response_msg.c @@ -0,0 +1,197 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2024 Eshan Kelkar + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include +#include + +#include +#include +#include +#include + +/* For the ability to access the members of the sftp_aio_struct in the test */ +#include "sftp_aio.c" + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* Test that sftp_recv_response_msg() works properly in blocking mode */ +static void torture_sftp_recv_response_msg_blocking(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + sftp_file file = NULL; + sftp_aio aio = NULL; + sftp_message msg = NULL; + ssize_t bytes_requested; + int rc; + + /* + * For sending an sftp request and obtaining its request id, this test uses + * the sftp aio API + */ + file = sftp_open(sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + /* Send an sftp read request */ + bytes_requested = sftp_aio_begin_read(file, 16, &aio); + assert_int_equal(bytes_requested, 16); + assert_non_null(aio); + + /* Wait for the response (blocking mode) */ + rc = sftp_recv_response_msg(sftp, aio->id, true, &msg); + assert_int_equal(rc, SSH_OK); + + sftp_message_free(msg); + sftp_aio_free(aio); + sftp_close(file); +} + +/* Test that sftp_recv_response_msg() works properly in non blocking mode */ +static void torture_sftp_recv_response_msg_non_blocking(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + sftp_message msg = NULL; + sftp_file file = NULL; + sftp_aio aio = NULL; + ssize_t bytes_requested; + int rc; + + /* + * At this point, the sftp channel shouldn't contain any outstanding + * responses. + * + * Hence, sftp_recv_response_msg() should return SSH_AGAIN immediately when + * we try to receive a response for any request ID in non-blocking mode. + */ + rc = sftp_recv_response_msg(sftp, 1984, false, &msg); + assert_int_equal(rc, SSH_AGAIN); + + /* + * Validate that after a response arrives in the sftp channel, trying to + * receive the response in non-blocking mode works properly. + * + * For sending an sftp request and obtaining its request id, this test uses + * the sftp aio API + */ + file = sftp_open(sftp, SSH_EXECUTABLE, O_RDONLY, 0); + assert_non_null(file); + + bytes_requested = sftp_aio_begin_read(file, 16, &aio); + assert_int_equal(bytes_requested, 16); + assert_non_null(aio); + + /* Poll the sftp channel for the response */ + rc = ssh_channel_poll_timeout(sftp->channel, 60000, 0); + assert_int_not_equal(rc, SSH_ERROR); + assert_int_not_equal(rc, SSH_EOF); + assert_int_not_equal(rc, 0); + + /* + * The response has arrived, trying to obtain it in non blocking mode + * should work + */ + rc = sftp_recv_response_msg(sftp, aio->id, false, &msg); + assert_int_equal(rc, SSH_OK); + + sftp_message_free(msg); + sftp_aio_free(aio); + sftp_close(file); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_recv_response_msg_blocking, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_sftp_recv_response_msg_non_blocking, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_rename.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_rename.c new file mode 100644 index 000000000000..c2d11a3137c1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_rename.c @@ -0,0 +1,120 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "torture.h" +#include "sftp.c" + +#include +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_rename(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + + char name_1[128] = {0}; + char name_2[128] = {0}; + + int fd; + int rc; + + snprintf(name_1, sizeof(name_1), + "%s/libssh_sftp_rename_test_1", t->testdir); + snprintf(name_2, sizeof(name_2), + "%s/libssh_sftp_rename_test_2", t->testdir); + + fd = open(name_1, O_CREAT, S_IRWXU); + assert_return_code(fd, errno); + close(fd); + + /* try to rename an existing file */ + rc = sftp_rename(t->sftp, name_1, name_2); + assert_int_equal(rc, SSH_OK); + + /* check whether any file with name_1 exists, it shouldn't */ + rc = access(name_1, F_OK); + assert_int_not_equal(rc, 0); + + /* check whether file with name_2 exists, it should */ + rc = access(name_2, F_OK); + assert_int_equal(rc, 0); + + unlink(name_2); + + /* + * try to rename a file that does not exist, + * this should fail (-ve test case) + */ + rc = sftp_rename(t->sftp, name_1, name_2); + assert_int_not_equal(rc, 0); +} + + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_rename, + session_setup, + session_teardown) + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_request_id.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_request_id.c new file mode 100644 index 000000000000..21f2774a5dfc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_request_id.c @@ -0,0 +1,183 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "sftp.c" +#include "torture.h" + +#include +#include + +static int sshd_setup(void **state) +{ + torture_setup_sshd_server(state, false); + + return 0; +} + +static int sshd_teardown(void **state) +{ + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + struct passwd *pwd = NULL; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_sftp_request_id_null(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + int rc; + + rc = sftp_get_new_id(sftp, NULL); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_sftp_request_id_add(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + uint32_t id1, id2; + int rc; + size_t count; + + /* The list of IDs should be empty at first */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + /* Request a new ID */ + rc = sftp_get_new_id(sftp, &id1); + assert_int_equal(rc, SSH_OK); + + /* Check that the list has one ID now */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 1); + + /* Request another ID */ + rc = sftp_get_new_id(sftp, &id2); + assert_int_equal(rc, SSH_OK); + + /* Check that the IDs differ */ + assert_int_not_equal(id1, id2); + + /* Check that the list has two IDs now */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 2); +} + +static void torture_sftp_request_id_remove(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + sftp_attributes attr = NULL; + size_t count; + + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + /* We send a request and receive a response */ + attr = sftp_stat(sftp, SSH_EXECUTABLE); + assert_non_null(attr); + + /* The number of outstanding requests should be back to 0 */ + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + sftp_attributes_free(attr); +} + +static void torture_sftp_request_id_unknown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + sftp_session sftp = t->sftp; + ssh_buffer buffer = NULL; + sftp_message msg = NULL; + uint32_t id = 0; + int rc; + size_t count; + + count = ssh_list_count(sftp->outstanding_ids); + assert_int_equal(count, 0); + + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + rc = ssh_buffer_pack(buffer, "ds", id, "/tmp"); + assert_int_equal(rc, SSH_OK); + + /* Send a request without saving the request ID */ + rc = sftp_packet_write(sftp, SSH_FXP_OPENDIR, buffer); + assert_int_not_equal(rc, -1); + SSH_BUFFER_FREE(buffer); + + /* An attempt to receive the response should fail */ + rc = sftp_recv_response_msg(sftp, id, true, &msg); + assert_int_equal(rc, SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_request_id_null, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_request_id_add, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_request_id_remove, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_request_id_unknown, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/client/torture_sftp_setstat.c b/src/libs/libssh-0.12.2/tests/client/torture_sftp_setstat.c new file mode 100644 index 000000000000..736e47c17c93 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/client/torture_sftp_setstat.c @@ -0,0 +1,382 @@ +#define LIBSSH_STATIC + +#include "config.h" + +#include "libssh/sftp.h" +#include "sftp.c" +#include "torture.h" + +#include +#include +#include +#include +#include + +static int +sshd_setup(void **state) +{ + /* + * The OpenSSH invokes the sftp server command with execve(), which does + * not inherit the environment variables (including LD_PRELOAD, which + * is needed for the fs_wrapper). Using `internal-sftp` works around this, + * keeping the old environment around. + */ + setenv("TORTURE_SFTP_SERVER", "internal-sftp", 1); + + torture_setup_sshd_server(state, false); + return 0; +} + +static int +sshd_teardown(void **state) +{ + unsetenv("TORTURE_SFTP_SERVER"); + torture_teardown_sshd_server(state); + return 0; +} + +static int +session_setup_setstat(void **state) +{ + + struct torture_state *s = *state; + struct torture_sftp *t = NULL; + struct passwd *pwd = NULL; + static char name[128] = {0}; + const char *test_1 = "l&setstat_test\n"; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + t = s->ssh.tsftp; + + snprintf(name, sizeof(name), "%s/libssh_sftp_setstat_test", t->testdir); + torture_write_file(name, test_1); + s->private_data = name; + + return 0; +} + +static int +session_setup_lsetstat(void **state) +{ + + struct torture_state *s = *state; + struct torture_sftp *t = NULL; + struct passwd *pwd = NULL; + static char path[128] = {0}; + const char *test_1 = "lsetstat_test_1\n"; + int rc; + + char tmp_file[128] = {0}; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = torture_ssh_session(s, + TORTURE_SSH_SERVER, + NULL, + TORTURE_SSH_USER_ALICE, + NULL); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = torture_sftp_session(s->ssh.session); + assert_non_null(s->ssh.tsftp); + + t = s->ssh.tsftp; + + rc = sftp_extension_supported(t->sftp, "lsetstat@openssh.com", "1"); + if (rc == 0) { + skip(); + } + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", t->testdir); + torture_write_file(tmp_file, test_1); + + snprintf(path, sizeof(path), "%s/linkname", t->testdir); + rc = symlink(tmp_file, path); + assert_int_equal(rc, SSH_OK); + s->private_data = path; + + return 0; +} +static int +session_teardown(void **state) +{ + struct torture_state *s = *state; + + torture_rmdirs(s->ssh.tsftp->testdir); + torture_sftp_close(s->ssh.tsftp); + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/*setstat tests*/ +static void +torture_sftp_setstat_chown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct sftp_attributes_struct attr; + struct passwd *pwd = NULL; + sftp_attributes tmp_attr = NULL; + const char *name = (char *)s->private_data; + int rc; + + ZERO_STRUCT(attr); + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + tmp_attr = sftp_stat(t->sftp, name); + assert_non_null(tmp_attr); + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + sftp_attributes_free(tmp_attr); +} + +static void +torture_sftp_setstat_size(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + size_t len = 30; + struct sftp_attributes_struct attr; + struct stat sb; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE; + attr.size = len; + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(len, sb.st_size); +} + +static void +torture_sftp_setstat_chmod(void **state) +{ + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_PERMISSIONS; + attr.permissions = mode; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(sb.st_mode & ACCESSPERMS, mode); +} + +static void +torture_sftp_setstat_utimes(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + int atime = 10676, mtime = 13467; + const char *name = (char *)s->private_data; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attr.mtime = mtime; + attr.atime = atime; + + rc = sftp_setstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = stat(name, &sb); + assert_int_equal(rc, SSH_OK); + assert_int_equal(sb.st_mtime, mtime); + assert_int_equal(sb.st_atime, atime); +} + +static void +torture_sftp_setstat_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME | SSH_FILEXFER_ATTR_UIDGID | + SSH_FILEXFER_ATTR_PERMISSIONS | SSH_FILEXFER_ATTR_SIZE; + + /* testing null sftp */ + rc = sftp_setstat(NULL, name, &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing non-existing file */ + rc = sftp_setstat(t->sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing null attributes */ + rc = sftp_setstat(t->sftp, name, NULL); + assert_int_equal(rc, SSH_ERROR); +} + +/*lsetstat tests*/ +static void +torture_sftp_lsetstat_chown(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + struct passwd *pwd = NULL; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + sftp_attributes tmp_attr = NULL; + + ZERO_STRUCT(attr); + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + attr.flags = SSH_FILEXFER_ATTR_UIDGID; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + rc = sftp_lsetstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + tmp_attr = sftp_lstat(t->sftp, name); + assert_non_null(tmp_attr); + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + sftp_attributes_free(tmp_attr); +} + +static void +torture_sftp_lsetstat_utimes(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + struct stat sb; + int atime = 10676, mtime = 13467; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME; + attr.mtime = mtime; + attr.atime = atime; + + rc = sftp_lsetstat(t->sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + rc = lstat(name, &sb); + assert_int_equal(rc, SSH_OK); + assert_int_equal(sb.st_mtime, mtime); + assert_int_equal(sb.st_atime, atime); +} + +static void +torture_sftp_lsetstat_negative(void **state) +{ + struct torture_state *s = *state; + struct torture_sftp *t = s->ssh.tsftp; + const char *name = (char *)s->private_data; + int rc; + struct sftp_attributes_struct attr; + + ZERO_STRUCT(attr); + + attr.flags = SSH_FILEXFER_ATTR_ACMODTIME | SSH_FILEXFER_ATTR_UIDGID; + + /* testing non-existing file */ + rc = sftp_lsetstat(t->sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + + /* testing null attributes */ + rc = sftp_lsetstat(t->sftp, name, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* testing null sftp */ + rc = sftp_lsetstat(NULL, name, &attr); + assert_int_equal(rc, SSH_ERROR); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_sftp_setstat_chown, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_chmod, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_utimes, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_size, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_setstat_negative, + session_setup_setstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_utimes, + session_setup_lsetstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_chown, + session_setup_lsetstat, + session_teardown), + cmocka_unit_test_setup_teardown(torture_sftp_lsetstat_negative, + session_setup_lsetstat, + session_teardown)}; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/cmdline.c b/src/libs/libssh-0.12.2/tests/cmdline.c new file mode 100644 index 000000000000..ad58af78a1fe --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/cmdline.c @@ -0,0 +1,72 @@ +#include "config.h" +#include "torture.h" + +#ifdef HAVE_ARGP_H +#include + +const char *argp_program_version = "libssh test 0.2"; +const char *argp_program_bug_address = ""; + +static char **cmdline; + +/* Program documentation. */ +static char doc[] = "libssh test test"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "verbose", + .key = 'v', + .arg = NULL, + .flags = 0, + .doc = "Make libssh test more verbose", + .group = 0 + }, + {NULL, 0, NULL, 0, NULL, 0} +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) { + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct argument_s *arguments = state->input; + + /* arg is currently not used */ + (void) arg; + + switch (key) { + case 'v': + arguments->verbose++; + break; + case ARGP_KEY_ARG: + /* End processing here. */ + arguments->pattern = state->argv[state->next - 1]; + cmdline = &state->argv [state->next - 1]; + state->next = state->argc; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +/* Our argp parser. */ +/* static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; */ +static struct argp argp = {options, parse_opt, NULL, doc, NULL, NULL, NULL}; +#endif /* HAVE_ARGP_H */ + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments) { + /* + * Parse our arguments; every option seen by parse_opt will + * be reflected in arguments. + */ +#ifdef HAVE_ARGP_H + argp_parse(&argp, argc, argv, 0, 0, arguments); +#else + (void) argc; + (void) argv; + (void) arguments; +#endif /* HAVE_ARGP_H */ +} diff --git a/src/libs/libssh-0.12.2/tests/ctest-default.cmake b/src/libs/libssh-0.12.2/tests/ctest-default.cmake new file mode 100644 index 000000000000..51bbebabdc97 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/ctest-default.cmake @@ -0,0 +1,72 @@ +## The directory to run ctest in. +set(CTEST_DIRECTORY "$ENV{HOME}/workspace/tmp/dashboards/libssh") + +## The hostname of the machine +set(CTEST_SITE "host.libssh.org") +## The buildname +set(CTEST_BUILD_NAME "Linux_2.6-GCC_4.5-x86_64-default") + +## The Makefile generator to use +set(CTEST_CMAKE_GENERATOR "Unix Makefiles") + +## The Build configuration to use. +set(CTEST_BUILD_CONFIGURATION "Debug") + +## The build options for the project +set(CTEST_BUILD_OPTIONS "-DUNIT_TESTING=ON -WITH_SFTP=ON -DWITH_SERVER=ON -DWITH_ZLIB=ON -DWITH_PCAP=ON -DDEBUG_CRYPTO=ON -DWITH_GCRYPT=OFF") + +#set(CTEST_CUSTOM_MEMCHECK_IGNORE torture_rand) + +## The Model to set: Nightly, Continuous, Experimental +set(CTEST_MODEL "Experimental") + +## The branch +#set(CTEST_GIT_BRANCH "--branch v0-5") + +## Whether to enable memory checking. +set(WITH_MEMCHECK FALSE) + +## Whether to enable code coverage. +set(WITH_COVERAGE FALSE) + +####################################################################### + +if (WITH_COVERAGE AND NOT WIN32) + set(CTEST_BUILD_CONFIGURATION "Profiling") +endif (WITH_COVERAGE AND NOT WIN32) + +set(CTEST_SOURCE_DIRECTORY "${CTEST_DIRECTORY}/${CTEST_BUILD_NAME}/source") +set(CTEST_BINARY_DIRECTORY "${CTEST_DIRECTORY}/${CTEST_BUILD_NAME}/build") + +set(CTEST_MEMORYCHECK_SUPPRESSIONS_FILE ${CMAKE_SOURCE_DIR}/tests/valgrind.supp) +set(CTEST_MEMORYCHECK_COMMAND_OPTIONS " --trace-children-skip=${SSHD_EXECUTABLE}") + +find_program(CTEST_GIT_COMMAND NAMES git) +find_program(CTEST_COVERAGE_COMMAND NAMES gcov) +find_program(CTEST_MEMORYCHECK_COMMAND NAMES valgrind) + +if(NOT EXISTS "${CTEST_SOURCE_DIRECTORY}") + set(CTEST_CHECKOUT_COMMAND "${CTEST_GIT_COMMAND} clone ${CTEST_GIT_BRANCH} git://git.libssh.org/projects/libssh.git ${CTEST_SOURCE_DIRECTORY}") +endif() + +set(CTEST_UPDATE_COMMAND "${CTEST_GIT_COMMAND}") + +set(CTEST_CONFIGURE_COMMAND "${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE:STRING=${CTEST_BUILD_CONFIGURATION}") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} -DUNIT_TESTING:BOOL=ON ${CTEST_BUILD_OPTIONS}") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} \"-G${CTEST_CMAKE_GENERATOR}\"") +set(CTEST_CONFIGURE_COMMAND "${CTEST_CONFIGURE_COMMAND} \"${CTEST_SOURCE_DIRECTORY}\"") + +ctest_empty_binary_directory(${CTEST_BINARY_DIRECTORY}) + +ctest_start(${CTEST_MODEL} TRACK ${CTEST_MODEL}) +ctest_update(SOURCE ${CTEST_SOURCE_DIRECTORY}) +ctest_configure(BUILD ${CTEST_BINARY_DIRECTORY}) +ctest_build(BUILD ${CTEST_BINARY_DIRECTORY}) +ctest_test(BUILD ${CTEST_BINARY_DIRECTORY}) +if (WITH_COVERAGE) + ctest_coverage(BUILD ${CTEST_BINARY_DIRECTORY}) +endif () +if (WITH_MEMCHECK) + ctest_memcheck(BUILD ${CTEST_BINARY_DIRECTORY}) +endif () +ctest_submit() diff --git a/src/libs/libssh-0.12.2/tests/etc/group.in b/src/libs/libssh-0.12.2/tests/etc/group.in new file mode 100644 index 000000000000..df5ae8ab3730 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/group.in @@ -0,0 +1,5 @@ +users:x:9000: +sshd:x:65531: +nobody:x:65533: +nogroup:x:65534:nobody +root:x:0: diff --git a/src/libs/libssh-0.12.2/tests/etc/hosts.in b/src/libs/libssh-0.12.2/tests/etc/hosts.in new file mode 100644 index 000000000000..ea519350f432 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/hosts.in @@ -0,0 +1,12 @@ +127.0.0.10 server.libssh.site +127.0.0.21 client.libssh.site + +127.0.0.11 kdc.libssh.site + +123.0.0.11 testing +fd00::5357:5f0a testing + +127.0.0.10 afboth +fd00::5357:5f0a afboth +127.0.0.10 afinet +fd00::5357:5f0a afinet6 diff --git a/src/libs/libssh-0.12.2/tests/etc/openssl.cnf b/src/libs/libssh-0.12.2/tests/etc/openssl.cnf new file mode 100644 index 000000000000..7149b6bf7cc6 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/openssl.cnf @@ -0,0 +1,11 @@ +openssl_conf = openssl_init +[openssl_init] +providers = provider_sect +[provider_sect] +default = default_sect +pkcs11 = pkcs11_sect +[default_sect] +activate = 1 +[pkcs11_sect] +activate = 1 +pkcs11-module-assume-fips = true diff --git a/src/libs/libssh-0.12.2/tests/etc/pam.d/sshd.in b/src/libs/libssh-0.12.2/tests/etc/pam.d/sshd.in new file mode 100644 index 000000000000..57c66f9413b8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/pam.d/sshd.in @@ -0,0 +1,4 @@ +auth required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb +account required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb +password required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb +session required @PAM_WRAPPER_MODULE_DIR@/pam_matrix.so passdb=@CMAKE_CURRENT_BINARY_DIR@/etc/pam_matrix_passdb diff --git a/src/libs/libssh-0.12.2/tests/etc/pam_matrix_passdb.in b/src/libs/libssh-0.12.2/tests/etc/pam_matrix_passdb.in new file mode 100644 index 000000000000..9404bc0e702c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/pam_matrix_passdb.in @@ -0,0 +1,4 @@ +bob:secret:sshd +alice:secret:sshd +charlie:secret:sshd +doe:secret:sshd diff --git a/src/libs/libssh-0.12.2/tests/etc/passwd.in b/src/libs/libssh-0.12.2/tests/etc/passwd.in new file mode 100644 index 000000000000..fff7878030f2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/passwd.in @@ -0,0 +1,9 @@ +bob:x:5000:9000:bob gecos:@HOMEDIR@/bob:/bin/sh +alice:x:5001:9000:alice gecos:@HOMEDIR@/alice:/bin/sh +charlie:x:5002:9000:charlie gecos:@HOMEDIR@/charlie:/bin/sh +doe:x:5003:9000:doe gecos:@HOMEDIR@/doe:/bin/sh +frank:x:5003:9000:doe gecos:@HOMEDIR@/frank:/bin/sh +sshd:x:65530:65531:sshd:@HOMEDIR@:/sbin/nologin +nobody:x:65533:65534:nobody gecos:@HOMEDIR@:/bin/false +root:x:0:0:root gecos:@HOMEDIR@:/bin/false +@LOCAL_USER@:x:@LOCAL_UID@:9000:local user:@HOMEDIR@:/bin/false diff --git a/src/libs/libssh-0.12.2/tests/etc/shadow.in b/src/libs/libssh-0.12.2/tests/etc/shadow.in new file mode 100644 index 000000000000..0f03b1499195 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/etc/shadow.in @@ -0,0 +1,4 @@ +alice:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: +bob:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: +charlie:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: +doe:$6$0jWkA8VP$MvBUvtGy38jWCZ5KtqnZEKQWXvvImDkDhDQII1kTqtAp3/xH31b71c.AjGkBFle.2QwCJQH7OzB/NXiMprusr/::0::::: diff --git a/src/libs/libssh-0.12.2/tests/external_override/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/external_override/CMakeLists.txt new file mode 100644 index 000000000000..fcce52c7b030 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/CMakeLists.txt @@ -0,0 +1,205 @@ +project(external-override C) + +include_directories(${CMAKE_SOURCE_DIR}/include) + +set(LIBSSH_OVERRIDE_TESTS + torture_override +) + +# chacha20_override +add_library(chacha20_override SHARED + chacha20_override.c + ${libssh_SOURCE_DIR}/src/external/chacha.c + ) +set(CHACHA20_OVERRIDE_LIBRARY + ${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}chacha20_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# poly1305_override +add_library(poly1305_override SHARED + poly1305_override.c + ${libssh_SOURCE_DIR}/src/external/poly1305.c + ) +set(POLY1305_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}poly1305_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +if (WITH_GCRYPT) + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_gcrypt.c + ${libssh_SOURCE_DIR}/src/md_gcrypt.c + ) + set(override_libs + ${GCRYPT_LIBRARIES} + ) +elseif (WITH_MBEDTLS) + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_mbedcrypto.c + ${libssh_SOURCE_DIR}/src/md_mbedcrypto.c + ) + set(override_libs + ${MBEDTLS_CRYPTO_LIBRARY} + ) +else () + set (override_src + ${libssh_SOURCE_DIR}/src/getrandom_crypto.c + ${libssh_SOURCE_DIR}/src/md_crypto.c + ) + set(override_libs + OpenSSL::Crypto + ) +endif (WITH_GCRYPT) + +# ed25519_override +add_library(ed25519_override SHARED + ed25519_override.c + ${libssh_SOURCE_DIR}/src/external/fe25519.c + ${libssh_SOURCE_DIR}/src/external/ge25519.c + ${libssh_SOURCE_DIR}/src/external/sc25519.c + ${libssh_SOURCE_DIR}/src/external/ed25519.c + ${override_src} + ) +target_link_libraries(ed25519_override + PRIVATE ${override_libs}) +set(ED25519_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}ed25519_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# curve25519_override +add_library(curve25519_override SHARED + curve25519_override.c + ${libssh_SOURCE_DIR}/src/external/curve25519_ref.c + ${libssh_SOURCE_DIR}/src/external/fe25519.c + ${libssh_SOURCE_DIR}/src/external/ge25519.c + ${libssh_SOURCE_DIR}/src/external/sc25519.c + ${libssh_SOURCE_DIR}/src/external/ed25519.c + ${override_src} +) +target_link_libraries(curve25519_override + PRIVATE ${override_libs}) +set(CURVE25519_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}curve25519_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# sntrup761_override +add_library(sntrup761_override SHARED + sntrup761_override.c + ${libssh_SOURCE_DIR}/src/external/sntrup761.c + ${override_src} +) +target_link_libraries(sntrup761_override + PRIVATE ${override_libs}) +set(SNTRUP761_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}sntrup761_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +# mlkem768_override +add_library(mlkem768_override SHARED + mlkem768_override.c + ${libssh_SOURCE_DIR}/src/external/libcrux_mlkem768_sha3.c + ${override_src} +) +target_link_libraries(mlkem768_override + PRIVATE ${override_libs}) +set(MLKEM768_OVERRIDE_LIBRARY +${libssh_BINARY_DIR}/lib/${CMAKE_SHARED_LIBRARY_PREFIX}mlkem768_override${CMAKE_SHARED_LIBRARY_SUFFIX}) + +set(OVERRIDE_LIBRARIES + ${CHACHA20_OVERRIDE_LIBRARY}:${POLY1305_OVERRIDE_LIBRARY}:${ED25519_OVERRIDE_LIBRARY}:${CURVE25519_OVERRIDE_LIBRARY}:${SNTRUP761_OVERRIDE_LIBRARY}:${MLKEM768_OVERRIDE_LIBRARY} +) + +if (WITH_MBEDTLS) + if (HAVE_MBEDTLS_CHACHA20_H AND HAVE_MBEDTLS_POLY1305_H) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") + endif () + + if(HAVE_MBEDTLS_CURVE25519) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") + endif() + + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=1") +elseif (WITH_GCRYPT) + if (HAVE_GCRYPT_CHACHA_POLY) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") + endif () + + if(HAVE_GCRYPT_CURVE25519) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") + else() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=1") + endif() + + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=1") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=0") + if(HAVE_GCRYPT_MLKEM) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=0") + else() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=1") + endif() +else () + if (HAVE_OPENSSL_EVP_CHACHA20) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=0") + else () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CHACHAPOLY=1") + endif () + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_CURVE25519=0") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_ED25519=0") + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_SNTRUP761=1") + if(HAVE_OPENSSL_MLKEM) + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=0") + else() + list(APPEND OVERRIDE_RESULTS "-DSHOULD_CALL_INTERNAL_MLKEM=1") + endif() +endif () + +if (NOT OSX) + # Remove any preload string from the environment variables list + foreach(env_string ${TORTURE_ENVIRONMENT}) + if (${env_string} MATCHES "^LD_PRELOAD=*") + list(REMOVE_ITEM TORTURE_ENVIRONMENT ${env_string}) + set(PRELOAD_STRING "${env_string}:") + endif () + endforeach () + + if ("${PRELOAD_STRING}" STREQUAL "") + set(PRELOAD_STRING "LD_PRELOAD=") + endif () + + list(APPEND TORTURE_ENVIRONMENT + "${PRELOAD_STRING}${OVERRIDE_LIBRARIES}") +endif() + +foreach(_OVERRIDE_TEST ${LIBSSH_OVERRIDE_TESTS}) + add_cmocka_test(${_OVERRIDE_TEST} + SOURCES ${_OVERRIDE_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + ${OVERRIDE_RESULTS} + LINK_LIBRARIES + ${TORTURE_SHARED_LIBRARY} + chacha20_override + poly1305_override + ed25519_override + curve25519_override + sntrup761_override + mlkem768_override + ) + + if (OSX) + set_property( + TEST + ${_OVERRIDE_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${OVERRIDE_LIBRARIES}) + + else () + set_property( + TEST + ${_OVERRIDE_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_ENVIRONMENT}) + + endif() +endforeach() diff --git a/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.c b/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.c new file mode 100644 index 000000000000..7e166e7c8abd --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.c @@ -0,0 +1,80 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "chacha20_override.h" + +static bool internal_function_called = false; + +void __wrap_chacha_keysetup(struct chacha_ctx *x, + const uint8_t *k, + uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + chacha_keysetup(x, k, kbits); +} + +void __wrap_chacha_ivsetup(struct chacha_ctx *x, + const uint8_t *iv, + const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + chacha_ivsetup(x, iv, ctr); +} + +void __wrap_chacha_encrypt_bytes(struct chacha_ctx *x, + const uint8_t *m, + uint8_t *c, + uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + chacha_encrypt_bytes(x, m, c, bytes); +} + +bool internal_chacha20_function_called(void) +{ + return internal_function_called; +} + +void reset_chacha20_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.h b/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.h new file mode 100644 index 000000000000..58f8f211e4ec --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/chacha20_override.h @@ -0,0 +1,51 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/chacha.h" + +void __wrap_chacha_keysetup(struct chacha_ctx *x, + const uint8_t *k, + uint32_t kbits) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_MINKEYLEN))) +#endif +; + +void __wrap_chacha_ivsetup(struct chacha_ctx *x, + const uint8_t *iv, + const uint8_t *ctr) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 2, CHACHA_NONCELEN))) + __attribute__((__bounded__(__minbytes__, 3, CHACHA_CTRLEN))) +#endif +; + +void __wrap_chacha_encrypt_bytes(struct chacha_ctx *x, + const uint8_t *m, + uint8_t *c, + uint32_t bytes) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__buffer__, 2, 4))) + __attribute__((__bounded__(__buffer__, 3, 4))) +#endif +; + +bool internal_chacha20_function_called(void); +void reset_chacha20_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.c b/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.c new file mode 100644 index 000000000000..983d57cdf410 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.c @@ -0,0 +1,58 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "curve25519_override.h" + +static bool internal_function_called = false; + +int __wrap_crypto_scalarmult_base(unsigned char *q, + const unsigned char *n) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_scalarmult_base(q, n); +} + +int __wrap_crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_scalarmult(q, n, p); +} + +bool internal_curve25519_function_called(void) +{ + return internal_function_called; +} + +void reset_curve25519_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.h b/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.h new file mode 100644 index 000000000000..634c1c40c8ce --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/curve25519_override.h @@ -0,0 +1,31 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/curve25519.h" + +int __wrap_crypto_scalarmult_base(unsigned char *q, + const unsigned char *n); + +int __wrap_crypto_scalarmult(unsigned char *q, + const unsigned char *n, + const unsigned char *p); + +bool internal_curve25519_function_called(void); +void reset_curve25519_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.c b/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.c new file mode 100644 index 000000000000..439a5dab41fa --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.c @@ -0,0 +1,71 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +#include "ed25519_override.h" + +static bool internal_function_called = false; + +int __wrap_crypto_sign_ed25519_keypair(ed25519_pubkey pk, + ed25519_privkey sk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_sign_ed25519_keypair(pk, sk); +} + +int __wrap_crypto_sign_ed25519(unsigned char *sm, + uint64_t *smlen, + const unsigned char *m, + uint64_t mlen, + const ed25519_privkey sk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_sign_ed25519(sm, smlen, m, mlen, sk); +} + +int __wrap_crypto_sign_ed25519_open(unsigned char *m, + uint64_t *mlen, + const unsigned char *sm, + uint64_t smlen, + const ed25519_pubkey pk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return crypto_sign_ed25519_open(m, mlen, sm, smlen, pk); +} + +bool internal_ed25519_function_called(void) +{ + return internal_function_called; +} + +void reset_ed25519_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.h b/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.h new file mode 100644 index 000000000000..0abe2e249e39 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/ed25519_override.h @@ -0,0 +1,39 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/ed25519.h" + +int __wrap_crypto_sign_ed25519_keypair(ed25519_pubkey pk, + ed25519_privkey sk); + +int __wrap_crypto_sign_ed25519(unsigned char *sm, + uint64_t *smlen, + const unsigned char *m, + uint64_t mlen, + const ed25519_privkey sk); + +int __wrap_crypto_sign_ed25519_open(unsigned char *m, + uint64_t *mlen, + const unsigned char *sm, + uint64_t smlen, + const ed25519_pubkey pk); + +bool internal_ed25519_function_called(void); +void reset_ed25519_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.c b/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.c new file mode 100644 index 000000000000..be135cda6f0b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.c @@ -0,0 +1,83 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * Jakub Jelen + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include + +#include + +#include "libssh/mlkem_native.h" +#include "mlkem768_override.h" + +static bool internal_function_called = false; + +libcrux_ml_kem_mlkem768_MlKem768KeyPair +__wrap_libcrux_ml_kem_mlkem768_portable_generate_key_pair( + uint8_t randomness[64U]) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_generate_key_pair(randomness); +} + +bool __wrap_libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_validate_public_key(public_key); +} + +tuple_c2 __wrap_libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_encapsulate(public_key, randomness); +} + +void __wrap_libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, + uint8_t ret[32U]) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return libcrux_ml_kem_mlkem768_portable_decapsulate(private_key, + ciphertext, + ret); +} + +bool internal_mlkem768_function_called(void) +{ + return internal_function_called; +} + +void reset_mlkem768_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.h b/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.h new file mode 100644 index 000000000000..ca9522e39db3 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/mlkem768_override.h @@ -0,0 +1,43 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * Jakub Jelen + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/mlkem_native.h" + +libcrux_ml_kem_mlkem768_MlKem768KeyPair +__wrap_libcrux_ml_kem_mlkem768_portable_generate_key_pair( + uint8_t randomness[64U]); + +bool __wrap_libcrux_ml_kem_mlkem768_portable_validate_public_key( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key); + +tuple_c2 __wrap_libcrux_ml_kem_mlkem768_portable_encapsulate( + libcrux_ml_kem_types_MlKemPublicKey_30 *public_key, + uint8_t randomness[32U]); + +void __wrap_libcrux_ml_kem_mlkem768_portable_decapsulate( + libcrux_ml_kem_types_MlKemPrivateKey_d9 *private_key, + libcrux_ml_kem_mlkem768_MlKem768Ciphertext *ciphertext, + uint8_t ret[32U]); + +bool internal_mlkem768_function_called(void); +void reset_mlkem768_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.c b/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.c new file mode 100644 index 000000000000..4d78272f99b2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.c @@ -0,0 +1,54 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include +#include +#include + +static bool internal_function_called = false; + +void __wrap_poly1305_auth(uint8_t out[POLY1305_TAGLEN], + const uint8_t *m, + size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + poly1305_auth(out, m, inlen, key); +} + +bool internal_poly1305_function_called(void) +{ + return internal_function_called; +} + +void reset_poly1305_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.h b/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.h new file mode 100644 index 000000000000..877178006ee1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/poly1305_override.h @@ -0,0 +1,35 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/poly1305.h" + +void __wrap_poly1305_auth(uint8_t out[POLY1305_TAGLEN], + const uint8_t *m, + size_t inlen, + const uint8_t key[POLY1305_KEYLEN]) +#ifdef HAVE_GCC_BOUNDED_ATTRIBUTE + __attribute__((__bounded__(__minbytes__, 1, POLY1305_TAGLEN))) + __attribute__((__bounded__(__buffer__, 2, 3))) + __attribute__((__bounded__(__minbytes__, 4, POLY1305_KEYLEN))) +#endif +; + +bool internal_poly1305_function_called(void); +void reset_poly1305_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.c b/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.c new file mode 100644 index 000000000000..dcdca326cf15 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.c @@ -0,0 +1,73 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * Jakub Jelen + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include +#include +#include + +#include +#include + +#include "sntrup761_override.h" + +static bool internal_function_called = false; + +void __wrap_sntrup761_keypair(uint8_t *pk, + uint8_t *sk, + void *random_ctx, + sntrup761_random_func *random) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return sntrup761_keypair(pk, sk, random_ctx, random); +} + +void __wrap_sntrup761_enc(uint8_t *c, + uint8_t *k, + const uint8_t *pk, + void *random_ctx, + sntrup761_random_func *random) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return sntrup761_enc(c, k, pk, random_ctx, random); +} + +void __wrap_sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk) +{ + fprintf(stderr, "%s: Internal implementation was called\n", __func__); + internal_function_called = true; + return sntrup761_dec(k, c, sk); +} + +bool internal_sntrup761_function_called(void) +{ + return internal_function_called; +} + +void reset_sntrup761_function_called(void) +{ + internal_function_called = false; +} diff --git a/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.h b/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.h new file mode 100644 index 000000000000..4ec9cb5ebc06 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/sntrup761_override.h @@ -0,0 +1,40 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 - 2025 Red Hat, Inc. + * + * Authors: Anderson Toshiyuki Sasaki + * Jakub Jelen + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "libssh/sntrup761.h" + +void __wrap_sntrup761_keypair(uint8_t *pk, + uint8_t *sk, + void *random_ctx, + sntrup761_random_func *random); + +void __wrap_sntrup761_enc(uint8_t *c, + uint8_t *k, + const uint8_t *pk, + void *random_ctx, + sntrup761_random_func *random); + +void __wrap_sntrup761_dec(uint8_t *k, const uint8_t *c, const uint8_t *sk); + +bool internal_sntrup761_function_called(void); +void reset_sntrup761_function_called(void); diff --git a/src/libs/libssh-0.12.2/tests/external_override/torture_override.c b/src/libs/libssh-0.12.2/tests/external_override/torture_override.c new file mode 100644 index 000000000000..160864cc935e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/external_override/torture_override.c @@ -0,0 +1,466 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2021 by Anderson Toshiyuki Sasaki - Red Hat, Inc. + * + * The SSH Library is free software: you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation, either + * version 2.1 of the License, or (at your option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with the SSH Library; see the file COPYING. If not, + * see . + */ + +#include "config.h" + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#include "chacha20_override.h" +#include "curve25519_override.h" +#include "ed25519_override.h" +#include "mlkem768_override.h" +#include "poly1305_override.h" +#include "sntrup761_override.h" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_st { + char *temp_dir; + char *orig_dir; +}; + +static int sshd_setup(void **state) +{ + struct torture_state *s; + struct test_st *test_state = NULL; + char *temp_dir; + int rc; + + torture_setup_sshd_server(state, false); + + test_state = malloc(sizeof(struct test_st)); + assert_non_null(test_state); + + s = *((struct torture_state **)state); + s->private_data = test_state; + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + test_state->temp_dir = temp_dir; + + return 0; +} + +static int sshd_teardown(void **state) +{ + struct torture_state *s = *state; + struct test_st *test_state = s->private_data; + int rc; + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state); + + torture_teardown_sshd_server(state); + + return 0; +} + +static int session_setup(void **state) +{ + struct torture_state *s = *state; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool false_v = false; + int rc; + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + /* Prevent parsing configuration files that can introduce different + * algorithms then we want to test */ + ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &false_v); + + reset_chacha20_function_called(); + reset_poly1305_function_called(); + reset_curve25519_function_called(); + reset_ed25519_function_called(); + reset_sntrup761_function_called(); + reset_mlkem768_function_called(); + + return 0; +} + +static int session_teardown(void **state) +{ + struct torture_state *s = *state; + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void test_algorithm(ssh_session session, + const char *kex, + const char *cipher, + const char *hostkey) +{ + char data[256] = {0}; + int rc; + + if (kex != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + assert_ssh_return_code(session, rc); + } + + if (cipher != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, cipher); + assert_ssh_return_code(session, rc); + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, cipher); + assert_ssh_return_code(session, rc); + } + + if (hostkey != NULL) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkey); + assert_ssh_return_code(session, rc); + } + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* send ignore packets of all sizes */ + memset(data, 'A', sizeof(data) - 1); + ssh_send_ignore(session, data); + ssh_handle_packets(session, 50); + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + rc = ssh_get_error_code(session); + assert_int_equal(rc, SSH_REQUEST_DENIED); + } + + ssh_disconnect(session); +} + +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM +static void torture_override_chacha20_poly1305(void **state) +{ + struct torture_state *s = *state; + + bool internal_chacha20_called; + bool internal_poly1305_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /* kex */ + "chacha20-poly1305@openssh.com", + NULL /* hostkey */); + + internal_chacha20_called = internal_chacha20_function_called(); + internal_poly1305_called = internal_poly1305_function_called(); + +#if SHOULD_CALL_INTERNAL_CHACHAPOLY + assert_true(internal_chacha20_called); + assert_true(internal_poly1305_called); +#else + assert_false(internal_chacha20_called || + internal_poly1305_called); +#endif + +} +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ + +#ifdef OPENSSH_CURVE25519_SHA256 +static void torture_override_ecdh_curve25519_sha256(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "curve25519-sha256", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_CURVE25519_SHA256 */ + +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG +static void torture_override_ecdh_curve25519_sha256_libssh_org(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "curve25519-sha256@libssh.org", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM +static void +torture_override_ecdh_sntrup761x25519_sha512_openssh_com(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + bool internal_sntrup761_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512@openssh.com", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + internal_sntrup761_called = internal_sntrup761_function_called(); + +#if SHOULD_CALL_INTERNAL_SNTRUP761 + assert_true(internal_sntrup761_called); +#else + assert_false(internal_sntrup761_called); +#endif + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ + +#ifdef OPENSSH_SNTRUP761X25519_SHA512 +static void +torture_override_ecdh_sntrup761x25519_sha512(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + bool internal_sntrup761_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "sntrup761x25519-sha512", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + internal_sntrup761_called = internal_sntrup761_function_called(); + +#if SHOULD_CALL_INTERNAL_SNTRUP761 + assert_true(internal_sntrup761_called); +#else + assert_false(internal_sntrup761_called); +#endif + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ + +#ifdef OPENSSH_MLKEM768X25519_SHA256 +static void torture_override_mlkem768x25519_sha256(void **state) +{ + struct torture_state *s = *state; + bool internal_curve25519_called; + bool internal_mlkem768_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + "mlkem768x25519-sha256", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_curve25519_called = internal_curve25519_function_called(); + internal_mlkem768_called = internal_mlkem768_function_called(); + +#if SHOULD_CALL_INTERNAL_MLKEM + assert_true(internal_mlkem768_called); +#else + assert_false(internal_mlkem768_called); +#endif + +#if SHOULD_CALL_INTERNAL_CURVE25519 + assert_true(internal_curve25519_called); +#else + assert_false(internal_curve25519_called); +#endif +} +#endif /* OPENSSH_MLKEM768X25519_SHA256 */ + +#ifdef OPENSSH_MLKEM768NISTP256_SHA256 +static void torture_override_mlkem768nistp256_sha256(void **state) +{ + struct torture_state *s = *state; + bool internal_mlkem768_called; + + test_algorithm(s->ssh.session, + "mlkem768nistp256-sha256", + NULL, /* cipher */ + NULL /* hostkey */); + + internal_mlkem768_called = internal_mlkem768_function_called(); + +#if SHOULD_CALL_INTERNAL_MLKEM + assert_true(internal_mlkem768_called); +#else + assert_false(internal_mlkem768_called); +#endif +} +#endif /* OPENSSH_MLKEM768NISTP256_SHA256 */ + +#ifdef OPENSSH_SSH_ED25519 +static void torture_override_ed25519(void **state) +{ + struct torture_state *s = *state; + bool internal_ed25519_called; + + if (ssh_fips_mode()) { + skip(); + } + + test_algorithm(s->ssh.session, + NULL, /* kex */ + NULL, /* cipher */ + "ssh-ed25519"); + + internal_ed25519_called = internal_ed25519_function_called(); + +#if SHOULD_CALL_INTERNAL_ED25519 + assert_true(internal_ed25519_called); +#else + assert_false(internal_ed25519_called); +#endif +} +#endif /* OPENSSH_SSH_ED25519 */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { +#ifdef OPENSSH_CHACHA20_POLY1305_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_override_chacha20_poly1305, + session_setup, + session_teardown), +#endif /* OPENSSH_CHACHA20_POLY1305_OPENSSH_COM */ +#ifdef OPENSSH_CURVE25519_SHA256 + cmocka_unit_test_setup_teardown(torture_override_ecdh_curve25519_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256 */ +#ifdef OPENSSH_CURVE25519_SHA256_LIBSSH_ORG + cmocka_unit_test_setup_teardown(torture_override_ecdh_curve25519_sha256_libssh_org, + session_setup, + session_teardown), +#endif /* OPENSSH_CURVE25519_SHA256_LIBSSH_ORG */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM + cmocka_unit_test_setup_teardown(torture_override_ecdh_sntrup761x25519_sha512_openssh_com, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM */ +#ifdef OPENSSH_SNTRUP761X25519_SHA512 + cmocka_unit_test_setup_teardown(torture_override_ecdh_sntrup761x25519_sha512, + session_setup, + session_teardown), +#endif /* OPENSSH_SNTRUP761X25519_SHA512 */ +#ifdef OPENSSH_MLKEM768X25519_SHA256 + cmocka_unit_test_setup_teardown(torture_override_mlkem768x25519_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_MLKEM768X25519_SHA256 */ +#ifdef OPENSSH_MLKEM768NISTP256_SHA256 + cmocka_unit_test_setup_teardown(torture_override_mlkem768nistp256_sha256, + session_setup, + session_teardown), +#endif /* OPENSSH_MLKEM768NISTP256_SHA256 */ +#ifdef OPENSSH_SSH_ED25519 + cmocka_unit_test_setup_teardown(torture_override_ed25519, + session_setup, + session_teardown), +#endif /* OPENSSH_SSH_ED25519 */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, sshd_setup, sshd_teardown); + if (rc != 0) { + return rc; + } + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/fs_wrapper.c b/src/libs/libssh-0.12.2/tests/fs_wrapper.c new file mode 100644 index 000000000000..b718eff09b83 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fs_wrapper.c @@ -0,0 +1,255 @@ +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/******************************************************************************* + * Structs + ******************************************************************************/ +struct file { + char *name; + uid_t uid; + gid_t gid; +} file = {0}; + +/******************************************************************************* + * Destructor + ******************************************************************************/ + +void destructor(void) __attribute__((destructor)); + +void +destructor(void) +{ + free(file.name); +} + +/******************************************************************************* + * Chown wrapping + ******************************************************************************/ + +/** Records the UID and GID and pretend syscall worked */ +static int +chown_helper(const char *pathname, uid_t owner, gid_t group) +{ + if (strlen(pathname) > 7 && strncmp(pathname, "/dev/pt", 7) == 0) { + /* + * The OpenSSH server modified the PTY which requires root permissions + * see torture_request_pty_modes + * */ + return 0; + } + if (strlen(pathname) > 4 && strncmp(pathname, "/tmp", 4) == 0) { + /* + * faking chown because It requires root permissions to modify the owner + * under /tmp + * It's also a helper for torture_sftp_setstat + * */ + if (file.name != NULL) { + free((char *)file.name); + } + file.name = strdup(pathname); + file.uid = owner; + file.gid = group; + return 0; + } + return -1; +} + +#define WRAP_CHOWN(syscall_name) \ + typedef int (*__libc_##syscall_name)(const char *pathname, \ + uid_t owner, \ + gid_t group); \ + int syscall_name(const char *pathname, uid_t owner, gid_t group); \ + int syscall_name(const char *pathname, uid_t owner, gid_t group) \ + { \ + __libc_##syscall_name original_##syscall_name = NULL; \ + int rc; \ + \ + rc = chown_helper(pathname, owner, group); \ + if (rc == 0) { \ + return 0; \ + } \ + original_##syscall_name = \ + (__libc_##syscall_name)dlsym(RTLD_NEXT, #syscall_name); \ + return (*original_##syscall_name)(pathname, owner, group); \ + } + +WRAP_CHOWN(chown) +WRAP_CHOWN(chown32) +WRAP_CHOWN(lchown) + +/* fchownat */ +typedef int (*__libc_fchownat)(int dirfd, + const char *pathname, + uid_t owner, + gid_t group, + int flags); + +int +fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags); + +int +fchownat(int dirfd, const char *pathname, uid_t owner, gid_t group, int flags) +{ + __libc_fchownat original_fchownat = NULL; + int rc; + + rc = chown_helper(pathname, owner, group); + if (rc == 0) { + return 0; + } + + original_fchownat = (__libc_fchownat)dlsym(RTLD_NEXT, "fchownat"); + return (*original_fchownat)(dirfd, pathname, owner, group, flags); +} + +/******************************************************************************* + * Stat wrapping + ******************************************************************************/ + +/** Returns previously set UID/GID for the filename */ +static void +stat_helper(const char *pathname, struct stat *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->st_uid = file.uid; + statbuf->st_gid = file.gid; + } +} + +static void +stat64_helper(const char *pathname, struct stat64 *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->st_uid = file.uid; + statbuf->st_gid = file.gid; + } +} + +#define WRAP_STAT(syscall_name, struct_name) \ + typedef int (*__libc_##syscall_name)(const char *pathname, \ + struct struct_name *statbuf); \ + int syscall_name(const char *pathname, struct struct_name *statbuf); \ + int syscall_name(const char *pathname, struct struct_name *statbuf) \ + { \ + int rc; \ + __libc_##syscall_name original_##syscall_name = NULL; \ + \ + original_##syscall_name = \ + (__libc_##syscall_name)dlsym(RTLD_NEXT, #syscall_name); \ + rc = (*original_##syscall_name)(pathname, statbuf); \ + struct_name##_helper(pathname, statbuf); \ + \ + return rc; \ + } + +WRAP_STAT(stat, stat) +WRAP_STAT(lstat, stat) +/* i686 arch */ +WRAP_STAT(stat64, stat64) +WRAP_STAT(lstat64, stat64) + +#define WRAP_XSTAT(syscall_name) \ + typedef int (*__libc_##syscall_name)(int ver, \ + const char *pathname, \ + struct stat *statbuf); \ + int syscall_name(int ver, const char *pathname, struct stat *statbuf); \ + int syscall_name(int ver, const char *pathname, struct stat *statbuf) \ + { \ + int rc; \ + __libc_##syscall_name original_##syscall_name = NULL; \ + \ + original_##syscall_name = \ + (__libc_##syscall_name)dlsym(RTLD_NEXT, #syscall_name); \ + rc = (*original_##syscall_name)(ver, pathname, statbuf); \ + stat_helper(pathname, statbuf); \ + \ + return rc; \ + } + +WRAP_XSTAT(__xstat) /* CentOS8 */ +WRAP_XSTAT(__lxstat) + +/* i686 arch (likely not wrappable) */ +static void +statx_helper(const char *pathname, struct statx *statbuf) +{ + if (file.name != NULL && strcmp(pathname, file.name) == 0) { + statbuf->stx_uid = file.uid; + statbuf->stx_gid = file.gid; + } +} + +typedef int (*__libc_statx)(int dirfd, + const char *pathname, + int flags, + unsigned int mask, + struct statx *statbuf); +int statx(int dirfd, + const char *pathname, + int flags, + unsigned int mask, + struct statx *statbuf); +int +statx(int dirfd, + const char *pathname, + int flags, + unsigned int mask, + struct statx *statbuf) +{ + int rc; + __libc_statx original_statx = NULL; + + original_statx = (__libc_statx)dlsym(RTLD_NEXT, "statx"); + rc = (*original_statx)(dirfd, pathname, flags, mask, statbuf); + statx_helper(pathname, statbuf); + + return rc; +} + +static int is_file_blocked(const char *pathname) +{ + if (pathname == NULL) { + return 0; + } + + static const char *blocked_files[] = { + /* Block for torture_gssapi_server_key_exchange_null */ + "/etc/ssh/ssh_host_ecdsa_key", + "/etc/ssh/ssh_host_rsa_key", + "/etc/ssh/ssh_host_ed25519_key", + }; + + for (size_t i = 0; i < sizeof(blocked_files) / sizeof(blocked_files[0]); + i++) { + if (strcmp(pathname, blocked_files[i]) == 0) { + errno = ENOENT; /* No such file or directory */ + return 1; + } + } + return 0; +} + +#define WRAP_FOPEN(func_name) \ + FILE *func_name(const char *pathname, const char *mode) \ + { \ + typedef FILE *(*orig_func_t)(const char *pathname, const char *mode); \ + static orig_func_t orig_func = NULL; \ + if (orig_func == NULL) { \ + orig_func = (orig_func_t)dlsym(RTLD_NEXT, #func_name); \ + } \ + if (is_file_blocked(pathname)) { \ + return NULL; \ + } \ + return orig_func(pathname, mode); \ + } + +WRAP_FOPEN(fopen) +WRAP_FOPEN(fopen64) diff --git a/src/libs/libssh-0.12.2/tests/fuzz/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/fuzz/CMakeLists.txt new file mode 100644 index 000000000000..8f2f97e6d1dd --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/CMakeLists.txt @@ -0,0 +1,40 @@ +project(fuzzing CXX) + +macro(fuzzer name) + add_executable(${name} ${name}.c) + target_link_libraries(${name} PRIVATE ${TORTURE_LINK_LIBRARIES}) + if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") + set_target_properties(${name} + PROPERTIES + COMPILE_FLAGS "-fsanitize=fuzzer" + LINK_FLAGS "-fsanitize=fuzzer") + # Run the fuzzer to make sure it works + add_test(${name} ${CMAKE_CURRENT_BINARY_DIR}/${name} -runs=1) + # Run the fuzzer with nalloc to make sure it works + add_test(${name}_nalloc ${CMAKE_CURRENT_BINARY_DIR}/${name} -runs=1) + set_property(TEST ${name}_nalloc PROPERTY ENVIRONMENT NALLOC_FREQ 32) + else() + target_sources(${name} PRIVATE fuzzer.c) + # Run the fuzzer to make sure it works + if (EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus") + file(GLOB files "${CMAKE_CURRENT_SOURCE_DIR}/${name}_corpus/*") + set(i 0) + foreach(file ${files}) + add_test(${name}_${i} + ${CMAKE_CURRENT_BINARY_DIR}/${name} ${file}) + math(EXPR i "${i} + 1") + endforeach() + endif() + endif() +endmacro() + +fuzzer(ssh_client_fuzzer) +fuzzer(ssh_client_config_fuzzer) +fuzzer(ssh_known_hosts_fuzzer) +fuzzer(ssh_privkey_fuzzer) +fuzzer(ssh_pubkey_fuzzer) +fuzzer(ssh_sshsig_fuzzer) +if (WITH_SERVER) + fuzzer(ssh_server_fuzzer) + fuzzer(ssh_bind_config_fuzzer) +endif (WITH_SERVER) diff --git a/src/libs/libssh-0.12.2/tests/fuzz/README.md b/src/libs/libssh-0.12.2/tests/fuzz/README.md new file mode 100644 index 000000000000..a8f9a1be68d8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/README.md @@ -0,0 +1,146 @@ +# Simple fuzzers for libssh + +This directory contains fuzzer programs, that are usable either in +oss-fuzz infrastructure or suitable for running fuzzing locally or +even for reproducing crashes with given trace files. + +When building with clang, fuzzers are automatically built with address +sanitizer. With gcc, they are built as they are without instrumentation, +but they are suitable for debugging. + +## Background + +### Turn off encryption + +Fuzzing ssh protocol is complicated by the way that all the communication +between client and server is encrypted and authenticated using keys based +on random data, making it impossible to fuzz the actual underlying protocol +as every change in the encrypted data causes integrity errors. For that reason, +libssh needs to implement "none" cipher and MAC as described in RFC 4253 +and these need to be used during fuzzing to be able to accomplish +reproducibility and for fuzzers to be able to progress behind key exchange. +This is enabled with the `WITH_INSECURE_NONE` CMake option. + +### Do not allow filesystem modification + +The OpenSSH configuration files are quite rich and expects users to know what +they do when they write their configuration files. The fuzzer driver is not an +average user so it is very happy to try whatever commands come to its "mind", +including `rm -rf /` and libssh would be very happy to run it by default. This +might remove some parts of the system that are mandatory for fuzzing. +To avoid executing dangerous commands like this, the `WITH_EXEC=OFF` CMake +option prevents invoking any external command through `exec()` syscall. + +## Corpus creation + +For effective fuzzing, we need to provide corpus of initial (valid) inputs that +can be used for deriving other inputs. libssh already supports creation of pcap +files (packet capture), which include all the information we need for fuzzing. +This file is also created from date before encryption and after decryption so +it is in plain text as we expect it, but we still need to adjust configuration +to use none cipher for the key exchange to be plausible. + +### Creating packet capture using example libssh client + + * Compile libssh with support for none cipher and pcap: + + cmake -DWITH_INSECURE_NONE=ON -DWITH_EXEC=OFF -DWITH_PCAP=ON ../ + + * Create a configuration file enabling none cipher and mac: + + printf 'Ciphers none\nMACs none' > /tmp/ssh_config + + * Generate test host key: + + ./examples/keygen2 -f /tmp/hostkey -t rsa + + * Run example libssh server: + + ./examples/samplesshd-cb -f /tmp/ssh_config -k /tmp/hostkey -p 22222 127.0.0.1 + + * In other terminal, run the example libssh client with pcap enabled (use mypassword for password): + + ./examples/ssh-client -F /tmp/ssh_config -l myuser -P /tmp/ssh.pcap -p 22222 127.0.0.1 + + * Kill the server (in the first terminal, press Ctrl+C) + + * Convert the pcap file to raw traces (separate client and server messages) usable by fuzzer: + + tshark -r /tmp/ssh.pcap -T fields -e data -Y "tcp.dstport==22222" | tr -d '\n',':' | xxd -r -ps > /tmp/ssh_server + tshark -r /tmp/ssh.pcap -T fields -e data -Y "tcp.dstport!=22222" | tr -d '\n',':' | xxd -r -ps > /tmp/ssh_client + + * Now we should be able to "replay" the sessions in respective fuzzers, getting some more coverage: + + LIBSSH_VERBOSITY=9 ./tests/fuzz/ssh_client_fuzzer /tmp/ssh_client + LIBSSH_VERBOSITY=9 ./tests/fuzz/ssh_server_fuzzer /tmp/ssh_server + + (note, that the client fuzzer fails now because of invalid hostkey signature; TODO) + + * Store the appropriately named traces in the fuzers directory: + + cp /tmp/ssh_client tests/fuzz/ssh_client_fuzzer_corpus/$(sha1sum /tmp/ssh_client | cut -d ' ' -f 1) + cp /tmp/ssh_server tests/fuzz/ssh_server_fuzzer_corpus/$(sha1sum /tmp/ssh_server | cut -d ' ' -f 1) + +## Debugging issues reported by oss-fuzz + +OSS Fuzz provides helper scripts to reproduce issues locally. Even though the +fuzzing scripts can ran anywhere, the best bet for reproducing is to use +their container infrastructure. There is a +[complete documentation](https://google.github.io/oss-fuzz/advanced-topics/reproducing/) +but I will try to focus here on the workflow I use and libssh specifics. + +### Environment + +The helper scripts are written in Python and use docker to run containers +so these needs to be installed. I am using podman instead of docker for +some time, but it has some quirks that needs to be addressed in advance +and that I describe in the rejected [PR](https://github.com/google/oss-fuzz/pull/4774). +You can either pick up my branch or workaround them locally: + + * Package `podman-docker` installs symlink from `/bin/docker` to `/bin/podman` + * The directories mounted to the containers need to have `container_file_t` + SELinux labels -- this is needed for the `build` directory that is created + under the oss-fuzz repository, for testcases and for source files + * `podman` does not like combination of `--privileged` and + `--cap-add SYS_PTRACE` flags. Podman can work with non-privileged containers + so you can just remove the `--privileged` from the `infra/helper.py` + +### Reproduce locally + +Clone the above repository from https://github.com/google/oss-fuzz/, apply +changes from previous section if needed, setup local clone of libssh repository +and build the fuzzers locally (where `~/devel/libssh` is path to local libssh +checkout): + + python infra/helper.py build_fuzzers libssh ~/devel/libssh/ + +Now, download the testcase from oss-fuzz.com (the file under `~/Downloads`) +and we are ready to reproduce the issue locally (replace the `ssh_client_fuzzer` +with the fuzzer name if the issue happens in other fuzzer): + + python infra/helper.py reproduce libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264 + +This should give you the same error/leak/crash as you see on the testcase +detail in oss-fuzz.com. + +I find it very useful to run libssh in debug mode, to see what happened and +what exit path was taken to get to the error. Fortunately, we can simply +pass environment variables to the container: + + python infra/helper.py reproduce -eLIBSSH_VERBOSITY=9 libssh ssh_client_fuzzer ~/Downloads/clusterfuzz-testcase-ssh_client_fuzzer-4637376441483264 + +### Fix the issue and verify the fix + +Now, we can properly investigate the issue and once we have a fix, we can +make changes in our local checkout and repeat the steps above (from building +fuzzers) to verify the issue is no longer present. + +### Fuzzing locally + +We can use the oss-fuzz tools even further and run the fuzzing process +locally, to verify there are no similar issues happening very close to +existing code paths and which would cause more reports very soon after +we would fix the current issue. The following command will run fuzzer +until it finds an issue or until killed: + + python infra/helper.py run_fuzzer libssh ssh_client_fuzzer diff --git a/src/libs/libssh-0.12.2/tests/fuzz/fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/fuzzer.c new file mode 100644 index 000000000000..bd7a9edbcd50 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/fuzzer.c @@ -0,0 +1,49 @@ +/* Simpler gnu89 version of StandaloneFuzzTargetMain.c from LLVM */ + +#include "config.h" + +#include +#include +#include +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + +int LLVMFuzzerTestOneInput (const unsigned char *data, size_t size); +__attribute__((weak)) int LLVMFuzzerInitialize(int *argc, char ***argv); + +int +main (int argc, char **argv) +{ + FILE *f = NULL; + size_t n_read, len; + unsigned char *buf = NULL; + + if (argc < 2) { + return 1; + } + + if (LLVMFuzzerInitialize) { + LLVMFuzzerInitialize(&argc, &argv); + } + + f = fopen (argv[1], "r"); + assert (f); + fseek (f, 0, SEEK_END); + len = ftell (f); + fseek (f, 0, SEEK_SET); + buf = (unsigned char*) malloc (len); + n_read = fread (buf, 1, len, f); + fclose (f); + assert (n_read == len); + LLVMFuzzerTestOneInput (buf, len); + + free (buf); + printf ("Done!\n"); + +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/nallocinc.c b/src/libs/libssh-0.12.2/tests/fuzz/nallocinc.c new file mode 100644 index 000000000000..6ce3c442beb1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/nallocinc.c @@ -0,0 +1,344 @@ +/* + MIT License + + Copyright (c) 2025 Catena cyber + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE. +*/ + +/* Nalloc fuzz : framework to make allocations and IO fail while fuzzing */ + +/* Environment variables to control nalloc fuzz behavior : + * NALLOC_VERBOSE: set it to log failed allocations with their stacktraces + * NALLOC_FREQ: set it to control how frequently allocations fail + * value 0 disables nalloc (no allocations fail) + * value 1..31 : allocations fail always (1) or very rarely (31 -> 1 / 2^31) + * value 32 : allocations fail at a random rate between 5 and 20 for each run + */ +#if defined(__clang__) && defined(__has_feature) +#if __has_feature(address_sanitizer) +#define NALLOC_ASAN 1 +#endif +#endif + +#include +#include +#include +#include +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +static const uint32_t nalloc_crc32_table[] = { + 0x00000000, 0x04c11db7, 0x09823b6e, 0x0d4326d9, 0x130476dc, 0x17c56b6b, + 0x1a864db2, 0x1e475005, 0x2608edb8, 0x22c9f00f, 0x2f8ad6d6, 0x2b4bcb61, + 0x350c9b64, 0x31cd86d3, 0x3c8ea00a, 0x384fbdbd, 0x4c11db70, 0x48d0c6c7, + 0x4593e01e, 0x4152fda9, 0x5f15adac, 0x5bd4b01b, 0x569796c2, 0x52568b75, + 0x6a1936c8, 0x6ed82b7f, 0x639b0da6, 0x675a1011, 0x791d4014, 0x7ddc5da3, + 0x709f7b7a, 0x745e66cd, 0x9823b6e0, 0x9ce2ab57, 0x91a18d8e, 0x95609039, + 0x8b27c03c, 0x8fe6dd8b, 0x82a5fb52, 0x8664e6e5, 0xbe2b5b58, 0xbaea46ef, + 0xb7a96036, 0xb3687d81, 0xad2f2d84, 0xa9ee3033, 0xa4ad16ea, 0xa06c0b5d, + 0xd4326d90, 0xd0f37027, 0xddb056fe, 0xd9714b49, 0xc7361b4c, 0xc3f706fb, + 0xceb42022, 0xca753d95, 0xf23a8028, 0xf6fb9d9f, 0xfbb8bb46, 0xff79a6f1, + 0xe13ef6f4, 0xe5ffeb43, 0xe8bccd9a, 0xec7dd02d, 0x34867077, 0x30476dc0, + 0x3d044b19, 0x39c556ae, 0x278206ab, 0x23431b1c, 0x2e003dc5, 0x2ac12072, + 0x128e9dcf, 0x164f8078, 0x1b0ca6a1, 0x1fcdbb16, 0x018aeb13, 0x054bf6a4, + 0x0808d07d, 0x0cc9cdca, 0x7897ab07, 0x7c56b6b0, 0x71159069, 0x75d48dde, + 0x6b93dddb, 0x6f52c06c, 0x6211e6b5, 0x66d0fb02, 0x5e9f46bf, 0x5a5e5b08, + 0x571d7dd1, 0x53dc6066, 0x4d9b3063, 0x495a2dd4, 0x44190b0d, 0x40d816ba, + 0xaca5c697, 0xa864db20, 0xa527fdf9, 0xa1e6e04e, 0xbfa1b04b, 0xbb60adfc, + 0xb6238b25, 0xb2e29692, 0x8aad2b2f, 0x8e6c3698, 0x832f1041, 0x87ee0df6, + 0x99a95df3, 0x9d684044, 0x902b669d, 0x94ea7b2a, 0xe0b41de7, 0xe4750050, + 0xe9362689, 0xedf73b3e, 0xf3b06b3b, 0xf771768c, 0xfa325055, 0xfef34de2, + 0xc6bcf05f, 0xc27dede8, 0xcf3ecb31, 0xcbffd686, 0xd5b88683, 0xd1799b34, + 0xdc3abded, 0xd8fba05a, 0x690ce0ee, 0x6dcdfd59, 0x608edb80, 0x644fc637, + 0x7a089632, 0x7ec98b85, 0x738aad5c, 0x774bb0eb, 0x4f040d56, 0x4bc510e1, + 0x46863638, 0x42472b8f, 0x5c007b8a, 0x58c1663d, 0x558240e4, 0x51435d53, + 0x251d3b9e, 0x21dc2629, 0x2c9f00f0, 0x285e1d47, 0x36194d42, 0x32d850f5, + 0x3f9b762c, 0x3b5a6b9b, 0x0315d626, 0x07d4cb91, 0x0a97ed48, 0x0e56f0ff, + 0x1011a0fa, 0x14d0bd4d, 0x19939b94, 0x1d528623, 0xf12f560e, 0xf5ee4bb9, + 0xf8ad6d60, 0xfc6c70d7, 0xe22b20d2, 0xe6ea3d65, 0xeba91bbc, 0xef68060b, + 0xd727bbb6, 0xd3e6a601, 0xdea580d8, 0xda649d6f, 0xc423cd6a, 0xc0e2d0dd, + 0xcda1f604, 0xc960ebb3, 0xbd3e8d7e, 0xb9ff90c9, 0xb4bcb610, 0xb07daba7, + 0xae3afba2, 0xaafbe615, 0xa7b8c0cc, 0xa379dd7b, 0x9b3660c6, 0x9ff77d71, + 0x92b45ba8, 0x9675461f, 0x8832161a, 0x8cf30bad, 0x81b02d74, 0x857130c3, + 0x5d8a9099, 0x594b8d2e, 0x5408abf7, 0x50c9b640, 0x4e8ee645, 0x4a4ffbf2, + 0x470cdd2b, 0x43cdc09c, 0x7b827d21, 0x7f436096, 0x7200464f, 0x76c15bf8, + 0x68860bfd, 0x6c47164a, 0x61043093, 0x65c52d24, 0x119b4be9, 0x155a565e, + 0x18197087, 0x1cd86d30, 0x029f3d35, 0x065e2082, 0x0b1d065b, 0x0fdc1bec, + 0x3793a651, 0x3352bbe6, 0x3e119d3f, 0x3ad08088, 0x2497d08d, 0x2056cd3a, + 0x2d15ebe3, 0x29d4f654, 0xc5a92679, 0xc1683bce, 0xcc2b1d17, 0xc8ea00a0, + 0xd6ad50a5, 0xd26c4d12, 0xdf2f6bcb, 0xdbee767c, 0xe3a1cbc1, 0xe760d676, + 0xea23f0af, 0xeee2ed18, 0xf0a5bd1d, 0xf464a0aa, 0xf9278673, 0xfde69bc4, + 0x89b8fd09, 0x8d79e0be, 0x803ac667, 0x84fbdbd0, 0x9abc8bd5, 0x9e7d9662, + 0x933eb0bb, 0x97ffad0c, 0xafb010b1, 0xab710d06, 0xa6322bdf, 0xa2f33668, + 0xbcb4666d, 0xb8757bda, 0xb5365d03, 0xb1f740b4}; + +// Nallocfuzz data to take a decision +uint32_t nalloc_random_state = 0; +__thread unsigned int nalloc_running = 0; +bool nalloc_initialized = false; +uint32_t nalloc_runs = 0; + +// Nalloc fuzz parameters +uint32_t nalloc_bitmask = 0xFF; +bool nalloc_random_bitmask = true; +uint32_t nalloc_magic = 0x294cee63; +bool nalloc_verbose = false; + +#ifdef NALLOC_ASAN +extern void __sanitizer_print_stack_trace(void); +#endif + +// Generic init, using env variables to get parameters +void nalloc_init(const char *prog) +{ + if (nalloc_initialized) { + return; + } + nalloc_initialized = true; + char *bitmask = getenv("NALLOC_FREQ"); + if (bitmask) { + int shift = atoi(bitmask); + if (shift > 0 && shift < 31) { + nalloc_bitmask = 1 << shift; + nalloc_random_bitmask = false; + } else if (shift == 0) { + nalloc_random_bitmask = false; + nalloc_bitmask = 0; + } + } else if (prog == NULL || strstr(prog, "nalloc") == NULL) { + nalloc_random_bitmask = false; + nalloc_bitmask = 0; + return; + } + + char *verbose = getenv("NALLOC_VERBOSE"); + if (verbose) { + nalloc_verbose = true; + } +} + +// add one byte to the CRC +static inline void nalloc_random_update(uint8_t b) +{ + nalloc_random_state = + ((uint32_t)((uint32_t)nalloc_random_state << 8)) ^ + nalloc_crc32_table[((nalloc_random_state >> 24) ^ b) & 0xFF]; +} + +// Start the failure injections, using a buffer as seed +static int nalloc_start(const uint8_t *data, size_t size) +{ + if (nalloc_random_bitmask) { + if (nalloc_random_state & 0x10) { + nalloc_bitmask = 0xFFFFFFFF; + } else { + nalloc_bitmask = 1 << (5 + (nalloc_random_state & 0xF)); + } + } else if (nalloc_bitmask == 0) { + // nalloc disabled + return 2; + } + nalloc_random_state = 0; + for (size_t i = 0; i < size; i++) { + nalloc_random_update(data[i]); + } + if (__sync_fetch_and_add(&nalloc_running, 1)) { + __sync_fetch_and_sub(&nalloc_running, 1); + return 0; + } + nalloc_runs++; + return 1; +} + +// Stop the failure injections +static void nalloc_end() +{ + __sync_fetch_and_sub(&nalloc_running, 1); +} + +static bool nalloc_backtrace_exclude(size_t size, const char *op) +{ + if (nalloc_verbose) { + fprintf(stderr, "failed %s(%zu) \n", op, size); +#ifdef NALLOC_ASAN + __sanitizer_print_stack_trace(); +#endif + } + + return false; +} + +// +static bool nalloc_fail(size_t size, const char *op) +{ + // do not fail before thread init + if (nalloc_runs == 0) { + return false; + } + if (__sync_fetch_and_add(&nalloc_running, 1) != 1) { + // do not fail allocations outside of fuzzer input + // and do not fail inside of this function + __sync_fetch_and_sub(&nalloc_running, 1); + return false; + } + nalloc_random_update((uint8_t)size); + if (size >= 0x100) { + nalloc_random_update((uint8_t)(size >> 8)); + if (size >= 0x10000) { + nalloc_random_update((uint8_t)(size >> 16)); + // bigger may already fail or oom + } + } + if (((nalloc_random_state ^ nalloc_magic) & nalloc_bitmask) == 0) { + if (nalloc_backtrace_exclude(size, op)) { + __sync_fetch_and_sub(&nalloc_running, 1); + return false; + } + __sync_fetch_and_sub(&nalloc_running, 1); + return true; + } + __sync_fetch_and_sub(&nalloc_running, 1); + return false; +} + +// ASAN interceptor for libc routines +#ifdef NALLOC_ASAN +extern void *__interceptor_malloc(size_t); +extern void *__interceptor_calloc(size_t, size_t); +extern void *__interceptor_realloc(void *, size_t); +extern void *__interceptor_reallocarray(void *, size_t, size_t); + +extern ssize_t __interceptor_read(int, void *, size_t); +extern ssize_t __interceptor_write(int, const void *, size_t); +extern ssize_t __interceptor_recv(int, void *, size_t, int); +extern ssize_t __interceptor_send(int, const void *, size_t, int); + +#define nalloc_malloc(s) __interceptor_malloc(s) +#define nalloc_calloc(s, n) __interceptor_calloc(s, n) +#define nalloc_realloc(p, s) __interceptor_realloc(p, s) +#define nalloc_reallocarray(p, s, n) __interceptor_reallocarray(p, s, n) + +#define nalloc_read(f, b, s) __interceptor_read(f, b, s) +#define nalloc_write(f, b, s) __interceptor_write(f, b, s) +#define nalloc_recv(f, b, s, x) __interceptor_recv(f, b, s, x) +#define nalloc_send(f, b, s, x) __interceptor_send(f, b, s, x) + +#else +extern void *__libc_malloc(size_t); +extern void *__libc_calloc(size_t, size_t); +extern void *__libc_realloc(void *, size_t); +extern void *__libc_reallocarray(void *, size_t, size_t); + +extern ssize_t __read(int, void *, size_t); +extern ssize_t __write(int, const void *, size_t); +extern ssize_t __recv(int, void *, size_t, int); +extern ssize_t __send(int, const void *, size_t, int); + +#define nalloc_malloc(s) __libc_malloc(s) +#define nalloc_calloc(s, n) __libc_calloc(s, n) +#define nalloc_realloc(p, s) __libc_realloc(p, s) +#define nalloc_reallocarray(p, s, n) __libc_reallocarray(p, s, n) + +#define nalloc_read(f, b, s) __read(f, b, s) +#define nalloc_write(f, b, s) __write(f, b, s) +#define nalloc_recv(f, b, s, x) __recv(f, b, s, x) +#define nalloc_send(f, b, s, x) __send(f, b, s, x) +#endif + +// nalloc standard function overwrites with pseudo-random failures +ssize_t read(int fd, void *buf, size_t count) +{ + if (nalloc_fail(count, "read")) { + errno = EIO; + return -1; + } + return nalloc_read(fd, buf, count); +} + +ssize_t write(int fd, const void *buf, size_t count) +{ + if (nalloc_fail(count, "write")) { + errno = EIO; + return -1; + } + return nalloc_write(fd, buf, count); +} + +ssize_t recv(int fd, void *buf, size_t count, int flags) +{ + if (nalloc_fail(count, "recv")) { + errno = EIO; + return -1; + } + return nalloc_recv(fd, buf, count, flags); +} + +ssize_t send(int fd, const void *buf, size_t count, int flags) +{ + if (nalloc_fail(count, "send")) { + errno = EIO; + return -1; + } + return nalloc_send(fd, buf, count, flags); +} + +void *calloc(size_t nmemb, size_t size) +{ + if (nalloc_fail(size, "calloc")) { + errno = ENOMEM; + return NULL; + } + return nalloc_calloc(nmemb, size); +} + +void *malloc(size_t size) +{ + if (nalloc_fail(size, "malloc")) { + errno = ENOMEM; + return NULL; + } + return nalloc_malloc(size); +} + +void *realloc(void *ptr, size_t size) +{ + if (nalloc_fail(size, "realloc")) { + errno = ENOMEM; + return NULL; + } + return nalloc_realloc(ptr, size); +} + +void *reallocarray(void *ptr, size_t nmemb, size_t size) +{ + if (nalloc_fail(size, "reallocarray")) { + errno = ENOMEM; + return NULL; + } + return nalloc_reallocarray(ptr, nmemb, size); +} + +#ifdef __cplusplus +} // extern "C" { +#endif diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_bind_config_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_bind_config_fuzzer.c new file mode 100644 index 000000000000..fb5e03ee1098 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_bind_config_fuzzer.c @@ -0,0 +1,75 @@ +/* + * Copyright 2021 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/server.h" +#include "libssh/bind_config.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_bind bind = NULL; + char *input = NULL; + + input = (char *)malloc(size + 1); + if (!input) { + return 1; + } + strncpy(input, (const char *)data, size); + input[size] = '\0'; + + assert(nalloc_start(data, size) > 0); + + bind = ssh_bind_new(); + if (bind == NULL) { + goto out; + } + + ssh_bind_config_parse_string(bind, input); + + ssh_bind_free(bind); + +out: + free(input); + + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer.c new file mode 100644 index 000000000000..a994f807e0f7 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer.c @@ -0,0 +1,78 @@ +/* + * Copyright 2021 Stanislav Zidek + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/options.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_session session = NULL; + char *input = NULL; + + input = (char *)malloc(size+1); + if (!input) { + return 1; + } + strncpy(input, (const char *)data, size); + input[size] = '\0'; + + assert(nalloc_start(data, size) > 0); + + session = ssh_new(); + if (session == NULL) { + goto out; + } + + /* Make sure we have default options set */ + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, NULL); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + + ssh_config_parse_string(session, input); + + ssh_free(session); + +out: + free(input); + + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop new file mode 100644 index 000000000000..f1b37a527a4c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/infinite_loop @@ -0,0 +1,5 @@ +Host ssh-host +Hostname 10.1.1.1 + +Host 10.1.1.* 10.1.20.* +ProxyJump ssh-host \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username new file mode 100644 index 000000000000..d766c61cdd1f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_config_fuzzer_corpus/wrong_username @@ -0,0 +1,7 @@ +Host jumpbox +User oliverw +Hostname jumpbox.example.org + +Host myserver.example.org +Hostname 1.2.3.4 +ProxyJump jumpbox \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer.c new file mode 100644 index 000000000000..4f70282c32f8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer.c @@ -0,0 +1,224 @@ +/* + * Copyright 2019 Andreas Schneider + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include +#include + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +static int auth_callback(const char *prompt, + char *buf, + size_t len, + int echo, + int verify, + void *userdata) +{ + (void)prompt; /* unused */ + (void)echo; /* unused */ + (void)verify; /* unused */ + (void)userdata; /* unused */ + + snprintf(buf, len, "secret"); + + return 0; +} + +struct ssh_callbacks_struct cb = { + .userdata = NULL, + .auth_function = auth_callback, +}; + +static void select_loop(ssh_session session, ssh_channel channel) +{ + ssh_connector connector_in, connector_out, connector_err; + + ssh_event event = ssh_event_new(); + + /* stdin */ + connector_in = ssh_connector_new(session); + ssh_connector_set_out_channel(connector_in, channel, SSH_CONNECTOR_STDINOUT); + ssh_connector_set_in_fd(connector_in, 0); + ssh_event_add_connector(event, connector_in); + + /* stdout */ + connector_out = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_out, 1); + ssh_connector_set_in_channel(connector_out, channel, SSH_CONNECTOR_STDINOUT); + ssh_event_add_connector(event, connector_out); + + /* stderr */ + connector_err = ssh_connector_new(session); + ssh_connector_set_out_fd(connector_err, 2); + ssh_connector_set_in_channel(connector_err, channel, SSH_CONNECTOR_STDERR); + ssh_event_add_connector(event, connector_err); + + while (ssh_channel_is_open(channel)) { + ssh_event_dopoll(event, 60000); + } + ssh_event_remove_connector(event, connector_in); + ssh_event_remove_connector(event, connector_out); + ssh_event_remove_connector(event, connector_err); + + ssh_connector_free(connector_in); + ssh_connector_free(connector_out); + ssh_connector_free(connector_err); + + ssh_event_free(event); +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_session session = NULL; + ssh_channel channel = NULL; + const char *env = NULL; + int socket_fds[2] = {-1, -1}; + ssize_t nwritten; + bool no = false; + int rc; + long timeout = 1; /* use short timeout to avoid timeouts during fuzzing */ + + /* This is the maximum that can be handled by the socket buffer before the + * other side will read some data. Other option would be feeding the socket + * from different thread which would not mind if it would be blocked, but I + * believe all the important inputs should fit into this size */ + if (size > 219264) { + return -1; + } + + /* Set up the socket to send data */ + rc = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); + assert(rc == 0); + + nwritten = send(socket_fds[1], data, size, 0); + assert((size_t)nwritten == size); + + rc = shutdown(socket_fds[1], SHUT_WR); + assert(rc == 0); + + assert(nalloc_start(data, size) > 0); + + session = ssh_new(); + if (session == NULL) { + goto out; + } + + env = getenv("LIBSSH_VERBOSITY"); + if (env != NULL && strlen(env) > 0) { + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY_STR, env); + } + rc = ssh_options_set(session, SSH_OPTIONS_FD, &socket_fds[0]); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.0.0.1"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_USER, "alice"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &no); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_options_set(session, SSH_OPTIONS_TIMEOUT, &timeout); + if (rc != SSH_OK) { + goto out; + } + + ssh_callbacks_init(&cb); + ssh_set_callbacks(session, &cb); + + rc = ssh_connect(session); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_userauth_none(session, NULL); + if (rc != SSH_OK) { + goto out; + } + + channel = ssh_channel_new(session); + if (channel == NULL) { + goto out; + } + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) { + goto out; + } + + rc = ssh_channel_request_exec(channel, "ls"); + if (rc != SSH_OK) { + goto out; + } + + select_loop(session, channel); + +out: + ssh_channel_free(channel); + ssh_disconnect(session); + ssh_free(session); + + close(socket_fds[0]); + close(socket_fds[1]); + + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer_corpus/0f9d75a6c1d365115772a502d42b6e48f453198a b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer_corpus/0f9d75a6c1d365115772a502d42b6e48f453198a new file mode 100644 index 000000000000..2667602969d6 Binary files /dev/null and b/src/libs/libssh-0.12.2/tests/fuzz/ssh_client_fuzzer_corpus/0f9d75a6c1d365115772a502d42b6e48f453198a differ diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer.c new file mode 100644 index 000000000000..839ae537acaf --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer.c @@ -0,0 +1,103 @@ +/* + * Copyright 2022 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "knownhosts.c" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + char *hostname = NULL; + const uint8_t *hostname_end = NULL; + size_t hostname_len = 0; + char filename[256]; + struct ssh_list *entries = NULL; + struct ssh_iterator *it = NULL; + FILE *fp = NULL; + + /* Interpret the first part of the string (until the first NULL byte) + * as a hostname we are searching for in the file */ + hostname_end = memchr(data, '\0', size); + if (hostname_end == NULL) { + return 1; + } + hostname_len = hostname_end - data + 1; + if (hostname_len > 253) { + /* This is the maximum valid length of a hostname */ + return 1; + } + hostname = malloc(hostname_len); + if (hostname == NULL) { + return 1; + } + memcpy(hostname, data, hostname_len); + + snprintf(filename, sizeof(filename), "/tmp/libfuzzer.%d", getpid()); + fp = fopen(filename, "wb"); + if (!fp) { + free(hostname); + return 1; + } + fwrite(data + hostname_len, size - hostname_len, 1, fp); + fclose(fp); + + assert(nalloc_start(data, size) > 0); + + ssh_known_hosts_read_entries(hostname, filename, &entries); + for (it = ssh_list_get_iterator(entries); + it != NULL; + it = ssh_list_get_iterator(entries)) { + struct ssh_knownhosts_entry *entry = NULL; + + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + ssh_knownhosts_entry_free(entry); + ssh_list_remove(entries, it); + } + ssh_list_free(entries); + + ssh_finalize(); + + free(hostname); + unlink(filename); + + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 b/src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 new file mode 100644 index 000000000000..18e779e09d32 Binary files /dev/null and b/src/libs/libssh-0.12.2/tests/fuzz/ssh_known_hosts_fuzzer_corpus/d7c0eade3f3b70d94b1a7090e09eb8607da0ace4 differ diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer.c new file mode 100644 index 000000000000..ff79103d5986 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer.c @@ -0,0 +1,72 @@ +/* + * Copyright 2023 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "config.h" + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/priv.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + uint8_t *input = NULL; + int rc; + + assert(nalloc_start(data, size) > 0); + + input = bin_to_base64(data, size); + if (input == NULL) { + goto out; + } + + rc = ssh_pki_import_privkey_base64((char *)input, NULL, NULL, NULL, &pkey); + free(input); + if (rc != SSH_OK) { + goto out; + } + ssh_key_free(pkey); + +out: + nalloc_end(); + return 0; +} + diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a b/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a new file mode 100644 index 000000000000..2759f43e9856 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_privkey_fuzzer_corpus/855ce609b52aec530bf631a78da7038bed99040a @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVgAAAKg+IRNSPiET +UgAAAAtzc2gtZWQyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVg +AAAED2zFg52qYItoZaSUnir4VKubTxJveL9D2oWK7Prg/O24ujq/HWVfplmh7TmVZOS7Cs +lRk3RPyER6vlQX0o+E5WAAAAHmpqZWxlbkB0NDcwcy5qamVsZW4ucmVkaGF0LmNvbQECAw +QFBgc= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer.c new file mode 100644 index 000000000000..bb96dcc33c34 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer.c @@ -0,0 +1,87 @@ +/* + * Copyright 2023 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +#include "config.h" + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" +#include "libssh/misc.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + char *filename = NULL; + int fd; + int rc; + ssize_t sz; + + filename = strdup("/tmp/libssh_pubkey_XXXXXX"); + if (filename == NULL) { + return -1; + } + fd = mkstemp(filename); + if (fd == -1) { + free(filename); + close(fd); + return -1; + } + sz = ssh_writen(fd, data, size); + close(fd); + if (sz == SSH_ERROR) { + unlink(filename); + free(filename); + return -1; + } + + assert(nalloc_start(data, size) > 0); + + rc = ssh_pki_import_pubkey_file(filename, &pkey); + if (rc != SSH_OK) { + goto out; + } + ssh_key_free(pkey); + +out: + unlink(filename); + free(filename); + nalloc_end(); + return 0; +} + diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 b/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 new file mode 100644 index 000000000000..accd5b65a629 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_pubkey_fuzzer_corpus/b2c9f01394a2835b2cd7c520395a4977143e8d23 @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIujq/HWVfplmh7TmVZOS7CslRk3RPyER6vlQX0o+E5W jjelen@t470s.jjelen.redhat.com diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer.c new file mode 100644 index 000000000000..e5504c7242f1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer.c @@ -0,0 +1,281 @@ +/* +# Copyright 2016 Google Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# +################################################################################ +*/ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include +#include +#include + +#include "nallocinc.c" + +static const char kRSAPrivateKeyPEM[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" + "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" + "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" + "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" + "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" + "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" + "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" + "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" + "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" + "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" + "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" + "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" + "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" + "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" + "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" + "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" + "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" + "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" + "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" + "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" + "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" + "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" + "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" + "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" + "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" + "-----END RSA PRIVATE KEY-----\n"; + +/* A userdata struct for session. */ +struct session_data_struct { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + size_t auth_attempts; + bool authenticated; +}; + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +static int auth_none(ssh_session session, const char *user, void *userdata) +{ + struct session_data_struct *sdata = + (struct session_data_struct *)userdata; + + (void)session; + (void)user; + + if (sdata->auth_attempts > 0) { + sdata->authenticated = true; + } + sdata->auth_attempts++; + + if (!sdata->authenticated) { + return SSH_AUTH_PARTIAL; + } + + return SSH_AUTH_SUCCESS; +} + +static ssh_channel channel_open(ssh_session session, void *userdata) +{ + struct session_data_struct *sdata = + (struct session_data_struct *)userdata; + + sdata->channel = ssh_channel_new(session); + + return sdata->channel; +} + +static int write_rsa_hostkey(const char *rsakey_path) +{ + FILE *fp = NULL; + size_t nwritten; + + fp = fopen(rsakey_path, "wb"); + if (fp == NULL) { + return -1; + } + + nwritten = fwrite(kRSAPrivateKeyPEM, 1, strlen(kRSAPrivateKeyPEM), fp); + fclose(fp); + + if (nwritten != strlen(kRSAPrivateKeyPEM)) { + return -1; + } + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + int socket_fds[2] = {-1, -1}; + ssize_t nwritten; + bool no = false; + const char *env = NULL; + int rc; + ssh_bind sshbind = NULL; + ssh_session session = NULL; + ssh_event event = NULL; + + /* Our struct holding information about the session. */ + struct session_data_struct sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = false, + }; + + struct ssh_server_callbacks_struct server_cb = { + .userdata = &sdata, + .auth_none_function = auth_none, + .channel_open_request_session_function = channel_open, + }; + + /* This is the maximum that can be handled by the socket buffer before the + * other side will read some data. Other option would be feeding the socket + * from different thread which would not mind if it would be blocked, but I + * believe all the important inputs should fit into this size */ + if (size > 219264) { + return -1; + } + + /* Write SSH RSA host key to disk */ + rc = write_rsa_hostkey("/tmp/libssh_fuzzer_private_key"); + assert(rc == 0); + + /* Set up the socket to send data */ + rc = socketpair(AF_UNIX, SOCK_STREAM, 0, socket_fds); + assert(rc == 0); + + nwritten = send(socket_fds[1], data, size, 0); + assert((size_t)nwritten == size); + + rc = shutdown(socket_fds[1], SHUT_WR); + assert(rc == 0); + + assert(nalloc_start(data, size) > 0); + + /* Set up the libssh server */ + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + goto out; + } + + session = ssh_new(); + if (session == NULL) { + goto out; + } + + env = getenv("LIBSSH_VERBOSITY"); + if (env != NULL && strlen(env) > 0) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, + env); + if (rc != SSH_OK) { + goto out; + } + } + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + "/tmp/libssh_fuzzer_private_key"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_C_S, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_CIPHERS_S_C, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_C_S, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_HMAC_S_C, "none"); + if (rc != SSH_OK) { + goto out; + } + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_PROCESS_CONFIG, &no); + if (rc != SSH_OK) { + goto out; + } + + ssh_set_auth_methods(session, SSH_AUTH_METHOD_NONE); + + ssh_callbacks_init(&server_cb); + ssh_set_server_callbacks(session, &server_cb); + + rc = ssh_bind_accept_fd(sshbind, session, socket_fds[0]); + if (rc != SSH_OK) { + goto out; + } + + event = ssh_event_new(); + if (event == NULL) { + goto out; + } + + if (ssh_handle_key_exchange(session) == SSH_OK) { + ssh_event_add_session(event, session); + + size_t n = 0; + while (sdata.authenticated == false || sdata.channel == NULL) { + if (sdata.auth_attempts >= 3 || n >= 100) { + break; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + break; + } + + n++; + } + } + +out: + nalloc_end(); + + ssh_event_free(event); + + close(socket_fds[0]); + close(socket_fds[1]); + + ssh_disconnect(session); + ssh_free(session); + ssh_bind_free(sshbind); + + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer_corpus/fd7bd24a85e712fb59159a512b69d34ca21c8383 b/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer_corpus/fd7bd24a85e712fb59159a512b69d34ca21c8383 new file mode 100644 index 000000000000..2667602969d6 Binary files /dev/null and b/src/libs/libssh-0.12.2/tests/fuzz/ssh_server_fuzzer_corpus/fd7bd24a85e712fb59159a512b69d34ca21c8383 differ diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer.c b/src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer.c new file mode 100644 index 000000000000..8493f2b53b6b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer.c @@ -0,0 +1,72 @@ +/* + * Copyright 2025 Jakub Jelen + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include +#include +#include +#include + +#define LIBSSH_STATIC 1 +#include "libssh/libssh.h" + +#include "nallocinc.c" + +static void _fuzz_finalize(void) +{ + ssh_finalize(); +} + +int LLVMFuzzerInitialize(int *argc, char ***argv) +{ + (void)argc; + + nalloc_init(*argv[0]); + + ssh_init(); + + atexit(_fuzz_finalize); + + return 0; +} + +int LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) +{ + ssh_key pkey = NULL; + const char input[] = "badc0de"; + const char namespace[] = "namespace"; + char *signature = NULL; + int rc; + + assert(nalloc_start(data, size) > 0); + + signature = (char *)malloc(size + 1); + if (signature == NULL) { + goto out; + } + strncpy(signature, (const char *)data, size); + signature[size] = '\0'; + + rc = sshsig_verify(input, sizeof(input), signature, namespace, &pkey); + free(signature); + if (rc != SSH_OK) { + goto out; + } + ssh_key_free(pkey); + +out: + nalloc_end(); + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 b/src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 new file mode 100644 index 000000000000..d9de5df5e6e4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/fuzz/ssh_sshsig_fuzzer_corups/5645ecda3771cd2737f0aff9b88eb26a36b10964 @@ -0,0 +1,14 @@ +-----BEGIN SSH SIGNATURE----- +U1NIU0lHAAAAAQAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBALP3yM/hsvPV41IV3mzatq +7NStESRGVw233KH29dxEgyfX0m3fkZQlDOovn6BFVdt8VnWp3bNgZJ+9rRopyWnSIDllPp +KMafoEZrSSxPzjYgCiUKkSt3jiTQR+gLfejTKieBsL+ehuFuvLj4A8FFUMFSHOhHOkcqYs ++wxPkvvoErwUCFVELe15D3Fzsjec7o+ag4WTOJelezoPS1o+P9iBeWnLyo3yDKXqpp6fc+ +gU2GULbkFOm9VbhGIV8rzOi5DMJ3bFRoeOpAyjJkUIcgPAOqrywJYjDKvPJOYEeAHiXk56 +g0f0NdtCOjzKmDZeky05PPyqJzjjw0f11xm94heu8AAAAJbmFtZXNwYWNlAAAAAAAAAAZz +aGE1MTIAAAEUAAAADHJzYS1zaGEyLTUxMgAAAQApuWdMEHGcQgCagN8Tgcs72DEuLMBp/v +DXbjHbSyGRrcWcusZEvLClWkEJaouuvf7Vpqs1SaJvwW9nIcK0Md9UgZMXFOFMbKGg8LzC +YKp7O6Qud7skUgWclP4qyQrFWhYOfuijNY2rWajy+F42DI28j84CYx9bvHHWtqCEGihKdn +KLJltw/D7T3GnoKOeknOUl1Kr4Ca3G+qxSLxNsu0sa6TtP7ZnH+75tSlHunhVhOKHKf/f4 +YpjMCjuPIOolMbFm+UFojZcGMVvyZKelV2m4dPQ7OMpGcl7KTRMAbzm7yfsQeHSc132pnn +OwfsIiy75wDBtvudMSFOYftG1EeEzN +-----END SSH SIGNATURE----- diff --git a/src/libs/libssh-0.12.2/tests/generate.py b/src/libs/libssh-0.12.2/tests/generate.py new file mode 100755 index 000000000000..08c2d5b1a8b7 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/generate.py @@ -0,0 +1,10 @@ +#!/usr/bin/python +import os +a="" +for i in xrange(4096): + a+=chr(i % 256); +while True: + try: + os.write(1,a) + except: + exit(0) diff --git a/src/libs/libssh-0.12.2/tests/gss/kdcsetup.sh b/src/libs/libssh-0.12.2/tests/gss/kdcsetup.sh new file mode 100755 index 000000000000..c5e38bc883c2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/gss/kdcsetup.sh @@ -0,0 +1,53 @@ +#!/bin/sh + +SOCKDIR=$1 +WORKDIR=$SOCKDIR/gss + +mkdir "$WORKDIR"/k "$WORKDIR"/d + +cat< "$WORKDIR"/k/kdc.conf +[realms] + LIBSSH.SITE = { + database_name = $WORKDIR/principal + key_stash_file = $WORKDIR/stash + kdc_listen = $(hostname -f) + kdc_tcp_listen = $(hostname -f) + default_principal_flags = +preauth,+forwardable + } +[logging] + kdc = FILE:$WORKDIR/kdc.log + debug = true +EOF + +cat< "$WORKDIR"/k/krb5.conf +[libdefaults] + default_realm = LIBSSH.SITE + forwardable = true + +[realms] + LIBSSH.SITE = { + kdc = $(hostname -f) + } +[domain_realm] + .$(hostname -d) = LIBSSH.SITE + +EOF + +kdb5_util -P foo create -s + +bash "$WORKDIR"/kadmin.sh + +krb5kdc -w 1 -P "$WORKDIR"/pid + +# Wait till KDC binds to the ports, 0x58 is port 88 +i=0 +while [ ! -S "$SOCKDIR"/T0B0058 ] && [ ! -S "$SOCKDIR"/U0B0058 ]; do + i=$((i + 1)) + [ "$i" -eq 5 ] && exit 1 + sleep 1 +done + +bash "$WORKDIR"/kinit.sh + +klist +exit 0 diff --git a/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa new file mode 100644 index 000000000000..a0b679c209bc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdzc2gtcn +NhAAAAAwEAAQAAAQEA0DehrU/ohoimMKojFXdo1uEAcqx4fS87AjDUz8t4s436ppP+0+U3 ++qrhOCE/mXZvXewjTHltmEtCHNSbsWhYTjwrEQUDRNOVahn2PEQTcX/9itvsv9PX79Imbv +ZsLR0f1FsmorkWpnDQmuga7hYBFBj4sV+VML5ieoK2OraUEq46ILsxqRgUTetkErlzX7S2 +SPE7vNM0ahmA+HNBuKNUD+BOtCzkqN54flGA9TZ7kapC7xqiRHK+ZzahQ2PFR4BxbVP1uT +DsanbjKOpBC4hISao3hi4iUnyj0gJ8itmkhQS+oI/2KWSGW01/k9W7jOUXDSt7LGUTSW6s +ILYHzmefCwAAA9B/6IFvf+iBbwAAAAdzc2gtcnNhAAABAQDQN6GtT+iGiKYwqiMVd2jW4Q +ByrHh9LzsCMNTPy3izjfqmk/7T5Tf6quE4IT+Zdm9d7CNMeW2YS0Ic1JuxaFhOPCsRBQNE +05VqGfY8RBNxf/2K2+y/09fv0iZu9mwtHR/UWyaiuRamcNCa6BruFgEUGPixX5UwvmJ6gr +Y6tpQSrjoguzGpGBRN62QSuXNftLZI8Tu80zRqGYD4c0G4o1QP4E60LOSo3nh+UYD1NnuR +qkLvGqJEcr5nNqFDY8VHgHFtU/W5MOxqduMo6kELiEhJqjeGLiJSfKPSAnyK2aSFBL6gj/ +YpZIZbTX+T1buM5RcNK3ssZRNJbqwgtgfOZ58LAAAAAwEAAQAAAQEAxjzxFU0LGWtortSN +apaxnkPCZWuHm8gn6kILm3shg/IdPhORfrSxw1qF6ybcooN8LHPyd5D0oxaj70cMpK+vw2 +zNo/qdzh2UF9x375Dw4hL1lgslMM3EvXPbW7IJ9DnSYCAYfLyzr+ug8JsjaKJSjIvp2xYh +uLLKl9FzJhtGhzDaJr9FCbSmd5R7Telz4En0Lwo/VxYvyzCoRwzhVUVqJZpdtF7/1du4tT +agfPzPYY9zM9muR7AawtzMc4UFvMzl1OtjOHYtqSMVBZx44fRpXT3/fy7A98+7erd0zTWj +s6gaz6I8VmRPk4iTdBH4KBzC8dGZQNMrY9SQ/ZANet8eYQAAAIBI8hS6bX00NpNXwOaEqr +jZKf/u1W71KHpYBAY1w3xanGqPVOX5PEsFH6NLjqSLF75Bk22pvJvZ8EAaoISyvLSqqO5t +1vvjCjVKALSaVIFDcA20NpCXRugmVT1HeQNKHCTt3yOoraL8Sh9wlRbxLmlxgISYS6uH6e +dEPa6qFshMVwAAAIEA7RFx7+mZJfrSJUu9pYiZJc6+Ns2WrSA2mgI+mIhWqreDK4Kw0a5g +akqD0mb9oPHySnf3lCe+17yqNxH2fcX0G3B5LxiRnFVNm2wC4ZGvb+yMU2+0uSI/Sf8L1N +sfWm+z4VC93Qhe0fIpuk8JAMNOwCvFcEFu5rr9sxHtPWjWtj0AAACBAODYXjs6jqsvW+3P +e0efFp9kIezi8CejSxVXX0/zmWMCpTw1laiUmK41coKTKBSDZcNgrsF/ns1uCrPg6C2u/y +evF8J+DeqU3vo1QhnRAJA2fLZk1Dr/GfAsp9mS9w6FdIQiQjQ0f/X9rYgAr9x4qMogYgrG +zkb7k6FUoGgD7sbnAAAAE2xpYnNzaF90b3J0dXJlX2F1dGgBAgMEBQYH +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa-cert.pub b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa-cert.pub new file mode 100644 index 000000000000..ceb440399915 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa-cert.pub @@ -0,0 +1 @@ +ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgllK1Wz9hM1kUks5QXU/vXbDmzpQWMtFWObMvi9ymg38AAAADAQABAAABAQDQN6GtT+iGiKYwqiMVd2jW4QByrHh9LzsCMNTPy3izjfqmk/7T5Tf6quE4IT+Zdm9d7CNMeW2YS0Ic1JuxaFhOPCsRBQNE05VqGfY8RBNxf/2K2+y/09fv0iZu9mwtHR/UWyaiuRamcNCa6BruFgEUGPixX5UwvmJ6grY6tpQSrjoguzGpGBRN62QSuXNftLZI8Tu80zRqGYD4c0G4o1QP4E60LOSo3nh+UYD1NnuRqkLvGqJEcr5nNqFDY8VHgHFtU/W5MOxqduMo6kELiEhJqjeGLiJSfKPSAnyK2aSFBL6gj/YpZIZbTX+T1buM5RcNK3ssZRNJbqwgtgfOZ58LAAAAAAAAAAAAAAABAAAAE3RvcnR1cmVfYXV0aF9jYXJsb3MAAAAJAAAABWFsaWNlAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTEtZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAFnBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnBlcm1pdC11c2VyLXJjAAAAAAAAAAAAAAEXAAAAB3NzaC1yc2EAAAADAQABAAABAQCnA2n5vHzZbs/GvRkGloJNV1CXHIS5Xnrm05HusUJSWyPq3I1iCMHdYA7oezHa9GCFYbIenaYPy+G6USQRjYQz8SvAZo06SFNeJSsa1kAIqxzdPT9kBrRrYK39PZQPsYVfRPqZBdmc+jwrfz97IFEJyXMI47FoTGkgEq7eu3z2px/tdIZ34I5Hr5DDBxicZi4jluyRUJHfSPoBxyhF7OkPX4bYkrc691jeIQDxubl650WYLHgFfad0xTzBIFE6XUb55Dp5AgRdevSoso1Pe0IKFxxMVpP664LCbYK06Lv6kcotfFlpvUtR1yx8jToGcSoq5sSzTwvXSHCQQ9ZA1hvFAAABFAAAAAxyc2Etc2hhMi01MTIAAAEAeI9eUAWyL5DVWBj0vU2kKdGxMXQx1Y8R68DXwXhnxfLwilJPa6IFg+g988lpF5aZzvAiX6TgDtJAhzfuBU+ZREGfdclUQIpz3xwDG76Gmg/DpQHdmqU76n2Na32s6+4SsSmWWKx6cPPdjbCRS0VMSrMohLuDyPGMoC7RjLfDDxqW5TIbMtqQdOiPl/0PpR73Q0FjB50Ec12buQDkExlEOi2Y+yB830vuTJN3ds7bx6NXM1Jjftg/8D0SzNRAIYDQFnpyXKO6kNrN66o48E3mrnVHXuFBTf+kpdYrK+1LKQVk/hVLBHr+NuVmQltL0zcjJfiXj7i5ZqcsqR1UAU5hKg== libssh_torture_auth diff --git a/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa.pub b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa.pub new file mode 100644 index 000000000000..3f83c6fcc0e1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/certauth/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDQN6GtT+iGiKYwqiMVd2jW4QByrHh9LzsCMNTPy3izjfqmk/7T5Tf6quE4IT+Zdm9d7CNMeW2YS0Ic1JuxaFhOPCsRBQNE05VqGfY8RBNxf/2K2+y/09fv0iZu9mwtHR/UWyaiuRamcNCa6BruFgEUGPixX5UwvmJ6grY6tpQSrjoguzGpGBRN62QSuXNftLZI8Tu80zRqGYD4c0G4o1QP4E60LOSo3nh+UYD1NnuRqkLvGqJEcr5nNqFDY8VHgHFtU/W5MOxqduMo6kELiEhJqjeGLiJSfKPSAnyK2aSFBL6gj/YpZIZbTX+T1buM5RcNK3ssZRNJbqwgtgfOZ58L libssh_torture_auth diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ecdsa b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa new file mode 100644 index 000000000000..7a1827c699d7 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHbL0nzpzLS3ImIlhEffbDzPlIw/tn5QcfB64PbSiBl6oAoGCCqGSM49 +AwEHoUQDQgAERzA8X8OP7C3W/e1UNLh+21xIZVBiQ7i4Qb4xoOebRWuwzitEZon/ +8Dz+VpE29krJgCagqSt5RLllOx8eS2i8fw== +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ecdsa.pub b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa.pub new file mode 100644 index 000000000000..43b613bd8f01 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBEcwPF/Dj+wt1v3tVDS4fttcSGVQYkO4uEG+MaDnm0VrsM4rRGaJ//A8/laRNvZKyYAmoKkreUS5ZTsfHktovH8= comment diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk new file mode 100644 index 000000000000..4c35105ab87f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk @@ -0,0 +1,14 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAjwAAACJzay1lY2 +RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQRv1/dD0qNb +Bbm4JmHwa9AQdHwzYOBDkptAAUJcyLX3kc8koKLoQF6rhUKGeZP6pv+AanVRTyOd/ITGUm +Zbgt7hAAAAFHNzaDp0ZXN0QGV4YW1wbGUuY29tAAABkH7S+n5+0vp+AAAAInNrLWVjZHNh +LXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBG/X90PSo1sFub +gmYfBr0BB0fDNg4EOSm0ABQlzItfeRzySgouhAXquFQoZ5k/qm/4BqdVFPI538hMZSZluC +3uEAAAAUc3NoOnRlc3RAZXhhbXBsZS5jb20BAAAA4y0tLS0tQkVHSU4gRUMgUFJJVkFURS +BLRVktLS0tLQpNSGNDQVFFRUlIRGZSL1NqWkRlczZrUmtTM0dLQTZoTUtSYmxRQjFWQlp3 +KzdqR2pIWU5xb0FvR0NDcUdTTTQ5CkF3RUhvVVFEUWdBRWI5ZjNROUtqV3dXNXVDWmg4R3 +ZRRUhSOE0yRGdRNUtiUUFGQ1hNaTE5NUhQSktDaTZFQmUKcTRWQ2hubVQrcWIvZ0dwMVVV +OGpuZnlFeGxKbVc0TGU0UT09Ci0tLS0tRU5EIEVDIFBSSVZBVEUgS0VZLS0tLS0KAAAAAA +AAAAABAgMEBQYHCAk= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk.pub b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk.pub new file mode 100644 index 000000000000..fbb35ffd6b79 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ecdsa_sk.pub @@ -0,0 +1 @@ +sk-ecdsa-sha2-nistp256@openssh.com AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBG/X90PSo1sFubgmYfBr0BB0fDNg4EOSm0ABQlzItfeRzySgouhAXquFQoZ5k/qm/4BqdVFPI538hMZSZluC3uEAAAAUc3NoOnRlc3RAZXhhbXBsZS5jb20= phoenix@phoenix-pc diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ed25519 b/src/libs/libssh-0.12.2/tests/keys/id_ed25519 new file mode 100644 index 000000000000..2759f43e9856 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ed25519 @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW +QyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVgAAAKg+IRNSPiET +UgAAAAtzc2gtZWQyNTUxOQAAACCLo6vx1lX6ZZoe05lWTkuwrJUZN0T8hEer5UF9KPhOVg +AAAED2zFg52qYItoZaSUnir4VKubTxJveL9D2oWK7Prg/O24ujq/HWVfplmh7TmVZOS7Cs +lRk3RPyER6vlQX0o+E5WAAAAHmpqZWxlbkB0NDcwcy5qamVsZW4ucmVkaGF0LmNvbQECAw +QFBgc= +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ed25519.pub b/src/libs/libssh-0.12.2/tests/keys/id_ed25519.pub new file mode 100644 index 000000000000..accd5b65a629 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ed25519.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIIujq/HWVfplmh7TmVZOS7CslRk3RPyER6vlQX0o+E5W jjelen@t470s.jjelen.redhat.com diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk b/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk new file mode 100644 index 000000000000..1622d28a7024 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk @@ -0,0 +1,8 @@ +-----BEGIN OPENSSH PRIVATE KEY----- +b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAWgAAABpzay1zc2 +gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACDa9bna/CE9hXZDMX6I37Re6AlzNVZh0yB8D/U3 +8SS2vgAAABRzc2g6dGVzdEBleGFtcGxlLmNvbQAAALC75D22u+Q9tgAAABpzay1zc2gtZW +QyNTUxOUBvcGVuc3NoLmNvbQAAACDa9bna/CE9hXZDMX6I37Re6AlzNVZh0yB8D/U38SS2 +vgAAABRzc2g6dGVzdEBleGFtcGxlLmNvbQEAAABA7QoCSXA/S9yF96YpCLNTVap+mYg0vH +yhKlMAUNnPqeXa9bna/CE9hXZDMX6I37Re6AlzNVZh0yB8D/U38SS2vgAAAAAAAAAAAQ== +-----END OPENSSH PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk.pub b/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk.pub new file mode 100644 index 000000000000..e31326e8389b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_ed25519_sk.pub @@ -0,0 +1 @@ +sk-ssh-ed25519@openssh.com AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAINr1udr8IT2FdkMxfojftF7oCXM1VmHTIHwP9TfxJLa+AAAAFHNzaDp0ZXN0QGV4YW1wbGUuY29t phoenix@phoenix-pc diff --git a/src/libs/libssh-0.12.2/tests/keys/id_rsa b/src/libs/libssh-0.12.2/tests/keys/id_rsa new file mode 100644 index 000000000000..0e3db26b47ef --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEogIBAAKCAQEAs/fIz+Gy89XjUhXebNq2rs1K0RJEZXDbfcofb13ESDJ9fSbd ++RlCUM6i+foEVV23xWdands2Bkn72tGinJadIgOWU+koxp+gRmtJLE/ONiAKJQqR +K3eOJNBH6At96NMqJ4Gwv56G4W68uPgDwUVQwVIc6Ec6Rypiz7DE+S++gSvBQIVU +Qt7XkPcXOyN5zuj5qDhZM4l6V7Og9LWj4/2IF5acvKjfIMpeqmnp9z6BTYZQtuQU +6b1VuEYhXyvM6LkMwndsVGh46kDKMmRQhyA8A6qvLAliMMq88k5gR4AeJeTnqDR/ +Q120I6PMqYNl6TLTk8/KonOOPDR/XXGb3iF67wIDAQABAoIBAAVoL2dXf5nl1jOU +Jp+cnpp33oSTiOyHTIDl/rXI2mnU4oJNFaQzRxPIcYsTIOgzrZ7HsShG+sOLm36C +h+EugUARXYXd3nTBPP6AoK0tJKPpqIReYegtal7exxpIphrFpWGUeuv25lSFkDP6 +d5pp67gzMF0mLrEOq/NTe0eFULLuwa6+IKXU7deiU90pzi4jrjcIWNoGHSw1YYAZ +TC8KAxA/tYH9myya5krRCjA9B345DJ9Wd71wX+RZNgbSkIri/6dDTtvsYvqcQKo0 +OZ3MUDJnKmkfPLP84qZPRoEwUI1gts1WUdoNK6LK7yOJmPL5FMyTwZx3XtDw3gAv +TVhI7ikCgYEA5Ay0TCySPQAaC14WtjgIAmTa19mAtOFpbRxToi40WjXk3R6mMqyp +biAcNecdZRC6zzgAUp8g1O3Yc1d9fG/3FpM5eUbIer7mMLTRuQQysoJY2Ayw9OEA +qPHS/K6LPOD09aZo14fRUqVO8rwMbHtq2yhH8p3FM8WZRe5ms8zpyLUCgYEAygZ3 +RTMWbgcGdNoaPa5Ms9KRqAxKJLin2fE99KowZeJfvZN24sXExawQdy4BKVYT0H6e +MNEIPiEBVA4a1GDk/tyOrEt684IsidROngJaGbqb+SYm6feQAioYu0wkG/I2hS12 +/Z/aK6wFz5hWzBv/YvJqC7xD1YwZm1QXDyAiL5MCgYAXz8fHqGPAoNEXXMSsVB9p ++JPtM9W/jUXP0cRdy8tFnBkAiaG66tJqIEoxyqcEFYIb/vHxrpHkCc2vBXSh2KMJ +JWg75IssXeB1N3wqgGi2wOt7659SgmfqPA3WunbpbWfGepC56IGPypj6uW3mqeBX +b9ZLW/PqWviNF757iarjfQKBgGxKBPqRxM8bcumF0xUG7dRh5XN3ivKeDFL1Tels +pF6odftPJSWvLqdqcLUBctvuaNaUWEUAdvOei3C70sPOYFEAdnWCTBhkyWzj4XQu +/I7YCS0Gt0soSQfv+qvCx4Q3U+QVF7ghTDemkMLS/IuR4lXubMt3kcDQxRUOgQG5 +jrmDAoGALauF7ZyzEnQgsgMVzfm9znl5I2aIsLgdsAv3lINVrvtTKhddp7cdd+2j +dwZlaMnLET/3MY/Cvf13vEsS+bdNXjsdQidqBL8pe5PXY/pafBhtduQuvGzlHJA5 +CEBnwB0SdtsXbzSpOAPZqea4Nz9MkQ8LMsINdPpxCuFhjeYa9Ow= +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_rsa.pub b/src/libs/libssh-0.12.2/tests/keys/id_rsa.pub new file mode 100644 index 000000000000..15a35b3ad11f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_rsa.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz98jP4bLz1eNSFd5s2rauzUrREkRlcNt9yh9vXcRIMn19Jt35GUJQzqL5+gRVXbfFZ1qd2zYGSfva0aKclp0iA5ZT6SjGn6BGa0ksT842IAolCpErd44k0EfoC33o0yongbC/nobhbry4+APBRVDBUhzoRzpHKmLPsMT5L76BK8FAhVRC3teQ9xc7I3nO6PmoOFkziXpXs6D0taPj/YgXlpy8qN8gyl6qaen3PoFNhlC25BTpvVW4RiFfK8zouQzCd2xUaHjqQMoyZFCHIDwDqq8sCWIwyrzyTmBHgB4l5OeoNH9DXbQjo8ypg2XpMtOTz8qic448NH9dcZveIXrv asn@krikkit.cryptomilk.site diff --git a/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected b/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected new file mode 100644 index 000000000000..034cb2875289 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected @@ -0,0 +1,30 @@ +-----BEGIN RSA PRIVATE KEY----- +Proc-Type: 4,ENCRYPTED +DEK-Info: AES-128-CBC,0B181CC88F75C33B7DEBE5C18B481F18 + +rYtUw8FhEv48JmNTm3i1TEqEgElC629iaMQu/YzRV5zL/n83HwMKbRpAZ31Cch2a +8thZRQ6YsL/56vr+fKKVgDF7y3wmStp5sVkOQXMeZ9D746ZEGcYGnYH8JQhibDDB +sTK2kQrmBERg7H8rOoNCzxxoK+VZl2Z+S+yLVq0//qxBfuluZwCdk9Tie69Cd/Dk +PeBjOVPnFCavCKCWpUs/So8VQq9jXG84hRltwC0htSTEq+xfgNtS64f63WL6gEnB +WZ5KSO1gyzKC5/YAB6LXPxIIVzfZYXiuOWV/t8DXZK/lvhqQ3gSyPZezSrX8wEMC +xQeX41etQGjCcgxWH41iPCNTuoIKo2t/BPlfLJilJotmUSnYOxDmkZbLabuyS+0p +WGtnEwFSrxQosx6u9GBHX94Ikex0bf00KzNpKExzAIRqTdesaviJ1QX/pRsvT/Xp +TtH2aWV5kYNc+B+BrCQU7mlx/eEtXR2H5zJQxLSrTVKb1vUIHytufnPePk2BkcQ2 +CTE1xT+ZkUaY1WiCBxWgVTflL5FY9E6BerKEGVSfloso8tGCgsoO/Fch0Ho5/bXp +T+3nQEY780KduKJ8xCJJDQgD8GbjNR6sCtcPrewqEsgrpAbJUKyXhU7klGC09zzI +/JnNmdd10w2l/5A92GGrCgXnTYb8/w9J/qa6qyAAYU9/8rPo7ErGb7mKclmzz63j +cksImoExfrr9CIr7wjrXFO0OoupmMegNOZtgwsN7i0FI8vWYc6a3IaFWSWfE29Ux +rw9TK9L9pDvhCqS/WjW86S25muqnTSMQ/bhmiPw8z8tOjdi2YRqNcU2TyWoB2Mct +W+w9G5dSukMwkXQ2RNjDo2GfuXLXpUe5zCVixI2wxYGvIqTGkDZn/u1Jdxy1IxNc +qEsEZAOCVnJU1cQpB9ENsyrRUIsdQVWNQSvsUZz2XSELULwIFTcCTHr2PAJ5xzZ6 +VQy3DGEpZf7+yGACoi8LY8f5Ve5C9NciyA4/C/uvOUd7PhAf4g41mKw8+bAr8NFt +ubeXTo0iI29FkmmebfM1sRBHvomGT7qYsHBW2pgqBrm3X9kFcQ9EFhr6S2ULMcIn +4iX1mbqvC0c1CUmZakkNg94FQp2zbUclAuDkg3BTA0gwbyudvx0ccBmzQ43/6AJ5 +xz1hrfusX5Vcjz6+i5WHJDK/mlUDwTV5GAhcmar9eEcFXJEosD+mrAalflz3Vc2X +5A9plGfKkaFdth8YUGjLr+O2O5ggkDpCMbjYo4HQ6/dslYvqvnavJYrRKrEZbtvj +8fR5E11tPrK1aKzPHO0VLKf4UHs57JNqicSlYGy78FSCPG4d17KQlFyzbXsfbsvp +9EQK4N2jwRNZAOHuTuoqQ8TNzDahdlmbBS2Akd3rVV9H1/eNeN3r6Demww+yixoy +uPhjofn0P28eH7Gqiyhh20QYYqG7aky9IYMPnIBtA1hJp9MtMa1m8aHGxxZrUigj +S62Q34JzA8A6Rwc2kTHRzXG2o6oQ3vCQfy0JGlmDlG2yofcn7YgrMCv+srTniuiA +YBnOeic5cllYnDB9bpF2kufJT6CigoxP18HIw+jhYabuOTHO67MYf2En+is8vlQS +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected.pub b/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected.pub new file mode 100644 index 000000000000..15a35b3ad11f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/id_rsa_protected.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCz98jP4bLz1eNSFd5s2rauzUrREkRlcNt9yh9vXcRIMn19Jt35GUJQzqL5+gRVXbfFZ1qd2zYGSfva0aKclp0iA5ZT6SjGn6BGa0ksT842IAolCpErd44k0EfoC33o0yongbC/nobhbry4+APBRVDBUhzoRzpHKmLPsMT5L76BK8FAhVRC3teQ9xc7I3nO6PmoOFkziXpXs6D0taPj/YgXlpy8qN8gyl6qaen3PoFNhlC25BTpvVW4RiFfK8zouQzCd2xUaHjqQMoyZFCHIDwDqq8sCWIwyrzyTmBHgB4l5OeoNH9DXbQjo8ypg2XpMtOTz8qic448NH9dcZveIXrv asn@krikkit.cryptomilk.site diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256 b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256 new file mode 100644 index 000000000000..3ba5d0f783bf --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256 @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIHdAXnAQz7Xy1DSC74tj4oPqcMFNld9f6sw/mnalVEjloAoGCCqGSM49 +AwEHoUQDQgAE5a5++ALfsz8CYb2pAzlWlj6ookcas3UmHaOsictgRgl7Nqdd9vTq +QPQSbF4oRMhSbfTlqO924OJwzc1WaYKnFw== +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub new file mode 100644 index 000000000000..aa1d415e93fa --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256.pub @@ -0,0 +1,4 @@ +-----BEGIN PUBLIC KEY----- +MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE5a5++ALfsz8CYb2pAzlWlj6ookca +s3UmHaOsictgRgl7Nqdd9vTqQPQSbF4oRMhSbfTlqO924OJwzc1WaYKnFw== +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub new file mode 100644 index 000000000000..5ae82b88c0e4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_256_openssh.pub @@ -0,0 +1,2 @@ +#ecdsa public key in openssh format for authorized_keys +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBOWufvgC37M/AmG9qQM5VpY+qKJHGrN1Jh2jrInLYEYJezanXfb06kD0EmxeKETIUm305ajvduDicM3NVmmCpxc= sprasad@linux.fritz.box diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384 b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384 new file mode 100644 index 000000000000..672e59c77953 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384 @@ -0,0 +1,6 @@ +-----BEGIN EC PRIVATE KEY----- +MIGkAgEBBDCM82jhy7V0k9pYkhGeqk8xYcH72RgLnLmhY/9nuq5+9+e6bZr6sdR+ +ZYknMIZB4rmgBwYFK4EEACKhZANiAAT/c5JrFJK9xmqVZpuDWcOULHYwnmdjBfa9 +7W17gxC5m6armE67TQGD/3KwI7k7+3ngqGsYuA9UWSiyZxuhIa0FMegEN+hGylp6 +H/LokPiBQX7FMImJEHRMTr7ti9OJxYU= +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub new file mode 100644 index 000000000000..d40891238077 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384.pub @@ -0,0 +1,5 @@ +-----BEGIN PUBLIC KEY----- +MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAE/3OSaxSSvcZqlWabg1nDlCx2MJ5nYwX2 +ve1te4MQuZumq5hOu00Bg/9ysCO5O/t54KhrGLgPVFkosmcboSGtBTHoBDfoRspa +eh/y6JD4gUF+xTCJiRB0TE6+7YvTicWF +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub new file mode 100644 index 000000000000..225ff3fcd3e5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_384_openssh.pub @@ -0,0 +1,2 @@ +#ecdsa public key in openssh format for authorized_keys +ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzODQAAABhBP9zkmsUkr3GapVmm4NZw5QsdjCeZ2MF9r3tbXuDELmbpquYTrtNAYP/crAjuTv7eeCoaxi4D1RZKLJnG6EhrQUx6AQ36EbKWnof8uiQ+IFBfsUwiYkQdExOvu2L04nFhQ== sprasad@linux.fritz.box diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521 b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521 new file mode 100644 index 000000000000..c1a9a2d88cf4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521 @@ -0,0 +1,7 @@ +-----BEGIN EC PRIVATE KEY----- +MIHcAgEBBEIAIFhJ0TIwP+05/nqBcBIit6DoynzszbP5B7K8tk6d+R741dByNe7x +lsXvKpHkZ+oGn575LQmCJ1BQ+BENxj0G+b2gBwYFK4EEACOhgYkDgYYABADI9DFD +5j3ibs7pr0NRqf4AzMwq4J+OlvBl60fWGiNVvCsC4EQL99TAGcx8VryXybJZ9fmG +C0obHdEYXaRddOpOQgGLzYGTYFzQmF91PLNHIUb1K2IGUN8V8Ehr9qYMGcF/2HFN +VJ/7Iievz67wKcaVJiAoJ7zNpKpFE1fEUHqQSc9vdA== +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub new file mode 100644 index 000000000000..9f7be9986e15 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521.pub @@ -0,0 +1,6 @@ +-----BEGIN PUBLIC KEY----- +MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQAyPQxQ+Y94m7O6a9DUan+AMzMKuCf +jpbwZetH1hojVbwrAuBEC/fUwBnMfFa8l8myWfX5hgtKGx3RGF2kXXTqTkIBi82B +k2Bc0JhfdTyzRyFG9StiBlDfFfBIa/amDBnBf9hxTVSf+yInr8+u8CnGlSYgKCe8 +zaSqRRNXxFB6kEnPb3Q= +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub new file mode 100644 index 000000000000..5ede53226f60 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ecdsa_521_openssh.pub @@ -0,0 +1,2 @@ +#ecdsa public key in openssh format for authorized_keys +ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1MjEAAACFBADI9DFD5j3ibs7pr0NRqf4AzMwq4J+OlvBl60fWGiNVvCsC4EQL99TAGcx8VryXybJZ9fmGC0obHdEYXaRddOpOQgGLzYGTYFzQmF91PLNHIUb1K2IGUN8V8Ehr9qYMGcF/2HFNVJ/7Iievz67wKcaVJiAoJ7zNpKpFE1fEUHqQSc9vdA== sprasad@linux.fritz.box diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519 b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519 new file mode 100644 index 000000000000..e17372fdbfc2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519 @@ -0,0 +1,3 @@ +-----BEGIN PRIVATE KEY----- +MC4CAQAwBQYDK2VwBCIEINATLZmMLR6HQ2076Uj6VQDYcxPIXrBV2TLU9UyJZSpK +-----END PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519.pub new file mode 100644 index 000000000000..d643f956ca7d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519.pub @@ -0,0 +1,3 @@ +-----BEGIN PUBLIC KEY----- +MCowBQYDK2VwAyEA3VoMJl9A48CsizGdLy4yKltC2Mz8UPvv6GmTKj2L3lY= +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519_openssh.pub new file mode 100644 index 000000000000..96e321912fc8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_ed25519_openssh.pub @@ -0,0 +1 @@ +ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIN1aDCZfQOPArIsxnS8uMipbQtjM/FD77+hpkyo9i95W diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa new file mode 100644 index 000000000000..caedcdd8160b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqsV/cOD8KGdJfTEZ+hemwBONeWEVZQsY05EorJ7prWcrRyHs +wg3+AhwFGW17HVKBt2hgJSnmAbU6dYZ/2t4OYWvCCGIBGSbMQldfesuZ160OEu0i +FCVZinAeUVn10iTxwMOM6oUQY75UF5tKg72WGuY5x5zolPAyDXkK0aJ/ZHB9dmfG +v0zjutY2aURGYK+dzNT/xzFsKRQiBc7ROi5eMkXNpK+wgqFrs5Ydpj+xiGt6sowb +114hF6YyVdXpNw+EQrYqpFVRahUDxo2qFBFqXVrCJrmtdFf6Z03FnGwRnFumLVsc +1P9SLVIk4OFh9KndHIevCfj3vfFgQo/A1RzZqwIDAQABAoIBAF9/9xb/GsTVjilu +ziIoG96KkLFyc26AWC/om6B9dhy891zeQfL7tDMlzEw+B32MaHJS8oGV/sSxvrF+ +t/2D9PLSOj5J4GdaZRwslH6tYalVY+t9pXMyt/JNZQcFkg0lD/VG5oU4SMQe6hQR +ighGe60rVuCkhQTVlogmSsCVaTyXKa5oUeZCIaO5gxxvTYsgZiF+U/wsmzoRm1kY +WlqwqA3KM0p/AtFUNQD0iS3zoALwwsYg6MID657IbyyjEcnwpss+3hMV2SnQNpYY ++sBccnS8YCGHkA/KYc0e1gUmFhQkTzeSnhgFg/W7JdTJdyHKCiisNMpWOtqtXano +fiXFClECgYEA3dEMsF2AA5m14V68907J1Wi3Pf9cInmsrDlxpGcdyJiii4zeg1/w +6tLQ6H4+wFoRXTIcduNpZZS8al/zQXqQGH4A9Em38PLkOjPk5mdAV3KCv0LzMkPq +IwgfBFBPRPUVUtfiydLpzqeTQZ6RxokqPy4JY4NEy22gfWsMhKq9YVcCgYEAxRan +Ax0GnTpnZ9hYcJEi+fw7cmK6bX/fVzaNSqSirWNOgjlw1IHt/zp5jylW1HDbk7zJ +5fLnS78fm5WWq2Ov/U71C8KClKVWecKbdYDtuHs7VPM6b1jee/EUqkZevj+vPp4A +2vITwPCUk2EMwuPs8kcEwohfgbS3erNfBkDl8c0CgYAGzLa/3U3kTlz1+KqARkkH +orsjSmWvpN03NleWe5a9JHivIHVdv54qBKZkyiHwUZKAsd2Pg28irwmLlT9mvXQX +XB15X50k5L08T4TvzzB4vcjmRg4gd1aBFmmk/zU+3uh1bqrEFxQVwqtP8qVzWZcS +8r1U8Jw2RDkMPzDWDEnb5wKBgQCjxtsqXyhr52iehs2XXUQvcEcGGrzI2YYCtzZa +XM1CQE+xL4JvVTks6q6xJK0fBFmzPyKXj25dJ4lghMIb0k8gtEg1aYGRfIOL0wfU +cTsaHm2DfkuE09iuLfv6M1fvyIQcCLi1OIzpvAH7Jp6wJS91dUajUSfsZPxDXmMM +k0mB0QKBgB2cD2IMG2d3dOOoBbIw8NvVDR6bpdF4DdYJlCpGa7sfzw3y9yXnejzW +HQUZGvgvhMsMe9LolS0jE6D2MyOsZF3CPFLuDVemfMxDJRTqvsRAjuEPlz6co/uZ +ZVKlh/bW/iakKpaW9vSF1dNwiyWJhzVkutBho3FzZKZSbIkfFztB +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa.pub new file mode 100644 index 000000000000..b1f8f64235c6 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa.pub @@ -0,0 +1,9 @@ +-----BEGIN PUBLIC KEY----- +MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAqsV/cOD8KGdJfTEZ+hem +wBONeWEVZQsY05EorJ7prWcrRyHswg3+AhwFGW17HVKBt2hgJSnmAbU6dYZ/2t4O +YWvCCGIBGSbMQldfesuZ160OEu0iFCVZinAeUVn10iTxwMOM6oUQY75UF5tKg72W +GuY5x5zolPAyDXkK0aJ/ZHB9dmfGv0zjutY2aURGYK+dzNT/xzFsKRQiBc7ROi5e +MkXNpK+wgqFrs5Ydpj+xiGt6sowb114hF6YyVdXpNw+EQrYqpFVRahUDxo2qFBFq +XVrCJrmtdFf6Z03FnGwRnFumLVsc1P9SLVIk4OFh9KndHIevCfj3vfFgQo/A1RzZ +qwIDAQAB +-----END PUBLIC KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa_openssh.pub b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa_openssh.pub new file mode 100644 index 000000000000..e3e2d6f464a2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/pkcs11/id_pkcs11_rsa_openssh.pub @@ -0,0 +1,2 @@ +#rsa public key in openssh format for authorized_keys +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCqxX9w4PwoZ0l9MRn6F6bAE415YRVlCxjTkSisnumtZytHIezCDf4CHAUZbXsdUoG3aGAlKeYBtTp1hn/a3g5ha8IIYgEZJsxCV196y5nXrQ4S7SIUJVmKcB5RWfXSJPHAw4zqhRBjvlQXm0qDvZYa5jnHnOiU8DINeQrRon9kcH12Z8a/TOO61jZpREZgr53M1P/HMWwpFCIFztE6Ll4yRc2kr7CCoWuzlh2mP7GIa3qyjBvXXiEXpjJV1ek3D4RCtiqkVVFqFQPGjaoUEWpdWsImua10V/pnTcWcbBGcW6YtWxzU/1ItUiTg4WH0qd0ch68J+Pe98WBCj8DVHNmr diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key b/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key new file mode 100644 index 000000000000..1fcd836f5359 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key @@ -0,0 +1,5 @@ +-----BEGIN EC PRIVATE KEY----- +MHcCAQEEIB9v2n1oaXvBECf0gDPxTibeUPvvkI1anNWDAIkNjs5JoAoGCCqGSM49 +AwEHoUQDQgAEqkTqNu7gRegPJRy0WiseJz9NAdBimzyNSzNwI5eAkEqv9D6Y95KL +7DBEnDQ2p08iOLw+vN1PKHsCM7b/ONbYVg== +-----END EC PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key.pub b/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key.pub new file mode 100644 index 000000000000..460153a5c934 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_ecdsa_key.pub @@ -0,0 +1 @@ +ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBKpE6jbu4EXoDyUctForHic/TQHQYps8jUszcCOXgJBKr/Q+mPeSi+wwRJw0NqdPIji8PrzdTyh7AjO2/zjW2FY= asn@magrathea diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_key b/src/libs/libssh-0.12.2/tests/keys/ssh_host_key new file mode 100644 index 000000000000..41d3bd654d8c Binary files /dev/null and b/src/libs/libssh-0.12.2/tests/keys/ssh_host_key differ diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_key.pub b/src/libs/libssh-0.12.2/tests/keys/ssh_host_key.pub new file mode 100644 index 000000000000..ae7cc2327386 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_key.pub @@ -0,0 +1 @@ +2048 65537 25221975523736997039149017470335977198642717886559395625730372192276493838727011206749822289920387480933533054627057418868711378045090730895752530916661328094497437687453813456961487210492465678475508526337829331199296553120728607984859224949182503917312492825658971738208505685553964707412720244524969161284321098487507924676797222812771309962906894332072854924265623785469343453142982185436565166155021228521252914913227554455102103918367844210755391318078654400527927267478149210805219779896806429660492177158822689909493046725157917529664436252598971135251689616517266344945600782273453037452082373553352939812279 asn@magrathea diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key b/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key new file mode 100644 index 000000000000..5032fb740184 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEAqzabeU0oKbHDwdlqindABvtzgWCvXdHJ+d2Ew6te2LXjkwju +y7u6B7y63NZRy57ccrE4YSeWItVoZn+DWN+guU354Ss/tzQ9/thUmLrvtKNvJwuF +F5Ch7Q4BsrXGsb2GSv+7W5tpx3yAqH1TvKQj/MmQVX+/9KtXEnh7/vpiCqoOXQAR +zvIIQxoo1aUQBHAkRW7Yw0Ds7AjC9uV1ns4xdBXPQmDk23pWvCq7E+7rOEbKRrjj +lApS4lJYy0oEXsFdqUPd+PtT1gG0nIElHFQtsTgUvwYQmJEzrBxv41odwRvxbMjg +THPr4SLRRRSuPwICWyvxqPpa7EsfMBnbPEIUzQIDAQABAoIBAEUO15MLvgFjRDQy +P7jt9JNcZPBwUQukjLUN1nkd7Dm407wAxGDErXplc3GTuJZK01wngzgcwX/3WA7P +q+jy+l8DxqA904tPtRnPo/+elwTjTvgOu3YPzmBRX/n3O9eBPGOP1sBSZU4jN7m+ +I0JZanKR0nfJ+WD0o0A9/LWRxG3MFIntBamtT6pgee8sAu44IvW0o7tHJabMq02J +Z/ndrJmox34wq6SMFANax+N1x9sZa60bL7gEoDWQJNKOaMrbtOaIoTGFIc4hFqoA +SzjNqcGsHPWs44cw0mNkUGq37jEvaCwzAp+U80ma1skBhXuJL9sQOxl1v5qW91c/ +Cnm5WYECgYEA4DPvqbLt+VdyTtmCQ370yiCk4OPPMPzbM65IVKgQL/rN6HdNShTO +uLF6P8XC8vNP2OSydJeFt+kMKd7E/4o5LfvEqUGXZJDkB7fLjrOjyZU3bxtIx95x +qYGWRcWbd3sHzlBJGuFVSE7GREE+lqhkSu4ry4l/GAKxSymAXgGd/9ECgYEAw37L +ppZIavcLE2rZgXHoqMiJzeGzsidJbkHss4k7ubLe8vyBMiv0HC2anxPa2+yNWuF2 ++pEr84bllh149VKeild24UEBAR2w/P41ggWqiUP7PKllh+huWzG4+KNFbfUP4dd0 +4LkVgfsCz32qD8qxXNCxJCZ8H2fmjKsYw/oCID0CgYAiuSh3GdUtdtOnTpyUI4d5 +/pBKnD2skpzIZkehhN3s8GUPidqYjJxvkl0in1hQFErbhp/02rrE/vz5Rx0vjpLI +gmO06wmtc5s9bsPB+CR3xfpt5MXi3pqv6/gAGli3qoBM/bY0yY1Rw5GFZK1y2+Wc +jUKPJV5fs5sNzwGojYuQ4QKBgQCNNgqOo2Fd+mLCvNyt1wTy3iBEWfL+DcjJ3s7G +hKtioKTQqbn87qjercZRf/sH/t/ANLpHlhNETj2KaHGV6v7f+PvDC7xY/QR6SnmG +GOetTTCuCcJwIGGOd+UfnHgrS+gT/xjKtoalpBXMoP31eDkTTR+XeEESQm/TTkeO +UAm3FQKBgD8Y7CLHpyZZ+eOnxRSPU4m4AWAEp7JOwHDRWWQeUornrXDYgD87d2M9 +iIAEuOzNggA56Nm3AzBOPRj4HkBh57ToVKPswHwB0oWvrtSjpLkkU6q8xRG3XuJD +2AskDaZONzIDoJGfZ3+W7YbKELK7DPtFXL15sOfBmpoEkI9RA5vM +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key.pub b/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key.pub new file mode 100644 index 000000000000..efa21919418b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/ssh_host_rsa_key.pub @@ -0,0 +1 @@ +ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCrNpt5TSgpscPB2WqKd0AG+3OBYK9d0cn53YTDq17YteOTCO7Lu7oHvLrc1lHLntxysThhJ5Yi1Whmf4NY36C5TfnhKz+3ND3+2FSYuu+0o28nC4UXkKHtDgGytcaxvYZK/7tbm2nHfICofVO8pCP8yZBVf7/0q1cSeHv++mIKqg5dABHO8ghDGijVpRAEcCRFbtjDQOzsCML25XWezjF0Fc9CYOTbela8KrsT7us4RspGuOOUClLiUljLSgRewV2pQ934+1PWAbScgSUcVC2xOBS/BhCYkTOsHG/jWh3BG/FsyOBMc+vhItFFFK4/AgJbK/Go+lrsSx8wGds8QhTN asn@magrathea diff --git a/src/libs/libssh-0.12.2/tests/keys/user_ca b/src/libs/libssh-0.12.2/tests/keys/user_ca new file mode 100644 index 000000000000..dc9d80c2dea1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/keys/user_ca @@ -0,0 +1,27 @@ +-----BEGIN RSA PRIVATE KEY----- +MIIEowIBAAKCAQEApwNp+bx82W7Pxr0ZBpaCTVdQlxyEuV565tOR7rFCUlsj6tyN +YgjB3WAO6Hsx2vRghWGyHp2mD8vhulEkEY2EM/ErwGaNOkhTXiUrGtZACKsc3T0/ +ZAa0a2Ct/T2UD7GFX0T6mQXZnPo8K38/eyBRCclzCOOxaExpIBKu3rt89qcf7XSG +d+COR6+QwwcYnGYuI5bskVCR30j6AccoRezpD1+G2JK3OvdY3iEA8bm5eudFmCx4 +BX2ndMU8wSBROl1G+eQ6eQIEXXr0qLKNT3tCChccTFaT+uuCwm2CtOi7+pHKLXxZ +ab1LUdcsfI06BnEqKubEs08L10hwkEPWQNYbxQIDAQABAoIBACW2AaHgS5iVCtln +LVVterKX+pyEVfu9N6cTMqpg4AbUiYGol0wBijTAUd1wo8s6zuiPLLb5BdwfPzLg +y3IjMCzCUgy5mz4Dwr9JSThgFElgyb2y7LNbSDXOuLqrwtjgTqs6WhNfXMmzPw7b +Rqw4mdPJ5u2k7BQO3NXfIhks4ISYzpzNAwj1a2NMphvkZyvfRnWiQ0pvEXQCxwuR +74iGpPFeyFjjku/O4TiHZllPmDdD3ERalkf8RIudQ5gcbL4fRoONTzfZHtmARWoP +Jury4Zfr5b3VGSnkUDaGlzilXvBusAZOCaaU7chvOPVjXMbSAUEpFBmnRHk5dfrH +fCXECcECgYEA0KMtV3IzwMToVdvzcMQc1ovDvKZAQPneLTxFgNpOeycOhzulzY9p +3fRi5QUOA/Ff+LcCL86APqwoEYe4bgam6mwGFFhv1usf4ulbLNk8ZeR51CG6emPt +tLpg6PThxhMnNpu+StrBAOxeo9pZGd+Plt6d4vfoalOHVkPlSv7OC9kCgYEAzO1I +HuZAQkVdKLGuZlf8E4VEaiMBKdl5+H+8w9peOOax6nqAIrwp2d0aZ52LDjwg7d3C +eSmxu0U1jsbzexVVePr/NmdJOu3+gB0GvlzRjS1xT+MCZIye5a7Nxc7lBp5rFmgV +dJTA6XXRoykinZIxz068SHqtNhNOzO4hUmPDN80CgYAlxOR4aBwmUX8dy+uOBnKS +BEsy44XOPW2TEs4iPWLnuHJQ2ONzCvtHSu58NyYKYK/W/opOzTs6HUBDrCYfBOVC +mrufA0N7zKTBFy2COPFOIMZNOK3haiWmCfdxNKOKj/0RTbBtLJyz5hZb4zMuE+KS +lUpPxEE2vlhJrZDcurPiQQKBgQCIEqMKCX/vwVlLlTglsxSp7ZrxEw9Jt6O68y7n +qc9Y3y6ScQc2iVUM2jkXRlA4goqnB9KDW8EthZY7mTXBq/fWXmwqtsi0faW5cgyx +SLbIlL0h+63yEEHOZ5UxXOFM1NJszW45vDCglOBABCd9E79JVZHGWtc7CfUQNKsh +pybQnQKBgHbPnITR7esVQYLq3PHSsdOdkFiiVf3D7wHiNZcXWjJvUqMF4tH5XAzY +QafKqKk0FzO92ZOhQeB5xauFY5wzsa+Xl8cQkyvtWngFIKbWydEehZWVgXcedxEC +xjbZWKmsYDqBYi3bw9Dxb0AvT+kDtq0Azi8QTDAvRwylvtkYj/V8 +-----END RSA PRIVATE KEY----- diff --git a/src/libs/libssh-0.12.2/tests/pkcs11/setup-softhsm-tokens.sh b/src/libs/libssh-0.12.2/tests/pkcs11/setup-softhsm-tokens.sh new file mode 100755 index 000000000000..ddc684a06543 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkcs11/setup-softhsm-tokens.sh @@ -0,0 +1,93 @@ +#!/bin/bash + +# The client keys are stored in a SoftHSM device. + +TESTDIR=$1 +PRIVKEY=$2 +OBJNAME=$3 +TOKENLABEL=$3 # yeah. The same as object label +LOADPUBLIC=$4 +LIBSOFTHSM_PATH=$5 +shift 5 + +PUBKEY="$PRIVKEY.pub" + +echo "TESTDIR: $TESTDIR" +echo "PRIVKEY: $PRIVKEY" +echo "PUBKEY: $PUBKEY" +echo "OBJNAME: $OBJNAME" +echo "TOKENLABEL: $TOKENLABEL" +echo "LOADPUBLIC: $LOADPUBLIC" + +if [ ! -d "$TESTDIR/db" ]; then + # Create temporary directory for tokens + install -d -m 0755 "$TESTDIR/db" + + # Create SoftHSM configuration file + cat >"$TESTDIR/softhsm.conf" < + */ + +#ifndef __PKD_CLIENT_H__ +#define __PKD_CLIENT_H__ + +#include "config.h" +#include "tests_config.h" + +/* OpenSSH */ + +#define OPENSSH_BINARY SSH_EXECUTABLE +#define OPENSSH_KEYGEN "ssh-keygen" + +#define OPENSSH_HOSTKEY_ALGOS \ + "-o HostKeyAlgorithms=" \ + OPENSSH_KEYS + +#define OPENSSH_PKACCEPTED_TYPES \ + "-o PubkeyAcceptedKeyTypes=" \ + OPENSSH_KEYS + +#ifdef HAVE_SK_DUMMY +#define SECURITY_KEY_PROVIDER \ + "-oSecurityKeyProvider=\"" SK_DUMMY_LIBRARY_PATH "\" " +#else +#define SECURITY_KEY_PROVIDER "" +#endif + +/* GlobalKnownHostsFile is just a place holder and won't actually set the hostkey */ +#define OPENSSH_CMD_START(hostkey_algos) \ + OPENSSH_BINARY " " \ + "-o UserKnownHostsFile=/dev/null " \ + "-o StrictHostKeyChecking=no " \ + SECURITY_KEY_PROVIDER \ + "-o GlobalKnownHostsFile=%s " \ + "-F /dev/null " \ + hostkey_algos " " \ + OPENSSH_PKACCEPTED_TYPES " " \ + "-i " CLIENT_ID_FILE " " \ + "1> %s.out " \ + "2> %s.err " \ + "-vvv " + +#define OPENSSH_CMD_END "-p 1234 localhost ls" + +#define OPENSSH_CMD \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) OPENSSH_CMD_END + +#define OPENSSH_KEX_CMD(kexalgo) \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-o KexAlgorithms=" kexalgo " " OPENSSH_CMD_END + +#define OPENSSH_CIPHER_CMD(ciphers) \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-c " ciphers " " OPENSSH_CMD_END + +#define OPENSSH_MAC_CMD(macs) \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-c aes128-ctr,aes192-ctr,aes256-ctr,aes256-cbc,aes192-cbc,aes128-cbc -o MACs=" macs " " OPENSSH_CMD_END + +#define OPENSSH_HOSTKEY_CMD(hostkeyalgo) \ + OPENSSH_CMD_START("-o HostKeyAlgorithms=" hostkeyalgo " ") OPENSSH_CMD_END + +#define OPENSSH_CERT_CMD \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-o CertificateFile=" CLIENT_ID_FILE "-cert.pub " OPENSSH_CMD_END + +#define OPENSSH_SHA256_CERT_CMD \ + OPENSSH_CMD_START(OPENSSH_HOSTKEY_ALGOS) "-o CertificateFile=" CLIENT_ID_FILE "-sha256-cert.pub " OPENSSH_CMD_END + +/* Dropbear */ + +#define DROPBEAR_BINARY DROPBEAR_EXECUTABLE +#define DROPBEAR_KEYGEN "dropbearkey" + +/* HostKeyAlias is just a place holder and won't actually set the hostkey */ +#define DROPBEAR_CMD_START \ + DROPBEAR_BINARY " " \ + "-y -y " \ + "-o HostKeyAlias=%s " \ + "-i " CLIENT_ID_FILE " " \ + "1> %s.out " \ + "2> %s.err " + +#define DROPBEAR_CMD_END "-p 1234 localhost ls" + +#define DROPBEAR_CMD \ + DROPBEAR_CMD_START DROPBEAR_CMD_END + +#if 0 /* dbclient does not expose control over kex algo */ +#define DROPBEAR_KEX_CMD(kexalgo) \ + DROPBEAR_CMD +#endif + +#define DROPBEAR_CIPHER_CMD(ciphers) \ + DROPBEAR_CMD_START "-c " ciphers " " DROPBEAR_CMD_END + +#define DROPBEAR_MAC_CMD(macs) \ + DROPBEAR_CMD_START "-m " macs " " DROPBEAR_CMD_END + +/* PuTTY */ + +#define PUTTY_BINARY PUTTY_EXECUTABLE +#define PUTTY_KEYGEN PUTTYGEN_EXECUTABLE + +#define PUTTY_CMD_START \ + PUTTY_BINARY " " \ + "-batch -ssh -P 1234 " \ + "-i " CLIENT_ID_FILE " " \ + "-hostkey $(" OPENSSH_KEYGEN \ + " -l -f %s.pub -E md5 | awk '{print $2}' | cut -d: -f2-) " \ + "1> %s.out 2> %s.err " + +#define PUTTY_CMD_END " localhost ls" + +#define PUTTY_CMD \ + PUTTY_CMD_START PUTTY_CMD_END + +#endif /* __PKD_CLIENT_H__ */ diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.c b/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.c new file mode 100644 index 000000000000..6c9ca03e2f92 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.c @@ -0,0 +1,596 @@ +/* + * pkd_daemon.c -- a sample public-key testing daemon using libssh + * + * Uses public key authentication to establish an exec channel and + * echo back payloads to the user. + * + * (c) 2014 Jon Simons + */ + +#include "config.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#include "torture.h" // for ssh_fips_mode() +#include "pkd_daemon.h" + +#include // for cmocka +#include + +static int pkdout_enabled; +static int pkderr_enabled; + +static void pkdout(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2); +static void pkderr(const char *fmt, ...) PRINTF_ATTRIBUTE(1, 2); + +static void pkdout(const char *fmt, ...) { + va_list vargs; + if (pkdout_enabled) { + va_start(vargs, fmt); + vfprintf(stdout, fmt, vargs); + va_end(vargs); + } +} + +static void pkderr(const char *fmt, ...) { + va_list vargs; + if (pkderr_enabled) { + va_start(vargs, fmt); + vfprintf(stderr, fmt, vargs); + va_end(vargs); + } +} + +/* + * pkd state: only one thread can run pkd at a time --------------------- + */ + +static struct { + int rc; + pthread_t tid; + int keep_going; + volatile int pkd_ready; +} ctx; + +static struct { + int server_fd; + int req_exec_received; + int close_received; + int eof_received; +} pkd_state; + +static void pkd_sighandler(int signum) { + (void) signum; +} + +static int pkd_init_libssh(void) +{ + int rc = ssh_threads_set_callbacks(ssh_threads_get_pthread()); + return (rc == SSH_OK) ? 0 : 1; +} + +static int pkd_init_server_fd(short port) { + int rc = 0; + int yes = 1; + struct sockaddr_in addr; + + int server_fd = socket(PF_INET, SOCK_STREAM, 0); + if (server_fd < 0) { + rc = -1; + goto out; + } + + rc = setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int)); + if (rc != 0) { + goto outclose; + } + + memset(&addr, 0x0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = INADDR_ANY; + rc = bind(server_fd, (struct sockaddr *)&addr, sizeof(addr)); + if (rc != 0) { + goto outclose; + } + + rc = listen(server_fd, 128); + if (rc == 0) { + goto out; + } + +outclose: + close(server_fd); + server_fd = -1; +out: + pkd_state.server_fd = server_fd; + return rc; +} + +static int pkd_accept_fd(void) +{ + int fd = -1; + struct sockaddr_in addr; + socklen_t len = sizeof(addr); + + do { + fd = accept(pkd_state.server_fd, (struct sockaddr *) &addr, &len); + } while ((ctx.keep_going != 0) && (fd < 0) && (errno == EINTR)); + + return fd; +} + +static void pkd_eof(ssh_session session, + ssh_channel channel, + void *userdata) { + (void) session; + (void) channel; + (void) userdata; + pkdout("pkd_eof\n"); + pkd_state.eof_received = 1; +} + +static void pkd_chan_close(ssh_session session, + ssh_channel channel, + void *userdata) { + (void) session; + (void) channel; + (void) userdata; + pkdout("pkd_chan_close\n"); + pkd_state.close_received = 1; +} + +static int pkd_req_exec(ssh_session s, + ssh_channel c, + const char *cmd, + void *userdata) { + (void) s; + (void) c; + (void) cmd; + (void) userdata; + /* assumes pubkey authentication has already succeeded */ + pkdout("pkd_req_exec\n"); + pkd_state.req_exec_received = 1; + return 0; +} + +/* assumes there is only ever a single channel */ +static struct ssh_channel_callbacks_struct pkd_channel_cb = { + .channel_eof_function = pkd_eof, + .channel_close_function = pkd_chan_close, + .channel_exec_request_function = pkd_req_exec, +}; + +static int pkd_auth_pubkey_cb(ssh_session s, + const char *user, + ssh_key key, + char state, + void *userdata) { + (void) s; + (void) user; + (void) key; + (void) state; + (void) userdata; + pkdout("pkd_auth_pubkey_cb keytype %s, state: %d\n", + ssh_key_type_to_char(ssh_key_type(key)), state); + if ((state == SSH_PUBLICKEY_STATE_NONE) || + (state == SSH_PUBLICKEY_STATE_VALID)) { + return SSH_AUTH_SUCCESS; + } + return SSH_AUTH_DENIED; +} + +static int pkd_service_request_cb(ssh_session session, + const char *service, + void *userdata) { + (void) session; + (void) userdata; + pkdout("pkd_service_request_cb: %s\n", service); + return (0 == (strcmp(service, "ssh-userauth"))) ? 0 : -1; +} + +static ssh_channel pkd_channel_openreq_cb(ssh_session s, + void *userdata) { + ssh_channel c = NULL; + ssh_channel *out = (ssh_channel *) userdata; + + /* assumes pubkey authentication has already succeeded */ + pkdout("pkd_channel_openreq_cb\n"); + + c = ssh_channel_new(s); + if (c == NULL) { + pkderr("ssh_channel_new: %s\n", ssh_get_error(s)); + return NULL; + } + + ssh_callbacks_init(&pkd_channel_cb); + pkd_channel_cb.userdata = userdata; + if (ssh_set_channel_callbacks(c, &pkd_channel_cb) != SSH_OK) { + pkderr("ssh_set_channel_callbacks: %s\n", ssh_get_error(s)); + ssh_channel_free(c); + c = NULL; + } + + *out = c; + + return c; +} + +static struct ssh_server_callbacks_struct pkd_server_cb = { + .auth_pubkey_function = pkd_auth_pubkey_cb, + .service_request_function = pkd_service_request_cb, + .channel_open_request_session_function = pkd_channel_openreq_cb, +}; + +static int pkd_exec_hello(int fd, struct pkd_daemon_args *args) +{ + int rc = -1; + ssh_bind b = NULL; + ssh_session s = NULL; + ssh_event e = NULL; + ssh_channel c = NULL; + enum ssh_bind_options_e opts = -1; + + int level = args->opts.libssh_log_level; + enum pkd_hostkey_type_e type = args->type; + const char *hostkeypath = args->hostkeypath; + const char *all_kex = NULL; + const char *all_ciphers = NULL; + const char *all_macs = NULL; + const uint64_t rekey_data_limit = args->rekey_data_limit; + bool process_config = false; + + pkd_state.eof_received = 0; + pkd_state.close_received = 0; + pkd_state.req_exec_received = 0; + + b = ssh_bind_new(); + if (b == NULL) { + pkderr("ssh_bind_new\n"); + goto outclose; + } + + if (type == PKD_RSA || + type == PKD_ED25519 || + type == PKD_ECDSA) { + opts = SSH_BIND_OPTIONS_HOSTKEY; + } else { + pkderr("unknown hostkey type: %d\n", type); + rc = -1; + goto outclose; + } + + rc = ssh_bind_options_set(b, opts, hostkeypath); + if (rc != 0) { + pkderr("ssh_bind_options_set: %s\n", ssh_get_error(b)); + goto outclose; + } + + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_LOG_VERBOSITY, &level); + if (rc != 0) { + pkderr("ssh_bind_options_set log verbosity: %s\n", ssh_get_error(b)); + goto outclose; + } + + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_PROCESS_CONFIG, + &process_config); + if (rc != 0) { + pkderr("ssh_bind_options_set process config: %s\n", ssh_get_error(b)); + goto outclose; + } + + if (!ssh_fips_mode()) { + const char *all_hostkeys = NULL; + /* Add methods not enabled by default */ + + /* Enable all supported key exchange methods */ + all_kex = ssh_get_supported_methods(SSH_KEX); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_KEY_EXCHANGE, all_kex); + if (rc != 0) { + pkderr("ssh_bind_options_set kex methods: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* Enable all supported ciphers */ + all_ciphers = ssh_get_supported_methods(SSH_CRYPT_C_S); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_CIPHERS_C_S, all_ciphers); + if (rc != 0) { + pkderr("ssh_bind_options_set Ciphers C-S: %s\n", ssh_get_error(b)); + goto outclose; + } + + all_ciphers = ssh_get_supported_methods(SSH_CRYPT_S_C); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_CIPHERS_S_C, all_ciphers); + if (rc != 0) { + pkderr("ssh_bind_options_set Ciphers S-C: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* Enable all hostkey algorithms */ + all_hostkeys = ssh_get_supported_methods(SSH_HOSTKEYS); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, all_hostkeys); + if (rc != 0) { + pkderr("ssh_bind_options_set Hostkeys: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* Enable all message authentication codes */ + all_macs = ssh_get_supported_methods(SSH_MAC_C_S); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_HMAC_C_S, all_macs); + if (rc != 0) { + pkderr("ssh_bind_options_set MACs C-S: %s\n", ssh_get_error(b)); + goto outclose; + } + + all_macs = ssh_get_supported_methods(SSH_MAC_S_C); + rc = ssh_bind_options_set(b, SSH_BIND_OPTIONS_HMAC_S_C, all_macs); + if (rc != 0) { + pkderr("ssh_bind_options_set MACs S-C: %s\n", ssh_get_error(b)); + goto outclose; + } + + } + + s = ssh_new(); + if (s == NULL) { + pkderr("ssh_new\n"); + goto outclose; + } + + rc = ssh_options_set(s, SSH_OPTIONS_REKEY_DATA, &rekey_data_limit); + if (rc != 0) { + pkderr("ssh_options_set rekey data: %s\n", ssh_get_error(s)); + goto outclose; + } + + /* + * ssh_bind_accept loads host key as side-effect. If this + * succeeds, the given 'fd' will be closed upon 'ssh_free(s)'. + */ + rc = ssh_bind_accept_fd(b, s, fd); + if (rc != SSH_OK) { + pkderr("ssh_bind_accept_fd: %s\n", ssh_get_error(b)); + goto outclose; + } + + /* accept only publickey-based auth */ + ssh_set_auth_methods(s, SSH_AUTH_METHOD_PUBLICKEY); + + /* initialize callbacks */ + ssh_callbacks_init(&pkd_server_cb); + pkd_server_cb.userdata = &c; + rc = ssh_set_server_callbacks(s, &pkd_server_cb); + if (rc != SSH_OK) { + pkderr("ssh_set_server_callbacks: %s\n", ssh_get_error(s)); + goto out; + } + + /* first do key exchange */ + rc = ssh_handle_key_exchange(s); + if (rc != SSH_OK) { + pkderr("ssh_handle_key_exchange: %s\n", ssh_get_error(s)); + goto out; + } + + /* setup and pump event to carry out exec channel */ + e = ssh_event_new(); + if (e == NULL) { + pkderr("ssh_event_new\n"); + goto out; + } + + rc = ssh_event_add_session(e, s); + if (rc != SSH_OK) { + pkderr("ssh_event_add_session\n"); + goto out; + } + + /* poll until exec channel established */ + while ((ctx.keep_going != 0) && + (rc != SSH_ERROR) && (pkd_state.req_exec_received == 0)) { + rc = ssh_event_dopoll(e, -1 /* infinite timeout */); + } + + if (rc == SSH_ERROR) { + pkderr("ssh_event_dopoll\n"); + goto out; + } else if (c == NULL) { + pkderr("poll loop exited but exec channel not ready\n"); + rc = -1; + goto out; + } + + rc = ssh_channel_write(c, args->payload.buf, args->payload.len); + if (rc != (int)args->payload.len) { + pkderr("ssh_channel_write partial (%d != %zd)\n", rc, args->payload.len); + } + + rc = ssh_channel_request_send_exit_status(c, 0); + if (rc != SSH_OK) { + pkderr("ssh_channel_request_send_exit_status: %s\n", + ssh_get_error(s)); + goto out; + } + + rc = ssh_channel_send_eof(c); + if (rc != SSH_OK) { + pkderr("ssh_channel_send_eof: %s\n", ssh_get_error(s)); + goto out; + } + + rc = ssh_channel_close(c); + if (rc != SSH_OK) { + pkderr("ssh_channel_close: %s\n", ssh_get_error(s)); + goto out; + } + + while ((ctx.keep_going != 0) && + (pkd_state.eof_received == 0) && + (pkd_state.close_received == 0)) { + rc = ssh_event_dopoll(e, 1000 /* milliseconds */); + if (rc == SSH_ERROR) { + /* log, but don't consider this fatal */ + pkdout("ssh_event_dopoll for eof + close: %s\n", ssh_get_error(s)); + rc = 0; + break; + } else { + rc = 0; + } + } + + while ((ctx.keep_going != 0) && + (ssh_is_connected(s))) { + rc = ssh_event_dopoll(e, 1000 /* milliseconds */); + if (rc == SSH_ERROR) { + /* log, but don't consider this fatal */ + pkdout("ssh_event_dopoll for session connection: %s\n", ssh_get_error(s)); + rc = 0; + break; + } else { + rc = 0; + } + } + goto out; + +outclose: + close(fd); +out: + if (c != NULL) { + ssh_channel_free(c); + } + if (e != NULL) { + ssh_event_remove_session(e, s); + ssh_event_free(e); + } + if (s != NULL) { + ssh_disconnect(s); + ssh_free(s); + } + if (b != NULL) { + ssh_bind_free(b); + } + return rc; +} + +/* + * main loop ------------------------------------------------------------ + */ + +static void *pkd_main(void *args) { + int rc = -1; + struct pkd_daemon_args *a = (struct pkd_daemon_args *) args; + + struct sigaction act = { .sa_handler = pkd_sighandler, }; + + pkd_state.server_fd = -1; + pkd_state.req_exec_received = 0; + pkd_state.close_received = 0; + pkd_state.eof_received = 0; + + /* SIGUSR1 is used to interrupt 'pkd_accept_fd'. */ + rc = sigaction(SIGUSR1, &act, NULL); + if (rc != 0) { + pkderr("sigaction: %d\n", rc); + goto out; + } + + /* Ignore SIGPIPE */ + signal(SIGPIPE, SIG_IGN); + + rc = pkd_init_libssh(); + if (rc != 0) { + pkderr("pkd_init_libssh: %d\n", rc); + goto out; + } + + rc = pkd_init_server_fd(1234); + if (rc != 0) { + pkderr("pkd_init_server_fd: %d\n", rc); + goto out; + } + + ctx.pkd_ready = 1; + + while (ctx.keep_going != 0) { + int fd = pkd_accept_fd(); + if (fd < 0) { + if (ctx.keep_going != 0) { + pkderr("pkd_accept_fd"); + rc = -1; + } else { + rc = 0; + } + break; + } + + rc = pkd_exec_hello(fd, a); + if (rc != 0) { + pkderr("pkd_exec_hello: %d\n", rc); + break; + } + } + + if (pkd_state.server_fd != -1) { + close(pkd_state.server_fd); + } + pkd_state.server_fd = -1; +out: + ctx.rc = rc; + + return NULL; +} + +/* + * pkd start and stop used by setup/teardown test scaffolding ----------- + */ + +int pkd_start(struct pkd_daemon_args *args) { + int rc = 0; + + pkdout_enabled = args->opts.log_stdout; + pkderr_enabled = args->opts.log_stderr; + + /* Initialize the pkd context. */ + ctx.rc = -1; + ctx.keep_going = 1; + ctx.pkd_ready = 0; + rc = pthread_create(&ctx.tid, NULL, &pkd_main, args); + assert_int_equal(rc, 0); + + /* Busy-spin until pkd thread is ready. */ + while (ctx.pkd_ready == 0); + + return rc; +} + +void pkd_stop(struct pkd_result *out) { + int rc = 0; + + ctx.keep_going = 0; + close(pkd_state.server_fd); + + rc = pthread_kill(ctx.tid, SIGUSR1); + assert_int_not_equal(rc, EINVAL); + assert_int_not_equal(rc, ENOTSUP); + + rc = pthread_join(ctx.tid, NULL); + assert_int_equal(rc, 0); + + assert_non_null(out); + out->ok = (ctx.rc == 0); + + return; +} diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.h b/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.h new file mode 100644 index 000000000000..2745f11a1db9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_daemon.h @@ -0,0 +1,62 @@ +/* + * pkd_daemon.h -- tests use this interface to start, stop pkd + * instances and get results + * + * (c) 2014 Jon Simons + */ + +#ifndef __PKD_DAEMON_H__ +#define __PKD_DAEMON_H__ + +#include "config.h" + +enum pkd_hostkey_type_e { + PKD_RSA, + PKD_ED25519, + PKD_ECDSA +}; + +struct pkd_daemon_args { + enum pkd_hostkey_type_e type; + const char *hostkeypath; + + struct { + const uint8_t *buf; + size_t len; + } payload; + + uint64_t rekey_data_limit; + + int original_dir_fd; + + struct { + int list; + + int log_stdout; + int log_stderr; + int libssh_log_level; + + const char *testname; + const char *testmatch; + unsigned int iterations; + + struct { + const char *argv_mkdtemp_str; + char *mkdtemp_str; + } socket_wrapper; + + struct { + const char *argv_mkdtemp_str; + char *mkdtemp_str; + } temp_dir; + } opts; +}; + +struct pkd_result { + int ok; +}; + +int pkd_start(struct pkd_daemon_args *args); +void pkd_stop(struct pkd_result *out); + +#endif /* __PKD_DAEMON_H__ */ diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_hello.c b/src/libs/libssh-0.12.2/tests/pkd/pkd_hello.c new file mode 100644 index 000000000000..feedaf34eaff --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_hello.c @@ -0,0 +1,1161 @@ +/* + * pkd_hello.c -- + * + * (c) 2014, 2017-2018, 2022 Jon Simons + */ +#include "config.h" + +#include +#include // for cmocka +#include // for cmocka +#include // for cmocka +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "torture.h" // for ssh_fips_mode() + +#include "pkd_client.h" +#include "pkd_daemon.h" +#include "pkd_keyutil.h" +#include "pkd_util.h" + +#if defined(HAVE_LIBCRYPTO) +/* for OPENSSL_cleanup() of OpenSSL context */ +#include +#endif + +#define DEFAULT_ITERATIONS 10 +static struct pkd_daemon_args pkd_dargs; + +static uint8_t default_payload_buf[] = { + 'h', 'e', 'l', 'l', 'o', '\n', +}; + +static size_t default_payload_len = sizeof(default_payload_buf); + +#ifdef HAVE_ARGP_H +#include +#define PROGNAME "pkd_hello" +#define ARGP_PROGNAME "libssh " PROGNAME +const char *argp_program_version = ARGP_PROGNAME " 2022-11-12"; +const char *argp_program_bug_address = "Jon Simons "; + +static char doc[] = \ + "\nExample usage:\n\n" + " " PROGNAME "\n" + " Run all tests with default number of iterations.\n" + " " PROGNAME " --list\n" + " List available individual test names.\n" + " " PROGNAME " -i 1000 -t torture_pkd_rsa_ecdh_sha2_nistp256\n" + " Run only the torture_pkd_rsa_ecdh_sha2_nistp256 testcase 1000 times.\n" + " " PROGNAME " -i 1000 -m curve25519\n" + " Run all tests with the string 'curve25519' 1000 times.\n" + " " PROGNAME " -v -v -v -v -e -o\n" + " Run all tests with maximum libssh and pkd logging.\n" +; + +static struct argp_option options[] = { + { "buffer", 'b', "string", 0, + "Use the given string for test buffer payload contents", 0 }, + { "stderr", 'e', NULL, 0, + "Emit pkd stderr messages", 0 }, + { "list", 'l', NULL, 0, + "List available individual test names", 0 }, + { "iterations", 'i', "number", 0, + "Run each test for the given number of iterations (default is 10)", 0 }, + { "match", 'm', "testmatch", 0, + "Run all tests with the given string", 0 }, + { "temp-dir", 'L', "", 0, + "Run in a temporary directory using the given mkdtemp template", 0 }, + { "socket-wrapper-dir", 'w', "", 0, + "Run in socket-wrapper mode using the given mkdtemp directory template", 0 }, + { "stdout", 'o', NULL, 0, + "Emit pkd stdout messages", 0 }, + { "rekey", 'r', "limit", 0, + "Set the given rekey data limit, in bytes, using SSH_OPTIONS_REKEY_DATA", 0 }, + { "test", 't', "testname", 0, + "Run tests matching the given testname", 0 }, + { "verbose", 'v', NULL, 0, + "Increase libssh verbosity (can be used multiple times)", 0 }, + { NULL, 0, NULL, 0, + NULL, 0 }, +}; + +static error_t parse_opt(int key, char *arg, struct argp_state *state) { + (void) arg; + (void) state; + + switch(key) { + case 'b': + pkd_dargs.payload.buf = (uint8_t *) arg; + pkd_dargs.payload.len = strlen(arg); + break; + case 'e': + pkd_dargs.opts.log_stderr = 1; + break; + case 'l': + pkd_dargs.opts.list = 1; + break; + case 'L': + pkd_dargs.opts.temp_dir.argv_mkdtemp_str = arg; + break; + case 'i': + pkd_dargs.opts.iterations = atoi(arg); + break; + case 'm': + pkd_dargs.opts.testmatch = arg; + break; + case 'o': + pkd_dargs.opts.log_stdout = 1; + break; + case 'r': + pkd_dargs.rekey_data_limit = atoi(arg); + break; + case 't': + pkd_dargs.opts.testname = arg; + break; + case 'v': + pkd_dargs.opts.libssh_log_level += 1; + break; + case 'w': + pkd_dargs.opts.socket_wrapper.argv_mkdtemp_str = arg; + break; + default: + return ARGP_ERR_UNKNOWN; + } + + return 0; +} + +static struct argp parser = { + options, + parse_opt, + NULL, + doc, + NULL, + NULL, + NULL +}; +#endif /* HAVE_ARGP_H */ + +static struct pkd_state *torture_pkd_setup(enum pkd_hostkey_type_e type, + const char *hostkeypath) { + int rc = 0; + + pkd_dargs.type = type; + pkd_dargs.hostkeypath = hostkeypath; + + rc = pkd_start(&pkd_dargs); + assert_int_equal(rc, 0); + + return NULL; +} + +static int torture_pkd_teardown(void **state) { + struct pkd_result result = { .ok = 0 }; + + (void) state; + + pkd_stop(&result); + assert_int_equal(result.ok, 1); + + return 0; +} + +/* + * one setup for each server keytype ------------------------------------ + */ + +static int torture_pkd_setup_noop(void **state) { + *state = (void *) torture_pkd_setup(PKD_RSA, NULL /*path*/); + + return 0; +} + +static int torture_pkd_setup_rsa(void **state) { + setup_rsa_key(); + *state = (void *) torture_pkd_setup(PKD_RSA, LIBSSH_RSA_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ed25519(void **state) { + setup_ed25519_key(); + *state = (void *) torture_pkd_setup(PKD_ED25519, LIBSSH_ED25519_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ecdsa_256(void **state) { + setup_ecdsa_keys(); + *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_256_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ecdsa_384(void **state) { + setup_ecdsa_keys(); + *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_384_TESTKEY); + + return 0; +} + +static int torture_pkd_setup_ecdsa_521(void **state) { + setup_ecdsa_keys(); + *state = (void *) torture_pkd_setup(PKD_ECDSA, LIBSSH_ECDSA_521_TESTKEY); + + return 0; +} + +/* + * Test matrices: f(clientname, testname, ssh-command, setup-function, teardown-function). + */ + +#define PKDTESTS_DEFAULT_FIPS(f, client, cmd) \ + f(client, rsa_default, cmd, setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_default, cmd, setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_default, cmd, setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_default, cmd, setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define PKDTESTS_DEFAULT(f, client, cmd) \ + /* Default passes by server key type. */ \ + PKDTESTS_DEFAULT_FIPS(f, client, cmd) \ + f(client, ed25519_default, cmd, setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) + +#define GEX_SHA256 "diffie-hellman-group-exchange-sha256" +#define GEX_SHA1 "diffie-hellman-group-exchange-sha1" + +#if defined(WITH_GEX) +#define PKDTESTS_KEX_FIPS(f, client, kexcmd) \ + f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) +#else /* !defined(WITH_GEX) */ +#define PKDTESTS_KEX_FIPS(f, client, kexcmd) \ + f(client, rsa_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group14_sha256,kexcmd("diffie-hellman-group14-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group16_sha512,kexcmd("diffie-hellman-group16-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group18_sha512,kexcmd("diffie-hellman-group18-sha512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) +#endif + +#ifdef OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM +#define SNTRUP_OPENSSH_NAME "sntrup761x25519-sha512@openssh.com" +#define PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) \ + f(client, rsa_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_sntrup761x25519_sha512_openssh_com, kexcmd(SNTRUP_OPENSSH_NAME), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) +#endif + +#ifdef OPENSSH_SNTRUP761X25519_SHA512 +#define SNTRUP_NAME "sntrup761x25519-sha512" +#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \ + f(client, rsa_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_sntrup761x25519_sha512, kexcmd(SNTRUP_NAME), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) +#endif + +#if defined(OPENSSH_MLKEM768X25519_SHA256) +#define PKDTESTS_KEX_MLKEM768X25519(f, client, kexcmd) \ + f(client, rsa_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_mlkem768x25519_sha256, kexcmd("mlkem768x25519-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_MLKEM768X25519(f, client, kexcmd) +#endif + +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) +#define PKDTESTS_KEX_MLKEM768NISTP256(f, client, kexcmd) \ + f(client, rsa_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_mlkem768nistp256_sha256, kexcmd("mlkem768nistp256-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_MLKEM768NISTP256(f, client, kexcmd) +#endif + +#if defined(HAVE_MLKEM1024) && defined(OPENSSH_MLKEM1024NISTP384_SHA384) +#define PKDTESTS_KEX_MLKEM1024NISTP384(f, client, kexcmd) \ + f(client, rsa_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ed25519_mlkem1024nistp384_sha384, kexcmd("mlkem1024nistp384-sha384"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#else +#define PKDTESTS_KEX_MLKEM1024NISTP384(f, client, kexcmd) +#endif + +#define PKDTESTS_KEX_COMMON(f, client, kexcmd) \ + PKDTESTS_KEX_FIPS(f, client, kexcmd) \ + PKDTESTS_KEX_SNTRUP761(f, client, kexcmd) \ + PKDTESTS_KEX_SNTRUP761_OPENSSH(f, client, kexcmd) \ + PKDTESTS_KEX_MLKEM768X25519(f, client, kexcmd) \ + PKDTESTS_KEX_MLKEM768NISTP256(f, client, kexcmd) \ + PKDTESTS_KEX_MLKEM1024NISTP384(f, client, kexcmd) \ + f(client, rsa_curve25519_sha256, kexcmd("curve25519-sha256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group14_sha1, kexcmd("diffie-hellman-group14-sha1"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#if defined(WITH_GEX) + /* GEX_SHA256 is included in PKDTESTS_KEX_FIPS if available */ +#define PKDTESTS_KEX(f, client, kexcmd) \ + /* Kex algorithms. */ \ + PKDTESTS_KEX_COMMON(f, client, kexcmd) \ + f(client, rsa_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) +#else +#define PKDTESTS_KEX(f, client, kexcmd) \ + /* Kex algorithms. */ \ + f(client, ed25519_curve25519_sha256, kexcmd("curve25519-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_curve25519_sha256_libssh_org, kexcmd("curve25519-sha256@libssh.org"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_ecdh_sha2_nistp256, kexcmd("ecdh-sha2-nistp256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_ecdh_sha2_nistp384, kexcmd("ecdh-sha2-nistp384"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_ecdh_sha2_nistp521, kexcmd("ecdh-sha2-nistp521"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group14_sha256, kexcmd("diffie-hellman-group14-sha256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group16_sha512, kexcmd("diffie-hellman-group16-sha512"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group18_sha512, kexcmd("diffie-hellman-group18-sha512"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group1_sha1, kexcmd("diffie-hellman-group1-sha1"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group_exchange_sha256, kexcmd(GEX_SHA256), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_diffie_hellman_group_exchange_sha1, kexcmd(GEX_SHA1), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#endif + +#define PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ + f(client, rsa_aes128_ctr, ciphercmd("aes128-ctr"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_ctr, ciphercmd("aes256-ctr"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_aes128_ctr, ciphercmd("aes128-ctr"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_ctr, ciphercmd("aes256-ctr"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define PKDTESTS_CIPHER_FIPS(f, client, ciphercmd) \ + PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ + f(client, rsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_cbc, ciphercmd("aes256-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define CHACHA20 "chacha20-poly1305@openssh.com" +#define PKDTESTS_CIPHER_CHACHA(f, client, ciphercmd) \ + f(client, rsa_chacha20, ciphercmd(CHACHA20), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ed25519_chacha20, ciphercmd(CHACHA20), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ecdsa_256_chacha20, ciphercmd(CHACHA20), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_chacha20, ciphercmd(CHACHA20), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_chacha20, ciphercmd(CHACHA20), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define PKDTESTS_CIPHER(f, client, ciphercmd) \ + /* Ciphers. */ \ + PKDTESTS_CIPHER_COMMON(f, client, ciphercmd) \ + PKDTESTS_CIPHER_CHACHA(f, client, ciphercmd) \ + f(client, ed25519_aes128_ctr, ciphercmd("aes128-ctr"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes256_ctr, ciphercmd("aes256-ctr"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) + +#define AES128_GCM "aes128-gcm@openssh.com" +#define AES256_GCM "aes256-gcm@openssh.com" + +#define PKDTESTS_CIPHER_OPENSSHONLY_FIPS(f, client, ciphercmd) \ + f(client, rsa_aes128_gcm, ciphercmd(AES128_GCM), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_gcm, ciphercmd(AES256_GCM), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ecdsa_256_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_aes128_gcm, ciphercmd(AES128_GCM), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_gcm, ciphercmd(AES256_GCM), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + +#define PKDTESTS_CIPHER_OPENSSHONLY(f, client, ciphercmd) \ + /* Ciphers. */ \ + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(f, client, ciphercmd) \ + f(client, rsa_3des_cbc, ciphercmd("3des-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes128_cbc, ciphercmd("aes128-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes192_cbc, ciphercmd("aes192-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes256_cbc, ciphercmd("aes256-cbc"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_aes192_ctr, ciphercmd("aes192-ctr"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, ed25519_3des_cbc, ciphercmd("3des-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes128_cbc, ciphercmd("aes128-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes256_cbc, ciphercmd("aes256-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes192_cbc, ciphercmd("aes192-cbc"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes192_ctr, ciphercmd("aes192-ctr"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes128_gcm, ciphercmd(AES128_GCM), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_aes256_gcm, ciphercmd(AES256_GCM), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ecdsa_256_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_3des_cbc, ciphercmd("3des-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes128_cbc, ciphercmd("aes128-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes192_cbc, ciphercmd("aes192-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes256_cbc, ciphercmd("aes256-cbc"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_aes192_ctr, ciphercmd("aes192-ctr"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) + + +#define PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#define PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_hmac_sha1, maccmd("hmac-sha1"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_hmac_sha1, maccmd("hmac-sha1"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#ifdef DROPBEAR_SUPPORTS_HMAC_SHA1 +#define PKDTESTS_MAC_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) \ + f(client, ed25519_hmac_sha1, maccmd("hmac-sha1"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) +#define PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) +#else +#define PKDTESTS_MAC_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_BASE(f, client, maccmd) +#define PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) \ + PKDTESTS_MAC_FIPS_SHA1(f, client, maccmd) +#endif + +#define PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ + PKDTESTS_MAC_OPENSSHONLY_FIPS_SHA1(f, client, maccmd) \ + f(client, ecdsa_256_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_256_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_256, teardown, LIBSSH_ECDSA_256_TESTKEY) \ + f(client, ecdsa_384_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_384_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_384, teardown, LIBSSH_ECDSA_384_TESTKEY) \ + f(client, ecdsa_521_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, ecdsa_521_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ecdsa_521, teardown, LIBSSH_ECDSA_521_TESTKEY) \ + f(client, rsa_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#define PKDTESTS_MAC(f, client, maccmd) \ + /* MACs. */ \ + PKDTESTS_MAC_FIPS(f, client, maccmd) \ + f(client, ed25519_hmac_sha2_256, maccmd("hmac-sha2-256"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) + +#define PKDTESTS_MAC_OPENSSHONLY(f, client, maccmd) \ + PKDTESTS_MAC_OPENSSHONLY_FIPS(f, client, maccmd) \ + f(client, ed25519_hmac_sha1_etm, maccmd("hmac-sha1-etm@openssh.com"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_hmac_sha2_256_etm, maccmd("hmac-sha2-256-etm@openssh.com"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_hmac_sha2_512, maccmd("hmac-sha2-512"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) \ + f(client, ed25519_hmac_sha2_512_etm, maccmd("hmac-sha2-512-etm@openssh.com"), setup_ed25519, teardown, LIBSSH_ED25519_TESTKEY) + + +#define PKDTESTS_HOSTKEY_OPENSSHONLY_FIPS(f, client, hkcmd) \ + f(client, rsa_sha2_256, hkcmd("rsa-sha2-256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_sha2_512, hkcmd("rsa-sha2-512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_sha2_256_512, hkcmd("rsa-sha2-256,rsa-sha2-512"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) \ + f(client, rsa_sha2_512_256, hkcmd("rsa-sha2-512,rsa-sha2-256"), setup_rsa, teardown, LIBSSH_RSA_TESTKEY) + +#define PKDTESTS_HOSTKEY_OPENSSHONLY(f, client, hkcmd) \ + PKDTESTS_HOSTKEY_OPENSSHONLY_FIPS(f, client, hkcmd) + +static void torture_pkd_client_noop(void **state) { + struct pkd_state *pstate = (struct pkd_state *) (*state); + (void) pstate; + return; +} + +static void torture_pkd_runtest(const char *testname, + const char *testcmd) +{ + int i, rc; + char logfile[1024] = { 0 }; + int iterations = + (pkd_dargs.opts.iterations != 0) ? pkd_dargs.opts.iterations + : DEFAULT_ITERATIONS; + + for (i = 0; i < iterations; i++) { + rc = system_checked(testcmd); + assert_int_equal(rc, 0); + } + + /* Asserts did not trip: cleanup logs. */ + snprintf(&logfile[0], sizeof(logfile), "%s.out", testname); + unlink(logfile); + snprintf(&logfile[0], sizeof(logfile), "%s.err", testname); + unlink(logfile); +} + +/* + * Though each keytest function body is the same, separate functions are + * defined here to result in distinct output when running the tests. + */ + +#define emit_keytest(client, testname, sshcmd, setup, teardown, hostkey) \ + static void torture_pkd_## client ## _ ## testname(void **state) { \ + const char *tname = "torture_pkd_" #client "_" #testname; \ + char testcmd[2048] = { 0 }; \ + (void) state; \ + snprintf(&testcmd[0], sizeof(testcmd), sshcmd, hostkey, tname, tname); \ + torture_pkd_runtest(tname, testcmd); \ + } + +/* + * Actual test functions are emitted here. + */ +#define CLIENT_ID_FILE OPENSSH_RSA_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_rsa, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_rsa, OPENSSH_CERT_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_sha256_cert_rsa, OPENSSH_SHA256_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_rsa, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_rsa, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_MAC_CMD) +PKDTESTS_HOSTKEY_OPENSSHONLY(emit_keytest, openssh_rsa, OPENSSH_HOSTKEY_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE OPENSSH_ECDSA256_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_e256, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_e256, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_e256, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_e256, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_e256, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE + +/* Could add these passes, too: */ +//#define CLIENT_ID_FILE OPENSSH_ECDSA384_TESTKEY +//#define CLIENT_ID_FILE OPENSSH_ECDSA521_TESTKEY + +#define CLIENT_ID_FILE OPENSSH_ED25519_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_ed, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ed, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_ed, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_ed, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ed, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE + +#ifdef HAVE_SK_DUMMY +#define CLIENT_ID_FILE OPENSSH_ECDSA_SK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_ec_sk, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ec_sk, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_ec_sk, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_ec_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ec_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_ec_sk, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ec_sk, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE OPENSSH_ED25519_SK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, openssh_ed_sk, OPENSSH_CMD) +PKDTESTS_DEFAULT(emit_keytest, openssh_cert_ed_sk, OPENSSH_CERT_CMD) +PKDTESTS_KEX(emit_keytest, openssh_ed_sk, OPENSSH_KEX_CMD) +PKDTESTS_CIPHER(emit_keytest, openssh_ed_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_CIPHER_OPENSSHONLY(emit_keytest, openssh_ed_sk, OPENSSH_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, openssh_ed_sk, OPENSSH_MAC_CMD) +PKDTESTS_MAC_OPENSSHONLY(emit_keytest, openssh_ed_sk, OPENSSH_MAC_CMD) +#undef CLIENT_ID_FILE +#endif /* HAVE_SK_DUMMY */ + +#define CLIENT_ID_FILE DROPBEAR_RSA_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, dropbear_rsa, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear_rsa, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear_rsa, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE DROPBEAR_ECDSA256_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, dropbear_e256, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear_e256, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear_e256, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE DROPBEAR_ED25519_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, dropbear_ed, DROPBEAR_CMD) +PKDTESTS_CIPHER(emit_keytest, dropbear_ed, DROPBEAR_CIPHER_CMD) +PKDTESTS_MAC(emit_keytest, dropbear_ed, DROPBEAR_MAC_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE PUTTY_RSA_PPK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, putty_rsa, PUTTY_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE PUTTY_ED25519_PPK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, putty_ed, PUTTY_CMD) +#undef CLIENT_ID_FILE + +#define CLIENT_ID_FILE PUTTY_ECDSA256_PPK_TESTKEY +PKDTESTS_DEFAULT(emit_keytest, putty_e256, PUTTY_CMD) +#undef CLIENT_ID_FILE + +/* + * Define an array of testname strings mapped to their associated + * test function. Enables running tests individually by name from + * the command line. + */ + +#define emit_testmap(client, testname, sshcmd, setup, teardown, ...) \ + { "torture_pkd_" #client "_" #testname, \ + emit_unit_test(client, testname, sshcmd, setup, teardown, ##__VA_ARGS__) }, + +#define emit_unit_test(client, testname, sshcmd, setup, teardown, ...) \ + cmocka_unit_test_setup_teardown(torture_pkd_ ## client ## _ ## testname, \ + torture_pkd_ ## setup, \ + torture_pkd_ ## teardown) + +#define emit_unit_test_comma(client, testname, sshcmd, setup, teardown, ...) \ + emit_unit_test(client, testname, sshcmd, setup, teardown, ##__VA_ARGS__), + +struct { + const char *testname; + const struct CMUnitTest test; +} testmap[] = { + /* OpenSSH */ + + PKDTESTS_DEFAULT(emit_testmap, openssh_rsa, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_rsa, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_sha256_cert_rsa, OPENSSH_SHA256_CERT_CMD) + PKDTESTS_KEX(emit_testmap, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_HOSTKEY_OPENSSHONLY(emit_testmap, openssh_rsa, OPENSSH_HOSTKEY_CMD) + + PKDTESTS_DEFAULT(emit_testmap, openssh_e256, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_e256, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_testmap, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_e256, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_e256, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_testmap, openssh_ed, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_testmap, openssh_cert_ed, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_testmap, openssh_ed, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, openssh_ed, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_testmap, openssh_ed, OPENSSH_MAC_CMD) + + /* Dropbear */ + PKDTESTS_DEFAULT(emit_testmap, dropbear_rsa, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear_rsa, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear_rsa, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_testmap, dropbear_e256, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear_e256, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear_e256, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_testmap, dropbear_ed, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_testmap, dropbear_ed, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_testmap, dropbear_ed, DROPBEAR_MAC_CMD) + + /* PuTTY */ + PKDTESTS_DEFAULT(emit_testmap, putty_rsa, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_testmap, putty_e256, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_testmap, putty_ed, PUTTY_CMD) + + /* Noop */ + emit_testmap(client, noop, "", setup_noop, teardown, NULL) + + /* NULL tail entry */ + { .testname = NULL, + .test = { .name = NULL, + .test_func = NULL, + .setup_func = NULL, + .teardown_func = NULL } } +}; + +static int pkd_run_tests(void) { + int rc = -1; + int tindex = 0; + + const struct CMUnitTest openssh_tests[] = { + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_rsa, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_rsa, OPENSSH_CERT_CMD) + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_sha256_cert_rsa, + OPENSSH_SHA256_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_e256, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_e256, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ed, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ed, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_ed, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ed, OPENSSH_MAC_CMD) + +#ifdef HAVE_SK_DUMMY + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ec_sk, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ec_sk, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_ec_sk, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ec_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ec_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_ec_sk, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ec_sk, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_ed_sk, OPENSSH_CMD) + PKDTESTS_DEFAULT(emit_unit_test_comma, openssh_cert_ed_sk, OPENSSH_CERT_CMD) + PKDTESTS_KEX(emit_unit_test_comma, openssh_ed_sk, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, openssh_ed_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY(emit_unit_test_comma, openssh_ed_sk, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, openssh_ed_sk, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY(emit_unit_test_comma, openssh_ed_sk, OPENSSH_MAC_CMD) +#endif /* HAVE_SK_DUMMY */ + }; + + /* It is not possible to test hostkey and kex algorithms, because + * dbclient does not support setting hostkey and kex algorithms + * through cli (see 'man dbclient') + */ + const struct CMUnitTest dropbear_tests[] = { + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear_rsa, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear_rsa, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear_rsa, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear_e256, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear_e256, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear_e256, DROPBEAR_MAC_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, dropbear_ed, DROPBEAR_CMD) + PKDTESTS_CIPHER(emit_unit_test_comma, dropbear_ed, DROPBEAR_CIPHER_CMD) + PKDTESTS_MAC(emit_unit_test_comma, dropbear_ed, DROPBEAR_MAC_CMD) + }; + + const struct CMUnitTest putty_tests[] = { + PKDTESTS_DEFAULT(emit_unit_test_comma, putty_rsa, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, putty_e256, PUTTY_CMD) + + PKDTESTS_DEFAULT(emit_unit_test_comma, putty_ed, PUTTY_CMD) + }; + + const struct CMUnitTest openssh_fips_tests[] = { + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_CMD) + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_sha256_cert_rsa, + OPENSSH_SHA256_CERT_CMD) + PKDTESTS_KEX_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_rsa, OPENSSH_MAC_CMD) + + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_CMD) + PKDTESTS_DEFAULT_FIPS(emit_unit_test_comma, openssh_cert_e256, OPENSSH_CERT_CMD) + PKDTESTS_KEX_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_KEX_CMD) + PKDTESTS_CIPHER_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_CIPHER_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_CIPHER_CMD) + PKDTESTS_MAC_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + PKDTESTS_MAC_OPENSSHONLY_FIPS(emit_unit_test_comma, openssh_e256, OPENSSH_MAC_CMD) + }; + + const struct CMUnitTest noop_tests[] = { + emit_unit_test(client, noop, "", setup_noop, teardown, NULL) + }; + + /* Test list is populated depending on which clients are enabled. */ + struct CMUnitTest all_tests[(sizeof(openssh_tests) / sizeof(openssh_tests[0])) + + (sizeof(dropbear_tests) / sizeof(dropbear_tests[0])) + + (sizeof(putty_tests) / sizeof(putty_tests[0])) + + (sizeof(noop_tests) / sizeof(noop_tests[0]))]; + memset(&all_tests[0], 0x0, sizeof(all_tests)); + + /* Generate client keys and populate test list for each enabled client. */ + if (is_openssh_client_enabled()) { + setup_openssh_client_keys(); + if (ssh_fips_mode()) { + memcpy(&all_tests[tindex], &openssh_fips_tests[0], sizeof(openssh_fips_tests)); + tindex += (sizeof(openssh_fips_tests) / sizeof(openssh_fips_tests[0])); + } else { + memcpy(&all_tests[tindex], &openssh_tests[0], sizeof(openssh_tests)); + tindex += (sizeof(openssh_tests) / sizeof(openssh_tests[0])); + } + } + + if (is_dropbear_client_enabled()) { + setup_dropbear_client_keys(); + if (!ssh_fips_mode()) { + memcpy(&all_tests[tindex], &dropbear_tests[0], sizeof(dropbear_tests)); + tindex += (sizeof(dropbear_tests) / sizeof(dropbear_tests[0])); + } + } + + if (is_putty_client_enabled()) { + setup_putty_client_keys(); + if (!ssh_fips_mode()) { + memcpy(&all_tests[tindex], &putty_tests[0], sizeof(putty_tests)); + tindex += (sizeof(putty_tests) / sizeof(putty_tests[0])); + } + } + + memcpy(&all_tests[tindex], &noop_tests[0], sizeof(noop_tests)); + tindex += (sizeof(noop_tests) / sizeof(noop_tests[0])); + + if ((pkd_dargs.opts.testname == NULL) && + (pkd_dargs.opts.testmatch == NULL)) { + rc = _cmocka_run_group_tests("all tests", all_tests, tindex, NULL, NULL); + } else { + size_t i = 0; + size_t num_found = 0; + const char *testname = pkd_dargs.opts.testname; + const char *testmatch = pkd_dargs.opts.testmatch; + + struct CMUnitTest matching_tests[sizeof(all_tests)]; + memset(&matching_tests[0], 0x0, sizeof(matching_tests)); + + while (testmap[i].testname != NULL) { + if ((testname != NULL) && + (strcmp(testmap[i].testname, testname) == 0)) { + memcpy(&matching_tests[0], + &testmap[i].test, + sizeof(struct CMUnitTest)); + num_found += 1; + break; + } + + if ((testmatch != NULL) && + (strstr(testmap[i].testname, testmatch) != NULL)) { + memcpy(&matching_tests[num_found], + &testmap[i].test, + sizeof(struct CMUnitTest)); + num_found += 1; + } + + i += 1; + } + + if (num_found > 0) { + rc = _cmocka_run_group_tests("found", matching_tests, num_found, NULL, NULL); + } else { + fprintf(stderr, "Did not find test '%s'\n", testname); + } + } + + /* Clean up client keys for each enabled client. */ + if (is_dropbear_client_enabled()) { + cleanup_dropbear_client_keys(); + } + + if (is_openssh_client_enabled()) { + cleanup_openssh_client_keys(); + } + + if (is_putty_client_enabled()) { + cleanup_putty_client_keys(); + } + + /* Clean up any server keys that were generated. */ + cleanup_rsa_key(); + cleanup_ecdsa_keys(); + if (!ssh_fips_mode()) { + cleanup_ed25519_key(); + } + + return rc; +} + +static int pkd_init_temp_dir(void) { + int rc = 0; + char *mkdtemp_str = NULL; + pkd_dargs.original_dir_fd = -1; + + if (pkd_dargs.opts.temp_dir.argv_mkdtemp_str == NULL) { + return 0; + } + + pkd_dargs.original_dir_fd = open(".", O_RDONLY); + if (pkd_dargs.original_dir_fd < 0) { + fprintf(stderr, "pkd_init_temp_dir open failed\n"); + return -1; + } + + mkdtemp_str = strdup(pkd_dargs.opts.temp_dir.argv_mkdtemp_str); + if (mkdtemp_str == NULL) { + fprintf(stderr, "pkd_init_temp_dir strdup failed\n"); + goto errstrdup; + } + pkd_dargs.opts.temp_dir.mkdtemp_str = mkdtemp_str; + + if (mkdtemp(mkdtemp_str) == NULL) { + fprintf(stderr, "pkd_init_temp_dir mkdtemp '%s' failed\n", mkdtemp_str); + goto errmkdtemp; + } + + rc = chdir(mkdtemp_str); + if (rc != 0) { + fprintf(stderr, "pkd_init_temp_dir chdir '%s' failed\n", mkdtemp_str); + goto errchdir; + } + + return 0; + +errchdir: + rmdir(mkdtemp_str); +errmkdtemp: + free(mkdtemp_str); +errstrdup: + close(pkd_dargs.original_dir_fd); + pkd_dargs.original_dir_fd = -1; + rc = -1; + return rc; +} + +static int pkd_init_socket_wrapper(void) { + int rc = 0; + char *mkdtemp_str = NULL; + + if (pkd_dargs.opts.socket_wrapper.argv_mkdtemp_str == NULL) { + goto out; + } + + mkdtemp_str = strdup(pkd_dargs.opts.socket_wrapper.argv_mkdtemp_str); + if (mkdtemp_str == NULL) { + fprintf(stderr, "pkd_init_socket_wrapper strdup failed\n"); + goto errstrdup; + } + pkd_dargs.opts.socket_wrapper.mkdtemp_str = mkdtemp_str; + + if (mkdtemp(mkdtemp_str) == NULL) { + fprintf(stderr, "pkd_init_socket_wrapper mkdtemp '%s' failed\n", mkdtemp_str); + goto errmkdtemp; + } + + if (setenv("SOCKET_WRAPPER_DIR", mkdtemp_str, 1) != 0) { + fprintf(stderr, "pkd_init_socket_wrapper setenv failed\n"); + goto errsetenv; + } + + goto out; +errsetenv: +errmkdtemp: + free(mkdtemp_str); +errstrdup: + rc = -1; +out: + return rc; +} + +static int pkd_rmfiles(const char *path) { + char bin[1024] = { 0 }; + snprintf(&bin[0], sizeof(bin), "rm -f %s/*", path); + return system_checked(bin); +} + +static int pkd_cleanup_temp_dir(void) { + int rc = 0; + + if (pkd_dargs.opts.temp_dir.mkdtemp_str == NULL) { + return 0; + } + + if (fchdir(pkd_dargs.original_dir_fd) != 0) { + fprintf(stderr, "pkd_cleanup_temp_dir failed fchdir\n"); + rc = -1; + goto out; + } + + if (rmdir(pkd_dargs.opts.temp_dir.mkdtemp_str) != 0) { + fprintf(stderr, "pkd_cleanup_temp_dir rmdir '%s' failed\n", + pkd_dargs.opts.temp_dir.mkdtemp_str); + rc = -1; + goto out; + } + +out: + close(pkd_dargs.original_dir_fd); + pkd_dargs.original_dir_fd = -1; + free(pkd_dargs.opts.temp_dir.mkdtemp_str); + return rc; +} + +static int pkd_cleanup_socket_wrapper(void) { + int rc = 0; + + if (pkd_dargs.opts.socket_wrapper.mkdtemp_str == NULL) { + goto out; + } + + /* clean up socket-wrapper unix domain sockets */ + if (pkd_rmfiles(pkd_dargs.opts.socket_wrapper.mkdtemp_str) != 0) { + fprintf(stderr, "pkd_cleanup_socket_wrapper pkd_rmfiles '%s' failed\n", + pkd_dargs.opts.socket_wrapper.mkdtemp_str); + goto errrmfiles; + } + + if (rmdir(pkd_dargs.opts.socket_wrapper.mkdtemp_str) != 0) { + fprintf(stderr, "pkd_cleanup_socket_wrapper rmdir '%s' failed\n", + pkd_dargs.opts.socket_wrapper.mkdtemp_str); + goto errrmdir; + } + + goto outfree; +errrmdir: +errrmfiles: + rc = -1; +outfree: + free(pkd_dargs.opts.socket_wrapper.mkdtemp_str); +out: + return rc; +} + +int main(int argc, char **argv) { + int i = 0; + int rc = 0; + int exit_code = -1; + + unsetenv("SSH_AUTH_SOCK"); + + pkd_dargs.payload.buf = default_payload_buf; + pkd_dargs.payload.len = default_payload_len; + + rc = ssh_init(); + if (rc != 0) { + goto out; + } + +#ifdef HAVE_ARGP_H + argp_parse(&parser, argc, argv, 0, 0, NULL); +#else /* HAVE_ARGP_H */ + (void) argc; (void) argv; +#endif /* HAVE_ARGP_H */ + + rc = pkd_init_temp_dir(); + if (rc != 0) { + fprintf(stderr, "pkd_init_temp_dir failed: %d\n", rc); + goto out_finalize; + } + + rc = pkd_init_socket_wrapper(); + if (rc != 0) { + fprintf(stderr, "pkd_init_socket_wrapper failed: %d\n", rc); + goto out_tempdir; + } + + if (pkd_dargs.opts.list != 0) { + while (testmap[i].testname != NULL) { + printf("%s\n", testmap[i++].testname); + } + } else { + exit_code = pkd_run_tests(); + if (exit_code != 0) { + fprintf(stderr, "pkd_run_tests failed: %d\n", exit_code); + } + } + + rc = pkd_cleanup_socket_wrapper(); + if (rc != 0) { + fprintf(stderr, "pkd_cleanup_socket_wrapper failed: %d\n", rc); + } + +out_tempdir: + rc = pkd_cleanup_temp_dir(); + if (rc != 0) { + fprintf(stderr, "pkd_cleanup_temp_dir failed: %d\n", rc); + } + +out_finalize: + rc = ssh_finalize(); + if (rc != 0) { + fprintf(stderr, "ssh_finalize: %d\n", rc); + } +#if defined(HAVE_LIBCRYPTO) + OPENSSL_cleanup(); +#endif +out: + return exit_code; +} \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.c b/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.c new file mode 100644 index 000000000000..34e071e24e12 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.c @@ -0,0 +1,269 @@ +/* + * pkd_keyutil.c -- pkd test key utilities + * + * (c) 2014 Jon Simons + */ + +#include "config.h" + +#include // for cmocka +#include // for cmocka +#include // for cmocka +#include // for cmocka +#include + +#include +#include +#include +#include + +#include "torture.h" // for ssh_fips_mode() + +#include "pkd_client.h" +#include "pkd_keyutil.h" +#include "pkd_util.h" + +void setup_rsa_key(void) { + int rc = 0; + if (access(LIBSSH_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + LIBSSH_RSA_TESTKEY); + } + assert_int_equal(rc, 0); +} + +void setup_ed25519_key(void) { + int rc = 0; + if (access(LIBSSH_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " + LIBSSH_ED25519_TESTKEY); + } + assert_int_equal(rc, 0); +} + +void setup_ecdsa_keys(void) { + int rc = 0; + + if (access(LIBSSH_ECDSA_256_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f " + LIBSSH_ECDSA_256_TESTKEY); + assert_int_equal(rc, 0); + } + if (access(LIBSSH_ECDSA_384_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 384 -q -N \"\" -f " + LIBSSH_ECDSA_384_TESTKEY); + assert_int_equal(rc, 0); + } + if (access(LIBSSH_ECDSA_521_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 521 -q -N \"\" -f " + LIBSSH_ECDSA_521_TESTKEY); + assert_int_equal(rc, 0); + } +} + +void cleanup_rsa_key(void) { + cleanup_key(LIBSSH_RSA_TESTKEY); +} + +void cleanup_ed25519_key(void) { + cleanup_key(LIBSSH_ED25519_TESTKEY); +} + +void cleanup_ecdsa_keys(void) { + cleanup_key(LIBSSH_ECDSA_256_TESTKEY); + cleanup_key(LIBSSH_ECDSA_384_TESTKEY); + cleanup_key(LIBSSH_ECDSA_521_TESTKEY); +} + +void setup_openssh_client_keys(void) { + int rc = 0; + + if (access(OPENSSH_CA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + OPENSSH_CA_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + OPENSSH_RSA_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_RSA_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_RSA_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_RSA_TESTKEY "-sha256-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -t rsa-sha2-256 " + "-s " OPENSSH_CA_TESTKEY " " + OPENSSH_RSA_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA256_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f " + OPENSSH_ECDSA256_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA256_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ECDSA256_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA384_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 384 -q -N \"\" -f " + OPENSSH_ECDSA384_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA384_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ECDSA384_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA521_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 521 -q -N \"\" -f " + OPENSSH_ECDSA521_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ECDSA521_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ECDSA521_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + + if (!ssh_fips_mode()) { + + if (access(OPENSSH_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " + OPENSSH_ED25519_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ED25519_TESTKEY "-cert.pub", F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -I ident -s " OPENSSH_CA_TESTKEY " " + OPENSSH_ED25519_TESTKEY ".pub 2>/dev/null"); + } + assert_int_equal(rc, 0); + } + +#ifdef HAVE_SK_DUMMY + setenv("SSH_SK_PROVIDER", SK_DUMMY_LIBRARY_PATH, 1); + if (access(OPENSSH_ECDSA_SK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa-sk -q -N \"\" -f " + OPENSSH_ECDSA_SK_TESTKEY); + } + assert_int_equal(rc, 0); + + if (access(OPENSSH_ED25519_SK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519-sk -q -N \"\" -f " + OPENSSH_ED25519_SK_TESTKEY); + } + assert_int_equal(rc, 0); +#endif +} + +void cleanup_openssh_client_keys(void) { + cleanup_key(OPENSSH_CA_TESTKEY); + cleanup_key(OPENSSH_RSA_TESTKEY); + cleanup_file(OPENSSH_RSA_TESTKEY "-sha256-cert.pub"); + cleanup_key(OPENSSH_ECDSA256_TESTKEY); + cleanup_key(OPENSSH_ECDSA384_TESTKEY); + cleanup_key(OPENSSH_ECDSA521_TESTKEY); + if (!ssh_fips_mode()) { + cleanup_key(OPENSSH_ED25519_TESTKEY); + } +#ifdef HAVE_SK_DUMMY + cleanup_key(OPENSSH_ECDSA_SK_TESTKEY); + cleanup_key(OPENSSH_ED25519_SK_TESTKEY); +#endif +} + +void setup_dropbear_client_keys(void) +{ + int rc = 0; + if (access(DROPBEAR_RSA_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t rsa -f " + DROPBEAR_RSA_TESTKEY " 1>/dev/null 2>/dev/null"); + } + assert_int_equal(rc, 0); + if (access(DROPBEAR_ECDSA256_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t ecdsa -f " + DROPBEAR_ECDSA256_TESTKEY + " 1>/dev/null 2>/dev/null"); + } + assert_int_equal(rc, 0); + if (access(DROPBEAR_ED25519_TESTKEY, F_OK) != 0) { + rc = system_checked(DROPBEAR_KEYGEN " -t ed25519 -f " + DROPBEAR_ED25519_TESTKEY + " 1>/dev/null 2>/dev/null"); + } + assert_int_equal(rc, 0); +} + +void cleanup_dropbear_client_keys(void) +{ + cleanup_key(DROPBEAR_RSA_TESTKEY); + cleanup_key(DROPBEAR_ECDSA256_TESTKEY); + cleanup_key(DROPBEAR_ED25519_TESTKEY); +} + +void setup_putty_client_keys(void) +{ + int rc = 0; + + /* RSA Keys */ + if (access(PUTTY_RSA_TESTKEY, F_OK) != 0 || + access(PUTTY_RSA_PPK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t rsa -q -N \"\" -f " + PUTTY_RSA_TESTKEY); + assert_int_equal(rc, 0); + + rc = system_checked(PUTTY_KEYGEN " " PUTTY_RSA_TESTKEY + " -O private -o " PUTTY_RSA_PPK_TESTKEY); + assert_int_equal(rc, 0); + } + + /* ECDSA 256 Keys */ + if (access(PUTTY_ECDSA256_TESTKEY, F_OK) != 0 || + access(PUTTY_ECDSA256_PPK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ecdsa -b 256 -q -N \"\" -f " + PUTTY_ECDSA256_TESTKEY); + assert_int_equal(rc, 0); + + rc = system_checked(PUTTY_KEYGEN " " PUTTY_ECDSA256_TESTKEY + " -O private -o " PUTTY_ECDSA256_PPK_TESTKEY); + assert_int_equal(rc, 0); + } + + /* ED25519 Keys */ + if (access(PUTTY_ED25519_TESTKEY, F_OK) != 0 || + access(PUTTY_ED25519_PPK_TESTKEY, F_OK) != 0) { + rc = system_checked(OPENSSH_KEYGEN " -t ed25519 -q -N \"\" -f " + PUTTY_ED25519_TESTKEY); + assert_int_equal(rc, 0); + + rc = system_checked(PUTTY_KEYGEN " " PUTTY_ED25519_TESTKEY + " -O private -o " PUTTY_ED25519_PPK_TESTKEY); + assert_int_equal(rc, 0); + } +} + +void cleanup_putty_client_keys(void) +{ + cleanup_key(PUTTY_RSA_TESTKEY); + cleanup_file(PUTTY_RSA_PPK_TESTKEY); + + cleanup_key(PUTTY_ECDSA256_TESTKEY); + cleanup_file(PUTTY_ECDSA256_PPK_TESTKEY); + + cleanup_key(PUTTY_ED25519_TESTKEY); + cleanup_file(PUTTY_ED25519_PPK_TESTKEY); +} \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.h b/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.h new file mode 100644 index 000000000000..86357c448b1f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_keyutil.h @@ -0,0 +1,67 @@ +/* + * pkd_keyutil.h -- + * + * (c) 2014 Jon Simons + */ + +#ifndef __PKD_KEYUTIL_H__ +#define __PKD_KEYUTIL_H__ + +#include "config.h" + +/* Server keys. */ +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#define LIBSSH_ECDSA_256_TESTKEY "libssh_testkey.id_ecdsa256" +#define LIBSSH_ECDSA_384_TESTKEY "libssh_testkey.id_ecdsa384" +#define LIBSSH_ECDSA_521_TESTKEY "libssh_testkey.id_ecdsa521" + +void setup_rsa_key(void); +void setup_ed25519_key(void); +void setup_ecdsa_keys(void); +void cleanup_rsa_key(void); +void cleanup_ed25519_key(void); +void cleanup_ecdsa_keys(void); + +/* Client keys. */ +#define OPENSSH_RSA_TESTKEY "openssh_testkey.id_rsa" +#define OPENSSH_ECDSA256_TESTKEY "openssh_testkey.id_ecdsa256" +#define OPENSSH_ECDSA384_TESTKEY "openssh_testkey.id_ecdsa384" +#define OPENSSH_ECDSA521_TESTKEY "openssh_testkey.id_ecdsa521" +#define OPENSSH_ED25519_TESTKEY "openssh_testkey.id_ed25519" +#define OPENSSH_CA_TESTKEY "libssh_testkey.ca" +#define OPENSSH_ECDSA_SK_TESTKEY "openssh_testkey.id_ecdsa-sk" +#define OPENSSH_ED25519_SK_TESTKEY "openssh_testkey.id_ed25519-sk" + +#define DROPBEAR_RSA_TESTKEY "dropbear_testkey.id_rsa" +#define DROPBEAR_ECDSA256_TESTKEY "dropbear_testkey.id_ecdsa256" +#define DROPBEAR_ED25519_TESTKEY "dropbear_testkey.id_ed25519" + +#define PUTTY_RSA_TESTKEY "putty_testkey.id_rsa" +#define PUTTY_RSA_PPK_TESTKEY "putty_testkey.id_rsa.ppk" +#define PUTTY_ECDSA256_TESTKEY "putty_testkey.id_ecdsa256" +#define PUTTY_ECDSA256_PPK_TESTKEY "putty_testkey.id_ecdsa256.ppk" +#define PUTTY_ED25519_TESTKEY "putty_testkey.id_ed25519" +#define PUTTY_ED25519_PPK_TESTKEY "putty_testkey.id_ed25519.ppk" + +void setup_openssh_client_keys(void); +void cleanup_openssh_client_keys(void); + +void setup_dropbear_client_keys(void); +void cleanup_dropbear_client_keys(void); + +void setup_putty_client_keys(void); +void cleanup_putty_client_keys(void); + +#define cleanup_file(name) do {\ + if (access((name), F_OK) != -1) {\ + unlink((name));\ + }} while (0) + +#define cleanup_key(name) do {\ + cleanup_file((name));\ + cleanup_file((name ".pub"));\ + cleanup_file((name "-cert.pub"));\ + } while (0) + +#endif /* __PKD_KEYUTIL_H__ */ diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_util.c b/src/libs/libssh-0.12.2/tests/pkd/pkd_util.c new file mode 100644 index 000000000000..e4866bd5d7f3 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_util.c @@ -0,0 +1,121 @@ +/* + * pkd_util.c -- pkd utilities + * + * (c) 2014, 2018 Jon Simons + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "pkd_client.h" +#include "pkd_util.h" + +/** + * @brief runs system(3); exits if that is interrupted with SIGINT/QUIT + * @returns 0 upon success, non-zero otherwise + */ +int system_checked(const char *cmd) { + int rc = system(cmd); + + if (WIFSIGNALED(rc) && + ((WTERMSIG(rc) == SIGINT) || (WTERMSIG(rc) == SIGQUIT))) { + exit(1); + } + + if (rc == -1) { + return -1; + } + + return WEXITSTATUS(rc); +} + +static int bin_exists(const char *binary) { + char bin[1024] = { 0 }; + snprintf(&bin[0], sizeof(bin), "type %s 1>/dev/null 2>/dev/null", binary); + return (system_checked(bin) == 0); +} + +static int is_openssh_client_new_enough(void) { + int rc = -1; + FILE *fp = NULL; + char version_buff[1024] = { 0 }; + char *version; + + static int version_ok = 0; + unsigned long int major = 0; + char *tmp = NULL; + + if (version_ok) { + return version_ok; + } + + fp = popen("ssh -V 2>&1", "r"); + if (fp == NULL) { + fprintf(stderr, "failed to get OpenSSH client version\n"); + goto done; + } + + do { + if (fgets(&version_buff[0], sizeof(version_buff), fp) == NULL) { + fprintf(stderr, "failed to get OpenSSH client version string\n"); + goto errfgets; + } + version = strstr(version_buff, "OpenSSH"); + } while(version == NULL); + + /* "OpenSSH_...." */ + if (strlen(version) < 11) { + goto errversion; + } + + /* Extract major. */ + major = strtoul(version + 8, &tmp, 10); + if ((tmp == (version + 8)) || + ((errno == ERANGE) && (major == ULONG_MAX)) || + ((errno != 0) && (major == 0)) || + ((major < 1) || (major > 100))) { + fprintf(stderr, "failed to parse OpenSSH client version, " + "errno %d\n", errno); + errno = 0; + goto errversion; + } + + if (major < 7) { + fprintf(stderr, "error: minimum OpenSSH client version " + "required is 7, found: %ld\n", major); + goto errversion; + } + + version_ok = 1; + +errversion: +errfgets: + rc = pclose(fp); + if (rc != 0) { + fprintf(stderr, "failed to get OpenSSH client version: %d\n", rc); + } +done: + return version_ok; +} + +int is_openssh_client_enabled(void) { + return (bin_exists(OPENSSH_BINARY) && + bin_exists(OPENSSH_KEYGEN) && + is_openssh_client_new_enough()); +} + +int is_dropbear_client_enabled(void) { + return (bin_exists(DROPBEAR_BINARY) && bin_exists(DROPBEAR_KEYGEN)); +} + +int is_putty_client_enabled(void) +{ + return (bin_exists(PUTTY_BINARY) && + bin_exists(PUTTY_KEYGEN) && + bin_exists(OPENSSH_KEYGEN)); +} diff --git a/src/libs/libssh-0.12.2/tests/pkd/pkd_util.h b/src/libs/libssh-0.12.2/tests/pkd/pkd_util.h new file mode 100644 index 000000000000..8c4a637d43f1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/pkd/pkd_util.h @@ -0,0 +1,17 @@ +/* + * pkd_keyutil.h -- + * + * (c) 2014 Jon Simons + */ + +#ifndef __PKD_UTIL_H__ +#define __PKD_UTIL_H__ + +int system_checked(const char *cmd); + +/* Is client 'X' enabled? */ +int is_openssh_client_enabled(void); +int is_dropbear_client_enabled(void); +int is_putty_client_enabled(void); + +#endif /* __PKD_UTIL_H__ */ \ No newline at end of file diff --git a/src/libs/libssh-0.12.2/tests/server/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/server/CMakeLists.txt new file mode 100644 index 000000000000..ab9ce5b4e0ee --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/CMakeLists.txt @@ -0,0 +1,62 @@ +project(servertests C) + +if (WITH_SERVER AND UNIX AND NOT WIN32) + +find_package(socket_wrapper) + +add_subdirectory(test_server) + +set(LIBSSH_SERVER_TESTS + torture_server_default + torture_server_auth_kbdint + torture_server_config + torture_server_algorithms + torture_sftpserver +) + +if (WITH_GSSAPI AND GSSAPI_FOUND AND GSSAPI_TESTING) + set(LIBSSH_SERVER_TESTS + ${LIBSSH_SERVER_TESTS} + torture_gssapi_server_auth + torture_gssapi_server_auth_cb + torture_gssapi_server_delegation + torture_gssapi_server_key_exchange + torture_gssapi_server_key_exchange_null + torture_gssapi_server_key_exchange_fallback) +endif() + +include_directories(${libssh_SOURCE_DIR}/include + ${libssh_BINARY_DIR}/include + ${libssh_BINARY_DIR} + test_server) + +set(TORTURE_SERVER_ENVIRONMENT ${TORTURE_ENVIRONMENT}) +list(APPEND TORTURE_SERVER_ENVIRONMENT NSS_WRAPPER_HOSTS=${CMAKE_BINARY_DIR}/tests/etc/hosts) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif () + +foreach(_SRV_TEST ${LIBSSH_SERVER_TESTS}) + add_cmocka_test(${_SRV_TEST} + SOURCES ${_SRV_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TORTURE_LIBRARY} testserver util + ) + + if (OSX) + set_property( + TEST + ${_SRV_TEST} + PROPERTY + ENVIRONMENT DYLD_FORCE_FLAT_NAMESPACE=1;DYLD_INSERT_LIBRARIES=${SOCKET_WRAPPER_LIBRARY}) + else () + set_property( + TEST + ${_SRV_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_SERVER_ENVIRONMENT}) + endif() +endforeach() + +endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/server/test_server/CMakeLists.txt new file mode 100644 index 000000000000..f109a47d3c53 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/CMakeLists.txt @@ -0,0 +1,38 @@ +project(test_server C) + +if (WITH_SERVER AND UNIX AND NOT WIN32) + +find_package(socket_wrapper) + +set(server_SRCS + main.c +) + +add_library(testserver STATIC + test_server.c + default_cb.c + sftpserver_cb.c + testserver_common.c) +if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(testserver) +endif (WITH_COVERAGE) + +include_directories(${libssh_SOURCE_DIR}/include + ${libssh_BINARY_DIR}/include + ${libssh_BINARY_DIR}) + +if (ARGP_INCLUDE_DIR) + include_directories(${ARGP_INCLUDE_DIR}) +endif () + +if (UNIX AND NOT WIN32) + add_executable(test_server ${server_SRCS}) + target_compile_options(test_server PRIVATE ${DEFAULT_C_COMPILE_FLAGS}) + target_link_libraries(test_server + PRIVATE testserver ${TORTURE_LINK_LIBRARIES} ${ARGP_LIBRARIES} util) + if (WITH_COVERAGE) + append_coverage_compiler_flags_to_target(test_server) + endif (WITH_COVERAGE) +endif () + +endif (WITH_SERVER AND UNIX AND NOT WIN32) diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.c b/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.c new file mode 100644 index 000000000000..dcbfd5059db3 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.c @@ -0,0 +1,1105 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + + +#include "config.h" +#include "test_server.h" +#include "default_cb.h" +#include "testserver_common.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_LIBUTIL_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif + +#ifdef WITH_GSSAPI +#include +#endif + +int auth_none_cb(UNUSED_PARAM(ssh_session session), + const char *user, + void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_string banner = NULL; + + sdata = (struct session_data_st *)userdata; + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto denied; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + printf("None authentication of user %s\n", user); + + /* Send the banner */ + banner = ssh_string_from_char(SSHD_BANNER_MESSAGE); + if (banner == NULL) { + goto denied; + } + if (ssh_send_issue_banner(session, banner) == SSH_ERROR) { + fprintf(stderr, "Error: Failed to send the banner.\n"); + goto denied; + } +denied: + ssh_string_free(banner); + return SSH_AUTH_DENIED; +} + +int auth_pubkey_cb(UNUSED_PARAM(ssh_session session), + const char *user, + UNUSED_PARAM(struct ssh_key_struct *pubkey), + char signature_state, + void *userdata) +{ + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + printf("Public key authentication of user %s\n", user); + + switch(signature_state) { + case SSH_PUBLICKEY_STATE_NONE: + case SSH_PUBLICKEY_STATE_VALID: + break; + default: + goto denied; + } + + /* TODO */ + /* Check whether the user and public key are in authorized keys list */ + + /* Authenticated */ + printf("Authenticated\n"); + sdata->authenticated = 1; + sdata->auth_attempts = 0; + return SSH_AUTH_SUCCESS; + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} + +/* TODO implement proper pam authentication cb */ +int auth_password_cb(UNUSED_PARAM(ssh_session session), + const char *user, + const char *password, + void *userdata) +{ + bool known_user = false; + bool valid_password = false; + + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + if (sdata->password == NULL) { + fprintf(stderr, "Error: expected password not set\n"); + goto denied; + } + + printf("Password authentication of user %s\n", user); + + known_user = !(strcmp(user, sdata->username)); + valid_password = !(strcmp(password, sdata->password)); + + if (known_user && valid_password) { + sdata->authenticated = 1; + sdata->auth_attempts = 0; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} + +static int kbdint_check_response(ssh_session session, struct session_data_st *sdata) +{ + int count, cmp; + const char *answer = NULL; + + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + return 0; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + cmp = strcasecmp(sdata->username, answer); + if (cmp != 0) { + return 0; + } + answer = ssh_userauth_kbdint_getanswer(session, 1); + cmp = strcmp(sdata->password, answer); + if (cmp != 0) { + return 0; + } + + return 1; +} + +static int +auth_kbdint_cb(ssh_message message, ssh_session session, void *userdata) +{ + struct session_data_st *sdata = (struct session_data_st *)userdata; + + const char *name = "\n\nKeyboard-Interactive Fancy Authentication\n"; + const char *instruction = "Get yourself authenticated"; + const char *prompts[2] = {"Username: ", "Password: "}; + char echo[] = {1, 0}; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + return SSH_AUTH_DENIED; + } + + if (!ssh_message_auth_kbdint_is_response(message)) { + printf("User %s wants to auth with kbdint\n", + ssh_message_auth_user(message)); + ssh_message_auth_interactive_request(message, + name, + instruction, + 2, + prompts, + echo); + return SSH_AUTH_INFO; + } else { + if (kbdint_check_response(session, sdata)) { + sdata->authenticated = 1; + return SSH_AUTH_SUCCESS; + } + } + + return SSH_AUTH_DENIED; +} + +#if WITH_GSSAPI +int auth_gssapi_mic_cb(UNUSED_PARAM(ssh_session session), + const char *user, + const char *principal, + void *userdata) +{ + struct session_data_st *sdata = NULL; + krb5_context krb5_ctx; + krb5_principal krb5_princ; + + if (user == NULL || principal == NULL || userdata == NULL) { + fprintf(stderr, "Error: invalid arguments to GSSAPI auth callback\n"); + return SSH_AUTH_ERROR; + } + + sdata = (struct session_data_st *)userdata; + + if (krb5_init_context(&krb5_ctx) != 0) { + fprintf(stderr, "Error: failed to initialize krb5 context\n"); + return SSH_AUTH_ERROR; + } + + if (krb5_parse_name(krb5_ctx, principal, &krb5_princ) != 0) { + fprintf(stderr, "Error: failed to parse krb5 principal name\n"); + krb5_free_context(krb5_ctx); + return SSH_AUTH_ERROR; + } + + if (!krb5_kuserok(krb5_ctx, krb5_princ, user)) { + krb5_free_principal(krb5_ctx, krb5_princ); + krb5_free_context(krb5_ctx); + sdata->auth_attempts++; + return SSH_AUTH_DENIED; + } + + krb5_free_principal(krb5_ctx, krb5_princ); + krb5_free_context(krb5_ctx); + + printf("Authenticated\n"); + sdata->authenticated = 1; + sdata->auth_attempts = 0; + return SSH_AUTH_SUCCESS; +} +#endif + +int channel_data_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *data, + uint32_t len, + UNUSED_PARAM(int is_stderr), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if (len == 0 || cdata->pid < 1 || kill(cdata->pid, 0) < 0) { + rc = SSH_OK; + goto end; + } + + rc = write(cdata->child_stdin, (char *) data, len); + +end: + return rc; +} + +void channel_eof_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_close_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_signal_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *signal), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_exit_status_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(int exit_status), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +void channel_exit_signal_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *signal), + UNUSED_PARAM(int core), + UNUSED_PARAM(const char *errmsg), + UNUSED_PARAM(const char *lang), + void *userdata) +{ + struct channel_data_st *cdata; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto end; + } + +end: + return; +} + +int channel_pty_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *term), + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + rc = openpty(&cdata->pty_master, + &cdata->pty_slave, + NULL, + NULL, + cdata->winsize); + if (rc != 0) { + fprintf(stderr, "Failed to open pty\n"); + rc = SSH_ERROR; + goto end; + } + + rc = SSH_OK; + +end: + return rc; +} + +int channel_pty_resize_cb(ssh_session session, + ssh_channel channel, + int cols, + int rows, + int py, + int px, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + (void) session; + (void) channel; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->winsize->ws_row = rows; + cdata->winsize->ws_col = cols; + cdata->winsize->ws_xpixel = px; + cdata->winsize->ws_ypixel = py; + + if (cdata->pty_master != -1) { + rc = ioctl(cdata->pty_master, TIOCSWINSZ, cdata->winsize); + goto end; + } + + rc = SSH_ERROR; + +end: + return rc; +} + +void channel_auth_agent_req_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ +} + +void channel_x11_req_callback(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(int single_connection), + UNUSED_PARAM(const char *auth_protocol), + UNUSED_PARAM(const char *auth_cookie), + UNUSED_PARAM(uint32_t screen_number), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ +} + +static int exec_pty(const char *mode, + const char *command, + struct channel_data_st *cdata) +{ + int rc; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + cdata->pid = fork(); + switch(cdata->pid) { + case -1: + close(cdata->pty_master); + close(cdata->pty_slave); + fprintf(stderr, "Failed to fork\n"); + rc = SSH_ERROR; + goto end; + case 0: + close(cdata->pty_master); + if (login_tty(cdata->pty_slave) != 0) { + finalize_openssl(); + exit(1); + } + execl("/bin/sh", "sh", mode, command, NULL); + finalize_openssl(); + exit(0); + default: + close(cdata->pty_slave); + /* pty fd is bi-directional */ + cdata->child_stdout = cdata->child_stdin = cdata->pty_master; + } + + rc = SSH_OK; + +end: + return rc; +} + +static int exec_nopty(const char *command, struct channel_data_st *cdata) +{ + int in[2], out[2], err[2]; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + goto stdin_failed; + } + + /* Do the plumbing to be able to talk with the child process. */ + if (pipe(in) != 0) { + goto stdin_failed; + } + if (pipe(out) != 0) { + goto stdout_failed; + } + if (pipe(err) != 0) { + goto stderr_failed; + } + + switch(cdata->pid = fork()) { + case -1: + goto fork_failed; + case 0: + /* Finish the plumbing in the child process. */ + close(in[1]); + close(out[0]); + close(err[0]); + dup2(in[0], STDIN_FILENO); + dup2(out[1], STDOUT_FILENO); + dup2(err[1], STDERR_FILENO); + close(in[0]); + close(out[1]); + close(err[1]); + /* exec the requested command. */ + execl("/bin/sh", "sh", "-c", command, NULL); + finalize_openssl(); + exit(0); + } + + close(in[0]); + close(out[1]); + close(err[1]); + + cdata->child_stdin = in[1]; + cdata->child_stdout = out[0]; + cdata->child_stderr = err[0]; + + return SSH_OK; + +fork_failed: + close(err[0]); + close(err[1]); +stderr_failed: + close(out[0]); + close(out[1]); +stdout_failed: + close(in[0]); + close(in[1]); +stdin_failed: + return SSH_ERROR; +} + +int channel_shell_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if(cdata->pid > 0) { + rc = SSH_ERROR; + goto end; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + rc = exec_pty("-l", NULL, cdata); + goto end; + } + + /* Client requested a shell without a pty, let's pretend we allow that */ + rc = SSH_OK; + +end: + return rc; +} + +int channel_exec_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + const char *command, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + if(cdata->pid > 0) { + rc = SSH_ERROR; + goto end; + } + + if (cdata->pty_master != -1 && cdata->pty_slave != -1) { + rc = exec_pty("-c", command, cdata); + goto end; + } + + rc = exec_nopty(command, cdata); + +end: + return rc; +} + +int channel_env_request_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(const char *env_name), + UNUSED_PARAM(const char *env_value), + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + rc = SSH_OK; + +end: + return rc; +} + +int channel_subsystem_request_cb(ssh_session session, + ssh_channel channel, + const char *subsystem, + void *userdata) +{ + struct channel_data_st *cdata; + int rc; + + cdata = (struct channel_data_st *)userdata; + + if (cdata == NULL) { + fprintf(stderr, "NULL userdata\n"); + rc = SSH_ERROR; + goto end; + } + + rc = strcmp(subsystem, "sftp"); + if (rc == 0) { + rc = channel_exec_request_cb(session, + channel, + SFTP_SERVER_PATH, + userdata); + goto end; + } + + /* TODO add other subsystems */ + + rc = SSH_ERROR; + +end: + return rc; +} + +int channel_write_wontblock_cb(UNUSED_PARAM(ssh_session session), + UNUSED_PARAM(ssh_channel channel), + UNUSED_PARAM(size_t bytes), + UNUSED_PARAM(void *userdata)) +{ + /* TODO */ + + return 0; +} + +ssh_channel channel_new_session_cb(ssh_session session, void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_channel chan = NULL; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "NULL userdata"); + goto end; + } + + chan = ssh_channel_new(session); + if (chan == NULL) { + fprintf(stderr, "Error creating channel: %s\n", + ssh_get_error(session)); + goto end; + } + + sdata->channel = chan; + +end: + return chan; +} + +#ifdef WITH_PCAP +static void set_pcap(struct session_data_st *sdata, + ssh_session session, + char *pcap_file) +{ + int rc = 0; + + if (sdata == NULL) { + return; + } + + if (pcap_file == NULL) { + return; + } + + sdata->pcap = ssh_pcap_file_new(); + if (sdata->pcap == NULL) { + return; + } + + rc = ssh_pcap_file_open(sdata->pcap, pcap_file); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error opening pcap file\n"); + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; + return; + } + ssh_set_pcap_file(session, sdata->pcap); +} + +static void cleanup_pcap(struct session_data_st *sdata) +{ + if (sdata == NULL) { + return; + } + + if (sdata->pcap == NULL) { + return; + } + + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; +} +#endif + +static int process_stdout(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int process_stderr(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_server_callbacks_struct *get_default_server_cb(void) +{ + + struct ssh_server_callbacks_struct *cb; + + cb = (struct ssh_server_callbacks_struct *)calloc(1, + sizeof(struct ssh_server_callbacks_struct)); + + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->auth_none_function = auth_none_cb; + cb->auth_password_function = auth_password_cb; + cb->auth_pubkey_function = auth_pubkey_cb; + cb->channel_open_request_session_function = channel_new_session_cb; + cb->auth_kbdint_function = auth_kbdint_cb; +#if WITH_GSSAPI + cb->auth_gssapi_mic_function = auth_gssapi_mic_cb; +#endif + +end: + return cb; +} + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_channel_callbacks_struct *get_default_channel_cb(void) +{ + struct ssh_channel_callbacks_struct *cb; + + cb = (struct ssh_channel_callbacks_struct *)calloc(1, + sizeof(struct ssh_channel_callbacks_struct)); + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->channel_pty_request_function = channel_pty_request_cb; + cb->channel_pty_window_change_function = channel_pty_resize_cb; + cb->channel_shell_request_function = channel_shell_request_cb; + cb->channel_env_request_function = channel_env_request_cb; + cb->channel_subsystem_request_function = channel_subsystem_request_cb; + cb->channel_exec_request_function = channel_exec_request_cb; + cb->channel_data_function = channel_data_cb; + +end: + return cb; +}; + +void default_handle_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state) +{ + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_st cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_st sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD + }; + + struct ssh_channel_callbacks_struct *channel_cb = NULL; + struct ssh_server_callbacks_struct *server_cb = NULL; + + if (state == NULL) { + fprintf(stderr, "NULL server state provided\n"); + goto end; + } + + /* If callbacks were provided use them. Otherwise, use default callbacks */ + if (state->server_cb != NULL) { + /* This is a macro, it does not return a value */ + ssh_callbacks_init(state->server_cb); + + rc = ssh_set_server_callbacks(session, state->server_cb); + if (rc) { + goto end; + } + } else { + server_cb = get_default_server_cb(); + if (server_cb == NULL) { + goto end; + } + + server_cb->userdata = &sdata; + + /* This is a macro, it does not return a value */ + ssh_callbacks_init(server_cb); + + rc = ssh_set_server_callbacks(session, server_cb); + if (rc) { + goto end; + } + } + + sdata.server_state = (void *)state; + +#ifdef WITH_PCAP + set_pcap(&sdata, session, state->pcap_file); +#endif + + if (state->expected_username != NULL) { + sdata.username = state->expected_username; + } + + if (state->expected_password != NULL) { + sdata.password = state->expected_password; + } + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + goto end; + } + + /* Set the supported authentication methods */ + if (state->auth_methods) { + ssh_set_auth_methods(session, state->auth_methods); + } else { + ssh_set_auth_methods(session, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_PUBLICKEY | + SSH_AUTH_METHOD_INTERACTIVE | + SSH_AUTH_METHOD_GSSAPI_MIC); + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= state->max_tries || n >= 100) { + goto end; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + goto end; + } + n++; + } + + /* TODO check return values */ + if (state->channel_cb != NULL) { + ssh_callbacks_init(state->channel_cb); + + rc = ssh_set_channel_callbacks(sdata.channel, state->channel_cb); + if (rc) { + goto end; + } + } else { + channel_cb = get_default_channel_cb(); + if (channel_cb == NULL) { + goto end; + } + + channel_cb->userdata = &cdata; + + ssh_callbacks_init(channel_cb); + rc = ssh_set_channel_callbacks(sdata.channel, channel_cb); + if (rc) { + goto end; + } + } + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, -1) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while(ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } + +end: +#ifdef WITH_PCAP + cleanup_pcap(&sdata); +#endif + if (channel_cb != NULL) { + free(channel_cb); + } + if (server_cb != NULL) { + free(server_cb); + } + return; +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.h b/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.h new file mode 100644 index 000000000000..529c71e163f9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/default_cb.h @@ -0,0 +1,180 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include +#include + +#define SSHD_DEFAULT_USER "libssh" +#define SSHD_DEFAULT_PASSWORD "libssh" +#define SSHD_DEFAULT_PORT 2222 +#define SSHD_DEFAULT_ADDRESS "127.0.0.1" +#define SSHD_DEFAULT_PCAP_FILE "debug.server.pcap" + +#define SSHD_BANNER_MESSAGE "Test Banner Message\nlibssh-send-banner\n" + +#ifndef KEYS_FOLDER +#ifdef _WIN32 +#define KEYS_FOLDER +#else +#define KEYS_FOLDER "/etc/ssh/" +#endif +#endif + +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) +#define SFTP_SERVER_PATH "/usr/lib/sftp-server" + +#ifdef HAVE_PTY_H +#include +#endif + +/* A userdata struct for channel. */ +struct channel_data_st { + /* pid of the child process the channel will spawn. */ + pid_t pid; + /* For PTY allocation */ + socket_t pty_master; + socket_t pty_slave; + /* For communication with the child process. */ + socket_t child_stdin; + socket_t child_stdout; + /* Only used for subsystem and exec requests. */ + socket_t child_stderr; + /* Event which is used to poll the above descriptors. */ + ssh_event event; + /* Terminal size struct. */ + struct winsize *winsize; + /* This pointer will hold the server state for default callbacks */ + void *server_state; + /* This pointer is useful to set data for custom callbacks */ + void *extra_data; + sftp_session sftp; +}; + +/* A userdata struct for session. */ +struct session_data_st { + /* Pointer to the channel the session will allocate. */ + ssh_channel channel; + int auth_attempts; + int authenticated; + const char *username; + const char *password; +#ifdef WITH_PCAP + ssh_pcap_file pcap; +#endif + /* This pointer will hold the server state for default callbacks */ + void *server_state; + /* This pointer is useful to set data for custom callbacks */ + void *extra_data; +}; + +int auth_password_cb(ssh_session session, const char *user, + const char *password, void *userdata); + +#if WITH_GSSAPI +int auth_gssapi_mic_cb(ssh_session session, const char *user, + const char *principal, void *userdata); +#endif + +int channel_data_cb(ssh_session session, ssh_channel channel, + void *data, uint32_t len, int is_stderr, void *userdata); + +void channel_eof_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_close_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_signal_cb (ssh_session session, + ssh_channel channel, + const char *signal, + void *userdata); + +void channel_exit_status_cb (ssh_session session, + ssh_channel channel, + int exit_status, + void *userdata); + +void channel_exit_signal_cb(ssh_session session, + ssh_channel channel, + const char *signal, + int core, + const char *errmsg, + const char *lang, + void *userdata); + +int channel_pty_request_cb(ssh_session session, ssh_channel channel, + const char *term, int cols, int rows, int py, int px, void *userdata); + +int channel_pty_resize_cb(ssh_session session, ssh_channel channel, + int cols, int rows, int py, int px, void *userdata); + +int channel_shell_request_cb(ssh_session session, ssh_channel channel, + void *userdata); + +void channel_auth_agent_req_callback(ssh_session session, + ssh_channel channel, void *userdata); + +void channel_x11_req_callback(ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata); + +int channel_exec_request_cb(ssh_session session, + ssh_channel channel, + const char *command, + void *userdata); + +int channel_env_request_cb(ssh_session session, + ssh_channel channel, const char *env_name, const char *env_value, + void *userdata); + +int channel_subsystem_request_cb(ssh_session session, + ssh_channel channel, const char *subsystem, + void *userdata); + +int channel_write_wontblock_cb(ssh_session session, + ssh_channel channel, + size_t bytes, + void *userdata); + +ssh_channel channel_new_session_cb(ssh_session session, void *userdata); + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_server_callbacks_struct *get_default_server_cb(void); + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_channel_callbacks_struct *get_default_channel_cb(void); + +void default_handle_session_cb(ssh_event event, ssh_session session, + struct server_state_st *state); diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/main.c b/src/libs/libssh-0.12.2/tests/server/test_server/main.c new file mode 100644 index 000000000000..bc4db3595469 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/main.c @@ -0,0 +1,667 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "test_server.h" +#include "default_cb.h" + +#include + +#include +#include +#include + +#ifdef HAVE_ARGP_H +#include +#endif + +#include +#include +#include +#include + +struct arguments_st { + char *address; + char *port; + + char *ecdsa_key; + char *ed25519_key; + char *rsa_key; + char *host_key; + + char *verbosity; + char *auth_methods; + bool with_pcap; + + char *pcap_file; + + char *username; + char *password; + + char *config_file; + char *log_file; + bool with_global_config; + char *pid_file; +}; + +static void free_arguments(struct arguments_st *arguments) +{ + if (arguments == NULL) { + return; + } + + SAFE_FREE(arguments->address); + SAFE_FREE(arguments->port); + + SAFE_FREE(arguments->ecdsa_key); + SAFE_FREE(arguments->ed25519_key); + SAFE_FREE(arguments->rsa_key); + SAFE_FREE(arguments->host_key); + + SAFE_FREE(arguments->verbosity); + SAFE_FREE(arguments->auth_methods); + SAFE_FREE(arguments->pcap_file); + + SAFE_FREE(arguments->username); + SAFE_FREE(arguments->password); + SAFE_FREE(arguments->config_file); + SAFE_FREE(arguments->log_file); + SAFE_FREE(arguments->pid_file); +} + +#ifdef HAVE_ARGP_H + +static void print_auth_methods(int auth_methods) +{ + printf("auth_methods = \n"); + if (auth_methods & SSH_AUTH_METHOD_NONE) { + printf("\tSSH_AUTH_METHOD_NONE\n"); + } + if (auth_methods & SSH_AUTH_METHOD_PASSWORD) { + printf("\tSSH_AUTH_METHOD_PASSWORD\n"); + } + if (auth_methods & SSH_AUTH_METHOD_PUBLICKEY) { + printf("\tSSH_AUTH_METHOD_PUBLICKEY\n"); + } + if (auth_methods & SSH_AUTH_METHOD_HOSTBASED) { + printf("\tSSH_AUTH_METHOD_HOSTBASED\n"); + } + if (auth_methods & SSH_AUTH_METHOD_INTERACTIVE) { + printf("\tSSH_AUTH_METHOD_INTERACTIVE\n"); + } + if (auth_methods & SSH_AUTH_METHOD_GSSAPI_MIC) { + printf("\tSSH_AUTH_METHOD_GSSAPI_MIC\n"); + } + if (auth_methods & SSH_AUTH_METHOD_GSSAPI_KEYEX) { + printf("\tSSH_AUTH_METHOD_GSSAPI_KEYEX\n"); + } +} + +static void print_verbosity(int verbosity) +{ + printf("verbosity = "); + switch(verbosity) { + case SSH_LOG_NOLOG: + printf("NO LOG\n"); + break; + case SSH_LOG_WARNING: + printf("WARNING\n"); + break; + case SSH_LOG_PROTOCOL: + printf("PROTOCOL\n"); + break; + case SSH_LOG_PACKET: + printf("PACKET\n"); + break; + case SSH_LOG_FUNCTIONS: + printf("FUNCTIONS\n"); + break; + default: + printf("UNKNOWN\n");; + break; + } +} + +static void print_server_state(struct server_state_st *state) +{ + if (state) { + printf("===================| STATE |=====================\n"); + printf("address = %s\n", + state->address? state->address: "NULL"); + printf("port = %d\n", + state->port? state->port: 0); + printf("=================================================\n"); + printf("ecdsa_key = %s\n", + state->ecdsa_key? state->ecdsa_key: "NULL"); + printf("ed25519_key = %s\n", + state->ed25519_key? state->ed25519_key: "NULL"); + printf("rsa_key = %s\n", + state->rsa_key? state->rsa_key: "NULL"); + printf("host_key = %s\n", + state->host_key? state->host_key: "NULL"); + printf("=================================================\n"); + print_auth_methods(state->auth_methods); + print_verbosity(state->verbosity); + printf("with_pcap = %s\n", + state->with_pcap? "TRUE": "FALSE"); + printf("pcap_file = %s\n", + state->pcap_file? state->pcap_file: "NULL"); + printf("=================================================\n"); + printf("username = %s\n", + state->expected_username? state->expected_username: "NULL"); + printf("password = %s\n", + state->expected_password? state->expected_password: "NULL"); + printf("=================================================\n"); + printf("with_global_config = %s\n", + state->parse_global_config? "TRUE": "FALSE"); + printf("config_file = %s\n", + state->config_file? state->config_file: "NULL"); + printf("log_file = %s\n", state->log_file ? state->log_file : "NULL"); + printf("=================================================\n"); + } +} + +static int init_server_state(struct server_state_st *state, + struct arguments_st *arguments) +{ + int rc = 0; + + if (state == NULL) { + rc = SSH_ERROR; + goto end; + } + + /* Initialize server state. The "arguments structure" */ + if (arguments->address) { + state->address = arguments->address; + arguments->address = NULL; + } else { + state->address = strdup(SSHD_DEFAULT_ADDRESS); + if (state->address == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->port) { + state->port = atoi(arguments->port); + } else { + state->port = SSHD_DEFAULT_PORT; + } + + if (arguments->ecdsa_key) { + state->ecdsa_key = arguments->ecdsa_key; + arguments->ecdsa_key = NULL; + } else { + state->ecdsa_key = NULL; + } + + if (arguments->ed25519_key) { + state->ed25519_key = arguments->ed25519_key; + arguments->ed25519_key = NULL; + } else { + state->ed25519_key = NULL; + } + + if (arguments->rsa_key) { + state->rsa_key = arguments->rsa_key; + arguments->rsa_key = NULL; + } else { + state->rsa_key = NULL; + } + + if (arguments->host_key) { + state->host_key = arguments->host_key; + arguments->host_key = NULL; + } else { + state->host_key = NULL; + } + + if (arguments->username) { + state->expected_username = arguments->username; + arguments->username = NULL; + } else { + state->expected_username = strdup(SSHD_DEFAULT_USER); + if (state->expected_username == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->password) { + state->expected_password = arguments->password; + arguments->password = NULL; + } else { + state->expected_password = strdup(SSHD_DEFAULT_PASSWORD); + if (state->expected_password == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } + + if (arguments->verbosity) { + state->verbosity = atoi(arguments->verbosity); + } else { + state->verbosity = 0; + } + + if (arguments->auth_methods) { + state->auth_methods = atoi(arguments->auth_methods); + } else { + state->auth_methods = SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_PUBLICKEY | + SSH_AUTH_METHOD_INTERACTIVE | + SSH_AUTH_METHOD_GSSAPI_MIC; + } + + state->with_pcap = arguments->with_pcap; + + if (arguments->pcap_file) { + state->pcap_file = arguments->pcap_file; + arguments->pcap_file = NULL; + } else { + if (arguments->with_pcap) { + state->pcap_file = strdup(SSHD_DEFAULT_PCAP_FILE); + if (state->pcap_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto end; + } + } else { + state->pcap_file = NULL; + } + } + + state->parse_global_config = arguments->with_global_config; + state->gssapi_key_exchange_algs = NULL; + + if (arguments->config_file) { + state->config_file = arguments->config_file; + arguments->config_file = NULL; + } + + if (arguments->log_file) { + state->log_file = arguments->log_file; + arguments->log_file = NULL; + } + + /* TODO make configurable */ + state->max_tries = 3; + state->error = 0; + + + if (state) { + print_server_state(state); + } + + /* TODO make callbacks configurable through command line ? */ + /* Set callbacks to be used */ + state->handle_session = default_handle_session_cb; + + /* Check required parameters */ + if (state->address == NULL) { + rc = SSH_ERROR; + goto end; + } + +end: + if (rc != 0) { + free_server_state(state); + } + + return rc; +} + +const char *argp_program_version = "libssh test server " +SSH_STRINGIFY(LIBSSH_VERSION); +const char *argp_program_bug_address = ""; + +/* Program documentation. */ +static char doc[] = "libssh -- a Secure Shell protocol implementation"; + +/* A description of the arguments we accept. */ +static char args_doc[] = "BINDADDR"; + +/* The options we understand. */ +static struct argp_option options[] = { + { + .name = "port", + .key = 'p', + .arg = "PORT", + .flags = 0, + .doc = "Set the port to bind.", + .group = 0 + }, + { + .name = "ecdsakey", + .key = 'c', + .arg = "FILE", + .flags = 0, + .doc = "Set the ECDSA key.", + .group = 0 + }, + { + .name = "ed25519key", + .key = 'e', + .arg = "FILE", + .flags = 0, + .doc = "Set the ed25519 key.", + .group = 0 + }, + { + .name = "rsakey", + .key = 'r', + .arg = "FILE", + .flags = 0, + .doc = "Set the RSA key.", + .group = 0 + }, + { + .name = "hostkey", + .key = 'k', + .arg = "FILE", + .flags = 0, + .doc = "Set the host key.", + .group = 0 + }, + { + .name = "pcapfile", + .key = 'f', + .arg = "FILE", + .flags = 0, + .doc = "Set the pcap output file.", + .group = 0 + }, + { + .name = "pid_file", + .key = 'i', + .arg = "FILE", + .flags = 0, + .doc = "The server will write its pid in this file, if provided.", + .group = 0 + }, + { + .name = "auth-methods", + .key = 'a', + .arg = "METHODS", + .flags = 0, + .doc = "Set supported authentication methods.", + .group = 0 + }, + { + .name = "user", + .key = 'u', + .arg = "USERNAME", + .flags = 0, + .doc = "Set expected username.", + .group = 0 + }, + { + .name = "verbosity", + .key = 'v', + .arg = "VERBOSITY", + .flags = 0, + .doc = "Set output verbosity [0-4].", + .group = 0 + }, + { + .name = "with-pcap", + .key = 'w', + .arg = NULL, + .flags = 0, + .doc = "Use PCAP.", + .group = 0 + }, + { + .name = "without-global-config", + .key = 'g', + .arg = NULL, + .flags = 0, + .doc = "Do not use system-wide configuration file.", + .group = 0 + }, + { + .name = "config", + .key = 'C', + .arg = "CONFIG_FILE", + .flags = 0, + .doc = "Use this server configuration file.", + .group = 0 + }, + { + .name = "log_file", + .key = 'l', + .arg = "LOG_FILE", + .flags = 0, + .doc = "Output log to this file.", + .group = 0 + }, + { .name = NULL } +}; + +/* Parse a single option. */ +static error_t parse_opt (int key, char *arg, struct argp_state *state) +{ + /* Get the input argument from argp_parse, which we + * know is a pointer to our arguments structure. + */ + struct arguments_st *arguments = state->input; + error_t rc = 0; + + if (arguments == NULL) { + fprintf(stderr, "NULL pointer to arguments structure provided\n"); + rc = EINVAL; + goto end; + } + + switch (key) { + case 'c': + arguments->ecdsa_key = strdup(arg); + if (arguments->ecdsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'e': + arguments->ed25519_key = strdup(arg); + if (arguments->ed25519_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'f': + arguments->pcap_file = strdup(arg); + if (arguments->pcap_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'i': + arguments->pid_file = strdup(arg); + if (arguments->pid_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'k': + arguments->host_key = strdup(arg); + if (arguments->host_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'a': + arguments->auth_methods = strdup(arg); + if (arguments->auth_methods == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'p': + arguments->port = strdup(arg); + if (arguments->port == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'r': + arguments->rsa_key = strdup(arg); + if (arguments->rsa_key == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'u': + arguments->username = strdup(arg); + if (arguments->username == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'v': + arguments->verbosity = strdup(arg); + if (arguments->verbosity == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'w': + arguments->with_pcap = true; + break; + case 'g': + arguments->with_global_config = false; + break; + case 'C': + arguments->config_file = strdup(arg); + if (arguments->config_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case 'l': + arguments->log_file = strdup(arg); + if (arguments->log_file == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case ARGP_KEY_ARG: + if (state->arg_num >= 1) { + /* Too many arguments. */ + printf("Too many arguments\n"); + argp_usage(state); + } + arguments->address = strdup(arg); + if (arguments->address == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = ENOMEM; + goto end; + } + break; + case ARGP_KEY_END: + if (state->arg_num < 1) { + printf("Too few arguments\n"); + /* Not enough arguments. */ + argp_usage(state); + } + break; + default: + return ARGP_ERR_UNKNOWN; + } + +end: + return rc; +} + +/* Our argp parser. */ +static struct argp argp = {options, parse_opt, args_doc, doc, NULL, NULL, NULL}; + +#endif /* HAVE_ARGP_H */ + +int main(UNUSED_PARAM(int argc), UNUSED_PARAM(char **argv)) +{ + int rc; + FILE *pid_file; + pid_t pid; + + struct arguments_st arguments = { + .address = NULL, + .with_global_config = true, + }; + struct server_state_st *state = calloc(1, sizeof(struct server_state_st)); + + if (state == NULL) { + printf("Failed to allocate memory\n"); + return -1; + } + +#ifdef HAVE_ARGP_H + argp_parse (&argp, argc, argv, 0, 0, &arguments); +#endif + + if (arguments.pid_file) { + pid_file = fopen(arguments.pid_file, "w"); + if (pid_file == NULL) { + rc = -1; + free_server_state(state); + SAFE_FREE(state); + goto free_arguments; + } + pid = getpid(); + fprintf(pid_file, "%d\n", pid); + fclose(pid_file); + } + + /* Initialize the state using default or given parameters */ + rc = init_server_state(state, &arguments); + if (rc != 0) { + free_server_state(state); + SAFE_FREE(state); + goto free_arguments; + } + + /* Free the arguments used to initialize the state before fork */ + free_arguments(&arguments); + + /* Run the server: Frees the state in all processes */ + rc = run_server(state); + +free_arguments: + free_arguments(&arguments); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/sftpserver_cb.c b/src/libs/libssh-0.12.2/tests/server/test_server/sftpserver_cb.c new file mode 100644 index 000000000000..3dd78426ffde --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/sftpserver_cb.c @@ -0,0 +1,425 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "test_server.h" +#include "default_cb.h" + +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include // for cmocka +#include // for cmocka +#include // for cmocka + +#include + +#ifdef HAVE_LIBUTIL_H +#include +#endif +#ifdef HAVE_PTY_H +#include +#endif +#ifdef HAVE_UTMP_H +#include +#endif +#ifdef HAVE_UTIL_H +#include +#endif + +/* below are for sftp */ +#include +#include +#include +#include +#include + +#include +#include +#include +#include + + +#define BUF_SIZE 1048576 +#define SESSION_END (SSH_CLOSED | SSH_CLOSED_ERROR) + + +/* TODO implement proper pam authentication cb */ +static int sftp_auth_password_cb(UNUSED_PARAM(ssh_session session), + const char *user, + const char *password, + void *userdata) +{ + bool known_user = false; + bool valid_password = false; + + struct session_data_st *sdata; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "Error: NULL userdata\n"); + goto null_userdata; + } + + if (sdata->username == NULL) { + fprintf(stderr, "Error: expected username not set\n"); + goto denied; + } + + if (sdata->password == NULL) { + fprintf(stderr, "Error: expected password not set\n"); + goto denied; + } + + printf("Password authentication of user %s\n", user); + + known_user = !(strcmp(user, sdata->username)); + valid_password = !(strcmp(password, sdata->password)); + + if (known_user && valid_password) { + sdata->authenticated = 1; + sdata->auth_attempts = 0; + printf("Authenticated\n"); + return SSH_AUTH_SUCCESS; + } + +denied: + sdata->auth_attempts++; +null_userdata: + return SSH_AUTH_DENIED; +} + +static ssh_channel sftp_channel_new_session_cb(ssh_session session, void *userdata) +{ + struct session_data_st *sdata = NULL; + ssh_channel chan = NULL; + + sdata = (struct session_data_st *)userdata; + + if (sdata == NULL) { + fprintf(stderr, "NULL userdata"); + goto end; + } + + if (sdata->channel != NULL) { + fprintf(stderr, "Only one channel is supported\n"); + goto end; + } + + chan = ssh_channel_new(session); + if (chan == NULL) { + fprintf(stderr, "Error creating channel: %s\n", + ssh_get_error(session)); + goto end; + } + + sdata->channel = chan; + +end: + return chan; +} + +#ifdef WITH_PCAP +static void set_pcap(struct session_data_st *sdata, + ssh_session session, + char *pcap_file) +{ + int rc = 0; + + if (sdata == NULL) { + return; + } + + if (pcap_file == NULL) { + return; + } + + sdata->pcap = ssh_pcap_file_new(); + if (sdata->pcap == NULL) { + return; + } + + rc = ssh_pcap_file_open(sdata->pcap, pcap_file); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error opening pcap file\n"); + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; + return; + } + ssh_set_pcap_file(session, sdata->pcap); +} + +static void cleanup_pcap(struct session_data_st *sdata) +{ + if (sdata == NULL) { + return; + } + + if (sdata->pcap == NULL) { + return; + } + + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; +} +#endif + + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + */ +struct ssh_server_callbacks_struct *get_sftp_server_cb(void) +{ + + struct ssh_server_callbacks_struct *cb; + + cb = (struct ssh_server_callbacks_struct *)calloc(1, + sizeof(struct ssh_server_callbacks_struct)); + + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->auth_password_function = sftp_auth_password_cb; + cb->channel_open_request_session_function = sftp_channel_new_session_cb; + +end: + return cb; +} + +/* Default SFTP channel data callback with some additional checks */ +int sftp_channel_data_callback(ssh_session session, + ssh_channel channel, + void *data, + uint32_t len, + int is_stderr, + void *userdata) +{ + sftp_session *sftpp = (sftp_session *)userdata; + int rv; + + rv = sftp_channel_default_data_callback(session, + channel, + data, + len, + is_stderr, + userdata); + + if (sftpp != NULL && *sftpp != NULL) { + sftp_session sftp = *sftpp; + /* NOTE that this expects both server and clieng being libssh with this + * same version number */ + assert_true(sftp->client_version <= LIBSFTP_VERSION); + } + return rv; +} + +/* The caller is responsible to set the userdata to be provided to the callback + * The caller is responsible to free the allocated structure + * */ +struct ssh_channel_callbacks_struct *get_sftp_channel_cb(void) +{ + struct ssh_channel_callbacks_struct *cb; + + cb = (struct ssh_channel_callbacks_struct *)calloc(1, + sizeof(struct ssh_channel_callbacks_struct)); + if (cb == NULL) { + fprintf(stderr, "Out of memory\n"); + goto end; + } + + cb->channel_data_function = sftp_channel_data_callback; + cb->channel_subsystem_request_function = sftp_channel_default_subsystem_request; + +end: + return cb; +}; + +void sftp_handle_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state) +{ + int n; + int rc = 0; + + /* Our struct holding information about the channel. */ + struct channel_data_st cdata = { + .sftp = NULL, + }; + + /* Our struct holding information about the session. */ + struct session_data_st sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD, + }; + + struct ssh_channel_callbacks_struct *channel_cb = NULL; + struct ssh_server_callbacks_struct *server_cb = NULL; + + if (state == NULL) { + fprintf(stderr, "NULL server state provided\n"); + goto end; + } + + /* If callbacks were provided use them. Otherwise, use default callbacks */ + if (state->server_cb != NULL) { + /* This is a macro, it does not return a value */ + ssh_callbacks_init(state->server_cb); + + rc = ssh_set_server_callbacks(session, state->server_cb); + if (rc) { + goto end; + } + } else { + server_cb = get_sftp_server_cb(); + if (server_cb == NULL) { + goto end; + } + + server_cb->userdata = &sdata; + + /* This is a macro, it does not return a value */ + ssh_callbacks_init(server_cb); + + rc = ssh_set_server_callbacks(session, server_cb); + if (rc) { + goto end; + } + } + + sdata.server_state = (void *)state; + +#ifdef WITH_PCAP + set_pcap(&sdata, session, state->pcap_file); +#endif + + if (state->expected_username != NULL) { + sdata.username = state->expected_username; + } + + if (state->expected_password != NULL) { + sdata.password = state->expected_password; + } + + if (ssh_handle_key_exchange(session) != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + goto end; + } + + /* Set the supported authentication methods */ + if (state->auth_methods) { + ssh_set_auth_methods(session, state->auth_methods); + } else { + ssh_set_auth_methods(session, + SSH_AUTH_METHOD_PASSWORD | + SSH_AUTH_METHOD_PUBLICKEY); + } + + ssh_event_add_session(event, session); + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= state->max_tries || n >= 100) { + goto end; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + goto end; + } + n++; + } + + /* TODO check return values */ + if (state->channel_cb != NULL) { + ssh_callbacks_init(state->channel_cb); + + rc = ssh_set_channel_callbacks(sdata.channel, state->channel_cb); + if (rc) { + goto end; + } + } else { + channel_cb = get_sftp_channel_cb(); + if (channel_cb == NULL) { + goto end; + } + + channel_cb->userdata = &(cdata.sftp); + + ssh_callbacks_init(channel_cb); + rc = ssh_set_channel_callbacks(sdata.channel, channel_cb); + if (rc) { + goto end; + } + } + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + } while (ssh_channel_is_open(sdata.channel) && + !ssh_channel_is_eof(sdata.channel)); + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + sftp_server_free(cdata.sftp); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } + +end: +#ifdef WITH_PCAP + cleanup_pcap(&sdata); +#endif + if (channel_cb != NULL) { + free(channel_cb); + } + if (server_cb != NULL) { + free(server_cb); + } + return; +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/test_server.c b/src/libs/libssh-0.12.2/tests/server/test_server/test_server.c new file mode 100644 index 000000000000..69b6dac23296 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/test_server.c @@ -0,0 +1,395 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "test_server.h" +#include "testserver_common.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +#include + +void free_server_state(struct server_state_st *state) +{ + if (state == NULL) { + return; + } + + SAFE_FREE(state->address); + + SAFE_FREE(state->ecdsa_key); + SAFE_FREE(state->ed25519_key); + SAFE_FREE(state->rsa_key); + SAFE_FREE(state->host_key); + + SAFE_FREE(state->pcap_file); + + SAFE_FREE(state->expected_username); + SAFE_FREE(state->expected_password); + SAFE_FREE(state->config_file); + SAFE_FREE(state->log_file); + SAFE_FREE(state->server_cb); + SAFE_FREE(state->channel_cb); +} + +/* SIGCHLD handler for cleaning up dead children. */ +static void sigchld_handler(int signo) { + (void) signo; + while (waitpid(-1, NULL, WNOHANG) > 0); +} + +bool done = false; + +static void sigterm_handler(int signo) +{ + (void) signo; + fprintf(stderr, "Received SIGTERM. Gracefully exiting ...\n"); + done = true; +} + +int run_server(struct server_state_st *state) +{ + ssh_session session = NULL; + ssh_bind sshbind = NULL; + ssh_event event = NULL; + + struct sigaction sa = { + .sa_flags = 0 + }; + + int rc = SSH_ERROR; + + /* Check provided state */ + if (state == NULL) { + fprintf(stderr, "Invalid state\n"); + goto out; + } + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGCHLD handler\n"); + goto out; + } + + /* Set up SIGTERM handler. */ + sa.sa_handler = sigterm_handler; + sa.sa_flags = 0; + + if (sigaction(SIGTERM, &sa, NULL) != 0) { + fprintf(stderr, "Failed to register SIGTERM handler\n"); + goto out; + } + + /* Redirect all the output and errors to the file to avoid mixing up with + * the output from the client */ + if (state->log_file != NULL) { + int fd; + FILE *f = fopen(state->log_file, "a"); + if (f == NULL) { + fprintf(stderr, "Failed to open the log file: %s\n", strerror(errno)); + goto out; + } + fd = dup2(fileno(f), STDERR_FILENO); + if (fd == -1) { + fprintf(stderr, "dup2 of log file to stderr failed: %s\n", + strerror(errno)); + fclose(f); + goto out; + } + fd = dup2(fileno(f), STDOUT_FILENO); + if (fd == -1) { + fprintf(stderr, "dup2 of log file to stdout failed: %s\n", + strerror(errno)); + fclose(f); + goto out; + } + fclose(f); + } + + if (state->address == NULL) { + fprintf(stderr, "Missing bind address\n"); + goto out; + } + + if (state->host_key == NULL && state->rsa_key == NULL && + state->ecdsa_key == NULL && state->ed25519_key == NULL) { + fprintf(stderr, "Missing host key\n"); + } + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + fprintf(stderr, "Out of memory\n"); + goto out; + } + + if (state->verbosity) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &state->verbosity); + if (rc != 0) { + fprintf(stderr, + "Error setting verbosity level: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } + + if (!state->parse_global_config) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_PROCESS_CONFIG, + &(state->parse_global_config)); + if (rc != 0) { + goto out; + } + } + + if (state->config_file) { + rc = ssh_bind_options_parse_config(sshbind, state->config_file); + if (rc != 0) { + goto out; + } + } + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_BINDADDR, + state->address); + if (rc != 0) { + fprintf(stderr, + "Error setting bind address: %s\n", + ssh_get_error(sshbind)); + goto out; + } + +#ifdef WITH_GSSAPI + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE, + &(state->gssapi_key_exchange)); + if (rc != 0) { + fprintf(stderr, + "Error setting GSSAPI key exchange: %s\n", + ssh_get_error(sshbind)); + goto out; + } + + if (state->gssapi_key_exchange_algs != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + state->gssapi_key_exchange_algs); + if (rc != 0) { + fprintf(stderr, + "Error setting GSSAPI key exchange algorithms: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } +#endif /* WITH_GSSAPI */ + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_BINDPORT, + &(state->port)); + if (rc != 0) { + fprintf(stderr, + "Error setting bind port: %s\n", + ssh_get_error(sshbind)); + goto out; + } + + if (state->rsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + state->rsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting RSA key: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } + + if (state->ecdsa_key != NULL) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + state->ecdsa_key); + if (rc != 0) { + fprintf(stderr, + "Error setting ECDSA key: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } + + if (state->host_key) { + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_HOSTKEY, + state->host_key); + if (rc) { + fprintf(stderr, + "Error setting hostkey: %s\n", + ssh_get_error(sshbind)); + goto out; + } + } + + rc = ssh_bind_listen(sshbind); + if (rc != 0) { + fprintf(stderr, + "Error listening to socket: %s\n", + ssh_get_error(sshbind)); + goto out; + } + + printf("%d: Started libssh test server on port %d\n", getpid(), state->port); + + while (done == false) { + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Out of memory\n"); + rc = SSH_ERROR; + goto out; + } + + /* Blocks until there is a new incoming connection. */ + rc = ssh_bind_accept(sshbind, session); + if (rc != SSH_ERROR) { + pid_t pid = fork(); + + switch(pid) { + case 0: + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + /* Remove the SIGTERM handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGTERM, &sa, NULL); + /* Remove socket binding, which allows us to restart the + * parent process, without terminating existing sessions. */ + ssh_bind_free(sshbind); + + event = ssh_event_new(); + if (event != NULL) { + /* Blocks until the SSH session ends by either + * child process exiting, or client disconnecting. */ + state->handle_session(event, session, state); + ssh_event_free(event); + } else { + fprintf(stderr, "Could not create polling context\n"); + } + ssh_disconnect(session); + ssh_free(session); + + free_server_state(state); + SAFE_FREE(state); + finalize_openssl(); + exit(0); + case -1: + fprintf(stderr, "Failed to fork\n"); + } + fprintf(stderr, "Forked process PID %d\n", pid); + } else { + fprintf(stderr, + "Error accepting a connection: %s\n", + ssh_get_error(sshbind)); + } + + /* Since the session has been passed to a child fork, do some cleaning + * up at the parent process. */ + ssh_disconnect(session); + ssh_free(session); + } + + rc = 0; + +out: + free_server_state(state); + SAFE_FREE(state); + ssh_bind_free(sshbind); + return rc; +} + +pid_t +fork_run_server(struct server_state_st *state, + void (*free_test_state) (void **userdata), + void *userdata) +{ + pid_t pid; + int rc; + + char err_str[1024] = {0}; + + struct sigaction sa; + + /* Check provided state */ + if (state == NULL) { + fprintf(stderr, "Invalid state\n"); + return -1; + } + + /* Set up SIGCHLD handler. */ + sa.sa_handler = sigchld_handler; + sigemptyset(&sa.sa_mask); + sa.sa_flags = SA_RESTART | SA_NOCLDSTOP; + + if (sigaction(SIGCHLD, &sa, NULL) != 0) { + strerror_r(errno, err_str, 1024); + fprintf(stderr, "Failed to register SIGCHLD handler: %s\n", + err_str); + return -1; + } + + pid = fork(); + switch(pid) { + case 0: + /* no longer needed */ + free_test_state(userdata); + /* Remove the SIGCHLD handler inherited from parent. */ + sa.sa_handler = SIG_DFL; + sigaction(SIGCHLD, &sa, NULL); + + /* The child process starts a server which will listen for connections */ + rc = run_server(state); + finalize_openssl(); + exit(rc); + case -1: + strerror_r(errno, err_str, 1024); + fprintf(stderr, "Failed to fork: %s\n", + err_str); + return -1; + default: + /* Return the child pid */ + fprintf(stderr, "Forked process PID %d\n", pid); + return pid; + } +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/test_server.h b/src/libs/libssh-0.12.2/tests/server/test_server/test_server.h new file mode 100644 index 000000000000..b4e17f69fdab --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/test_server.h @@ -0,0 +1,82 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#include +#include +#include + +struct server_state_st { + /* Arguments */ + char *address; + int port; + + char *ecdsa_key; + char *ed25519_key; + char *rsa_key; + char *host_key; + + int verbosity; + int auth_methods; + bool with_pcap; + + char *pcap_file; + + char *expected_username; + char *expected_password; + + char *config_file; + bool parse_global_config; + + char *log_file; + bool gssapi_key_exchange; + const char *gssapi_key_exchange_algs; + + /* State */ + int max_tries; + int error; + + struct ssh_server_callbacks_struct *server_cb; + struct ssh_channel_callbacks_struct *channel_cb; + + /* Callback to handle the session, should block until disconnected */ + void (*handle_session)(ssh_event event, + ssh_session session, + struct server_state_st *state); +}; + +/*TODO: Add documentation */ +void free_server_state(struct server_state_st *state); + +/*TODO: Add documentation */ +int run_server(struct server_state_st *state); + +/*TODO: Add documentation */ +pid_t +fork_run_server(struct server_state_st *state, + void (*free_state) (void **userdata), + void *userdata); diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.c b/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.c new file mode 100644 index 000000000000..c097073871e7 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.c @@ -0,0 +1,36 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "testserver_common.h" + +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + +void finalize_openssl(void) +{ +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif +} diff --git a/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.h b/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.h new file mode 100644 index 000000000000..01a9c57f9bad --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/test_server/testserver_common.h @@ -0,0 +1,26 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 by Red Hat, Inc. + * + * Author: Jakub Jelen + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +void finalize_openssl(void); diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth.c new file mode 100644 index 000000000000..8058d4d05b19 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth.c @@ -0,0 +1,456 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int +setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int +teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int +session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void +torture_gssapi_server_auth_no_client_cred(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* No client credential */ + torture_setup_kdc_server( + (void**)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_invalid_host(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Invalid host principal */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site\n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site\n" + "kadmin.local addprinc -pw bar alice\n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_OK); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_auth_server_identity(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Invalid server identity option */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "invalid.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server((void **)&s); + + /* Valid server identity option*/ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + ssh_options_set(session, + SSH_OPTIONS_GSSAPI_SERVER_IDENTITY, + "server.libssh.site"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_no_client_cred, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_invalid_host, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_auth_server_identity, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth_cb.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth_cb.c new file mode 100644 index 000000000000..6a30e6c04269 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_auth_cb.c @@ -0,0 +1,518 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "libssh/gssapi.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static ssh_string +select_oid(ssh_session session, + const char *user, + int n_oid, + ssh_string *oids, + void *userdata) +{ + /* Choose the first oid */ + return oids[0]; +} + +static int +accept_sec_ctx(ssh_session session, + ssh_string input_token, + ssh_string *output_token, + void *userdata) +{ + ssh_string token; + OM_uint32 min_stat; + gss_buffer_desc itoken, otoken = GSS_C_EMPTY_BUFFER; + gss_name_t client_name = GSS_C_NO_NAME; + OM_uint32 ret_flags = 0; + gss_channel_bindings_t input_bindings = GSS_C_NO_CHANNEL_BINDINGS; + + itoken.length = ssh_string_len(input_token); + itoken.value = ssh_string_data(input_token); + + gss_accept_sec_context(&min_stat, + &session->gssapi->ctx, + session->gssapi->server_creds, + &itoken, + input_bindings, + &client_name, + NULL /*mech_oid*/, + &otoken, + &ret_flags, + NULL /*time*/, + &session->gssapi->client_creds); + + if (client_name != GSS_C_NO_NAME) { + session->gssapi->client_name = client_name; + session->gssapi->canonic_user = ssh_gssapi_name_to_char(client_name); + } + token = ssh_string_new(otoken.length); + ssh_string_fill(token, otoken.value, otoken.length); + *output_token = token; + + gss_release_buffer(&min_stat, &otoken); + gss_release_name(&min_stat, &client_name); + SSH_STRING_FREE(input_token); + + return 0; +} + +static int +verify_mic(ssh_session session, + ssh_string mic, + void *mic_buffer, + size_t mic_buffer_size, + void *userdata) +{ + /* Verify without checking */ + return 0; +} + +static int +setup_callback_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + struct session_data_st sdata = {.channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD}; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + ss->server_cb = get_default_server_cb(); + ss->server_cb->gssapi_select_oid_function = select_oid; + ss->server_cb->gssapi_accept_sec_ctx_function = accept_sec_ctx; + ss->server_cb->gssapi_verify_mic_function = verify_mic; + ss->server_cb->userdata = &sdata; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int +teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + SAFE_FREE(tss->ss->server_cb); + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int +session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void +torture_gssapi_server_auth_cb_no_client_cred(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* No client credential */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_cb_invalid_host(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Invalid host principal */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/invalid.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/invalid.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + torture_teardown_kdc_server((void **)&s); +} + +static void +torture_gssapi_server_auth_cb(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site\n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site\n" + "kadmin.local addprinc -pw bar alice\n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_OK); + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_auth_cb_bad_user(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site\n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site\n" + "kadmin.local addprinc -pw bar alice\n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb_no_client_cred, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb_invalid_host, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_auth_cb_bad_user, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_callback_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_delegation.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_delegation.c new file mode 100644 index 000000000000..dd3ad335b22d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_delegation.c @@ -0,0 +1,376 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void +free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void +setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_MIC; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int +auth_gssapi_mic(ssh_session session, + UNUSED_PARAM(const char *user), + UNUSED_PARAM(const char *principal), + void *userdata) +{ + OM_uint32 min_stat; + ssh_gssapi_creds creds = ssh_gssapi_get_creds(session); + assert_non_null(creds); + + gss_release_cred(&min_stat, creds); + + return SSH_AUTH_SUCCESS; +} + +static int +setup_callback_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024]; + pid_t pid; + struct session_data_st sdata = {.channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = SSHD_DEFAULT_USER, + .password = SSHD_DEFAULT_PASSWORD}; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + ss->server_cb = get_default_server_cb(); + ss->server_cb->auth_gssapi_mic_function = auth_gssapi_mic; + ss->server_cb->userdata = &sdata; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int +teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + SAFE_FREE(tss->ss->server_cb); + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int +session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int +session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void +torture_gssapi_server_delegate_creds(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + OM_uint32 maj_stat, min_stat; + gss_cred_id_t client_creds = GSS_C_NO_CREDENTIAL; + gss_OID_set no_mechs = GSS_C_NO_OID_SET; + int t = 1; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_GSSAPI_DELEGATE_CREDENTIALS, &t); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + maj_stat = gss_acquire_cred(&min_stat, + GSS_C_NO_NAME, + GSS_C_INDEFINITE, + GSS_C_NO_OID_SET, + GSS_C_INITIATE, + &client_creds, + &no_mechs, + NULL); + assert_int_equal(GSS_ERROR(maj_stat), 0); + + ssh_gssapi_set_creds(session, client_creds); + + rc = ssh_userauth_gssapi(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + gss_release_cred(&min_stat, &client_creds); + gss_release_oid_set(&min_stat, &no_mechs); + + torture_teardown_kdc_server((void **)&s); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_delegate_creds, + session_setup, + session_teardown), + + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_callback_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange.c new file mode 100644 index 000000000000..67704f6d088e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange.c @@ -0,0 +1,604 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/crypto.h" +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_KEYEX; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-," + "gss-group16-sha512-," + "gss-nistp256-sha256-," + "gss-curve25519-sha256-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024] = {0}; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_gssapi_server_key_exchange(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + assert_true(ssh_session_kex_is_gss(session)); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_no_tgt(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Don't run kinit */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_false(ssh_session_kex_is_gss(session)); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_alg(void **state, + const char *kex_string, + enum ssh_key_exchange_e kex_type) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + kex_string); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + assert_int_equal(session->current_crypto->kex_type, kex_type); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_gss_group14_sha256(void **state) +{ + torture_gssapi_server_key_exchange_alg(state, + "gss-group14-sha256-", + SSH_GSS_KEX_DH_GROUP14_SHA256); +} + +static void torture_gssapi_server_key_exchange_gss_group16_sha512(void **state) +{ + torture_gssapi_server_key_exchange_alg(state, + "gss-group16-sha512-", + SSH_GSS_KEX_DH_GROUP16_SHA512); +} + +static void torture_gssapi_server_key_exchange_gss_nistp256_sha256(void **state) +{ + torture_gssapi_server_key_exchange_alg(state, + "gss-nistp256-sha256-", + SSH_GSS_KEX_ECDH_NISTP256_SHA256); +} + +static void torture_gssapi_server_key_exchange_gss_curve25519_sha256(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + torture_gssapi_server_key_exchange_alg(state, + "gss-curve25519-sha256-", + SSH_GSS_KEX_CURVE25519_SHA256); +} + +static void torture_gssapi_server_key_exchange_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_auth_bad_user(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_DENIED); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_no_auth(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + bool f = false; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + /* Don't do GSSAPI Key Exchange */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &f); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + assert_false(ssh_session_kex_is_gss(session)); + + /* Still try to do "gssapi-keyex" auth */ + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + + torture_teardown_kdc_server((void **)&s); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_no_tgt, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_group14_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_group16_sha512, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_nistp256_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_gss_curve25519_sha256, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_auth, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_auth_bad_user, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_no_auth, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_fallback.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_fallback.c new file mode 100644 index 000000000000..a5988ed9d6fb --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_fallback.c @@ -0,0 +1,336 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/crypto.h" +#include "libssh/libssh.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + // char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_KEYEX; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024] = {0}; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_gssapi_server_key_exchange_fallback(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_GSSAPI_KEY_EXCHANGE_ALGS, + "gss-group16-sha512-"); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + assert_false(ssh_session_kex_is_gss(session)); + + rc = ssh_userauth_gssapi_keyex(session); + assert_int_equal(rc, SSH_AUTH_ERROR); + + torture_teardown_kdc_server((void **)&s); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_gssapi_server_key_exchange_fallback, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_null.c b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_null.c new file mode 100644 index 000000000000..1fc9fa866868 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_gssapi_server_key_exchange_null.c @@ -0,0 +1,346 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "libssh/libssh.h" +#include "torture.h" + +#include "test_server.h" +#include "default_cb.h" + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; +}; + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static void setup_config(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + char sshd_path[1024]; + char log_file[1024]; + char kdc_env[255] = {0}; + int rc; + + assert_non_null(state); + + tss = (struct test_server_st *)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + assert_non_null(s->gss_dir); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_GSSAPI_KEYEX; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + /* Use the default session handling function */ + ss->handle_session = default_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Enable GSSAPI key exchange */ + ss->gssapi_key_exchange = true; + ss->gssapi_key_exchange_algs = "gss-group14-sha256-," + "gss-group16-sha512-," + "gss-nistp256-sha256-," + "gss-curve25519-sha256-"; + + tss->state = s; + tss->ss = ss; + + *state = tss; +} + +static int setup_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + char pid_str[1024] = {0}; + pid_t pid; + int rc; + + setup_config(state); + + tss = *state; + ss = tss->ss; + s = tss->state; + + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + torture_teardown_kdc_server((void **)&s); + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s = NULL; + struct server_state_st *ss = NULL; + struct test_server_st *tss = NULL; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int verbosity = torture_libssh_verbosity(); + char *cwd = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tss->cwd = cwd; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, + SSH_OPTIONS_USER, + TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_gssapi_server_key_exchange_null(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + enum ssh_known_hosts_e known_hosts_state; + int rc; + bool t = true; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Valid */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + "echo bar | kinit alice"); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(s->ssh.session, rc); + + assert_true(ssh_session_kex_is_gss(session)); + + assert_string_equal(session->current_crypto->kex_methods[SSH_HOSTKEYS], + "null"); + + known_hosts_state = ssh_session_is_known_server(session); + assert_int_equal(known_hosts_state, SSH_KNOWN_HOSTS_UNKNOWN); + + torture_teardown_kdc_server((void **)&s); +} + +static void torture_gssapi_server_key_exchange_no_tgt(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + bool t = true; + + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Don't run kinit */ + torture_setup_kdc_server( + (void **)&s, + "kadmin.local addprinc -randkey host/server.libssh.site \n" + "kadmin.local ktadd -k $(dirname $0)/d/ssh.keytab host/server.libssh.site \n" + "kadmin.local addprinc -pw bar alice \n" + "kadmin.local list_principals", + + /* No TGT */ + ""); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + assert_ssh_return_code(s->ssh.session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + torture_teardown_kdc_server((void **)&s); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_null, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_gssapi_server_key_exchange_no_tgt, + session_setup, + session_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_server_algorithms.c b/src/libs/libssh-0.12.2/tests/server/torture_server_algorithms.c new file mode 100644 index 000000000000..02d8aaae2746 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_server_algorithms.c @@ -0,0 +1,454 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/token.h" + +#include "test_server.h" +#include "default_cb.h" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + char *cwd; + char *temp_dir; + char rsa_hostkey[1024]; +}; + +static int setup_files(void **state) +{ + struct test_server_st *tss; + struct torture_state *s; + char sshd_path[1024]; + char log_file[1024]; + + int rc; + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, sizeof(log_file), "%s/sshd/log", s->socket_dir); + + snprintf(tss->rsa_hostkey, + sizeof(tss->rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(tss->rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + /* not to mix up the client and server messages */ + s->log_file = strdup(log_file); + + tss->state = s; + *state = tss; + + return 0; +} + +static int teardown_files(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + torture_teardown_socket_dir((void **)&s); + SAFE_FREE(tss); + + return 0; +} + +static int setup_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + char *cwd = NULL; + char *tmp_dir = NULL; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + return 0; +} + +static int teardown_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + int rc; + + assert_non_null(tss); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static int start_server(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + /* Start the server using the default values */ + torture_setup_libssh_server((void **)&s, "./test_server/test_server"); + assert_non_null(s); + + return 0; +} + +static int stop_server(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + int rc; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + rc = torture_terminate_process(s->srv_pidfile); + assert_return_code(rc, errno); + + unlink(s->srv_pidfile); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +/* + * Check that the handshake works with an AEAD cipher configured + * but with no overlap for HMACs. AEAD ciphers have an implied HMAC + * so no HMAC overlap in the handshake should not fail the connection. + */ +static void test_algorithm_no_hmac_overlap(void **state, const char *algorithm) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + char config_content[4096]; + + ssh_session session = NULL; + + int rc; + + assert_non_null(tss); + s = tss->state; + assert_non_null(s); + + /* Prepare config file */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\nMACs %s\n", + tss->rsa_hostkey, + algorithm, + "hmac-sha2-512-etm@openssh.com"); + + assert_non_null(s->srv_config); + torture_write_file(s->srv_config, config_content); + + SSH_LOG(SSH_LOG_TRACE, + "Config file %s content: \n\n%s\n", + s->srv_config, + config_content); + + /* Start server */ + rc = start_server(state); + assert_int_equal(rc, 0); + + /* Setup session */ + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, algorithm); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_S_C, algorithm); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_C_S, "hmac-sha2-512"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha2-512"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_ssh_return_code(session, rc); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); + + SAFE_FREE(s->srv_additional_config); +} + +static void torture_algorithm_chacha20_with_no_hmac_overlap(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + test_algorithm_no_hmac_overlap(state, "chacha20-poly1305@openssh.com"); +} + +static void torture_algorithm_aes256gcm_with_no_hmac_overlap(void **state) +{ + test_algorithm_no_hmac_overlap(state, "aes256-gcm@openssh.com"); +} + +static void torture_algorithm_aes128gcm_with_no_hmac_overlap(void **state) +{ + test_algorithm_no_hmac_overlap(state, "aes128-gcm@openssh.com"); +} + +/* + * Check the self-compatibility of a given key exchange method. + */ +static void test_kex_self_compat(void **state, const char *kex) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + char config_content[4096]; + int rc; + + assert_non_null(tss); + s = tss->state; + assert_non_null(s); + + /* Prepare config file */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nKexAlgorithms %s\n", + tss->rsa_hostkey, + kex); + + assert_non_null(s->srv_config); + torture_write_file(s->srv_config, config_content); + + SSH_LOG(SSH_LOG_TRACE, + "Config file %s content: \n\n%s\n", + s->srv_config, + config_content); + + rc = start_server(state); + assert_int_equal(rc, 0); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_ssh_return_code(session, rc); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); +} + +static void torture_algorithm_mlkem768x25119_self_compat(void **state) +{ + if (ssh_fips_mode()) { + skip(); + } + test_kex_self_compat(state, "mlkem768x25519-sha256"); +} + +static void torture_algorithm_mlkem768nistp256_self_compat(void **state) +{ + test_kex_self_compat(state, "mlkem768nistp256-sha256"); +} + +#ifdef HAVE_MLKEM1024 +static void torture_algorithm_mlkem1024nistp384_self_compat(void **state) +{ + test_kex_self_compat(state, "mlkem1024nistp384-sha384"); +} +#endif /* HAVE_MLKEM1024 */ + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_algorithm_chacha20_with_no_hmac_overlap, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_aes256gcm_with_no_hmac_overlap, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_aes128gcm_with_no_hmac_overlap, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_mlkem768x25119_self_compat, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_algorithm_mlkem768nistp256_self_compat, + setup_temp_dir, teardown_temp_dir), +#ifdef HAVE_MLKEM1024 + cmocka_unit_test_setup_teardown(torture_algorithm_mlkem1024nistp384_self_compat, + setup_temp_dir, teardown_temp_dir), +#endif /* HAVE_MLKEM1024 */ + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_files, + teardown_files); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_server_auth_kbdint.c b/src/libs/libssh-0.12.2/tests/server/torture_server_auth_kbdint.c new file mode 100644 index 000000000000..c12626bb2585 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_server_auth_kbdint.c @@ -0,0 +1,818 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include +#include +#include + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +enum { + SUCCESS, + MORE, + FAILED +}; + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; +}; + +#ifdef WITH_PCAP +static void set_pcap(struct session_data_st *sdata, + ssh_session session, + char *pcap_file) +{ + int rc = 0; + + if (sdata == NULL) { + return; + } + + if (pcap_file == NULL) { + return; + } + + sdata->pcap = ssh_pcap_file_new(); + if (sdata->pcap == NULL) { + return; + } + + rc = ssh_pcap_file_open(sdata->pcap, pcap_file); + if (rc == SSH_ERROR) { + fprintf(stderr, "Error opening pcap file\n"); + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; + return; + } + ssh_set_pcap_file(session, sdata->pcap); +} + +static void cleanup_pcap(struct session_data_st *sdata) +{ + if (sdata == NULL) { + return; + } + + if (sdata->pcap == NULL) { + return; + } + + ssh_pcap_file_free(sdata->pcap); + sdata->pcap = NULL; +} +#endif + +static int process_stdout(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write(channel, buf, n); + } + } + + return n; +} + +static int process_stderr(socket_t fd, int revents, void *userdata) +{ + char buf[BUF_SIZE]; + int n = -1; + ssh_channel channel = (ssh_channel) userdata; + + if (channel != NULL && (revents & POLLIN) != 0) { + n = read(fd, buf, BUF_SIZE); + if (n > 0) { + ssh_channel_write_stderr(channel, buf, n); + } + } + + return n; +} + +static int authenticate_kbdint(ssh_session session, + ssh_message message, + void *userdata) +{ + int rc = 0; + int count; + int *step = NULL; + size_t expected_len; + + const char instruction[] = "Type the requested data"; + const char name[] = "Keyboard-Interactive Authentication\n"; + char initial_echo[] = {1, 0}; + char retype_echo[] = {0}; + const char *initial_prompt[2]; + const char *retype_prompt[1]; + int cmp; + + const char *answer; + + struct session_data_st *sdata = (struct session_data_st *)userdata; + + initial_prompt[0] = "username: "; + initial_prompt[1] = "password: "; + + /* Prompt for additional prompts */ + retype_prompt[0] = "retype password: "; + + if ((session == NULL) || (message == NULL) || (sdata == NULL)) { + fprintf(stderr, "Null argument provided\n"); + goto failed; + } + + if (sdata->extra_data == NULL) { + goto failed; + } + + step = (int *)sdata->extra_data; + + switch (*step) { + case 0: + ssh_message_auth_interactive_request(message, name, instruction, 2, + initial_prompt, initial_echo); + rc = MORE; + goto end; + case 1: + count = ssh_userauth_kbdint_getnanswers(session); + if (count != 2) { + goto failed; + } + + if ((sdata->username == NULL) || (sdata->password == NULL)) { + goto failed; + } + + /* Get and compare username */ + expected_len = strlen(sdata->username); + if (expected_len <= 0) { + goto failed; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + if (answer == NULL) { + goto failed; + } + + cmp = strncmp(answer, sdata->username, expected_len); + if (cmp != 0) { + goto failed; + } + + /* Get and compare password */ + expected_len = strlen(sdata->password); + if (expected_len <= 0) { + goto failed; + } + + answer = ssh_userauth_kbdint_getanswer(session, 1); + if (answer == NULL) { + goto failed; + } + + cmp = strncmp(answer, sdata->password, expected_len); + if (cmp != 0) { + goto failed; + } + + /* Username and password matched. Ask for a retype. */ + ssh_message_auth_interactive_request(message, + name, + instruction, + 1, + retype_prompt, + retype_echo); + + rc = MORE; + goto end; + case 2: + /* Get and compare password */ + expected_len = strlen(sdata->password); + if (expected_len <= 0) { + goto failed; + } + + answer = ssh_userauth_kbdint_getanswer(session, 0); + if (answer == NULL) { + goto failed; + } + + cmp = strncmp(answer, sdata->password, expected_len); + if (cmp != 0) { + goto failed; + } + + /* Password was correct, authenticated */ + rc = SUCCESS; + goto end; + default: + goto failed; + } + +failed: + if (step != NULL) { + *step = 0; + } + return FAILED; + +end: + if (step != NULL) { + (*step)++; + } + return rc; +} + +static int authenticate_callback(ssh_session session, + ssh_message message, + void *userdata) +{ + struct session_data_st *sdata = (struct session_data_st *)userdata; + int rc; + + if (sdata == NULL) { + fprintf(stderr, "Null userdata\n"); + goto denied; + } + + if (sdata->extra_data == NULL) { + sdata->extra_data = (void *)calloc(1, sizeof(int)); + } + + switch (ssh_message_type(message)) { + case SSH_REQUEST_AUTH: + switch (ssh_message_subtype(message)) { + case SSH_AUTH_METHOD_INTERACTIVE: + rc = authenticate_kbdint(session, message, (void *)sdata); + if (rc == SUCCESS) { + goto accept; + } + else if (rc == MORE) { + goto more; + } + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_INTERACTIVE); + goto denied; + default: + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_INTERACTIVE); + goto denied; + } + default: + ssh_message_auth_set_methods(message, SSH_AUTH_METHOD_INTERACTIVE); + goto denied; + } + + ssh_message_free(message); + +accept: + if (sdata) { + if (sdata->extra_data) { + free(sdata->extra_data); + sdata->extra_data = NULL; + } + } + ssh_message_auth_reply_success (message, 0); +more: + return 0; +denied: + if (sdata) { + if (sdata->extra_data) { + free(sdata->extra_data); + sdata->extra_data = NULL; + } + } + return 1; +} + +static void handle_kbdint_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state) +{ + int n; + int rc = 0; + + /* Structure for storing the pty size. */ + struct winsize wsize = { + .ws_row = 0, + .ws_col = 0, + .ws_xpixel = 0, + .ws_ypixel = 0 + }; + + /* Our struct holding information about the channel. */ + struct channel_data_st cdata = { + .pid = 0, + .pty_master = -1, + .pty_slave = -1, + .child_stdin = -1, + .child_stdout = -1, + .child_stderr = -1, + .event = NULL, + .winsize = &wsize + }; + + /* Our struct holding information about the session. */ + struct session_data_st sdata = { + .channel = NULL, + .auth_attempts = 0, + .authenticated = 0, + .username = TORTURE_SSH_USER_BOB, + .password = TORTURE_SSH_USER_BOB_PASSWORD + }; + + struct ssh_channel_callbacks_struct *channel_cb = NULL; + struct ssh_server_callbacks_struct *server_cb = NULL; + + if (state == NULL) { + fprintf(stderr, "NULL server state provided\n"); + goto end; + } + + server_cb = get_default_server_cb(); + if (server_cb == NULL) { + goto end; + } + + /* + * This test was written prior to adding the kbdint callback + * for the server. Hence, here the server uses the + * ssh_message_callback for kbdint authentication, + * instead of the kbdint callback. + * + * Setting the kbdint callback as NULL ensures that the + * default kbdint callback for test_server doesn't get used + * for kbdint authentication. + * + * The test for kbdint callback based authentication has + * been added in torture_server.c, libssh keeps this test to + * test the old way of doing kbdint authentication using + * ssh_message_callback. + */ + server_cb->auth_kbdint_function = NULL; + server_cb->userdata = &sdata; + + /* This is a macro, it does not return a value */ + ssh_callbacks_init(server_cb); + + rc = ssh_set_server_callbacks(session, server_cb); + if (rc) { + goto end; + } + +#ifdef WITH_PCAP + set_pcap(&sdata, session, state->pcap_file); +#endif + + rc = ssh_handle_key_exchange(session); + if (rc != SSH_OK) { + fprintf(stderr, "%s\n", ssh_get_error(session)); + goto end; + } + + /* Set the supported authentication methods */ + ssh_set_auth_methods(session, SSH_AUTH_METHOD_INTERACTIVE); + + ssh_set_message_callback(session, authenticate_callback, &sdata); + + rc = ssh_event_add_session(event, session); + if (rc != 0) { + fprintf(stderr, "Error adding session to event\n"); + goto end; + } + + n = 0; + while (sdata.authenticated == 0 || sdata.channel == NULL) { + /* If the user has used up all attempts, or if he hasn't been able to + * authenticate in 10 seconds (n * 100ms), disconnect. */ + if (sdata.auth_attempts >= state->max_tries || n >= 100) { + goto end; + } + + if (ssh_event_dopoll(event, 100) == SSH_ERROR) { + fprintf(stderr, "do_poll error: %s\n", ssh_get_error(session)); + goto end; + } + n++; + } + + channel_cb = get_default_channel_cb(); + if (channel_cb == NULL) { + goto end; + } + + channel_cb->userdata = &cdata; + + ssh_callbacks_init(channel_cb); + rc = ssh_set_channel_callbacks(sdata.channel, channel_cb); + if (rc != 0) { + goto end; + } + + do { + /* Poll the main event which takes care of the session, the channel and + * even our child process's stdout/stderr (once it's started). */ + rc = ssh_event_dopoll(event, -1); + if (rc == SSH_ERROR) { + ssh_channel_close(sdata.channel); + } + + /* If child process's stdout/stderr has been registered with the event, + * or the child process hasn't started yet, continue. */ + if (cdata.event != NULL || cdata.pid == 0) { + continue; + } + /* Executed only once, once the child process starts. */ + cdata.event = event; + /* If stdout valid, add stdout to be monitored by the poll event. */ + if (cdata.child_stdout != -1) { + if (ssh_event_add_fd(event, cdata.child_stdout, POLLIN, process_stdout, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stdout to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + + /* If stderr valid, add stderr to be monitored by the poll event. */ + if (cdata.child_stderr != -1){ + if (ssh_event_add_fd(event, cdata.child_stderr, POLLIN, process_stderr, + sdata.channel) != SSH_OK) { + fprintf(stderr, "Failed to register stderr to poll context\n"); + ssh_channel_close(sdata.channel); + } + } + } while(ssh_channel_is_open(sdata.channel) && + (cdata.pid == 0 || waitpid(cdata.pid, &rc, WNOHANG) == 0)); + + close(cdata.pty_master); + close(cdata.child_stdin); + close(cdata.child_stdout); + close(cdata.child_stderr); + + /* Remove the descriptors from the polling context, since they are now + * closed, they will always trigger during the poll calls. */ + ssh_event_remove_fd(event, cdata.child_stdout); + ssh_event_remove_fd(event, cdata.child_stderr); + + /* If the child process exited. */ + if (kill(cdata.pid, 0) < 0 && WIFEXITED(rc)) { + rc = WEXITSTATUS(rc); + ssh_channel_request_send_exit_status(sdata.channel, rc); + /* If client terminated the channel or the process did not exit nicely, + * but only if something has been forked. */ + } else if (cdata.pid > 0) { + kill(cdata.pid, SIGKILL); + } + + ssh_channel_send_eof(sdata.channel); + ssh_channel_close(sdata.channel); + + /* Wait up to 5 seconds for the client to terminate the session. */ + for (n = 0; n < 50 && (ssh_get_status(session) & SESSION_END) == 0; n++) { + ssh_event_dopoll(event, 100); + } + +end: +#ifdef WITH_PCAP + cleanup_pcap(&sdata); +#endif + if (channel_cb != NULL) { + free(channel_cb); + } + if (server_cb != NULL) { + free(server_cb); + } + return; +} + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static int setup_kbdint_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + char rsa_hostkey[1024] = {0}; + + char sshd_path[1024]; + char log_file[1024]; + + int rc; + + char pid_str[1024]; + + pid_t pid; + + assert_non_null(state); + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + + /* Create the server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->host_key = strdup(rsa_hostkey); + assert_non_null(rsa_hostkey); + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + ss->max_tries = 3; + ss->error = 0; + + tss->state = s; + tss->ss = ss; + + /* Set the session handling function */ + ss->handle_session = handle_kbdint_session_cb; + assert_non_null(ss->handle_session); + + /* Start the server */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + rc = torture_wait_for_daemon(15); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int teardown_kbdint_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static void torture_server_auth_kbdint(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int nprompts = 0; + int rc; + const char *prompt = NULL; + char echo; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + nprompts = ssh_userauth_kbdint_getnprompts(session); + assert_int_equal(nprompts, 2); + + prompt = ssh_userauth_kbdint_getprompt(NULL, 0, &echo); + assert_null(prompt); + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + assert_string_equal(prompt, "username: "); + assert_int_equal(echo, 1); + prompt = ssh_userauth_kbdint_getprompt(session, 1, &echo); + assert_string_equal(prompt, "password: "); + assert_int_equal(echo, 0); + prompt = ssh_userauth_kbdint_getprompt(session, 2, &echo); + assert_null(prompt); + + /* Reply the first 2 prompts using the username and password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, + TORTURE_SSH_USER_BOB); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, + TORTURE_SSH_USER_BOB_PASSWORD); + assert_false(rc < 0); + + /* Resend the password */ + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + nprompts = ssh_userauth_kbdint_getnprompts(session); + assert_int_equal(nprompts, 1); + + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + assert_string_equal(prompt, "retype password: "); + assert_int_equal(echo, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 0, + TORTURE_SSH_USER_BOB_PASSWORD); + assert_false(rc < 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + + /* Sometimes, SSH server send an empty query at the end of exchange */ + if (rc == SSH_AUTH_INFO) { + nprompts = ssh_userauth_kbdint_getnprompts(session); + assert_int_equal(nprompts, 0); + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + assert_null(prompt); + rc = ssh_userauth_kbdint(session, NULL, NULL); + } + + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_auth_kbdint, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_kbdint_server, + teardown_kbdint_server); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_server_config.c b/src/libs/libssh-0.12.2/tests/server/torture_server_config.c new file mode 100644 index 000000000000..c3c5f3a5b42c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_server_config.c @@ -0,0 +1,805 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/token.h" + +#include "test_server.h" +#include "default_cb.h" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + char *cwd; + char *temp_dir; + char ed25519_hostkey[1024]; + char rsa_hostkey[1024]; + char ecdsa_521_hostkey[1024]; + char ecdsa_384_hostkey[1024]; + char ecdsa_256_hostkey[1024]; +}; + +static int setup_files(void **state) +{ + struct test_server_st *tss; + struct torture_state *s; + char sshd_path[1024]; + + int rc; + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(tss->rsa_hostkey, + sizeof(tss->rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(tss->rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(tss->ecdsa_521_hostkey, + sizeof(tss->ecdsa_521_hostkey), + "%s/sshd/ssh_host_ecdsa_521_key", + s->socket_dir); + torture_write_file(tss->ecdsa_521_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + snprintf(tss->ecdsa_384_hostkey, + sizeof(tss->ecdsa_384_hostkey), + "%s/sshd/ssh_host_ecdsa_384_key", + s->socket_dir); + torture_write_file(tss->ecdsa_384_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P384, 0)); + + snprintf(tss->ecdsa_256_hostkey, + sizeof(tss->ecdsa_256_hostkey), + "%s/sshd/ssh_host_ecdsa_256_key", + s->socket_dir); + torture_write_file(tss->ecdsa_256_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P256, 0)); + + if (!ssh_fips_mode()) { + snprintf(tss->ed25519_hostkey, + sizeof(tss->ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(tss->ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + } + + tss->state = s; + *state = tss; + + return 0; +} + +static int teardown_files(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + torture_teardown_socket_dir((void **)&s); + SAFE_FREE(tss); + + return 0; +} + +static int setup_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + char *cwd = NULL; + char *tmp_dir = NULL; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + return 0; +} + +static int teardown_temp_dir(void **state) +{ + struct test_server_st *tss = *state; + int rc; + + assert_non_null(tss); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static int start_server(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + /* Start the server using the default values */ + torture_setup_libssh_server((void **)&s, "./test_server/test_server"); + assert_non_null(s); + + return 0; +} + +static int stop_server(void **state) +{ + struct torture_state *s; + struct test_server_st *tss; + + int rc; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + rc = torture_terminate_process(s->srv_pidfile); + assert_return_code(rc, errno); + + unlink(s->srv_pidfile); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + const char *compat_hostkeys = ssh_get_supported_methods(SSH_HOSTKEYS); + struct passwd *pwd; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOSTKEYS, compat_hostkeys); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + return 0; +} + +static int try_config_content(void **state, const char *config_content, + bool parse_global) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc; + + ssh_session session; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + assert_non_null(s->srv_config); + + if (parse_global) { + fprintf(stderr, "Using system-wide configuration\n"); + } else { + /* The string is duplicated to not break the cleanup on error */ + s->srv_additional_config = strdup("-g"); + } + + torture_write_file(s->srv_config, config_content); + + fprintf(stderr, "Config file %s content: \n\n%s\n", s->srv_config, + config_content); + + rc = start_server(state); + assert_int_equal(rc, 0); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + /* Authenticate as alice with bob */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); + + SAFE_FREE(s->srv_additional_config); + + return 0; +} + +static char *hostkey_files[6] = {0}; + +static size_t setup_hostkey_files(struct test_server_st *tss) +{ + size_t num_hostkey_files = 1; + + hostkey_files[0] = tss->rsa_hostkey; + +#ifdef TEST_ALL_CRYPTO_COMBINATIONS + hostkey_files[1] = tss->ecdsa_256_hostkey; + hostkey_files[2] = tss->ecdsa_384_hostkey; + hostkey_files[3] = tss->ecdsa_521_hostkey; + + num_hostkey_files = 4; + + if (!ssh_fips_mode()) { + hostkey_files[4] = tss->ed25519_hostkey; + num_hostkey_files++; + } +#endif /* TEST_ALL_CRYPTO_COMBINATIONS */ + + return num_hostkey_files; +} + +static void torture_server_config_hostkey(void **state) +{ + struct test_server_st *tss = *state; + size_t i, num_hostkey_files; + char config_content[4096]; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + for (i = 0; i < num_hostkey_files; i++) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\n", + hostkey_files[i]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } +} + +static void torture_server_config_ciphers(void **state) +{ + struct test_server_st *tss = *state; + size_t i, j, num_hostkey_files = 1; + char config_content[4096]; + + const char *ciphers; + + struct ssh_tokens_st *tokens; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_S_C); + assert_non_null(ciphers); + } else { + ciphers = ssh_kex_get_default_methods(SSH_CRYPT_S_C); + assert_non_null(ciphers); + } + + tokens = ssh_tokenize(ciphers, ','); + assert_non_null(tokens); + + for (i = 0; i < num_hostkey_files; i++) { + /* Try setting all default algorithms */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\n", + hostkey_files[i], ciphers); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nCiphers %s\n", + hostkey_files[i], tokens->tokens[j]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + j++; + } + } + + ssh_tokens_free(tokens); +} + +static void torture_server_config_macs(void **state) +{ + struct test_server_st *tss = *state; + size_t i, j, num_hostkey_files = 1; + char config_content[4096]; + + const char *macs; + + struct ssh_tokens_st *tokens; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + macs = ssh_kex_get_fips_methods(SSH_MAC_S_C); + assert_non_null(macs); + } else { + macs = ssh_kex_get_default_methods(SSH_MAC_S_C); + assert_non_null(macs); + } + + tokens = ssh_tokenize(macs, ','); + assert_non_null(tokens); + + for (i = 0; i < num_hostkey_files; i++) { + /* Try setting all default algorithms */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nMACs %s\n", + hostkey_files[i], macs); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nMACs %s\n", + hostkey_files[i], tokens->tokens[j]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + j++; + } + } + + ssh_tokens_free(tokens); +} + +static void torture_server_config_kex(void **state) +{ + struct test_server_st *tss = *state; + size_t i, j, num_hostkey_files = 1; + char config_content[4096]; + + const char *kex; + + struct ssh_tokens_st *tokens; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + kex = ssh_kex_get_fips_methods(SSH_KEX); + assert_non_null(kex); + } else { + kex = ssh_kex_get_default_methods(SSH_KEX); + assert_non_null(kex); + } + + tokens = ssh_tokenize(kex, ','); + assert_non_null(tokens); + + for (i = 0; i < num_hostkey_files; i++) { + /* Try setting all default algorithms */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nKexAlgorithms %s\n", + hostkey_files[i], kex); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* Try each algorithm individually */ + j = 0; + while(tokens->tokens[j] != NULL) { + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nKexAlgorithms %s\n", + hostkey_files[i], tokens->tokens[j]); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + j++; + } + } + + ssh_tokens_free(tokens); +} + +static void torture_server_config_hostkey_algorithms(void **state) +{ + struct test_server_st *tss = *state; + size_t i, num_hostkey_files = 5; + char config_content[4096]; + + const char *allowed; + + int rc; + + assert_non_null(tss); + + num_hostkey_files = setup_hostkey_files(tss); + + if (ssh_fips_mode()) { + allowed = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + assert_non_null(allowed); + } else { + allowed = ssh_kex_get_default_methods(SSH_HOSTKEYS); + assert_non_null(allowed); + } + + for (i = 0; i < num_hostkey_files; i++) { + /* Should work with all allowed */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostKeyAlgorithms %s\n", + hostkey_files[i], allowed); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } + + /* Should work with matching hostkey and allowed algorithm */ + + if (!ssh_fips_mode()) { + /* ed25519 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ed25519_hostkey, "ssh-ed25519"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ssh-rsa */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, "ssh-rsa"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + } + + /* rsa-sha2-256 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, "rsa-sha2-256"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ssh-sha2-512 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, "rsa-sha2-512"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ecdsa-sha2-nistp256 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ecdsa_256_hostkey, "ecdsa-sha2-nistp256"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ecdsa-sha2-nistp384 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ecdsa_384_hostkey, "ecdsa-sha2-nistp384"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + + /* ecdsa-sha2-nistp521 */ + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->ecdsa_521_hostkey, "ecdsa-sha2-nistp521"); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); + +} + +static void torture_server_config_unknown(void **state) +{ + struct test_server_st *tss = *state; + char config_content[4096]; + + int rc; + + assert_non_null(tss); + assert_non_null(tss->rsa_hostkey); + + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nUnknownOption unknown-value1,unknown-value2\n", + tss->rsa_hostkey); + + rc = try_config_content(state, config_content, false); + assert_int_equal(rc, 0); +} + +/* + * Check that the server returns the correct signature when the negotiated host + * key is RSA but the signature algorithm is not the server's preferred + * algorithm (e.g. when the client prefers ssh-rsa over rsa-sha2-256 or + * rsa-sha2-512). + * + * Related: T191, T240 + */ +static void torture_server_config_rsa_hostkey_order(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + char config_content[4096]; + size_t num_hostkey_files; + const char *allowed = NULL; + + ssh_session session = NULL; + + int rc; + + assert_non_null(tss); + s = tss->state; + assert_non_null(s); + + /* Prepare key files */ + num_hostkey_files = setup_hostkey_files(tss); + assert_true(num_hostkey_files > 0); + + /* Create the server configuration file */ + if (ssh_fips_mode()) { + allowed = "rsa-sha2-512,rsa-sha2-256"; + } else { + allowed = "rsa-sha2-256,ssh-rsa"; + } + + snprintf(config_content, + sizeof(config_content), + "HostKey %s\nHostkeyAlgorithms %s\n", + tss->rsa_hostkey, allowed); + + assert_non_null(s->srv_config); + torture_write_file(s->srv_config, config_content); + + fprintf(stderr, "Config file %s content: \n\n%s\n", s->srv_config, + config_content); + fflush(stderr); + + /* Start server */ + rc = start_server(state); + assert_int_equal(rc, 0); + + /* Setup session */ + rc = session_setup(state); + assert_int_equal(rc, 0); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + /* Set client order of preference different from the server */ + if (ssh_fips_mode()) { + /* Set the host keys with rsa-sha2-256 before rsa-sha2-512 */ + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "rsa-sha2-256,rsa-sha2-512"); + assert_int_equal(rc, SSH_OK); + } else { + /* Set the host keys with ssh-rsa before rsa-sha2-256 */ + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "ssh-rsa,rsa-sha2-256"); + assert_int_equal(rc, SSH_OK); + } + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_ssh_return_code(session, rc); + + rc = session_teardown(state); + assert_int_equal(rc, 0); + + rc = stop_server(state); + assert_int_equal(rc, 0); + + SAFE_FREE(s->srv_additional_config); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_config_hostkey, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_ciphers, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_macs, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_kex, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_hostkey_algorithms, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_unknown, + setup_temp_dir, teardown_temp_dir), + cmocka_unit_test_setup_teardown(torture_server_config_rsa_hostkey_order, + setup_temp_dir, teardown_temp_dir), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_files, + teardown_files); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_server_default.c b/src/libs/libssh-0.12.2/tests/server/torture_server_default.c new file mode 100644 index 000000000000..a3c3d9da56ad --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_server_default.c @@ -0,0 +1,657 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" + +#include "test_server.h" +#include "default_cb.h" + +#include "channels.c" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + char *cwd; + char *temp_dir; +}; + +static int libssh_server_setup(void **state) +{ + struct test_server_st *tss = NULL; + struct torture_state *s = NULL; + + char log_file[1024]; + + assert_non_null(state); + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + torture_setup_create_libssh_config((void **)&s); + + snprintf(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + + s->log_file = strdup(log_file); + + /* The second argument is the relative path to the "server" directory binary + */ + torture_setup_libssh_server((void **)&s, "./test_server/test_server"); + assert_non_null(s); + + tss->state = s; + + *state = tss; + + return 0; +} + +static int sshd_teardown(void **state) { + + struct test_server_st *tss = NULL; + struct torture_state *s = NULL; + + assert_non_null(state); + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + /* This function can be reused to teardown the server */ + torture_teardown_sshd_server((void **)&s); + + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + struct passwd *pwd; + char *cwd = NULL; + char *tmp_dir = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + s = tss->state; + assert_non_null(s); + + pwd = getpwnam("bob"); + assert_non_null(pwd); + + rc = setuid(pwd->pw_uid); + assert_return_code(rc, errno); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_server_auth_none(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + char *banner = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + banner = ssh_get_issue_banner(session); + assert_string_equal(banner, SSHD_BANNER_MESSAGE); + free(banner); + banner = NULL; + + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } +} + +static void torture_server_auth_password(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default user for the server */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_server_auth_pubkey(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Authenticate as alice with bob's pubkey */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session,NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PUBLICKEY); + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_server_auth_kbdint(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_BOB); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + rc = ssh_userauth_none(session, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_INTERACTIVE); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing a wrong password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, SSHD_DEFAULT_USER); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, "wrongpassword"); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing a wrong username */ + rc = ssh_userauth_kbdint_setanswer(session, 0, "wrongusername"); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_DENIED); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_INFO); + assert_int_equal(ssh_userauth_kbdint_getnprompts(session), 2); + + /* Passing the right password */ + rc = ssh_userauth_kbdint_setanswer(session, 0, SSHD_DEFAULT_USER); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint_setanswer(session, 1, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, 0); + + rc = ssh_userauth_kbdint(session, NULL, NULL); + assert_int_equal(rc, SSH_AUTH_SUCCESS); +} + +static void torture_server_hostkey_mismatch(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + char known_hosts_file[1024] = {0}; + FILE *file = NULL; + enum ssh_known_hosts_e found; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* Store the testkey in the knownhosts file */ + snprintf(known_hosts_file, + sizeof(known_hosts_file), + "%s/%s", + s->socket_dir, + TORTURE_KNOWN_HOSTS_FILE); + + file = fopen(known_hosts_file, "w"); + assert_non_null(file); + fprintf(file, + "127.0.0.10 %s\n", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + fclose(file); + + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, known_hosts_file); + assert_ssh_return_code(session, rc); + /* Using the default user for the server */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_ssh_return_code(session, rc); + + /* Configure the client to offer only rsa-sha2-256 hostkey algorithm */ + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + + rc = ssh_connect(session); + assert_ssh_return_code(session, rc); + + /* Make sure we can verify the signature */ + found = ssh_session_is_known_server(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); +} + +static void torture_server_unknown_global_request(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + ssh_channel channel; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Request asking for reply */ + rc = ssh_global_request(session, "unknown-request-00@test.com", NULL, 1); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* Request and don't ask for reply */ + rc = ssh_global_request(session, "another-bad-req-00@test.com", NULL, 0); + assert_ssh_return_code(session, rc); + + /* Open channel to make sure the session is still working */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + ssh_channel_close(channel); +} + +static void torture_server_unknown_channel_request(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + ssh_channel channel; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Open a channel session */ + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_ssh_return_code(session, rc); + + /* Request asking for reply */ + rc = channel_request(channel, "unknown-request-00@test.com", NULL, 1); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* Request and don't ask for reply */ + rc = channel_request(channel, "another-bad-req-00@test.com", NULL, 0); + assert_ssh_return_code(session, rc); + + ssh_channel_close(channel); + ssh_channel_free(channel); +} + +static void torture_server_no_more_sessions(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session = NULL; + ssh_channel channels[2]; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* Open a channel session */ + channels[0] = ssh_channel_new(session); + assert_non_null(channels[0]); + + rc = ssh_channel_open_session(channels[0]); + assert_ssh_return_code(session, rc); + + /* Send no-more-sessions@openssh.com global request */ + rc = ssh_request_no_more_sessions(session); + assert_ssh_return_code(session, rc); + + /* Try to open an extra session and expect failure */ + channels[1] = ssh_channel_new(session); + assert_non_null(channels[1]); + + rc = ssh_channel_open_session(channels[1]); + assert_int_equal(rc, SSH_ERROR); + + /* Free the unused channel */ + ssh_channel_close(channels[1]); + ssh_channel_free(channels[1]); + + /* Close and free open channel */ + ssh_channel_close(channels[0]); + ssh_channel_free(channels[0]); +} + +static void torture_server_set_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + const char *message = "Goodbye"; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(session,message); + assert_ssh_return_code(session, rc); + assert_string_equal(session->disconnect_message,message); +} + +static void torture_null_server_set_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(NULL,"Goodbye"); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_server_set_null_disconnect_message(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + ssh_session session; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_session_set_disconnect_message(session,NULL); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->disconnect_message,"Bye Bye"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_auth_none, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_password, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_pubkey, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_auth_kbdint, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_hostkey_mismatch, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_unknown_global_request, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_unknown_channel_request, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_no_more_sessions, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_set_disconnect_message, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_null_server_set_disconnect_message, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_set_null_disconnect_message, + session_setup, + session_teardown), + }; + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, libssh_server_setup, sshd_teardown); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/server/torture_sftpserver.c b/src/libs/libssh-0.12.2/tests/server/torture_sftpserver.c new file mode 100644 index 000000000000..d19ebb4f760a --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/server/torture_sftpserver.c @@ -0,0 +1,1568 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "libssh/sftp.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include + +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#include "libssh/buffer.h" +#include "libssh/libssh.h" +#include "libssh/priv.h" +#include "libssh/session.h" +#include "libssh/sftp_priv.h" +#include "torture.h" +#include "torture_key.h" + +#include "test_server.h" +#include "default_cb.h" + +#define TORTURE_KNOWN_HOSTS_FILE "libssh_torture_knownhosts" + +const char template[] = "temp_dir_XXXXXX"; + +struct test_server_st { + struct torture_state *state; + struct server_state_st *ss; + char *cwd; + char *temp_dir; +}; + +void sftp_handle_session_cb(ssh_event event, + ssh_session session, + struct server_state_st *state); + +static void free_test_server_state(void **state) +{ + struct test_server_st *tss = *state; + + torture_free_state(tss->state); + SAFE_FREE(tss); +} + +static int setup_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + //char trusted_ca_pubkey[1024]; + + char sshd_path[1024]; + char log_file[1024]; + int rc; + + char pid_str[1024]; + + pid_t pid; + + assert_non_null(state); + + tss = (struct test_server_st*)calloc(1, sizeof(struct test_server_st)); + assert_non_null(tss); + + torture_setup_socket_dir((void **)&s); + assert_non_null(s->socket_dir); + + /* Set the default interface for the server */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "10", 1); + setenv("PAM_WRAPPER", "1", 1); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd", + s->socket_dir); + + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + + snprintf(log_file, + sizeof(log_file), + "%s/sshd/log", + s->socket_dir); + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + torture_write_file(rsa_hostkey, torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + + /* Create default server state */ + ss = (struct server_state_st *)calloc(1, sizeof(struct server_state_st)); + assert_non_null(ss); + + ss->address = strdup("127.0.0.10"); + assert_non_null(ss->address); + + ss->port = 22; + + ss->ecdsa_key = strdup(ecdsa_hostkey); + assert_non_null(ss->ecdsa_key); + + ss->ed25519_key = strdup(ed25519_hostkey); + assert_non_null(ss->ed25519_key); + + ss->rsa_key = strdup(rsa_hostkey); + assert_non_null(ss->rsa_key); + + ss->host_key = NULL; + + /* Use default username and password (set in default_handle_session_cb) */ + ss->expected_username = NULL; + ss->expected_password = NULL; + + /* not to mix up the client and server messages */ + ss->verbosity = torture_libssh_verbosity(); + ss->log_file = strdup(log_file); + + ss->auth_methods = SSH_AUTH_METHOD_PASSWORD | SSH_AUTH_METHOD_PUBLICKEY; + +#ifdef WITH_PCAP + ss->with_pcap = 1; + ss->pcap_file = strdup(s->pcap_file); + assert_non_null(ss->pcap_file); +#endif + + /* TODO make configurable */ + ss->max_tries = 3; + ss->error = 0; + + tss->state = s; + tss->ss = ss; + + /* Use the default session handling function */ + ss->handle_session = sftp_handle_session_cb; + assert_non_null(ss->handle_session); + + /* Do not use global configuration */ + ss->parse_global_config = false; + + /* Start the server using the default values */ + pid = fork_run_server(ss, free_test_server_state, &tss); + if (pid < 0) { + fail(); + } + + snprintf(pid_str, sizeof(pid_str), "%d", pid); + + torture_write_file(s->srv_pidfile, (const char *)pid_str); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(5); + assert_int_equal(rc, 0); + + *state = tss; + + return 0; +} + +static int teardown_default_server(void **state) +{ + struct torture_state *s; + struct server_state_st *ss; + struct test_server_st *tss; + + tss = *state; + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + ss = tss->ss; + assert_non_null(ss); + + /* This function can be reused */ + torture_teardown_sshd_server((void **)&s); + + free_server_state(tss->ss); + SAFE_FREE(tss->ss); + SAFE_FREE(tss); + + return 0; +} + +static int session_setup(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int verbosity = torture_libssh_verbosity(); + char template2[] = "/tmp/ssh_torture_XXXXXX"; + char *cwd = NULL; + char *tmp_dir = NULL; + char *p = NULL; + bool b = false; + int rc; + + assert_non_null(tss); + + /* Make sure we do not test the agent */ + unsetenv("SSH_AUTH_SOCK"); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + p = mkdtemp(template2); + assert_non_null(p); + assert_non_null(tmp_dir); + + tss->cwd = cwd; + tss->temp_dir = tmp_dir; + + s = tss->state; + assert_non_null(s); + + s->ssh.session = ssh_new(); + assert_non_null(s->ssh.session); + + s->ssh.tsftp = (struct torture_sftp*)calloc(1, sizeof(struct torture_sftp)); + assert_non_null(s->ssh.tsftp); + s->ssh.tsftp->testdir = strdup(p); + assert_non_null(s->ssh.tsftp->testdir); + + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_ssh_return_code(s->ssh.session, rc); + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + assert_ssh_return_code(s->ssh.session, rc); + /* Make sure no other configuration options from system will get used */ + rc = ssh_options_set(s->ssh.session, SSH_OPTIONS_PROCESS_CONFIG, &b); + assert_ssh_return_code(s->ssh.session, rc); + + return 0; +} + +static int session_setup_sftp(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + ssh_session session = NULL; + sftp_session sftp = NULL; + int rc; + + assert_non_null(tss); + + rc = session_setup(state); + assert_int_equal(rc, 0); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + ssh_get_issue_banner(session); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + return 0; +} + +static int session_teardown(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + int rc = 0; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + SAFE_FREE(s->ssh.tsftp->testdir); + sftp_free(s->ssh.tsftp->sftp); + SAFE_FREE(s->ssh.tsftp); + + ssh_disconnect(s->ssh.session); + ssh_free(s->ssh.session); + + rc = torture_change_dir(tss->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tss->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tss->temp_dir); + SAFE_FREE(tss->cwd); + + return 0; +} + +static void torture_server_establish_sftp(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + ssh_session session; + sftp_session sftp; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default user for the server */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + ssh_get_issue_banner(session); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + + printf("in establish before sftp_new\n"); + sftp = sftp_new(session); + assert_non_null(sftp); + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + tsftp->sftp = sftp; +} + +static void torture_server_test_sftp_function(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + ssh_session session; + sftp_session sftp; + int rc; + char *rv_str; + sftp_dir dir; + + char data[65535] = {0}; + sftp_file source; + sftp_file to; + int read_len; + int write_len; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* TODO: implement proper pam authentication in callback */ + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_OK); + + /* Assert some information about the connected session */ + assert_int_equal(sftp->server_version, LIBSFTP_VERSION); + + /* symbol link */ + rc = sftp_symlink(sftp, "/tmp/this_is_the_link", "/tmp/sftp_symlink_test"); + assert_int_equal(rc, SSH_OK); + + rv_str = sftp_readlink(sftp, "/tmp/sftp_symlink_test"); + assert_non_null(rv_str); + ssh_string_free_char(rv_str); + + rc = sftp_unlink(sftp, "/tmp/sftp_symlink_test"); + assert_int_equal(rc, SSH_OK); + + /* open and close dir */ + dir = sftp_opendir(sftp, "./"); + assert_non_null(dir); + + rc = sftp_closedir(dir); + assert_int_equal(rc, SSH_OK); + + /* file read and write */ + source = sftp_open(sftp, "/usr/bin/ssh", O_RDONLY, 0); + assert_non_null(source); + + to = sftp_open(sftp, "ssh-copy", O_WRONLY | O_CREAT, 0700); + assert_non_null(to); + + read_len = sftp_read(source, data, 4096); + write_len = sftp_write(to, data, read_len); + assert_int_equal(write_len, read_len); + + rc = sftp_close(source); + assert_int_equal(rc, SSH_OK); + + rc = sftp_close(to); + assert_int_equal(rc, SSH_OK); + + rc = sftp_unlink(sftp, "ssh-copy"); + assert_int_equal(rc, SSH_OK); +} + +static void torture_server_sftp_init_repeat(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + ssh_session session = NULL; + sftp_session sftp = NULL; + ssh_buffer buffer = NULL; + sftp_packet packet = NULL; + uint32_t version; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, SSHD_DEFAULT_USER); + assert_int_equal(rc, SSH_OK); + + rc = ssh_connect(session); + assert_int_equal(rc, SSH_OK); + + rc = ssh_userauth_none(session, NULL); + /* This request should return a SSH_REQUEST_DENIED error */ + if (rc == SSH_AUTH_ERROR) { + assert_int_equal(ssh_get_error_code(session), SSH_REQUEST_DENIED); + } + rc = ssh_userauth_list(session, NULL); + assert_true(rc & SSH_AUTH_METHOD_PASSWORD); + + /* Using the default password for the server */ + rc = ssh_userauth_password(session, NULL, SSHD_DEFAULT_PASSWORD); + assert_int_equal(rc, SSH_AUTH_SUCCESS); + + /* init sftp session */ + tsftp = s->ssh.tsftp; + sftp = sftp_new(session); + assert_non_null(sftp); + tsftp->sftp = sftp; + + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + /* send one version N-1 */ + rc = ssh_buffer_pack(buffer, "d", LIBSFTP_VERSION - 1); + assert_int_equal(rc, SSH_OK); + rc = sftp_packet_write(sftp, SSH_FXP_INIT, buffer); + SSH_BUFFER_FREE(buffer); + assert_int_equal(rc, 9); + + packet = sftp_packet_read(sftp); + assert_non_null(packet); + assert_int_equal(packet->type, SSH_FXP_VERSION); + + /* Make sure we get the expected version N-1 */ + rc = ssh_buffer_unpack(packet->payload, "d", &version); + assert_int_equal(rc, SSH_OK); + assert_int_equal(version, LIBSFTP_VERSION - 1); + + /* Repeated INIT will fail on server */ + rc = sftp_init(sftp); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_server_sftp_open_read_write(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + sftp_session sftp = NULL; + ssh_session session = NULL; + sftp_attributes a = NULL; + sftp_file new_file = NULL; + char tmp_file[PATH_MAX] = {0}; + char data[10] = "0123456789"; + char read_data[10] = {0}; + struct stat sb; + int rc, write_len, read_len; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", tss->temp_dir); + + /* + * Create a new file + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0751); + assert_non_null(new_file); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* Verify locally the mode is correct */ + rc = stat(tmp_file, &sb); + assert_int_equal(rc, 0); + assert_int_equal(sb.st_mode, S_IFREG | 0751); + assert_int_equal(sb.st_size, sizeof(data)); /* 10b written */ + + /* Remote stat */ + a = sftp_stat(sftp, tmp_file); + assert_non_null(a); + assert_int_equal(a->permissions, S_IFREG | 0751); + assert_int_equal(a->size, sizeof(data)); /* 10b written */ + assert_int_equal(a->type, SSH_FILEXFER_TYPE_REGULAR); + sftp_attributes_free(a); + + /* + * Now that file exists and contains some data, lets try O_TRUNC, + * mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_TRUNC, 0); + assert_non_null(new_file); + + /* Verify that the existing data in the file has been truncated */ + a = sftp_stat(sftp, tmp_file); + assert_non_null(a); + assert_int_equal(a->size, 0); /* No content due to truncation */ + sftp_attributes_free(a); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* + * Now, lets try O_APPEND, mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_APPEND, 0); + assert_non_null(new_file); + + /* fstat is not implemented */ + a = sftp_fstat(new_file); + assert_null(a); + + /* Write should work ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + /* Reading should fail */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, SSH_ERROR); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* + * Now, lets try read+write, mode is ignored + */ + new_file = sftp_open(sftp, tmp_file, O_RDWR, 0); + assert_non_null(new_file); + + /* Reading should work */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, sizeof(read_data)); + assert_int_equal(sizeof(read_data), sizeof(data)); /* sanity */ + assert_memory_equal(read_data, data, sizeof(data)); + + rc = sftp_seek(new_file, 20); + assert_ssh_return_code(session, rc); + + /* Write should work also ok */ + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* Remove the file */ + rc = sftp_unlink(sftp, tmp_file); + assert_ssh_return_code(session, rc); + + /* again: the file does not exist anymore so we should fail now */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_ERROR); + + /* + * Now, lets try read+write+create + */ + new_file = sftp_open(sftp, tmp_file, O_RDWR | O_CREAT, 0700); + assert_non_null(new_file); + + /* Reading should not fail but return no data */ + read_len = sftp_read(new_file, read_data, sizeof(read_data)); + assert_int_equal(read_len, 0); + + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* be nice */ + rc = sftp_unlink(sftp, tmp_file); + assert_ssh_return_code(session, rc); + + /* null flags should be invalid */ + /* but there is no way in libssh client to force null flags so skip this + new_file = sftp_open(sftp, tmp_file, 0, 0700); + assert_null(new_file); + */ + + /* Only O_CREAT is invalid on file which does not exist. Read is implicit */ + new_file = sftp_open(sftp, tmp_file, O_CREAT, 0700); + assert_null(new_file); +} + +static void torture_server_sftp_mkdir(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + sftp_attributes a = NULL; + struct stat sb; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + + /* create a test dir */ + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* try the same path again -- we should get an error */ + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_int_equal(rc, SSH_ERROR); + + /* Verify locally the mode is correct */ + rc = stat(tmp_dir, &sb); + assert_int_equal(rc, 0); + assert_int_equal(sb.st_mode, S_IFDIR | 0751); + + /* Remote stat */ + a = sftp_stat(sftp, tmp_dir); + assert_non_null(a); + assert_int_equal(a->permissions, S_IFDIR | 0751); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_DIRECTORY); + sftp_attributes_free(a); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newdir/newfile", tss->temp_dir); + + /* create a file in there */ + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* remove of non-empty directory fails */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_ERROR); + + /* Unlink can not remove directory either */ + rc = sftp_unlink(sftp, tmp_dir); + assert_int_equal(rc, SSH_ERROR); + + /* Remove the file */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + + /* Now it should work */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_ssh_return_code(session, rc); +} + +static void torture_server_sftp_realpath(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + char path[PATH_MAX] = {0}; + char exp_path[PATH_MAX] = {0}; + char *new_path = NULL; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* first try with the empty string, which should be equivalent to CWD */ + new_path = sftp_canonicalize_path(sftp, path); + assert_non_null(new_path); + assert_string_equal(new_path, tss->cwd); + ssh_string_free_char(new_path); + + /* now, lets try some more complicated paths relative to the CWD */ + snprintf(path, sizeof(path), "%s/.././%s", + tss->temp_dir, tss->temp_dir); + new_path = sftp_canonicalize_path(sftp, path); + assert_non_null(new_path); + snprintf(exp_path, sizeof(exp_path), "%s/%s", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, exp_path); + ssh_string_free_char(new_path); + + /* and this one does not exists, which is an error */ + snprintf(path, sizeof(path), "%s/.././%s/nodir", + tss->temp_dir, tss->temp_dir); + new_path = sftp_canonicalize_path(sftp, path); + assert_null(new_path); + ssh_string_free_char(new_path); +} + +static void torture_server_sftp_symlink(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + char path[PATH_MAX] = {0}; + char abs_path[PATH_MAX] = {0}; + char data[42] = "012345678901234567890123456789012345678901"; + char *new_path = NULL; + sftp_attributes a = NULL; + sftp_dir dir; + int write_len, num_files = 0; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* create a test dir */ + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* create a file in there */ + snprintf(tmp_file, sizeof(tmp_file), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + write_len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(write_len, sizeof(data)); + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* now, lets create a (relative) symlink to the new file */ + snprintf(path, sizeof(path), "%s/newdir/linkname", tss->temp_dir); + rc = sftp_symlink(sftp, tmp_file, path); + assert_ssh_return_code(session, rc); + + /* when the destination exists, it should fail */ + rc = sftp_symlink(sftp, tmp_dir, tmp_file); + assert_int_equal(rc, SSH_ERROR); + + /* now, there are different versions of stat that follow symlinks or not */ + /* lstat should not follow the symlink and show information about the link + * itself */ + a = sftp_lstat(sftp, path); + assert_non_null(a); + assert_int_not_equal(a->size, sizeof(data)); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_SYMLINK); + sftp_attributes_free(a); + + /* readlink should give us more information about the target of the symlink + */ + new_path = sftp_readlink(sftp, path); + assert_non_null(new_path); + snprintf(abs_path, sizeof(abs_path), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, abs_path); + ssh_string_free_char(new_path); + + /* stat should follow the symlink and show information about the link + * target */ + a = sftp_stat(sftp, path); + assert_non_null(a); + assert_int_equal(a->size, sizeof(data)); + assert_int_equal(a->permissions, S_IFREG | 0700); + assert_int_equal(a->type, SSH_FILEXFER_TYPE_REGULAR); + sftp_attributes_free(a); + + /* on non-existing path, they fail */ + a = sftp_lstat(sftp, "non-existing"); + assert_null(a); + a = sftp_stat(sftp, "non-existing"); + assert_null(a); + + /**** readdir ****/ + dir = sftp_opendir(sftp, tmp_dir); + assert_non_null(dir); + while ((a = sftp_readdir(sftp, dir))) { + if (strcmp(a->name, ".") != 0 && + strcmp(a->name, "..") != 0 && + strcmp(a->name, "newfile") != 0 && + strcmp(a->name, "linkname") != 0) { + /* There is a file we did not create */ + assert_true(false); + } + + num_files++; + sftp_attributes_free(a); + } + assert_int_equal(num_files, 4); + rc = sftp_dir_eof(dir); + assert_int_equal(rc, 1); + rc = sftp_closedir(dir); + assert_ssh_return_code(session, rc); + + /* now, remove the target of the link, the stat should not handle that, + * while lstat should keep working */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + + a = sftp_lstat(sftp, path); + assert_non_null(a); + assert_int_not_equal(a->size, sizeof(data)); + sftp_attributes_free(a); + + a = sftp_stat(sftp, path); + assert_null(a); + + /* readlink works ok on broken symlinks */ + new_path = sftp_readlink(sftp, path); + assert_non_null(new_path); + snprintf(abs_path, sizeof(abs_path), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + assert_string_equal(new_path, abs_path); + ssh_string_free_char(new_path); + + /* readlink should fail on directories */ + new_path = sftp_readlink(sftp, tmp_dir); + assert_null(new_path); + /* readlink should fail on or on non-existing files */ + new_path = sftp_readlink(sftp, tmp_file); + assert_null(new_path); + + /* Clean up symlink */ + rc = sftp_unlink(sftp, path); + assert_int_equal(rc, SSH_OK); + /* Clean up temporary directory */ + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_OK); +} + +static void torture_server_sftp_extended(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s; + struct torture_sftp *tsftp; + sftp_session sftp; + ssh_session session; + sftp_file new_file = NULL; + char tmp_dir[PATH_MAX] = {0}; + char tmp_file[PATH_MAX] = {0}; + sftp_statvfs_t st = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* create a test dir */ + snprintf(tmp_dir, sizeof(tmp_dir), "%s/newdir", tss->temp_dir); + rc = sftp_mkdir(sftp, tmp_dir, 0751); + assert_ssh_return_code(session, rc); + + /* create a file in there */ + snprintf(tmp_file, sizeof(tmp_file), "%s/%s/newdir/newfile", + tss->cwd, tss->temp_dir); + new_file = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + + /* extended fstatvsf is not advertised nor supported now but calling this + * message will keep hanging the server. The extension protocol says that + * the clients can not request extension that are not supported by the + * server so before doing this, we should use sftp_extension_supported() + * anyway */ + /* st = sftp_fstatvfs(new_file); + assert_null(st); */ + + /* close */ + rc = sftp_close(new_file); + assert_ssh_return_code(session, rc); + + /* extended statvsf */ + st = sftp_statvfs(sftp, tmp_file); + assert_non_null(st); + /* probably hard to check more */ + sftp_statvfs_free(st); + + /* Clean up temporary directory */ + rc = sftp_unlink(sftp, tmp_file); + assert_int_equal(rc, SSH_OK); + rc = sftp_rmdir(sftp, tmp_dir); + assert_int_equal(rc, SSH_OK); +} + +static void +torture_server_sftp_setstat(void **state) +{ + + char name[128] = {0}; + char data[10] = "0123456789"; + int rc; + size_t len; + int atime = 10676, mtime = 13467; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + + struct passwd *pwd = NULL; + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + struct sftp_attributes_struct attr; + sftp_attributes tmp_attr = NULL; + + sftp_session sftp = NULL; + ssh_session session = NULL; + sftp_file new_file = NULL; + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + assert_non_null(tsftp->testdir); + snprintf(name, sizeof(name), "%s/server_setstat_test", tsftp->testdir); + new_file = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(len, sizeof(data)); + rc = sftp_close(new_file); + assert_int_equal(rc, SSH_OK); + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS | + SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_ACMODTIME; + + attr.size = len; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.permissions = mode; + attr.atime = atime; + attr.mtime = mtime; + + rc = sftp_setstat(sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(rc, SSH_OK); + + tmp_attr = sftp_stat(sftp, name); + assert_non_null(tmp_attr); + + assert_int_equal(tmp_attr->uid, pwd->pw_uid); + assert_int_equal(tmp_attr->gid, pwd->pw_gid); + + assert_int_equal(len, tmp_attr->size); + assert_int_equal(tmp_attr->permissions & ACCESSPERMS, mode); + assert_int_equal(tmp_attr->mtime, mtime); + assert_int_equal(tmp_attr->atime, atime); + + /*negative tests*/ + rc = sftp_setstat(sftp, "not existing", &attr); + assert_int_equal(rc, SSH_ERROR); + sftp_unlink(sftp, name); + sftp_attributes_free(tmp_attr); +} + +static void +torture_server_sftp_readdir(void **state) +{ + + char name[128] = {0}; + char data[10] = "0123456789"; + int rc; + size_t len; + int atime = 10676, mtime = 13467; + mode_t mode = S_IRUSR | S_IWUSR | S_IRGRP; + int num_files = 0; + sftp_dir dir; + sftp_attributes a = NULL; + + struct passwd *pwd = NULL; + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + struct sftp_attributes_struct attr; + + sftp_session sftp = NULL; + ssh_session session = NULL; + sftp_file new_file = NULL; + + pwd = getpwnam("alice"); + assert_non_null(pwd); + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + session = s->ssh.session; + assert_non_null(session); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + assert_non_null(tsftp->testdir); + snprintf(name, sizeof(name), "%s/server_setstat_test", tsftp->testdir); + new_file = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(new_file); + len = sftp_write(new_file, data, sizeof(data)); + assert_int_equal(len, sizeof(data)); + rc = sftp_close(new_file); + assert_int_equal(rc, SSH_OK); + + ZERO_STRUCT(attr); + attr.flags = SSH_FILEXFER_ATTR_SIZE | SSH_FILEXFER_ATTR_PERMISSIONS | + SSH_FILEXFER_ATTR_UIDGID | SSH_FILEXFER_ATTR_ACMODTIME; + + attr.size = len; + attr.uid = pwd->pw_uid; + attr.gid = pwd->pw_gid; + attr.permissions = mode; + attr.atime = atime; + attr.mtime = mtime; + + rc = sftp_setstat(sftp, name, &attr); + assert_int_equal(rc, SSH_OK); + + dir = sftp_opendir(sftp, tsftp->testdir); + assert_non_null(dir); + while ((a = sftp_readdir(sftp, dir))) { + if (strcmp(a->name, "server_setstat_test") == 0) { + /* verify long name is in the expected format */ + assert_string_equal(a->longname, + "-rw-r----- 1 5001 9000 10 Jan 1 03:44:27 1970 server_setstat_test"); + } else if (strcmp(a->name, ".") != 0 && + strcmp(a->name, "..") != 0) { + /* There is a file we did not create */ + assert_true(false); + } + + num_files++; + sftp_attributes_free(a); + } + assert_int_equal(num_files, 3); + rc = sftp_dir_eof(dir); + assert_int_equal(rc, 1); + rc = sftp_closedir(dir); + assert_ssh_return_code(session, rc); +} + + +/* The max number of handles is 256 in sftpserver.h -- keep in sync! */ +#define SFTP_HANDLES 256 +static void torture_server_sftp_handles_exhaustion(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_file handle, handles[SFTP_HANDLES] = {0}; + sftp_session sftp = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Occupy all handles */ + for (int i = 0; i < SFTP_HANDLES; i++) { + snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i); + handles[i] = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handles[i]); + } + + /* Next handle should fail, but not crash or OOB */ + snprintf(name, sizeof(name), "%s/failfn", tsftp->testdir); + handle = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_null(handle); + + /* cleanup */ + for (int i = 0; i < SFTP_HANDLES; i++) { + snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i); + rc = sftp_close(handles[i]); + assert_int_equal(rc, SSH_OK); + } +} + +static void torture_server_sftp_opendir_handles_exhaustion(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_file handles[SFTP_HANDLES] = {0}; + sftp_dir dir = NULL; + sftp_session sftp = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Occupy all handles with files */ + for (int i = 0; i < SFTP_HANDLES; i++) { + snprintf(name, sizeof(name), "%s/fn%d", tsftp->testdir, i); + handles[i] = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handles[i]); + } + + /* Opening a directory should fail gracefully without leaking h->name */ + dir = sftp_opendir(sftp, tsftp->testdir); + assert_null(dir); + + /* cleanup */ + for (int i = 0; i < SFTP_HANDLES; i++) { + rc = sftp_close(handles[i]); + assert_int_equal(rc, SSH_OK); + } +} + +static void torture_server_sftp_handle_overrun(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_session sftp = NULL; + sftp_file handle = NULL; + ssh_buffer buffer = NULL; + uint32_t id, bad_handle = SFTP_HANDLES, bad_handle_len = 4; + sftp_message msg = NULL; + sftp_status_message status = NULL; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Initialize the sftp handles by opening first file */ + snprintf(name, sizeof(name), "%s/file", tsftp->testdir); + handle = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handle); + + rc = sftp_close(handle); + assert_int_equal(rc, SSH_OK); + + /* Craft an malicious SFTP packet trying to access handle 256 + * (SFTP_HANDLES) */ + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + rc = sftp_get_new_id(sftp, &id); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_pack(buffer, + "ddPqd", + id, + (uint32_t)bad_handle_len, + (size_t)bad_handle_len, + &bad_handle, /* a 32b int as ssh_string */ + (uint64_t)0, + (uint32_t)1024); + assert_int_equal(rc, SSH_OK); + rc = sftp_packet_write(sftp, SSH_FXP_READ, buffer); + SSH_BUFFER_FREE(buffer); + assert_int_equal(rc, 29); + + rc = sftp_recv_response_msg(sftp, id, true, &msg); + assert_int_equal(rc, SSH_OK); + assert_int_equal(msg->packet_type, SSH_FXP_STATUS); + status = parse_status_msg(msg); + sftp_message_free(msg); + assert_int_equal(status->status, SSH_FX_INVALID_HANDLE); + status_msg_free(status); +} + +static void torture_server_sftp_payload_overrun(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char name[128] = {0}; + sftp_session sftp = NULL; + sftp_file handle = NULL; + ssh_buffer buffer = NULL; + uint32_t id, bad_payload_len = 0x7ffffffc; + int rc; + +#ifdef HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + /* This malformed message does not crash the server, but keeps waiting + * for more data as announced in the payloiad length so the opened FD on + * the server side is leaking when the server terminates. + * Given that the custom sftp server could store anything into the + * handles, it should take care of cleaning up the outstanding handles, + * but this is something to solve in the future. Now just skipping the + * test. + */ + skip(); + } +#endif /* HAVE_VALGRIND_VALGRIND_H */ + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + /* Open a file for writing */ + snprintf(name, sizeof(name), "%s/file", tsftp->testdir); + handle = sftp_open(sftp, name, O_WRONLY | O_CREAT, 0700); + assert_non_null(handle); + + /* Craft an malicious SFTP packet trying to write to the file with + * payload_length overrun */ + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + rc = sftp_get_new_id(sftp, &id); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_pack(buffer, + "dbdSqd", + bad_payload_len, + SSH_FXP_WRITE, + id, + handle->handle, + (uint64_t)0, + (uint32_t)0); + assert_int_equal(rc, SSH_OK); + rc = ssh_channel_write(sftp->channel, + ssh_buffer_get(buffer), + ssh_buffer_get_len(buffer)); + assert_int_equal(rc, 29); + SSH_BUFFER_FREE(buffer); + + /* We do not get answer for this malformed packet -- just kill the + * connection */ + ssh_string_free(handle->handle); + free(handle); +} + +static void torture_server_sftp_after_channel_close(void **state) +{ + struct test_server_st *tss = *state; + struct torture_state *s = NULL; + struct torture_sftp *tsftp = NULL; + char tmp_file[PATH_MAX] = {0}; + sftp_session sftp = NULL; + sftp_file handle = NULL; + struct stat sb; + int rc; + + assert_non_null(tss); + + s = tss->state; + assert_non_null(s); + + tsftp = s->ssh.tsftp; + assert_non_null(tsftp); + + sftp = tsftp->sftp; + assert_non_null(sftp); + + snprintf(tmp_file, sizeof(tmp_file), "%s/newfile", tss->temp_dir); + + /* Close the channel */ + ssh_channel_close(sftp->channel); + /* Reset the flags so the channel looks open for the caller so we do not + * have to reimplement sending the message in the test */ + sftp->channel->local_eof = 0; + sftp->channel->state = SSH_CHANNEL_STATE_OPEN; + sftp->channel->flags &= ~SSH_CHANNEL_FLAG_CLOSED_LOCAL; + + /* Create a new file */ + handle = sftp_open(sftp, tmp_file, O_WRONLY | O_CREAT, 0751); + assert_null(handle); + + /* Should not be created */ + rc = stat(tmp_file, &sb); + assert_int_equal(rc, -1); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_server_establish_sftp, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_test_sftp_function, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_init_repeat, + session_setup, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_open_read_write, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_mkdir, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_realpath, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_symlink, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_extended, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_setstat, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_readdir, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_handles_exhaustion, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_opendir_handles_exhaustion, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_handle_overrun, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_payload_overrun, + session_setup_sftp, + session_teardown), + cmocka_unit_test_setup_teardown(torture_server_sftp_after_channel_close, + session_setup_sftp, + session_teardown), + }; + + setenv("TZ", "UTC", 1); + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_default_server, + teardown_default_server); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/ssh_ping.c b/src/libs/libssh-0.12.2/tests/ssh_ping.c new file mode 100644 index 000000000000..31bb64b45417 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/ssh_ping.c @@ -0,0 +1,108 @@ +/* ssh_ping.c */ +/* +Copyright 2018 Red Hat, Inc + +Author: Jakub Jelen + +This file is part of the SSH Library + +You are free to copy this file, modify it in any way, consider it being public +domain. This does not apply to the rest of the library though, but it is +allowed to cut-and-paste working code from this file to any license of +program. +The goal is to show the API in action. It's not a reference on how terminal +clients must be made or how a client should react. +*/ + +#include "config.h" + +#include +#include +#include +#include +#include + +int main(int argc, char **argv) +{ + const char *banner = NULL; + ssh_session session = NULL; + const char *hostkeys = NULL; + const char *kex = NULL; + int rc = 1; +#ifdef WITH_GSSAPI + bool t = true; +#endif /* WITH_GSSAPI */ + + bool process_config = false; + + if (argc < 1 || argv[1] == NULL) { + fprintf(stderr, "Error: Need an argument (hostname)\n"); + goto out; + } + + ssh_init(); + + session = ssh_new(); + if (session == NULL) { + goto out; + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, argv[1]); + if (rc < 0) { + goto out; + } + + /* The automatic username is not available under uid wrapper */ + rc = ssh_options_set(session, SSH_OPTIONS_USER, "ping"); + if (rc < 0) { + goto out; + } + + /* Ignore system-wide configurations when simply trying to reach host */ + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + if (rc < 0) { + goto out; + } + + /* Enable all supported algorithms */ + hostkeys = ssh_get_supported_methods(SSH_HOSTKEYS); + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, hostkeys); + if (rc < 0) { + goto out; + } + + /* Enable all supported kex algorithms */ + kex = ssh_get_supported_methods(SSH_KEX); + rc = ssh_options_set(session, SSH_OPTIONS_KEY_EXCHANGE, kex); + if (rc < 0) { + goto out; + } + +#ifdef WITH_GSSAPI + rc = ssh_options_set(session, SSH_OPTIONS_GSSAPI_KEY_EXCHANGE, &t); + if (rc < 0) { + goto out; + } +#endif /* WITH_GSSAPI */ + + rc = ssh_connect(session); + if (rc != SSH_OK) { + fprintf(stderr, "Connection failed : %s\n", ssh_get_error(session)); + goto out; + } + + banner = ssh_get_serverbanner(session); + if (banner == NULL) { + fprintf(stderr, "Did not receive SSH banner\n"); + goto out; + } + + printf("OK: %s\n", banner); + rc = 0; + +out: + ssh_free(session); + ssh_finalize(); + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/suppressions/lsan.supp b/src/libs/libssh-0.12.2/tests/suppressions/lsan.supp new file mode 100644 index 000000000000..dc95dfa5b1d8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/suppressions/lsan.supp @@ -0,0 +1,6 @@ +leak:libcrypto.so + +## sk-dummy.so +# The sk-dummy.so enroll function allocates 1-byte memory for the signature, but marks the signature length as 0. +# Since, we use burn_free to free the signature, it skips the freeing because the size is 0, which results in a memory leak. +leak:sk-dummy.so diff --git a/src/libs/libssh-0.12.2/tests/test_socket.c b/src/libs/libssh-0.12.2/tests/test_socket.c new file mode 100644 index 000000000000..84f7b35e8012 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/test_socket.c @@ -0,0 +1,93 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2009 by Aris Adamantiadis + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* Simple test for the socket callbacks */ + +#include +#include +#include +#include + +#include +#include +#include + +int stop=0; +ssh_socket s; + +static int data_rcv(const void *data, size_t len, void *user){ + printf("Received data: '"); + fwrite(data,1,len,stdout); + printf("'\n"); + ssh_socket_write(s,"Hello you !\n",12); + ssh_socket_nonblocking_flush(s); + return len; +} + +static void controlflow(int code,void *user){ + printf("Control flow: %x\n",code); +} + +static void exception(int code, int errno_code,void *user){ + printf("Exception: %d (%d)\n",code,errno_code); + stop=1; +} + +static void connected(int code, int errno_code,void *user){ + if(code == SSH_SOCKET_CONNECTED_OK) + printf("Connected: %d (%d)\n",code, errno_code); + else { + printf("Error while connecting:(%d, %d:%s)\n",code,errno_code,strerror(errno_code)); + stop=1; + } +} + +struct ssh_socket_callbacks_struct callbacks={ + data_rcv, + controlflow, + exception, + connected, + NULL +}; +int main(int argc, char **argv){ + ssh_session session; + ssh_poll_ctx ctx; + int verbosity=SSH_LOG_FUNCTIONS; + if(argc < 3){ + printf("Usage : %s host port\n", argv[0]); + return EXIT_FAILURE; + } + session=ssh_new(); + ssh_options_set(session,SSH_OPTIONS_LOG_VERBOSITY,&verbosity); + ssh_init(); + s=ssh_socket_new(session); + ctx=ssh_poll_ctx_new(2); + ssh_socket_set_callbacks(s, &callbacks); + ssh_poll_ctx_add_socket(ctx,s); + if(ssh_socket_connect(s,argv[1],atoi(argv[2]),NULL) != SSH_OK){ + printf("ssh_socket_connect: %s\n",ssh_get_error(session)); + return EXIT_FAILURE; + } + while(!stop) + ssh_poll_ctx_dopoll(ctx,-1); + printf("finished\n"); + return EXIT_SUCCESS; +} diff --git a/src/libs/libssh-0.12.2/tests/tests_config.h.cmake b/src/libs/libssh-0.12.2/tests/tests_config.h.cmake new file mode 100644 index 000000000000..f448df634e2e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/tests_config.h.cmake @@ -0,0 +1,88 @@ +/* OpenSSH capabilities */ + +#cmakedefine OPENSSH_VERSION_MAJOR ${OPENSSH_VERSION_MAJOR} +#cmakedefine OPENSSH_VERSION_MINOR ${OPENSSH_VERSION_MINOR} +#cmakedefine OPENSSH_SUPPORTS_SSHSIG ${OPENSSH_SUPPORTS_SSHSIG} + +#cmakedefine OPENSSH_CIPHERS "${OPENSSH_CIPHERS}" +#cmakedefine OPENSSH_MACS "${OPENSSH_MACS}" +#cmakedefine OPENSSH_KEX "${OPENSSH_KEX}" +#cmakedefine OPENSSH_KEYS "${OPENSSH_KEYS}" + + +#cmakedefine OPENSSH_3DES_CBC 1 +#cmakedefine OPENSSH_AES128_CBC 1 +#cmakedefine OPENSSH_AES192_CBC 1 +#cmakedefine OPENSSH_AES256_CBC 1 +#cmakedefine OPENSSH_RIJNDAEL_CBC_LYSATOR_LIU_SE 1 +#cmakedefine OPENSSH_AES128_CTR 1 +#cmakedefine OPENSSH_AES192_CTR 1 +#cmakedefine OPENSSH_AES256_CTR 1 +#cmakedefine OPENSSH_AES128_GCM_OPENSSH_COM 1 +#cmakedefine OPENSSH_AES256_GCM_OPENSSH_COM 1 +#cmakedefine OPENSSH_CHACHA20_POLY1305_OPENSSH_COM 1 +#cmakedefine OPENSSH_BLOWFISH_CBC 1 +#cmakedefine OPENSSH_HMAC_SHA1 1 +#cmakedefine OPENSSH_HMAC_SHA1_96 1 +#cmakedefine OPENSSH_HMAC_SHA2_256 1 +#cmakedefine OPENSSH_HMAC_SHA2_512 1 +#cmakedefine OPENSSH_HMAC_MD5 1 +#cmakedefine OPENSSH_HMAC_MD5_96 1 +#cmakedefine OPENSSH_UMAC_64_OPENSSH_COM 1 +#cmakedefine OPENSSH_UMAC_128_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_SHA1_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_SHA1_96_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_SHA2_256_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_SHA2_512_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_MD5_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_HMAC_MD5_96_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_UMAC_64_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_UMAC_128_ETM_OPENSSH_COM 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP1_SHA1 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP14_SHA1 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP14_SHA256 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP16_SHA512 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP18_SHA512 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP_EXCHANGE_SHA1 1 +#cmakedefine OPENSSH_DIFFIE_HELLMAN_GROUP_EXCHANGE_SHA256 1 +#cmakedefine OPENSSH_ECDH_SHA2_NISTP256 1 +#cmakedefine OPENSSH_ECDH_SHA2_NISTP384 1 +#cmakedefine OPENSSH_ECDH_SHA2_NISTP521 1 +#cmakedefine OPENSSH_CURVE25519_SHA256 1 +#cmakedefine OPENSSH_CURVE25519_SHA256_LIBSSH_ORG 1 +#cmakedefine OPENSSH_SNTRUP761X25519_SHA512 1 +#cmakedefine OPENSSH_SNTRUP761X25519_SHA512_OPENSSH_COM 1 +#cmakedefine OPENSSH_MLKEM768X25519_SHA256 1 +#cmakedefine OPENSSH_MLKEM768NISTP256_SHA256 1 +#cmakedefine OPENSSH_MLKEM1024NISTP384_SHA384 1 +#cmakedefine OPENSSH_SSH_ED25519 1 +#cmakedefine OPENSSH_SSH_ED25519_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_SSH_RSA 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP256 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP384 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP521 1 +#cmakedefine OPENSSH_SSH_RSA_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP256_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP384_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_ECDSA_SHA2_NISTP521_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_SSH_ED25519_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_SSH_ED25519_CERT_V01_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_ECDSA_SHA2_NISTP256_OPENSSH_COM 1 +#cmakedefine OPENSSH_SK_ECDSA_SHA2_NISTP256_CERT_V01_OPENSSH_COM 1 + +/* Available programs */ + +#cmakedefine NCAT_EXECUTABLE "${NCAT_EXECUTABLE}" +#cmakedefine SSHD_EXECUTABLE "${SSHD_EXECUTABLE}" +#cmakedefine SSH_EXECUTABLE "${SSH_EXECUTABLE}" +#cmakedefine SSH_EXECUTABLE_SIZE "${SSH_EXECUTABLE_SIZE}" +#cmakedefine SSH_KEYGEN_EXECUTABLE "${SSH_KEYGEN_EXECUTABLE}" +#cmakedefine DROPBEAR_EXECUTABLE "${DROPBEAR_EXECUTABLE}" +#cmakedefine PUTTY_EXECUTABLE "${PUTTY_EXECUTABLE}" +#cmakedefine PUTTYGEN_EXECUTABLE "${PUTTYGEN_EXECUTABLE}" +#cmakedefine WITH_TIMEOUT ${WITH_TIMEOUT} +#cmakedefine TIMEOUT_EXECUTABLE "${TIMEOUT_EXECUTABLE}" +#cmakedefine SOFTHSM2_LIBRARY "${SOFTHSM2_LIBRARY}" +#cmakedefine PKCS11SPY "${PKCS11SPY}" +#cmakedefine HAVE_SK_DUMMY 1 +#cmakedefine SK_DUMMY_LIBRARY_PATH "${SK_DUMMY_LIBRARY_PATH}" diff --git a/src/libs/libssh-0.12.2/tests/torture.c b/src/libs/libssh-0.12.2/tests/torture.c new file mode 100644 index 000000000000..890c61d04988 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture.c @@ -0,0 +1,2288 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" +#include "tests_config.h" +#include +#include +#include +#include +#include +#include + +#ifndef _WIN32 +#include +#include +#include +#endif + +#ifdef HAVE_UNISTD_H +#include +#elif (defined _WIN32) || (defined _WIN64) +#include +#define chdir _chdir +#endif + +#include "libssh/libssh.h" +#include "libssh/misc.h" +#include "libssh/token.h" +#include "torture.h" +#include "torture_key.h" + +#ifdef HAVE_VALGRIND_VALGRIND_H +#include +#endif + +#ifdef WITH_GSSAPI +/* for OPENSSL_cleanup() of GSSAPI's OpenSSL context */ +#include +#endif + +#define TORTURE_SSHD_SRV_IPV4 "127.0.0.10" +#define TORTURE_SSHD_SRV1_IPV4 "127.0.0.11" +/* socket wrapper IPv6 prefix fd00::5357:5fxx */ +#define TORTURE_SSHD_SRV_IPV6 "fd00::5357:5f0a" +#define TORTURE_SSHD_SRV1_IPV6 "fd00::5357:5f0b" +#define TORTURE_SSHD_SRV_PORT 22 +#define TORTURE_SSHD_SRV_IFACE "10" +#define TORTURE_SSHD_SRV1_IFACE "11" + +#define TORTURE_SOCKET_DIR "/tmp/test_socket_wrapper_XXXXXX" +#define TORTURE_SSHD_PIDFILE "sshd/sshd.pid" +#define TORTURE_SSHD_CONFIG "sshd/sshd_config" +#define TORTURE_SSHD1_PIDFILE "sshd1/sshd.pid" +#define TORTURE_SSHD1_CONFIG "sshd1/sshd_config" +#define TORTURE_PCAP_FILE "socket_trace.pcap" + +static const char torture_rsa_certauth_pub[] = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCnA2n5vHzZbs/GvRkGloJNV1CXHI" + "S5Xnrm05HusUJSWyPq3I1iCMHdYA7oezHa9GCFYbIenaYPy+G6USQRjYQz8SvAZo06" + "SFNeJSsa1kAIqxzdPT9kBrRrYK39PZQPsYVfRPqZBdmc+jwrfz97IFEJyXMI47FoTG" + "kgEq7eu3z2px/tdIZ34I5Hr5DDBxicZi4jluyRUJHfSPoBxyhF7OkPX4bYkrc691je" + "IQDxubl650WYLHgFfad0xTzBIFE6XUb55Dp5AgRdevSoso1Pe0IKFxxMVpP664LCbY" + "K06Lv6kcotfFlpvUtR1yx8jToGcSoq5sSzTwvXSHCQQ9ZA1hvF " + "torture_certauth_key"; + +static int verbosity = 0; +static const char *pattern = NULL; + +#ifndef _WIN32 + +/* TODO missing code coverage */ +static int _torture_auth_kbdint(ssh_session session, const char *password) +{ + const char *prompt; + char echo; + int err; + + if (session == NULL || password == NULL) { + return SSH_AUTH_ERROR; + } + + err = ssh_userauth_kbdint(session, NULL, NULL); + if (err == SSH_AUTH_ERROR) { + return err; + } + + if (ssh_userauth_kbdint_getnprompts(session) != 1) { + return SSH_AUTH_ERROR; + } + + prompt = ssh_userauth_kbdint_getprompt(session, 0, &echo); + if (prompt == NULL) { + return SSH_AUTH_ERROR; + } + + if (ssh_userauth_kbdint_setanswer(session, 0, password) < 0) { + return SSH_AUTH_ERROR; + } + err = ssh_userauth_kbdint(session, NULL, NULL); + if (err == SSH_AUTH_INFO) { + if (ssh_userauth_kbdint_getnprompts(session) != 0) { + return SSH_AUTH_ERROR; + } + err = ssh_userauth_kbdint(session, NULL, NULL); + } + + return err; +} + +int torture_rmdirs(const char *path) +{ + DIR *d; + struct dirent *dp; + struct stat sb; + char *fname; + + if ((d = opendir(path)) != NULL) { + while (stat(path, &sb) == 0) { + /* if we can remove the directory we're done */ + if (rmdir(path) == 0) { + break; + } + switch (errno) { + case ENOTEMPTY: + case EEXIST: + case EBADF: + break; /* continue */ + default: + closedir(d); + return 0; + } + + while ((dp = readdir(d)) != NULL) { + size_t len; + /* skip '.' and '..' */ + if (dp->d_name[0] == '.' && + (dp->d_name[1] == '\0' || + (dp->d_name[1] == '.' && dp->d_name[2] == '\0'))) { + continue; + } + + len = strlen(path) + strlen(dp->d_name) + 2; + fname = malloc(len); + if (fname == NULL) { + closedir(d); + return -1; + } + snprintf(fname, len, "%s/%s", path, dp->d_name); + + /* stat the file */ + if (lstat(fname, &sb) != -1) { + if (S_ISDIR(sb.st_mode) && !S_ISLNK(sb.st_mode)) { + if (rmdir(fname) < 0) { /* can't be deleted */ + if (errno == EACCES) { + closedir(d); + SAFE_FREE(fname); + return -1; + } + torture_rmdirs(fname); + } + } else { + unlink(fname); + } + } /* lstat */ + SAFE_FREE(fname); + } /* readdir */ + + rewinddir(d); + } + } else { + return -1; + } + + closedir(d); + return 0; +} + +int torture_isdir(const char *path) +{ + struct stat sb; + + if (lstat(path, &sb) == 0 && S_ISDIR(sb.st_mode)) { + return 1; + } + + return 0; +} + +static pid_t torture_read_pidfile(const char *pidfile) +{ + char buf[8] = {0}; + long int tmp; + pid_t ret; + ssize_t rc; + int fd; + + fd = open(pidfile, O_RDONLY); + if (fd < 0) { + return -1; + } + + rc = read(fd, buf, sizeof(buf)); + close(fd); + if (rc <= 0) { + return -1; + } + + buf[sizeof(buf) - 1] = '\0'; + + tmp = strtol(buf, NULL, 10); + if (tmp == 0 || errno == ERANGE) { + return -1; + } + ret = (pid_t)tmp; + /* Check if we are out of pid_t range on this system */ + if ((long)ret != tmp) { + return -1; + } + + return ret; +} + +int torture_terminate_process(const char *pidfile) +{ +#ifndef WIN32 + ssize_t rc; + pid_t pid; + int is_running = 1; + int count; + + /* read the pidfile */ + pid = torture_read_pidfile(pidfile); + if (pid == -1) { + fprintf(stderr, "Failed to read PID file %s\n", pidfile); + return -1; + } + assert_int_not_equal(pid, -1); + + for (count = 0; count < 500; count++) { + /* Make sure the daemon goes away! */ + kill(pid, SIGTERM); + + /* 25 ms */ + usleep(25 * 1000); +#ifdef HAVE_VALGRIND_VALGRIND_H + if (RUNNING_ON_VALGRIND) { + SSH_LOG(SSH_LOG_INFO, + "Running within Valgrind, wait one more " + "second for the server to clean up."); + usleep(1000 * 1000); + } +#endif /* HAVE_VALGRIND_VALGRIND_H */ + + rc = kill(pid, 0); + if (rc != 0) { + /* Process not found */ + if (errno == ESRCH) { + is_running = 0; + rc = 0; + break; + } + } + } + + if (is_running) { + fprintf(stderr, + "WARNING: The process with pid %u is still running!\n", + pid); + } + + return rc; +#else + (void)pidfile; + return -1; /* Stub implementation for Windows */ +#endif +} + +ssh_session torture_ssh_session(struct torture_state *s, + const char *host, + const unsigned int *port, + const char *user, + const char *password) +{ + ssh_session session; + int method; + int rc; + + bool process_config = false; + + if (host == NULL) { + return NULL; + } + + session = ssh_new(); + if (session == NULL) { + return NULL; + } + +#ifdef WITH_PCAP + if (s != NULL && s->plain_pcap != NULL) { + ssh_set_pcap_file(session, s->plain_pcap); + } +#endif /* WITH_PCAP */ + + if (ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity) < 0) { + goto failed; + } + + if (ssh_options_set(session, SSH_OPTIONS_HOST, host) < 0) { + goto failed; + } + + if (port != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_PORT, port) < 0) { + goto failed; + } + } + + if (user != NULL) { + if (ssh_options_set(session, SSH_OPTIONS_USER, user) < 0) { + goto failed; + } + } + + if (ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config) < + 0) { + goto failed; + } + + if (ssh_connect(session)) { + goto failed; + } + + /* We are in testing mode, so consinder the hostkey as verified ;) */ + + /* This request should return a SSH_REQUEST_DENIED error */ + rc = ssh_userauth_none(session, NULL); + if (rc == SSH_ERROR) { + goto failed; + } + method = ssh_userauth_list(session, NULL); + if (method == 0) { + goto failed; + } + + if (password != NULL) { + if (method & SSH_AUTH_METHOD_PASSWORD) { + rc = ssh_userauth_password(session, NULL, password); + } else if (method & SSH_AUTH_METHOD_INTERACTIVE) { + rc = _torture_auth_kbdint(session, password); + } + } else { + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc == SSH_AUTH_ERROR) { + goto failed; + } + } + if (rc != SSH_AUTH_SUCCESS) { + goto failed; + } + + return session; +failed: + if (ssh_is_connected(session)) { + ssh_disconnect(session); + } + ssh_free(session); + + return NULL; +} + +/* always return verification successful */ +static int verify_knownhost_trust_all(UNUSED_PARAM(ssh_session jump_session), + UNUSED_PARAM(void *user)) +{ + return SSH_OK; +} + +/** + * @brief Create a session connected to server via proxyjump + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + * + * @warning It is expected that both sshd servers are setup before calling + * this, see torture_setup_sshd_server() and + * torture_setup_sshd_servers() + * + * TODO: If needed, in future, we can extend this function to: + * - allow caller to pass server host port, user, password similar to + * torture_ssh_session() or club this with that function + * + * - allow caller to customize jump hosts and callbacks for each of them + */ +ssh_session torture_ssh_session_proxyjump(void) +{ + /* + * We'll setup the connection chain: + * - client + * - jump host 1: doe (sshd server, IPV4) + * - jump host 2: alice (sshd server1, IPV6) + * - server: alice (sshd server, IPV4) + */ + char jump_host_list[1024] = {0}; + int jump_host_count = 2; + const char *jump_host_1_address = torture_server_address(AF_INET); + const char *jump_host_2_address = torture_server1_address(AF_INET6); + struct ssh_jump_callbacks_struct jump_host_callbacks = { + .before_connection = NULL, + .verify_knownhost = verify_knownhost_trust_all, + .authenticate = NULL, + }; + + ssh_session session = NULL; + bool process_config = false; + int rc, i; + + session = ssh_new(); + if (session == NULL) { + fprintf(stderr, "Failed to create new ssh session\n"); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, TORTURE_SSH_SERVER); + if (rc < 0) { + fprintf(stderr, + "Failed to set session host: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_USER, TORTURE_SSH_USER_ALICE); + if (rc < 0) { + fprintf(stderr, + "Failed to set session user: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + if (rc < 0) { + fprintf(stderr, + "Failed to set process config option: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = snprintf(jump_host_list, + sizeof(jump_host_list), + "doe@%s:22,alice@%s:22", + jump_host_1_address, + jump_host_2_address); + if (rc < 0) { + fprintf(stderr, "snprintf failed: %s\n", strerror(errno)); + goto failed; + } + + if (rc >= (int)sizeof(jump_host_list)) { + fprintf(stderr, "Insufficient jump host list buffer size\n"); + goto failed; + } + + rc = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP, jump_host_list); + if (rc < 0) { + fprintf(stderr, + "Failed to set jump hosts for the session: %s\n", + ssh_get_error(session)); + goto failed; + } + + for (i = 0; i < jump_host_count; ++i) { + rc = ssh_options_set(session, + SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, + &jump_host_callbacks); + if (rc < 0) { + fprintf(stderr, + "Failed to set jump callbacks for jump host %d: %s\n", + i + 1, + ssh_get_error(session)); + goto failed; + } + } + + rc = ssh_connect(session); + if (rc != SSH_OK) { + fprintf(stderr, + "Failed to connect to ssh server: %s\n", + ssh_get_error(session)); + goto failed; + } + + rc = ssh_userauth_publickey_auto(session, NULL, NULL); + if (rc != SSH_AUTH_SUCCESS) { + fprintf(stderr, "Public key authentication did not succeed\n"); + goto failed; + } + + return session; + +failed: + if (ssh_is_connected(session)) { + ssh_disconnect(session); + } + ssh_free(session); + + return NULL; +} + +#ifdef WITH_SERVER + +ssh_bind torture_ssh_bind(const char *addr, + const unsigned int port, + enum ssh_keytypes_e key_type, + const char *private_key_file) +{ + int rc; + ssh_bind sshbind = NULL; + enum ssh_bind_options_e opts = -1; + + sshbind = ssh_bind_new(); + if (sshbind == NULL) { + goto out; + } + + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDADDR, addr); + if (rc != 0) { + goto out_free; + } + + rc = ssh_bind_options_set(sshbind, SSH_BIND_OPTIONS_BINDPORT, &port); + if (rc != 0) { + goto out_free; + } + + rc = ssh_bind_options_set(sshbind, + SSH_BIND_OPTIONS_LOG_VERBOSITY, + &verbosity); + if (rc < 0) { + goto out_free; + } + + switch (key_type) { + case SSH_KEYTYPE_RSA: + case SSH_KEYTYPE_ECDSA_P256: + case SSH_KEYTYPE_ECDSA_P384: + case SSH_KEYTYPE_ECDSA_P521: + case SSH_KEYTYPE_ED25519: + opts = SSH_BIND_OPTIONS_HOSTKEY; + break; + default: + goto out_free; + } + + rc = ssh_bind_options_set(sshbind, opts, private_key_file); + if (rc != 0) { + goto out_free; + } + + rc = ssh_bind_listen(sshbind); + if (rc != SSH_OK) { + goto out_free; + } + + goto out; +out_free: + ssh_bind_free(sshbind); + sshbind = NULL; +out: + return sshbind; +} + +#endif /* WITH_SERVER */ + +#ifdef WITH_SFTP + +struct torture_sftp *torture_sftp_session_channel(ssh_session session, + ssh_channel channel) +{ + struct torture_sftp *t; + char template[] = "/tmp/ssh_torture_XXXXXX"; + char *p; + int rc; + + if (session == NULL) { + return NULL; + } + + t = malloc(sizeof(struct torture_sftp)); + if (t == NULL) { + return NULL; + } + + t->ssh = session; + if (channel == NULL) { + t->sftp = sftp_new(session); + if (t->sftp == NULL) { + goto failed; + } + } else { + t->sftp = sftp_new_channel(session, channel); + if (t->sftp == NULL) { + goto failed; + } + + rc = ssh_channel_open_session(channel); + if (rc != SSH_OK) { + goto failed; + } + + rc = ssh_channel_request_sftp(channel); + if (rc != SSH_OK) { + goto failed; + } + } + + rc = sftp_init(t->sftp); + if (rc < 0) { + goto failed; + } + + p = mkdtemp(template); + if (p == NULL) { + goto failed; + } + /* useful if TESTUSER is not the local user */ + chmod(template, 0777); + t->testdir = strdup(p); + if (t->testdir == NULL) { + goto failed; + } + + return t; +failed: + if (t->sftp != NULL) { + sftp_free(t->sftp); + } + ssh_disconnect(t->ssh); + ssh_free(t->ssh); + free(t); + + return NULL; +} + +struct torture_sftp *torture_sftp_session(ssh_session session) +{ + return torture_sftp_session_channel(session, NULL); +} + +void torture_sftp_close(struct torture_sftp *t) +{ + if (t == NULL) { + return; + } + + if (t->sftp != NULL) { + sftp_free(t->sftp); + } + + if (t->testdir) { + torture_rmdirs(t->testdir); + } + + free(t->testdir); + free(t); +} +#endif /* WITH_SFTP */ + +int torture_server_port(void) +{ + char *env = getenv("TORTURE_SERVER_PORT"); + + if (env != NULL && env[0] != '\0' && strlen(env) < 6) { + int port = atoi(env); + + if (port > 0 && port < 65536) { + return port; + } + } + + return TORTURE_SSHD_SRV_PORT; +} + +const char *torture_server_address(int family) +{ + switch (family) { + case AF_INET: { + const char *ip4 = getenv("TORTURE_SERVER_ADDRESS_IPV4"); + + if (ip4 != NULL && ip4[0] != '\0') { + return ip4; + } + + return TORTURE_SSHD_SRV_IPV4; + } + case AF_INET6: { + const char *ip6 = getenv("TORTURE_SERVER_ADDRESS_IPV6"); + + if (ip6 != NULL && ip6[0] != '\0') { + return ip6; + } + + return TORTURE_SSHD_SRV_IPV6; + } + default: + return NULL; + } + + return NULL; +} + +const char *torture_server1_address(int family) +{ + switch (family) { + case AF_INET: + return TORTURE_SSHD_SRV1_IPV4; + case AF_INET6: + return TORTURE_SSHD_SRV1_IPV6; + default: + return NULL; + } + + return NULL; +} + +void torture_setup_socket_dir(void **state) +{ + struct torture_state *s; + const char *p; + size_t len; + char *env = NULL; + char gss_dir[1024] = {0}; + int rc; + + s = calloc(1, sizeof(struct torture_state)); + assert_non_null(s); + +#ifdef WITH_PCAP + env = getenv("TORTURE_PLAIN_PCAP_FILE"); + if (env != NULL && env[0] != '\0') { + s->plain_pcap = ssh_pcap_file_new(); + assert_non_null(s->plain_pcap); + + rc = ssh_pcap_file_open(s->plain_pcap, env); + assert_int_equal(rc, SSH_OK); + } +#endif /* WITH_PCAP */ + + s->socket_dir = torture_make_temp_dir(TORTURE_SOCKET_DIR); + assert_non_null(s->socket_dir); + +#ifdef WITH_GSSAPI + snprintf(gss_dir, sizeof(gss_dir), "%s/gss", s->socket_dir); + rc = mkdir(gss_dir, 0755); + assert_return_code(rc, errno); + s->gss_dir = strdup(gss_dir); +#endif + + p = s->socket_dir; + + /* pcap file */ + len = strlen(p) + 1 + strlen(TORTURE_PCAP_FILE) + 1; + + s->pcap_file = malloc(len); + assert_non_null(s->pcap_file); + + snprintf(s->pcap_file, len, "%s/%s", p, TORTURE_PCAP_FILE); + + /* pid file */ + len = strlen(p) + 1 + strlen(TORTURE_SSHD_PIDFILE) + 1; + + s->srv_pidfile = malloc(len); + assert_non_null(s->srv_pidfile); + + snprintf(s->srv_pidfile, len, "%s/%s", p, TORTURE_SSHD_PIDFILE); + + /* config file */ + len = strlen(p) + 1 + strlen(TORTURE_SSHD_CONFIG) + 1; + + s->srv_config = malloc(len); + assert_non_null(s->srv_config); + + snprintf(s->srv_config, len, "%s/%s", p, TORTURE_SSHD_CONFIG); + + s->disable_hostkeys = false; + + setenv("SOCKET_WRAPPER_DIR", p, 1); + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "170", 1); + env = getenv("TORTURE_GENERATE_PCAP"); + if (env != NULL && env[0] == '1') { + setenv("SOCKET_WRAPPER_PCAP_FILE", s->pcap_file, 1); + } + + *state = s; +} + +static void torture_setup_second_sshd_dir(void **state) +{ + struct torture_state *s = *state; + size_t len; + + /* pid file */ + len = strlen(s->socket_dir) + 1 + strlen(TORTURE_SSHD1_PIDFILE) + 1; + + s->srv1_pidfile = malloc(len); + assert_non_null(s->srv1_pidfile); + + snprintf(s->srv1_pidfile, + len, + "%s/%s", + s->socket_dir, + TORTURE_SSHD1_PIDFILE); + + /* config file */ + len = strlen(s->socket_dir) + 1 + strlen(TORTURE_SSHD1_CONFIG) + 1; + + s->srv1_config = malloc(len); + assert_non_null(s->srv1_config); + + snprintf(s->srv1_config, len, "%s/%s", s->socket_dir, TORTURE_SSHD1_CONFIG); +} + +/** + * @brief Create a libssh server configuration file + * + * It is expected the socket directory to be already created before by calling + * torture_setup_socket_dir(). The created configuration file will be stored in + * the socket directory and the srv_config pointer in the state will be + * initialized. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + */ +void torture_setup_create_libssh_config(void **state) +{ + struct torture_state *s = *state; + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + char sshd_config[2048]; + char sshd_path[1024]; + const char *additional_config = NULL; + struct stat sb; + const char config_string[] = + "LogLevel DEBUG3\n" + "Port 22\n" + "ListenAddress 127.0.0.10\n" + "HostKey %s\n" + "HostKey %s\n" + "HostKey %s\n" + "%s\n"; /* The space for test-specific options */ + const char fips_config_string[] = + "LogLevel DEBUG3\n" + "Port 22\n" + "ListenAddress 127.0.0.10\n" + "HostKey %s\n" + "HostKey %s\n" + "%s\n"; /* The space for test-specific options */ + bool written = false; + int rc; + + assert_non_null(s->socket_dir); + + snprintf(sshd_path, sizeof(sshd_path), "%s/sshd", s->socket_dir); + + rc = lstat(sshd_path, &sb); + if (rc == 0) { /* The directory is already in place */ + written = true; + } + + if (!written) { + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + } + + snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/sshd/ssh_host_ed25519_key", + s->socket_dir); + + snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/sshd/ssh_host_rsa_key", + s->socket_dir); + + snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/sshd/ssh_host_ecdsa_key", + s->socket_dir); + + if (!written) { + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file(rsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + } + + additional_config = + (s->srv_additional_config != NULL ? s->srv_additional_config : ""); + + if (ssh_fips_mode()) { + snprintf(sshd_config, + sizeof(sshd_config), + fips_config_string, + rsa_hostkey, + ecdsa_hostkey, + additional_config); + } else { + snprintf(sshd_config, + sizeof(sshd_config), + config_string, + ed25519_hostkey, + rsa_hostkey, + ecdsa_hostkey, + additional_config); + } + + torture_write_file(s->srv_config, sshd_config); +} + +#ifdef SSHD_EXECUTABLE +static void +torture_setup_create_sshd_config(void **state, bool pam, bool second_sshd) +{ + struct torture_state *s = *state; + char ed25519_hostkey[1024] = {0}; + char rsa_hostkey[1024]; + char ecdsa_hostkey[1024]; + char trusted_ca_pubkey[1024]; + char sshd_config[4096]; + char sshd_path[1024]; + const char *additional_config = NULL; + struct stat sb; + const char *sftp_server_locations[] = { + "/usr/lib/ssh/sftp-server", + "/usr/libexec/ssh/sftp-server", /* Tumbleweed 20200829 */ + "/usr/libexec/sftp-server", + "/usr/libexec/openssh/sftp-server", + "/usr/lib/openssh/sftp-server", /* Debian */ + }; + const char config_string[] = + "Port 22\n" + "ListenAddress %s\n" + "ListenAddress %s\n" + "%s %s\n" /* ed25519 HostKey */ + "%s %s\n" /* RSA HostKey */ + "%s %s\n" /* ECDSA HostKey */ + "\n" + "TrustedUserCAKeys %s\n" + "\n" + "LogLevel DEBUG3\n" + "Subsystem sftp %s -l DEBUG3 -e\n" + "\n" + "PasswordAuthentication yes\n" + "PubkeyAuthentication yes\n" + "\n" + "StrictModes no\n" + "\n" + "%s\n" /* Here comes UsePam */ + "%s" /* The space for test-specific options */ + "\n" + /* add all supported algorithms */ + "HostKeyAlgorithms " OPENSSH_KEYS "\n" +#if OPENSSH_VERSION_MAJOR == 8 && OPENSSH_VERSION_MINOR >= 2 + "CASignatureAlgorithms " OPENSSH_KEYS "\n" +#endif +#if (OPENSSH_VERSION_MAJOR == 9 && OPENSSH_VERSION_MINOR >= 8) || \ + OPENSSH_VERSION_MAJOR > 9 + "PerSourcePenaltyExemptList 127.0.0.21\n" +#endif + "Ciphers " OPENSSH_CIPHERS "\n" + "KexAlgorithms " OPENSSH_KEX "\n" + "MACs " OPENSSH_MACS "\n" + "\n" + "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY " + "LC_MESSAGES\n" + "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" + "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" + "\n" + "PidFile %s\n"; + /* FIPS config */ + const char fips_config_string[] = + "Port 22\n" + "ListenAddress %s\n" + "ListenAddress %s\n" + "%s %s\n" /* RSA HostKey */ + "%s %s\n" /* ECDSA HostKey */ + "\n" + "TrustedUserCAKeys %s\n" /* Trusted CA */ + "\n" + "LogLevel DEBUG3\n" + "Subsystem sftp %s -l DEBUG3 -e\n" /* SFTP server */ + "\n" + "PasswordAuthentication yes\n" + "PubkeyAuthentication yes\n" + "\n" + "StrictModes no\n" + "\n" + "%s\n" /* Here comes UsePam */ + "%s" /* The space for test-specific options */ + "\n" +#if (OPENSSH_VERSION_MAJOR == 9 && OPENSSH_VERSION_MINOR >= 8) || \ + OPENSSH_VERSION_MAJOR > 9 + "PerSourcePenaltyExemptList 127.0.0.21\n" +#endif + "Ciphers " + "aes256-gcm@openssh.com,aes256-ctr,aes256-cbc," + "aes128-gcm@openssh.com,aes128-ctr,aes128-cbc" + "\n" + "MACs " + "hmac-sha2-256-etm@openssh.com,hmac-sha1-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com,hmac-sha2-256," + "hmac-sha1,hmac-sha2-512" + "\n" + "GSSAPIKeyExchange no\n" + "KexAlgorithms " +#if defined(OPENSSH_MLKEM768NISTP256_SHA256) + "mlkem768nistp256-sha256," +#ifdef HAVE_MLKEM1024 + "mlkem1024nistp384-sha384," +#endif +#endif + "ecdh-sha2-nistp256,ecdh-sha2-nistp384," + "ecdh-sha2-nistp521,diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512" + "\n" + "PubkeyAcceptedKeyTypes " + "rsa-sha2-256,rsa-sha2-256-cert-v01@openssh.com," + "ecdsa-sha2-nistp256,ecdsa-sha2-nistp256-cert-v01@openssh.com," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp384-cert-v01@openssh.com," + "rsa-sha2-512,rsa-sha2-512-cert-v01@openssh.com," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp521-cert-v01@openssh.com" + "\n" + "AcceptEnv LANG LC_CTYPE LC_NUMERIC LC_TIME LC_COLLATE LC_MONETARY " + "LC_MESSAGES\n" + "AcceptEnv LC_PAPER LC_NAME LC_ADDRESS LC_TELEPHONE LC_MEASUREMENT\n" + "AcceptEnv LC_IDENTIFICATION LC_ALL LC_LIBSSH\n" + "\n" + "PidFile %s\n"; + const char usepam_yes[] = "UsePAM yes\n" + "KbdInteractiveAuthentication yes\n"; + const char usepam_no[] = "UsePAM no\n" + "KbdInteractiveAuthentication no\n"; + size_t sftp_sl_size = ARRAY_SIZE(sftp_server_locations); + const char *sftp_server, *usepam; + size_t i; + bool written = false; + int rc; + + s->srv_pam = pam; + if (pam) { + usepam = usepam_yes; + } else { + usepam = usepam_no; + } + + assert_non_null(s->socket_dir); + + snprintf(sshd_path, + sizeof(sshd_path), + "%s/sshd%s", + s->socket_dir, + second_sshd ? "1" : ""); + + rc = lstat(sshd_path, &sb); + if (rc == 0) { /* The directory is already in place */ + written = true; + } + + if (!written) { + rc = mkdir(sshd_path, 0755); + assert_return_code(rc, errno); + } + + rc = snprintf(ed25519_hostkey, + sizeof(ed25519_hostkey), + "%s/ssh_host_ed25519_key", + sshd_path); + assert_true(rc >= 0); + + rc = snprintf(rsa_hostkey, + sizeof(rsa_hostkey), + "%s/ssh_host_rsa_key", + sshd_path); + assert_true(rc >= 0); + + rc = snprintf(ecdsa_hostkey, + sizeof(ecdsa_hostkey), + "%s/ssh_host_ecdsa_key", + sshd_path); + assert_true(rc >= 0); + + rc = snprintf(trusted_ca_pubkey, + sizeof(trusted_ca_pubkey), + "%s/user_ca.pub", + sshd_path); + assert_true(rc >= 0); + + if (!written) { + torture_write_file(ed25519_hostkey, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file(rsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(ecdsa_hostkey, + torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); + torture_write_file(trusted_ca_pubkey, torture_rsa_certauth_pub); + } + + sftp_server = getenv("TORTURE_SFTP_SERVER"); + if (sftp_server == NULL) { + for (i = 0; i < sftp_sl_size; i++) { + sftp_server = sftp_server_locations[i]; + rc = lstat(sftp_server, &sb); + if (rc == 0) { + break; + } + } + } + assert_non_null(sftp_server); + + additional_config = + (s->srv_additional_config != NULL ? s->srv_additional_config : ""); + + if (ssh_fips_mode()) { + snprintf(sshd_config, + sizeof(sshd_config), + fips_config_string, + second_sshd ? TORTURE_SSHD_SRV1_IPV4 : TORTURE_SSHD_SRV_IPV4, + second_sshd ? TORTURE_SSHD_SRV1_IPV6 : TORTURE_SSHD_SRV_IPV6, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : rsa_hostkey, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : ecdsa_hostkey, + trusted_ca_pubkey, + sftp_server, + usepam, + additional_config, + second_sshd ? s->srv1_pidfile : s->srv_pidfile); + } else { + snprintf(sshd_config, + sizeof(sshd_config), + config_string, + second_sshd ? TORTURE_SSHD_SRV1_IPV4 : TORTURE_SSHD_SRV_IPV4, + second_sshd ? TORTURE_SSHD_SRV1_IPV6 : TORTURE_SSHD_SRV_IPV6, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : ed25519_hostkey, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : rsa_hostkey, + s->disable_hostkeys ? "" : "HostKey", s->disable_hostkeys ? "" : ecdsa_hostkey, + trusted_ca_pubkey, + sftp_server, + usepam, + additional_config, + second_sshd ? s->srv1_pidfile : s->srv_pidfile); + } + + if (second_sshd) { + torture_write_file(s->srv1_config, sshd_config); + } else { + torture_write_file(s->srv_config, sshd_config); + } +} + +int torture_wait_for_daemon(unsigned int seconds) +{ + struct ssh_timestamp start; + int rc; + + ssh_timestamp_init(&start); + + while (!ssh_timeout_elapsed(&start, seconds * 1000)) { + rc = system(SSH_PING_EXECUTABLE " " TORTURE_SSH_SERVER); + if (rc == 0) { + return 0; + } + /* Wait 200 ms before retrying */ + usleep(200 * 1000); + } + return 1; +} + +void torture_set_kdc_env_str(const char *gss_dir, char *env, size_t size) +{ + int rc; + rc = snprintf(env, + size, + "KRB5CCNAME=%s/cc " + "KRB5_CONFIG=%s/k/krb5.conf " + "KRB5_KDC_PROFILE=%s/k " + "KRB5_KTNAME=%s/d/ssh.keytab " + "KRB5RCACHETYPE=none ", + gss_dir, + gss_dir, + gss_dir, + gss_dir); + if (rc < 0 || rc >= (int)size) { + fail_msg("snprintf failed"); + } +} + +void torture_set_env_from_str(const char *env) +{ + struct ssh_tokens_st *vars = NULL, *var = NULL; + + vars = ssh_tokenize(env, ' '); + if (vars == NULL) { + fail_msg("failed to tokenize environment string"); + } + + for (int i = 0; vars->tokens[i]; i++) { + var = ssh_tokenize(vars->tokens[i], '='); + if (var == NULL) { + ssh_tokens_free(vars); + fail_msg("invalid environment string format"); + } + if (var->tokens[0] != NULL && var->tokens[1] != NULL) { + setenv(var->tokens[0], var->tokens[1], 1); + } else { + ssh_tokens_free(var); + ssh_tokens_free(vars); + fail_msg("invalid environment string format"); + } + ssh_tokens_free(var); + } + ssh_tokens_free(vars); +} + +/** + * @brief Run a libssh based server under timeout. + * + * It is expected that the socket directory and libssh configuration file were + * already created before by calling torture_setup_socket_dir() and + * torture_setup_create_libssh_config() (or alternatively setup the state with + * the correct values). + * + * @param[in] state The content of the address pointed by this variable must be + * a pointer to an initialized instance of torture_state + * structure; it can be obtained by calling + * torture_setup_socket_dir() and + * torture_setup_create_libssh_config(). + * @param[in] server_path The path to the server executable. + * + * @note This function will use the state->srv_additional_config field as + * additional command line option used when starting the server instead of extra + * configuration file options. + * */ +void torture_setup_libssh_server(void **state, const char *server_path) +{ + struct torture_state *s; + char start_cmd[1024]; + char timeout_cmd[512]; + char env[1024]; + char kdc_env[255]; + char extra_options[1024]; + int rc; + char *ld_preload = NULL; + const char *force_fips = NULL; + + struct ssh_tokens_st *env_tokens; + struct ssh_tokens_st *arg_tokens; + + pid_t pid; + ssize_t printed; + + s = *state; + + /* Get all the wrapper libraries to be pre-loaded */ + ld_preload = getenv("LD_PRELOAD"); + + if (s->srv_additional_config != NULL) { + printed = snprintf(extra_options, + sizeof(extra_options), + " %s ", + s->srv_additional_config); + if (printed < 0 || printed >= (ssize_t)sizeof(extra_options)) { + fail_msg("Failed to print additional config!"); + /* Unreachable */ + __builtin_unreachable(); + } + } else { + printed = snprintf(extra_options, sizeof(extra_options), " "); + if (printed < 0 || printed >= (ssize_t)sizeof(extra_options)) { + fail_msg("Failed to print empty additional config!"); + /* Unreachable */ + __builtin_unreachable(); + } + } + + if (ssh_fips_mode()) { + force_fips = "OPENSSL_FORCE_FIPS_MODE=1 "; + } else { + force_fips = ""; + } + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + + /* Write the environment setting */ + /* OPENSSL variable is needed to enable SHA1 */ + printed = snprintf(env, + sizeof(env), + "SOCKET_WRAPPER_DIR=%s " + "SOCKET_WRAPPER_DEFAULT_IFACE=10 " + "LD_PRELOAD=%s " + "%s " + "OPENSSL_ENABLE_SHA1_SIGNATURES=1 " + "NSS_WRAPPER_HOSTNAME=server.libssh.site " + "%s ", + s->socket_dir, + ld_preload, + force_fips, + kdc_env); + if (printed < 0 || printed >= (ssize_t)sizeof(env)) { + fail_msg("Failed to print env!"); + /* Unreachable */ + __builtin_unreachable(); + } + +#ifdef WITH_TIMEOUT + snprintf(timeout_cmd, + sizeof(timeout_cmd), + "%s %s ", + TIMEOUT_EXECUTABLE, + "5m"); +#else + timeout_cmd[0] = '\0'; +#endif + + /* Write the start command */ + printed = snprintf(start_cmd, + sizeof(start_cmd), + "%s" + "%s -f%s -v4 -p22 -i%s -C%s%s%s%s%s", + timeout_cmd, + server_path, + s->pcap_file, + s->srv_pidfile, + s->srv_config, + s->log_file ? " -l " : "", + s->log_file ? s->log_file : "", + extra_options, + TORTURE_SSH_SERVER); + if (printed < 0 || printed >= (ssize_t)sizeof(start_cmd)) { + fail_msg("Failed to print start command!"); + /* Unreachable */ + __builtin_unreachable(); + } + + pid = fork(); + switch (pid) { + case 0: + env_tokens = ssh_tokenize(env, ' '); + if (env_tokens == NULL || env_tokens->tokens == NULL) { + fail_msg("Failed to tokenize env!"); + /* Unreachable */ + __builtin_unreachable(); + } + + arg_tokens = ssh_tokenize(start_cmd, ' '); + if (arg_tokens == NULL || arg_tokens->tokens == NULL) { + ssh_tokens_free(env_tokens); + fail_msg("Failed to tokenize args!"); + /* Unreachable */ + __builtin_unreachable(); + } + + execve(arg_tokens->tokens[0], + (char **)arg_tokens->tokens, + (char **)env_tokens->tokens); + + /* execve returns only in case of error */ + ssh_tokens_free(env_tokens); + ssh_tokens_free(arg_tokens); + fail_msg("Error in execve: %s", strerror(errno)); + /* Unreachable */ + __builtin_unreachable(); + case -1: + fail_msg("Failed to fork!"); + /* Unreachable */ + __builtin_unreachable(); + default: + /* The parent continues the execution of the tests */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the server is ready to accept connections */ + rc = torture_wait_for_daemon(15); + assert_int_equal(rc, 0); + break; + } +} + +static int torture_start_sshd_server(void **state, bool second_sshd) +{ + struct torture_state *s = *state; + char sshd_start_cmd[1024]; + int rc; + char kdc_env[255] = {0}; + + /* Set the default interface for the server + * default is 10 */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", + second_sshd ? TORTURE_SSHD_SRV_IFACE : TORTURE_SSHD_SRV_IFACE, + 1); + setenv("PAM_WRAPPER", "1", 1); + +#ifdef WITH_GSSAPI + setenv("NSS_WRAPPER_HOSTNAME", "server.libssh.site", 1); + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); +#endif + rc = snprintf(sshd_start_cmd, + sizeof(sshd_start_cmd), + "%s " SSHD_EXECUTABLE + " -r -f %s -E %s/sshd%s/daemon.log 2> %s/sshd%s/cwrap.log", + kdc_env, + second_sshd ? s->srv1_config : s->srv_config, + s->socket_dir, + second_sshd ? "1" : "", + s->socket_dir, + second_sshd ? "1" : ""); + if (rc < 0 || rc >= (int)sizeof(sshd_start_cmd)) { + fail_msg("snprintf failed"); + } + + rc = system(sshd_start_cmd); + assert_return_code(rc, errno); + + unsetenv("NSS_WRAPPER_HOSTNAME"); + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); + unsetenv("PAM_WRAPPER"); + + /* Wait until the sshd is ready to accept connections */ + rc = torture_wait_for_daemon(15); + assert_int_equal(rc, 0); + + return SSH_OK; +} + +void torture_setup_sshd_server(void **state, bool pam) +{ + int rc; + + torture_setup_socket_dir(state); + torture_setup_create_sshd_config(state, pam, false); + + rc = torture_start_sshd_server(state, false); + assert_int_equal(rc, 0); +} + +/* Create an another sshd instance in the same SOCKET_WRAPPER_DIR + * Param state has to be initialized with torture_setup_sshd_server */ +void torture_setup_sshd_servers(void **state, bool pam) +{ + int rc; + + torture_setup_second_sshd_dir(state); + torture_setup_create_sshd_config(state, pam, true); + + rc = torture_start_sshd_server(state, true); + assert_int_equal(rc, 0); +} + +#ifdef WITH_GSSAPI +/** + * @brief Setup KDC for GSSAPI testing + * + * This should be called after sshd or libssh server's setup functions. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + * @param[in] kadmin_script kadmin commands to be executed on the KDC + * @param[in] kinit_script kinit commands to get the TGT + * + */ +void torture_setup_kdc_server(void **state, + const char *kadmin_script, + const char *kinit_script) +{ + struct torture_state *s = *state; + int rc; + char command[1024] = {0}; + char kdc_env[255] = {0}; + char kadmin_file[255] = {0}; + char kinit_file[255] = {0}; + + /* Remove the previous files and folders, but keep the same directory + * because we pass only one temporary directory to the server */ + rc = snprintf(command, sizeof(command), "rm -rf %s/*", s->gss_dir); + if (rc < 0 || rc >= (int)sizeof(command)) { + fail_msg("snprintf failed"); + } + rc = system(command); + assert_return_code(rc, errno); + + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "11", 1); + setenv("NSS_WRAPPER_HOSTNAME", "kdc.libssh.site", 1); + + torture_set_kdc_env_str(s->gss_dir, kdc_env, sizeof(kdc_env)); + torture_set_env_from_str(kdc_env); + + snprintf(kadmin_file, sizeof(kadmin_file), "%s/kadmin.sh", s->gss_dir); + snprintf(kinit_file, sizeof(kinit_file), "%s/kinit.sh", s->gss_dir); + + torture_write_file(kadmin_file, kadmin_script); + torture_write_file(kinit_file, kinit_script); + + rc = snprintf(command, + sizeof(command), + "%s/tests/gss/kdcsetup.sh %s", + BINARYDIR, + s->socket_dir); + if (rc < 0 || rc >= (int)sizeof(command)) { + fail_msg("snprintf failed"); + } + rc = system(command); + assert_return_code(rc, errno); + assert_int_equal(rc, 0); + + unsetenv("NSS_WRAPPER_HOSTNAME"); + /* Back to client */ + setenv("SOCKET_WRAPPER_DEFAULT_IFACE", "21", 1); +} + +/** + * @brief Teardown KDC + * + * This should be called before sshd or libssh server's teardown functions. + * + * @param[in] state A pointer to a pointer to an initialized torture_state + * structure + */ +void torture_teardown_kdc_server(void **state) +{ + struct torture_state *s = *state; + int rc; + char pid_path[1024] = {0}; + + rc = snprintf(pid_path, sizeof(pid_path), "%s/pid", s->gss_dir); + if (rc < 0 || rc >= (int)sizeof(pid_path)) { + fail_msg("snprintf failed"); + } + rc = torture_terminate_process(pid_path); + assert_return_code(rc, errno); +} + +#endif /* WITH_GSSAPI */ + +void torture_free_state(struct torture_state *s) +{ + free(s->srv_config); + free(s->srv1_config); + free(s->socket_dir); +#ifdef WITH_GSSAPI + free(s->gss_dir); +#endif + free(s->pcap_file); + free(s->log_file); + free(s->srv_pidfile); + free(s->srv1_pidfile); + free(s->srv_additional_config); + free(s); +} + +void torture_teardown_socket_dir(void **state) +{ + struct torture_state *s = *state; + char *env = getenv("TORTURE_SKIP_CLEANUP"); + int rc; + + if (env != NULL && env[0] == '1') { + fprintf(stderr, + "[ TORTURE ] >>> Skipping cleanup of %s\n", + s->socket_dir); + } else { + rc = torture_rmdirs(s->socket_dir); + if (rc < 0) { + fprintf(stderr, + "torture_rmdirs(%s) failed: %s", + s->socket_dir, + strerror(errno)); + } + } +#ifdef WITH_PCAP + if (s->plain_pcap != NULL) { + ssh_pcap_file_free(s->plain_pcap); + } + s->plain_pcap = NULL; +#endif /* WITH_PCAP */ + torture_free_state(s); +} + +static int torture_reload_sshd_server(void **state) +{ + struct torture_state *s = *state; + int rc; + + rc = torture_terminate_process(s->srv_pidfile); + assert_return_code(rc, errno); + + return torture_start_sshd_server(state, false); +} + +/* @brief: Updates SSHD server configuration with more options and + * reloads the server to apply them. + * Note, that this still uses the default configuration options specified + * in this file and overwrites options previously specified by this function. + */ +int torture_update_sshd_config(void **state, const char *config) +{ + struct torture_state *s = *state; + int rc; + + /* Store the configuration in internal structure */ + SAFE_FREE(s->srv_additional_config); + s->srv_additional_config = strdup(config); + assert_non_null(s->srv_additional_config); + + /* Rewrite the configuration file */ + torture_setup_create_sshd_config(state, s->srv_pam, false); + + /* Reload the server */ + rc = torture_reload_sshd_server(state); + assert_int_equal(rc, SSH_OK); + + return SSH_OK; +} + +void torture_teardown_sshd_server(void **state) +{ + struct torture_state *s = *state; + + torture_terminate_process(s->srv_pidfile); + if (s->srv1_pidfile != NULL) { + torture_terminate_process(s->srv1_pidfile); + } + torture_teardown_socket_dir(state); +} + +void torture_teardown_sshd_server1(void **state) +{ + struct torture_state *s = *state; + + torture_terminate_process(s->srv1_pidfile); + SAFE_FREE(s->srv1_pidfile); + SAFE_FREE(s->srv1_config); +} +#endif /* SSHD_EXECUTABLE */ + +#ifdef WITH_PKCS11_URI +void torture_setup_tokens(const char *temp_dir, + const char *filename, + const char object_name[], + const char *load_public) +{ + char token_setup_start_cmd[1024] = {0}; + char socket_path[1204] = {0}; + char conf_path[1024] = {0}; +#ifdef WITH_PKCS11_PROVIDER + char *env = NULL; +#endif /* WITH_PKCS11_PROVIDER */ + int rc; + + rc = snprintf(token_setup_start_cmd, + sizeof(token_setup_start_cmd), + "%s/tests/pkcs11/setup-softhsm-tokens.sh %s %s %s %s %s", + BINARYDIR, + temp_dir, + filename, + object_name, + load_public, + SOFTHSM2_LIBRARY); + assert_int_not_equal(rc, sizeof(token_setup_start_cmd)); + + rc = system(token_setup_start_cmd); + assert_return_code(rc, errno); + +#ifdef WITH_PKCS11_PROVIDER + setenv("PKCS11_PROVIDER_MODULE", SOFTHSM2_LIBRARY, 1); + + /* This is useful for debugging PKCS#11 calls */ + env = getenv("TORTURE_PKCS11"); + if (env != NULL && env[0] != '\0') { +#ifdef PKCS11SPY + setenv("PKCS11SPY", SOFTHSM2_LIBRARY, 1); + setenv("PKCS11_PROVIDER_MODULE", PKCS11SPY, 1); +#else + fprintf(stderr, "[ TORTURE ] >>> pkcs11-spy not found\n"); +#endif /* PKCS11SPY */ + } +#endif /* WITH_PKCS11_PROVIDER */ + + snprintf(conf_path, sizeof(conf_path), "%s/softhsm.conf", temp_dir); + setenv("SOFTHSM2_CONF", conf_path, 1); +} + +void torture_cleanup_tokens(const char *temp_dir) +{ + unsetenv("SOFTHSM2_CONF"); +} +#endif /* WITH_PKCS11_URI */ + +char *torture_make_temp_dir(const char *template) +{ + char *new_dir = NULL; + char *template_copy = NULL; + + if (template == NULL) { + goto end; + } + + template_copy = strdup(template); + if (template_copy == NULL) { + goto end; + } + + new_dir = mkdtemp(template_copy); + if (new_dir == NULL) { + SAFE_FREE(template_copy); + } + +end: + return template_copy; +} + +char *torture_create_temp_file(const char *template) +{ + char *new_file = NULL; + FILE *fp = NULL; + mode_t mask; + int fd; + + new_file = strdup(template); + if (new_file == NULL) { + goto end; + } + + mask = umask(S_IRWXO | S_IRWXG); + fd = mkstemp(new_file); + umask(mask); + if (fd == -1) { + goto end; + } + + fp = fdopen(fd, "w"); + if (fp == NULL) { + SAFE_FREE(new_file); + close(fd); + goto end; + } + + fclose(fp); + +end: + return new_file; +} + +char *torture_get_current_working_dir(void) +{ + + char *cwd = NULL; + char *result = NULL; + + cwd = (char *)malloc(PATH_MAX + 1); + if (cwd == NULL) { + goto end; + } + + result = getcwd(cwd, PATH_MAX); + + if (result == NULL) { + SAFE_FREE(cwd); + goto end; + } + +end: + return cwd; +} + +#else /* _WIN32 */ + +char *torture_make_temp_dir(const char *template) +{ + DWORD rc = 0; + char tmp_dir_path[PATH_MAX]; + char tmp_file_name[PATH_MAX]; + char *prefix = NULL; + char *path = NULL; + char *prefix_end = NULL; + char *slash = NULL; + + BOOL created; + + if (template == NULL) { + goto end; + } + + prefix = strdup(template); + if (prefix == NULL) { + goto end; + } + + /* Replace slashes with backslashes */ + slash = strchr(prefix, '/'); + for (; slash != NULL; slash = strchr(prefix, '/')) { + *slash = '\\'; + } + + prefix_end = strstr(prefix, "XXXXXX"); + if (prefix_end != NULL) { + *prefix_end = '\0'; + } + + rc = GetTempPathA(PATH_MAX, tmp_dir_path); + if ((rc > PATH_MAX) || (rc == 0)) { + goto free_prefix; + } + + rc = GetTempFileNameA(tmp_dir_path, TEXT(prefix), 0, tmp_file_name); + if (rc == 0) { + goto free_prefix; + } + + path = strdup(tmp_file_name); + if (path == NULL) { + goto free_prefix; + } + + /* GetTempFileNameA() creates a temporary file; we need to remove it */ + rc = DeleteFileA(path); + if (rc == 0) { + rc = -1; + SAFE_FREE(path); + goto free_prefix; + } + + created = CreateDirectoryA(path, NULL); + if (!created) { + SAFE_FREE(path); + } + +free_prefix: + SAFE_FREE(prefix); +end: + return path; +} + +static int recursive_rm_dir_content(const char *path) +{ + WIN32_FIND_DATA file_data; + HANDLE file_handle; + DWORD attributes; + + DWORD last_error = 0; + + char file_path[PATH_MAX]; + + int rc = 0; + BOOL removed; + + strcpy(file_path, path); + strcat(file_path, "\\*"); + + file_handle = FindFirstFile(file_path, &file_data); + + if (file_handle == INVALID_HANDLE_VALUE) { + last_error = GetLastError(); + + /* Empty directory */ + if (last_error == ERROR_FILE_NOT_FOUND) { + rc = 0; + } else { + /*TODO print error message?*/ + rc = last_error; + } + goto end; + } else { + do { + rc = strcmp(file_data.cFileName, "."); + if (rc == 0) { + continue; + } + + rc = strcmp(file_data.cFileName, ".."); + if (rc == 0) { + continue; + } + + /* Create full file path */ + strcpy(file_path, path); + strcat(file_path, "\\"); + strcat(file_path, file_data.cFileName); + + attributes = GetFileAttributes(file_path); + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { + rc = recursive_rm_dir_content((const char *)file_path); + if (rc != 0) { + goto end; + } + + removed = RemoveDirectoryA(file_path); + + if (!removed) { + last_error = GetLastError(); + + /*TODO print error message?*/ + + rc = last_error; + goto end; + } + } else { + rc = remove(file_path); + if (rc) { + goto end; + } + } + + } while (FindNextFile(file_handle, &file_data)); + + FindClose(file_handle); + } + +end: + return rc; +} + +int torture_rmdirs(const char *path) +{ + int rc = 0; + BOOL removed; + + rc = recursive_rm_dir_content(path); + if (rc) { + return rc; + } + + removed = RemoveDirectoryA(path); + if (!removed) { + rc = -1; + } + + return rc; +} + +int torture_isdir(const char *path) +{ + + DWORD attributes = 0; + + attributes = GetFileAttributes(path); + if (attributes & FILE_ATTRIBUTE_DIRECTORY) { + return 1; + } + + return 0; +} + +char *torture_create_temp_file(const char *template) +{ + DWORD rc = 0; + char tmp_dir_path[PATH_MAX]; + char tmp_file_name[PATH_MAX]; + char *prefix = NULL; + char *path = NULL; + char *prefix_end = NULL; + char *slash = NULL; + + if (template == NULL) { + goto end; + } + + prefix = strdup(template); + if (prefix == NULL) { + goto end; + } + + /* Replace slashes with backslashes */ + slash = strchr(prefix, '/'); + for (; slash != NULL; slash = strchr(prefix, '/')) { + *slash = '\\'; + } + + prefix_end = strstr(prefix, "XXXXXX"); + if (prefix_end != NULL) { + *prefix_end = '\0'; + } + + rc = GetTempPathA(PATH_MAX, tmp_dir_path); + if ((rc > PATH_MAX) || (rc == 0)) { + goto free_prefix; + } + + /* Remark: this function creates the file */ + rc = GetTempFileNameA(tmp_dir_path, TEXT(prefix), 0, tmp_file_name); + if (rc == 0) { + goto free_prefix; + } + + path = strdup(tmp_file_name); + +free_prefix: + SAFE_FREE(prefix); +end: + return path; +} + +char *torture_get_current_working_dir(void) +{ + char *cwd = NULL; + char *result = NULL; + + cwd = (char *)malloc(_MAX_PATH + 1); + if (cwd == NULL) { + goto end; + } + + result = _getcwd(cwd, _MAX_PATH); + + if (result == NULL) { + SAFE_FREE(cwd); + goto end; + } + +end: + return cwd; +} + +#endif /* _WIN32 */ + +int torture_change_dir(char *path) +{ + int rc = 0; + + if (path == NULL) { + rc = -1; + goto end; + } + + rc = chdir(path); + +end: + return rc; +} + +int torture_libssh_verbosity(void) +{ + return verbosity; +} + +void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests) +{ + (void)tests; + (void)ntests; + + return; +} + +void torture_write_file(const char *filename, const char *data) +{ + int fd; + int rc; + + assert_non_null(filename); + assert_true(filename[0] != '\0'); + assert_non_null(data); + + fd = open(filename, O_WRONLY | O_TRUNC | O_CREAT, 0600); + assert_true(fd >= 0); + + rc = write(fd, data, strlen(data)); + assert_int_equal(rc, strlen(data)); + + close(fd); +} + +void torture_reset_config(ssh_session session) +{ + memset(session->opts.options_seen, 0, sizeof(session->opts.options_seen)); + if (ssh_libssh_proxy_jumps()) { + ssh_proxyjumps_free(session->opts.proxy_jumps); + } +} + +void torture_unsetenv(const char *variable) +{ + int rc; +#ifdef WIN32 + rc = _putenv_s(variable, ""); +#else + rc = unsetenv(variable); +#endif // WIN32 + assert_return_code(rc, errno); +} + +void torture_setenv(const char *variable, const char *value) +{ + int rc; +#ifdef WIN32 + if (value != NULL) { + rc = _putenv_s(variable, value); + assert_return_code(rc, errno); + } else { + torture_unsetenv(variable); + } +#else + rc = setenv(variable, value, 1); + assert_return_code(rc, errno); +#endif // WIN32 +} + +#if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) +__attribute__((weak)) int torture_run_tests(void) +{ + fail_msg("torture_run_tests from shared library called"); + + return -1; +} +#endif /* defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) */ + +/** + * Finalize the torture context. No-op except for OpenSSL or GSSAPI + * + * When OpenSSL is built without the at-exit handlers, it won't call the + * OPENSSL_cleanup() from destructor or at-exit handler, which means we need to + * do it manually in the tests. + * + * It is never a good idea to call this function from the library context as we + * can not be sure the libssh is really the last one using the OpenSSL. + * + * This needs to be called at the end of the main function or any time before + * any forked process (servers) exits. + */ +void torture_finalize(void) +{ +#if defined(HAVE_LIBCRYPTO) || defined(WITH_GSSAPI) + OPENSSL_cleanup(); +#endif +} + +int main(int argc, char **argv) +{ + struct argument_s arguments; + char *env = getenv("LIBSSH_VERBOSITY"); + int rv; + + arguments.verbose = 0; + arguments.pattern = NULL; + torture_cmdline_parse(argc, argv, &arguments); + verbosity = arguments.verbose; + pattern = arguments.pattern; + + if (verbosity == 0 && env != NULL && env[0] != '\0') { + if (env[0] > '0' && env[0] < '9') { + verbosity = atoi(env); + } + } + +#if defined HAVE_CMOCKA_SET_TEST_FILTER + cmocka_set_test_filter(pattern); +#endif + + rv = torture_run_tests(); + + torture_finalize(); + + return rv; +} + +/** + * @brief Setup an SSH agent for testing + * + * This function starts an SSH agent, exports the environment variables, + * and optionally adds an SSH key to the agent. + * + * @param s The torture state + * @param add_key Path to the key to add to the agent, or NULL to skip + * + * @return 0 on success, -1 on error + */ +int torture_setup_ssh_agent(struct torture_state *s, const char *add_key) +{ +#ifndef WIN32 + int rc; + char ssh_agent_cmd[4096]; + char ssh_agent_sock[1024]; + char ssh_agent_pidfile[1024]; + char long_cmd[2048]; + + /* Setup SSH agent */ + snprintf(ssh_agent_sock, + sizeof(ssh_agent_sock), + "%s/agent.sock", + s->socket_dir); + + snprintf(ssh_agent_pidfile, + sizeof(ssh_agent_pidfile), + "%s/agent.pid", + s->socket_dir); + + /* Create command to start SSH agent with our custom socket */ + snprintf(ssh_agent_cmd, + sizeof(ssh_agent_cmd), + "eval `ssh-agent -a %s`; echo $SSH_AGENT_PID > %s", + ssh_agent_sock, + ssh_agent_pidfile); + + /* Run ssh-agent as the normal user */ + torture_unsetenv("UID_WRAPPER_ROOT"); + + rc = system(ssh_agent_cmd); + if (rc != 0) { + return -1; + } + + /* Set environment variables for SSH agent */ + torture_setenv("SSH_AUTH_SOCK", ssh_agent_sock); + torture_setenv("TORTURE_SSH_AGENT_PIDFILE", ssh_agent_pidfile); + + /* Add key to the agent if specified */ + if (add_key != NULL) { + snprintf(long_cmd, sizeof(long_cmd), "ssh-add %s", add_key); + rc = system(long_cmd); + if (rc != 0) { + return -1; + } + } + + return 0; +#else + /* On Windows, we don't set up an SSH agent */ + (void)s; + (void)add_key; + + /* Return failure to make it explicit that agent forwarding isn't supported + * on Windows */ + return -1; +#endif +} + +/** + * @brief Teardown an SSH agent + * + * This function kills the SSH agent process and cleans up environment + * variables. + * + * @return 0 on success, -1 on error + */ +int torture_cleanup_ssh_agent(void) +{ +#ifndef WIN32 + const char *ssh_agent_pidfile; + int rc; + + ssh_agent_pidfile = getenv("TORTURE_SSH_AGENT_PIDFILE"); + if (ssh_agent_pidfile == NULL) { + return 0; + } + + rc = torture_terminate_process(ssh_agent_pidfile); + if (rc != 0) { + return -1; + } + + torture_unsetenv("TORTURE_SSH_AGENT_PIDFILE"); + torture_unsetenv("SSH_AUTH_SOCK"); + + return 0; +#else + /* On Windows, we don't start an SSH agent, so nothing to clean up */ + return -1; +#endif +} diff --git a/src/libs/libssh-0.12.2/tests/torture.h b/src/libs/libssh-0.12.2/tests/torture.h new file mode 100644 index 000000000000..0120bd6fe827 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture.h @@ -0,0 +1,202 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _TORTURE_H +#define _TORTURE_H + +#include +#include +#include +#include +#include + +#include "libssh/priv.h" +#include "libssh/server.h" +#include "libssh/sftp.h" + +#include + +#include "tests_config.h" +#include "torture_cmocka.h" + +#ifndef assert_return_code +/* hack for older versions of cmocka */ +#define assert_return_code(code, errno) assert_true(code >= 0) +#endif /* assert_return_code */ + +#define TORTURE_SSH_SERVER "127.0.0.10" +#define TORTURE_SSH_SERVER_IP6 "fd00::5357:5f0a" +#define TORTURE_SSH_USER_BOB "bob" +#define TORTURE_SSH_USER_BOB_PASSWORD "secret" + +#define TORTURE_SSH_USER_ALICE "alice" +#define TORTURE_SSH_USER_CHARLIE "charlie" + +/* Used by main to communicate with parse_opt. */ +struct argument_s { + const char *pattern; + int verbose; +}; + +struct torture_sftp { + ssh_session ssh; + sftp_session sftp; + char *testdir; +}; + +struct torture_ssh { + ssh_session session; + void *cb_state; /* For storing callback state */ + void *callbacks; /* For storing callbacks */ +}; + +struct torture_state { + char *socket_dir; + char *gss_dir; + char *pcap_file; + char *log_file; + char *srv_pidfile; + char *srv_config; + char *srv1_pidfile; + char *srv1_config; + bool srv_pam; + bool disable_hostkeys; + char *srv_additional_config; + struct { + ssh_session session; + struct torture_sftp *tsftp; + struct torture_ssh ssh; + } ssh; +#ifdef WITH_PCAP + ssh_pcap_file plain_pcap; +#endif + void *private_data; +}; + +#ifndef ZERO_STRUCT +#define ZERO_STRUCT(x) memset((char *)&(x), 0, sizeof(x)) +#endif + +void torture_cmdline_parse(int argc, char **argv, struct argument_s *arguments); + +int torture_rmdirs(const char *path); +int torture_isdir(const char *path); + +int torture_terminate_process(const char *pidfile); + +/* + * Returns the verbosity level asked by user + */ +int torture_libssh_verbosity(void); + +ssh_session torture_ssh_session(struct torture_state *s, + const char *host, + const unsigned int *port, + const char *user, + const char *password); + +ssh_session torture_ssh_session_proxyjump(void); + +ssh_bind torture_ssh_bind(const char *addr, + const unsigned int port, + enum ssh_keytypes_e key_type, + const char *private_key_file); + +struct torture_sftp *torture_sftp_session(ssh_session session); +struct torture_sftp *torture_sftp_session_channel(ssh_session session, + ssh_channel channel); +void torture_sftp_close(struct torture_sftp *t); + +void torture_write_file(const char *filename, const char *data); + +#define torture_filter_tests(tests) \ + _torture_filter_tests(tests, sizeof(tests) / sizeof(tests)[0]) +void _torture_filter_tests(struct CMUnitTest *tests, size_t ntests); + +const char *torture_server_address(int domain); +const char *torture_server1_address(int domain); +int torture_server_port(void); + +int torture_wait_for_daemon(unsigned int seconds); + +#ifdef SSHD_EXECUTABLE +void torture_setup_socket_dir(void **state); +void torture_setup_sshd_server(void **state, bool pam); +void torture_setup_sshd_servers(void **state, bool pam); + +void torture_teardown_socket_dir(void **state); +void torture_teardown_sshd_server(void **state); +void torture_teardown_sshd_server1(void **state); + +int torture_update_sshd_config(void **state, const char *config); +#endif /* SSHD_EXECUTABLE */ + +#ifdef WITH_PKCS11_URI +void torture_setup_tokens(const char *temp_dir, + const char *filename, + const char object_name[], + const char *load_public); +void torture_cleanup_tokens(const char *temp_dir); +#endif /* WITH_PKCS11_URI */ + +void torture_reset_config(ssh_session session); + +void torture_setup_create_libssh_config(void **state); + +void torture_setup_libssh_server(void **state, const char *server_path); + +#ifdef WITH_GSSAPI +void torture_setup_kdc_server(void **state, + const char *kadmin_script, + const char *kinit_script); +void torture_teardown_kdc_server(void **state); +void torture_set_kdc_env_str(const char *gss_dir, char *env, size_t size); +void torture_set_env_from_str(const char *env); +#endif /* WITH_GSSAPI */ + +#if defined(HAVE_WEAK_ATTRIBUTE) && defined(TORTURE_SHARED) +__attribute__((weak)) int torture_run_tests(void); +#else +/* + * This function must be defined in every unit test file. + */ +int torture_run_tests(void); +#endif + +void torture_free_state(struct torture_state *s); + +char *torture_make_temp_dir(const char *template); +char *torture_create_temp_file(const char *template); + +char *torture_get_current_working_dir(void); +int torture_change_dir(char *path); + +void torture_setenv(char const *variable, char const *value); +void torture_unsetenv(char const *variable); + +int torture_setup_ssh_agent(struct torture_state *s, const char *add_key); +int torture_cleanup_ssh_agent(void); + +void torture_finalize(void); + +#endif /* _TORTURE_H */ diff --git a/src/libs/libssh-0.12.2/tests/torture_cmocka.c b/src/libs/libssh-0.12.2/tests/torture_cmocka.c new file mode 100644 index 000000000000..9c259f7391ad --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_cmocka.c @@ -0,0 +1,102 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" + +void _assert_ssh_return_code(ssh_session session, + int rc, + const char * const file, + const int line) +{ + char ssh_error[1024] = {0}; + + if (session != NULL) { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code - %s", + ssh_get_error(session)); + } else { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code"); + } + + _assert_true(rc == SSH_OK, + ssh_error, + file, + line); +} + +void _assert_ssh_return_code_equal(ssh_session session, + int rc, + int expected_rc, + const char * const file, + const int line) +{ + char ssh_error[1024] = {0}; + + if (session != NULL) { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code - %s", + ssh_get_error(session)); + } else { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code"); + } + + _assert_true((rc == expected_rc), + ssh_error, + file, + line); +} + +void _assert_ssh_return_code_not_equal(ssh_session session, + int rc, + int unexpected_rc, + const char * const file, + const int line) +{ + char ssh_error[1024] = {0}; + + if (session != NULL) { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code - %s", + ssh_get_error(session)); + } else { + snprintf(ssh_error, + sizeof(ssh_error), + "ERROR: Invalid return code"); + } + + _assert_true((rc != unexpected_rc), + ssh_error, + file, + line); +} diff --git a/src/libs/libssh-0.12.2/tests/torture_cmocka.h b/src/libs/libssh-0.12.2/tests/torture_cmocka.h new file mode 100644 index 000000000000..c831743dc49d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_cmocka.h @@ -0,0 +1,55 @@ +/* + * torture.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _TORTURE_CMOCKA_H +#define _TORTURE_CMOCKA_H + +#include "libssh/session.h" + +void _assert_ssh_return_code(ssh_session session, + int rc, + const char * const file, + const int line); + +#define assert_ssh_return_code(session, rc) \ + _assert_ssh_return_code((session), (rc), __FILE__, __LINE__) + +void _assert_ssh_return_code_equal(ssh_session session, + int rc, + int expected_rc, + const char * const file, + const int line); + +#define assert_ssh_return_code_equal(session, rc, expected_rc) \ + _assert_ssh_return_code_equal((session), (rc), (expected_rc), __FILE__, __LINE__) + +void _assert_ssh_return_code_not_equal(ssh_session session, + int rc, + int expected_rc, + const char * const file, + const int line); + +#define assert_ssh_return_code_not_equal(session, rc, unexpected_rc) \ + _assert_ssh_return_code_not_equal((session), (rc), (unexpected_rc), __FILE__, __LINE__) + +#endif /* _TORTURE_CMOCKA_H */ diff --git a/src/libs/libssh-0.12.2/tests/torture_key.c b/src/libs/libssh-0.12.2/tests/torture_key.c new file mode 100644 index 000000000000..a230484aea9b --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_key.c @@ -0,0 +1,1137 @@ +/* + * torture_key.c - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include + +#include "torture.h" +#include "torture_key.h" + +enum torture_format_e { + FORMAT_PEM = 0, + FORMAT_OPENSSH, + FORMAT_PKCS8, +}; + +/**************************************************************************** + * RSA KEYS + ****************************************************************************/ +static const char torture_rsa_private_testkey[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "MIIEowIBAAKCAQEArAOREUWlBXJAKZ5hABYyxnRayDZP1bJeLbPVK+npxemrhHyZ\n" + "gjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZWwjMKu/QYcycYp5HL01goxOxuusZb\n" + "i+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDjUDniWabXQJge8ksGXGTiFeAJ/687\n" + "uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqeGDzyeBZ1NLIuaiOORyLGSW4duHLD\n" + "N78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9Uw4XQFTsWugUDEY1AU4c5g11nhzHz\n" + "Bi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHrGQIDAQABAoIBAFJTaqy/jllq8vZ4\n" + "TKiD900wBvrns5HtSlHJTe80hqQoT+Sa1cWSxPR0eekL32Hjy9igbMzZ83uWzh7I\n" + "mtgNODy9vRdznfgO8CfTCaBfAzQsjFpr8QikMT6EUI/LpiRL1UaGsNOlSEvnSS0Z\n" + "b1uDzAdrjL+nsEHEDJud+K9jwSkCRifVMy7fLfaum+YKpdeEz7K2Mgm5pJ/Vg+9s\n" + "vI2V1q7HAOI4eUVTgJNHXy5ediRJlajQHf/lNUzHKqn7iH+JRl01gt62X8roG62b\n" + "TbFylbheqMm9awuSF2ucOcx+guuwhkPir8BEMb08j3hiK+TfwPdY0F6QH4OhiKK7\n" + "MTqTVgECgYEA0vmmu5GOBtwRmq6gVNCHhdLDQWaxAZqQRmRbzxVhFpbv0GjbQEF7\n" + "tttq3fjDrzDf6CE9RtZWw2BUSXVq+IXB/bXb1kgWU2xWywm+OFDk9OXQs8ui+MY7\n" + "FiP3yuq3YJob2g5CCsVQWl2CHvWGmTLhE1ODll39t7Y1uwdcDobJN+ECgYEA0LlR\n" + "hfMjydWmwqooU9TDjXNBmwufyYlNFTH351amYgFUDpNf35SMCP4hDosUw/zCTDpc\n" + "+1w04BJJfkH1SNvXSOilpdaYRTYuryDvGmWC66K2KX1nLErhlhs17CwzV997nYgD\n" + "H3OOU4HfqIKmdGbjvWlkmY+mLHyG10bbpOTbujkCgYAc68xHejSWDCT9p2KjPdLW\n" + "LYZGuOUa6y1L+QX85Vlh118Ymsczj8Z90qZbt3Zb1b9b+vKDe255agMj7syzNOLa\n" + "/MseHNOyq+9Z9gP1hGFekQKDIy88GzCOYG/fiT2KKJYY1kuHXnUdbiQgSlghODBS\n" + "jehD/K6DOJ80/FVKSH/dAQKBgQDJ+apTzpZhJ2f5k6L2jDq3VEK2ACedZEm9Kt9T\n" + "c1wKFnL6r83kkuB3i0L9ycRMavixvwBfFDjuY4POs5Dh8ip/mPFCa0hqISZHvbzi\n" + "dDyePJO9zmXaTJPDJ42kfpkofVAnfohXFQEy+cguTk848J+MmMIKfyE0h0QMabr9\n" + "86BUsQKBgEVgoi4RXwmtGovtMew01ORPV9MOX3v+VnsCgD4/56URKOAngiS70xEP\n" + "ONwNbTCWuuv43HGzJoVFiAMGnQP1BAJ7gkHkjSegOGKkiw12EPUWhFcMg+GkgPhc\n" + "pOqNt/VMBPjJ/ysHJqmLfQK9A35JV6Cmdphe+OIl28bcKhAOz8Dw\n" + "-----END RSA PRIVATE KEY-----\n"; + +static const char torture_rsa_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsA5ERRaUFckAp\n" + "nmEAFjLGdFrINk/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34lHJaO+WXbDYYj7duW3\n" + "SP7H9lbCMwq79BhzJxinkcvTWCjE7G66xluL4qIdEYHrPQQx1cztTzZTuUD+P/8f\n" + "JmmnIONQOeJZptdAmB7ySwZcZOIV4An/rzu5X4klyMY/EAYVDHPKOK1/8Wsv1LRY\n" + "YplvKp4YPPJ4FnU0si5qI45HIsZJbh24csM3vwSawmfCqDaAlCZFJoPgE1kyO1t+\n" + "IVxIv1TDhdAVOxa6BQMRjUBThzmDXWeHMfMGL2ow63kPOtlCkPiPSADYs4ekeGg5\n" + "2DVm4esZAgMBAAECggEAUlNqrL+OWWry9nhMqIP3TTAG+uezke1KUclN7zSGpChP\n" + "5JrVxZLE9HR56QvfYePL2KBszNnze5bOHsia2A04PL29F3Od+A7wJ9MJoF8DNCyM\n" + "WmvxCKQxPoRQj8umJEvVRoaw06VIS+dJLRlvW4PMB2uMv6ewQcQMm534r2PBKQJG\n" + "J9UzLt8t9q6b5gql14TPsrYyCbmkn9WD72y8jZXWrscA4jh5RVOAk0dfLl52JEmV\n" + "qNAd/+U1TMcqqfuIf4lGXTWC3rZfyugbrZtNsXKVuF6oyb1rC5IXa5w5zH6C67CG\n" + "Q+KvwEQxvTyPeGIr5N/A91jQXpAfg6GIorsxOpNWAQKBgQDS+aa7kY4G3BGarqBU\n" + "0IeF0sNBZrEBmpBGZFvPFWEWlu/QaNtAQXu222rd+MOvMN/oIT1G1lbDYFRJdWr4\n" + "hcH9tdvWSBZTbFbLCb44UOT05dCzy6L4xjsWI/fK6rdgmhvaDkIKxVBaXYIe9YaZ\n" + "MuETU4OWXf23tjW7B1wOhsk34QKBgQDQuVGF8yPJ1abCqihT1MONc0GbC5/JiU0V\n" + "MffnVqZiAVQOk1/flIwI/iEOixTD/MJMOlz7XDTgEkl+QfVI29dI6KWl1phFNi6v\n" + "IO8aZYLrorYpfWcsSuGWGzXsLDNX33udiAMfc45Tgd+ogqZ0ZuO9aWSZj6YsfIbX\n" + "Rtuk5Nu6OQKBgBzrzEd6NJYMJP2nYqM90tYthka45RrrLUv5BfzlWWHXXxiaxzOP\n" + "xn3Splu3dlvVv1v68oN7bnlqAyPuzLM04tr8yx4c07Kr71n2A/WEYV6RAoMjLzwb\n" + "MI5gb9+JPYoolhjWS4dedR1uJCBKWCE4MFKN6EP8roM4nzT8VUpIf90BAoGBAMn5\n" + "qlPOlmEnZ/mTovaMOrdUQrYAJ51kSb0q31NzXAoWcvqvzeSS4HeLQv3JxExq+LG/\n" + "AF8UOO5jg86zkOHyKn+Y8UJrSGohJke9vOJ0PJ48k73OZdpMk8MnjaR+mSh9UCd+\n" + "iFcVATL5yC5OTzjwn4yYwgp/ITSHRAxpuv3zoFSxAoGARWCiLhFfCa0ai+0x7DTU\n" + "5E9X0w5fe/5WewKAPj/npREo4CeCJLvTEQ843A1tMJa66/jccbMmhUWIAwadA/UE\n" + "AnuCQeSNJ6A4YqSLDXYQ9RaEVwyD4aSA+Fyk6o239UwE+Mn/KwcmqYt9Ar0DfklX\n" + "oKZ2mF744iXbxtwqEA7PwPA=\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_rsa_private_testkey_passphrase[] = + "-----BEGIN RSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5375534F40903DD66B3851A0DA03F6FA\n" + "\n" + "m5YYTNOMd1xCKfifwCX4R1iLJoAc4cn1aFiL7f2kBbfE2jF1LTQBJV1h1CqYZfAB\n" + "WtM/7FkQPnKXqsMndP+v+1Xc+PYigE3AezJj/0g7xn/zIBwGjkLAp435AdL5i6Fg\n" + "OhOL8LyolRrcGn17jE4S4iGbzw8PVyfzNzdj0Emwql5F6M7pgLbInRNKM/TF4z2h\n" + "b6Pi9Bw43dwaJ7wiiy/vo/v4MyXsJBoeKbc4VCmxiYFvAYCvVFlDkyIw/QnR3MKQ\n" + "g/Zsk7Pw3aOioxk6LJpZ5x0tO23nXDG1aOZHWykI0BpJV+LIpD2oSYOHJyVO83XT\n" + "RQUMSTXc2K2+ejs0XQoLt/GxDDHe+8W8fWQK3C7Lyvl9oKjmb5sTWi3mdSv0C+zR\n" + "n5KSVbUKNXrjix7qPKkv5rWqb84CKVnCMb7tWaPLR19nQqKVYBIs6v0OTTvS6Le7\n" + "lz4lxBkcUy6vi0tWH9MvLuT+ugdHLJZ4UXBthCgV58pM1o+L+WMIl+SZXckiCAO3\n" + "7ercA57695IA6iHskmr3eazJsYFEVFdR/cm+IDy2FPkKmJMjXeIWuh3yASBk7LBR\n" + "EQq3CC7AioO+Vj8m/fEIiNZJSQ6p0NmgnPoO3rTYT/IobmE99/Ht6oNLmFX4Pr7e\n" + "F4CGWKzwxWpCnw2vVolCFByASmZycbJvrIonZBKY1toU28lRm4tCM6eCNISVLMeE\n" + "VtQ+1PH9/2KZspZl+SX/kjV3egggy0TFKRU8EcYPJFC3Vpy+shEai35KBVo44Z18\n" + "apza7exm3igNEqOqe07hLs3Bjhvk1oS+WhMbAG9ARTOKuyBOJh/ZV9tFMNZ6v+q5\n" + "TofgNcIhNYNascymU1io18xTW9c3RRcmRKqIWnj4EH8o7Aojv/l+zvdV7/GVlR4W\n" + "pR9cuJEiyiEjS46axoc6dSOtdnvag+BpFQb+lGY97F9nNGyBdtLD5ASVh5OVG4fu\n" + "Pf0O7Bdj1kIuBhV8axE/slf6UHANiodeqkR9B24+0Cy+miPiHazzUkbdSJ4r03g5\n" + "J1Y5S8qbl9++sqhQMLMUkeK4pDWh1aocA9bDA2RcBNuXGiZeRFUiqxcBS+iO418n\n" + "DFyWz4UfI/m1IRSjoo/PEpgu5GmosUzs3Dl4nAcf/REBEX6M/kKKxHTLjE8DxDsz\n" + "fn/vfsXV3s0tbN7YyJdP8aU+ApZntw1OF2TS2qS8CPWHTcCGGTab5WEGC3xFXKp0\n" + "uyonCxV7vNLOiIiHdQX+1bLu7ps7GBH92xGkPg7FrNNcMc07soP7jjjB578n9Gpl\n" + "cIDBdgovTRFHiWu3yRspVt0zPfMJB/hqn+IAp98wfvjl8OZM1ZZkejnwXnQil5ZU\n" + "wjEBEtx+nX56vdxipzKoHh5yDXmPbNajBYkg3rXJrLFh3Tsf0CzHcLdHNz/qJ9LO\n" + "wH16grjR1Q0CzCW3FAv0Q0euqkXac+TfuIg3HiTPrBPnJQW1uivrx1F5tpO/uboG\n" + "h28LwqJLYh+1T0V//uiy3SMATpYKvzg2byGct9VUib8QVop8LvVF/n42RaxtTCfw\n" + "JSvUyxoaZUjQkT7iF94HsF+FVVJdI55UjgnMiZ0d5vKffWyTHYcYHkFYaSloAMWN\n" + "-----END RSA PRIVATE KEY-----\n"; + +static const char torture_rsa_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIFLTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI0RSm1ZXOBD8CAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBBS+59quuIVuxN/H9Wltk8TBIIE\n" + "0J7OhRw35ANRyTU2qhlhS8NATcguoD1J4IMXpXpv38iCBWd2bjxvuWnEu4aBX7iU\n" + "desfz9n6AoTVqURaOMLsv6EFV0tycf+mZsmdUmrD2270Wyj6TtQD8LO/7ibifCeL\n" + "XCCKjxciueSggHp5lnfogZwn8wjSEDP7OqNVRTwm8QKNrE7J5m5giFrjXoyqKM7r\n" + "DBa35UIZAXXY8z9CkI+GsyRtaZik3VD+xHShwUriOYg4x4VGZQLj24tjoUnqU4ml\n" + "iRMhGyYpxN7CnfaIwHJr3T0dmbT/BIXOQ2B6sWakioZeUuA6OTBHbFTUN9TUHaF0\n" + "rDMVmjL6BQcEiWwjvtw/3NLdkcKFjMiLTWA2GL71KPGCecpMmAMjo+ijnxeVhqpQ\n" + "dnhowG92DhCSf/XZI0vaaYflrV54U9PgcSPDFWmTOVe5151Mi8eR9qrCanfyHmX1\n" + "MLXs8Mw6xWedNj8AWLV3JGiWEeAEATuTAQfTqmBZbzaFKfSKp5PZjWxa5bZIomzS\n" + "Q0AsONTeYmKK+Pv95RYlgR2kKqhwy3OmcOuepwnzSeAGh1BdBzd2raoipkq1fpY5\n" + "8e75dJnTGvWfqfh0VXz/Wud+hMz/98Mh6Bnp9l+Ddxpp4RioWB2aH0HM8ZGTlbhf\n" + "r5qFmDY7k+RfDDp7K7UYMA+2hHCxY1aFSHVYGRQKdYdKIugLtKx6YKLeGVCR7Gbm\n" + "l/88qiGshF/qhdFbPb4K0Tz2Ug5uklveOQSkKX6RSZ30IW+N3E4nH/wvyOwbCPk7\n" + "u+iHB2zzk2Hws4O52a0Gqj+RbeGzzhl1D9jH35GMHUsfhDSA3/mmrVC7hiN/Aplt\n" + "2OmKFAkobZh/1UJAHBY9feIhLmQUy9dwy0E8G/0LEyyZYEizDC76jsvbh2cPg3jM\n" + "JsI31qUaGggwh3wB034BvsYIf/ZqLCt8hAXF9U5U7T5y3r6FNNBla8zlj25ILog6\n" + "t/bhOwFKYXamAVYMhhvUiA3YIYuBxT7MrgL7gDtKh3N/DleS/pLjmOFfMI3dfCd0\n" + "KSQX46uw7aFbV0Has9uUuGle9Foq52QFvYnDHWJuIyOvJ5st1Hd3Mjjsl9t3JFVM\n" + "I1aDZ17Z4LoThdezNQKGaAe5z7gGFMKKsm55CMT/7FxvConALeQKGAV6jA5xZzl4\n" + "+QB14YlxlZTxYnXd/69KGV56wP8sb6uMVDC/f5Vd3oHsamJKpPgts8WCn11f9wFn\n" + "Mx8YY/vBVVLQMw1aB+82Vk+Ix8YDYIPj5bJk2BkyCCUnMYkKswUOVzsdUq0xssEp\n" + "PASw0YvQ9mY2aQ9exme99JuAj5t4qIXoYTSrX5iv6NXtzDHgTR1pl9gQQVQ0zAUO\n" + "ZHKZXYAv5rLZKRcyeCLw0LkuthY2QtN3PsBlaRtfwZTaqUbBGbvEkcx5fxdEsasS\n" + "yQkZKBBvIi42LUN9ZzywYNGbOanCZ04p/+QscmmnVGuDMZJyaDRaapW6f0nJQ+lQ\n" + "CaVPRzLKGnHV5hWQDjTaPIh2s9rJSZJ3HyE8qshETHW/vQoYIcVB9TX5TnOY02Ak\n" + "IINKfSZGgz/NBeJItjk30UuTcISk65ekoXZIHHgdxD9iHy9D0w6FXcPNLLsWQn7n\n" + "jS4Bvt0VZ9zVAiyyVO4yAaMgP+saitYpjMgI8g67geD3\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_rsa_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABDX\n" + "ClCBeHgYyOEqmWpAanz9AAAAEAAAAAEAAAEXAAAAB3NzaC1yc2EAAAADAQABAAAB\n" + "AQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP2PhOh5pyxa1VfGKe\n" + "453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjOFvRRTLot5fPLNDOv\n" + "9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpbJvTXSDipukpUy41d\n" + "7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdcARyhix4RMWZZqVY2\n" + "0H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1TjzkrBZ6B6thmZty\n" + "eb9IsiB0tg2g0JN2VTAGkxqpAAADwG8gm8jZpx+GIKdhV+igcvYvIhzA+fz6UdXf\n" + "d/8wnYzMXtg+Ys7XsKUsxtMD8HGPiuwYsTrd/YGiol7SpkJV0STqtW+UZrcKamJ5\n" + "reFaDoIU8hhWTXCe/ogplTxH/zNNK7Xx5OAGnNWE3zsR1vbZaCv+Vwwa27eUCbpv\n" + "V1+92nBwkah3FCKCbwYDvTVRn1TZHQwnuNxDCRrlwaMjf8eX2ssqLLX7jqrb3j1u\n" + "c28GR3fNJ8ENaWshZ77tqexUQCnCx14/qtT434CMvENXnCP5BP/cRmbOlCFQ6Id7\n" + "nLMW0uDIy/q3xBsAcdMyV0LJW7sJNXIjTnS4lyXd0XescXrqTAKxTkqd1E0VIBpc\n" + "37+7vqv9A9Xxq74jy//L9L4Yrbijc9Vt+oNWFgOuakZGBLIQvm36Oqb0z0oWJcUt\n" + "VdZcvkCNMeixBqCnrQ8egO3x0pnZwo6cwH586Me8FgFacOnzWjzuQT6vYJ4EK5ch\n" + "YNRQpjtz5+T3rZK7eIF1ZUobM4S6di7A6lW9tycQVhjo5XlhalMfCfajhazgcIrY\n" + "Qdaq8+AguP8H+3bvXPZmitL8/mv5uVjqxy1lYh2xLzViTmFnvfdbZ92BWI9C6JBI\n" + "+mRWzXeEY71MjfeEaPStwBm5OYBMFwYrXPL7E3JjAXRxbB+LKUksj/lRk3K7aQp4\n" + "IDKCzAACgkOixfP39BgKQkrLjAoi6mEDqu5Ajc3GoljXsJEkcbu0j+0tVth+41nV\n" + "8yCkP5SVUQTCSKzoduE+0pk6oYO6vrwKLM62cQRPXLl/XNoUqETIe8dklIKojYo6\n" + "3ho1RaHgYr9/NAS0029CFt/rGmONWF9ihKON6wMavJRcofZ25FeylKiP2rrqdDIb\n" + "EiWULZi3MUJfKBwSeZMwaYYmSpaOZF1U/MgvEfeRkE1UmDp3FmBLSNHBYhAxNazH\n" + "R393BTr1zk7h+8s7QK986ZtcKkyUNXEK1NkLLuKlqMwFnjiOdeAIGwz9NEn+Tj60\n" + "jE5IcCE06B6ze/MOZcsPp1SoZv4kKmgWY5Gdqv/9O9SyFQ0Yh4MvBSD8l4x0epId\n" + "8Xm54ISVWP1SZ1x3Oe8yvtwOGqDkZeOVjnP7EQ7R0+1PZzW5P/x47skACqadGChN\n" + "ahbngIl+EhPOqhx+wIfDbtzTmGABgNhcI/d02b8py5MXFnA+uzeSucDREYRdm2TO\n" + "TQQ2CtxB6lcatIYG4AhyouQbujLd/AwpZJ05S1i/Qt6NenTgK3YyTWdXLQnjZSMx\n" + "FBRkf+Jj9eVXieT4PJKtWuvxNNrJVA==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_rsa_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABFwAAAAdz\n" + "c2gtcnNhAAAAAwEAAQAAAQEA1717msM2gKxMJCyF08/xFssCzgrakC9z/cipK4c+\n" + "gKZVz9j4ToeacsWtVXxinuOd4zXmASdETraNwbIV4GV26POqC105G3tIDucYt6+V\n" + "o18Yzhb0UUy6LeXzyzQzr/fk57D5pDZ9IiHsj5363DyMo1rec0llH5DQvKDR8bIk\n" + "rIY6Wyb010g4qbpKVMuNXe44OLlhrqJzExe8rux37h6qu5YW9FpcEqX26gG32AEQ\n" + "XXXHXAEcoYseETFmWalWNtB91bI6vG4ygvjSVxXHtT0YPqnDlkHZVxHouHZMwLx7\n" + "30EcdU485KwWegerYZmbcnm/SLIgdLYNoNCTdlUwBpMaqQAAA7iQHqVWkB6lVgAA\n" + "AAdzc2gtcnNhAAABAQDXvXuawzaArEwkLIXTz/EWywLOCtqQL3P9yKkrhz6AplXP\n" + "2PhOh5pyxa1VfGKe453jNeYBJ0ROto3BshXgZXbo86oLXTkbe0gO5xi3r5WjXxjO\n" + "FvRRTLot5fPLNDOv9+TnsPmkNn0iIeyPnfrcPIyjWt5zSWUfkNC8oNHxsiSshjpb\n" + "JvTXSDipukpUy41d7jg4uWGuonMTF7yu7HfuHqq7lhb0WlwSpfbqAbfYARBddcdc\n" + "ARyhix4RMWZZqVY20H3Vsjq8bjKC+NJXFce1PRg+qcOWQdlXEei4dkzAvHvfQRx1\n" + "TjzkrBZ6B6thmZtyeb9IsiB0tg2g0JN2VTAGkxqpAAAAAwEAAQAAAQAdjR3uQAkq\n" + "LO+tENAwCE680YgL0x7HG0jnHWJWzQq5so8UjmLM1vRH/l3U1Nnpa8JHyi08QTWx\n" + "Fn5qZstqVluoYyAKuHVHF2bya6NOHeYAX9lU+X3z2O+zs8jmL7tYwjr/pZU8ch5H\n" + "25+8uGYRXtXg1mScJBSO81Y0UE8RrVYqr2Os583yB657kYiVYYYSZlRGd9wmfXnJ\n" + "w0t8LaYcTn+i/lOvrJGa0Q0iV6+4rYmjwYd/D/vyNzF31hUEFrn3vDSgTnJdShgH\n" + "VqW0OwNuEDe/4p8KkKR1EVVj6xv4zicwouY7aQI+zT3MwAzvNdvYwytsIj6bhT9x\n" + "oyeAAIW0vaKVAAAAgQD6pPfu6tb7DiTlaH3/IPdGh3PTIf0zXHZ/ygxORXBZdoLY\n" + "Fq2h/YnBd2Hs8vARAjGJYs78gTPP0FVXPV8ut38xct4DQ2hbPMrjWv5gdhDazq8Q\n" + "qaFEa0+DeYONej8ItKwpsV2Rskkv5Pfm7M6EffVty1uzOpIcT8RYDAYUlc5D/wAA\n" + "AIEA+44ykLho3BDWnUzshVEm6iNoqlZqcDVcNSpCuYDnCy5UrTDk0zj+OUG9M0Zx\n" + "4c7kAmu/poXSimgAgMh9GNCzy3+a70WvH+fBqvG5tXLaSOQCswSdQjltANAnlt5L\n" + "YDHzGGJBsS4pYxoz22MKhFbpYUCQJvotXnZJpTQU6hdFRX8AAACBANuNSlFq/vG8\n" + "Vf9c2YsPiITmOrYxpUDMiMLvUGQOdyIIc45EAggOFHNF3AdPZEhinpD92EK+LiJc\n" + "WYJ26muVcicZoddgmpcHRt2gByC+ckWOM4sLpih6EyQLFZfqTx2X+KOI0ZTt7zEi\n" + "zfm1MJUNDFOr3DM0VBIf34Bn1hU/isPXAAAAAAEC\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_rsa_public_testkey[] = + "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQCsA5ERRaUFckApnmEAFjLGdFrIN" + "k/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34lHJaO+WXbDYYj7duW3SP7H9lbCMwq79B" + "hzJxinkcvTWCjE7G66xluL4qIdEYHrPQQx1cztTzZTuUD+P/8fJmmnIONQOeJZptd" + "AmB7ySwZcZOIV4An/rzu5X4klyMY/EAYVDHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0" + "si5qI45HIsZJbh24csM3vwSawmfCqDaAlCZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6B" + "QMRjUBThzmDXWeHMfMGL2ow63kPOtlCkPiPSADYs4ekeGg52DVm4esZ " + "aris@aris-air\n"; + +static const char torture_rsa_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEArAOREUWlBXJAKZ5hABYy\n" + "xnRayDZP1bJeLbPVK+npxemrhHyZgjdbY3ADot+JRyWjvll2w2GI+3blt0j+x/ZW\n" + "wjMKu/QYcycYp5HL01goxOxuusZbi+KiHRGB6z0EMdXM7U82U7lA/j//HyZppyDj\n" + "UDniWabXQJge8ksGXGTiFeAJ/687uV+JJcjGPxAGFQxzyjitf/FrL9S0WGKZbyqe\n" + "GDzyeBZ1NLIuaiOORyLGSW4duHLDN78EmsJnwqg2gJQmRSaD4BNZMjtbfiFcSL9U\n" + "w4XQFTsWugUDEY1AU4c5g11nhzHzBi9qMOt5DzrZQpD4j0gA2LOHpHhoOdg1ZuHr\n" + "GQIDAQAB\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_rsa_testkey_cert[] = + "ssh-rsa-cert-v01@openssh.com AAAAHHNzaC1yc2EtY2VydC12MDFAb3BlbnNz" + "aC5jb20AAAAgL77S/SgY969FbEtNBsbLvvtGFgnEHaPb+V7ajwuf+R0AAAADAQABA" + "AABAQCsA5ERRaUFckApnmEAFjLGdFrINk/Vsl4ts9Ur6enF6auEfJmCN1tjcAOi34" + "lHJaO+WXbDYYj7duW3SP7H9lbCMwq79BhzJxinkcvTWCjE7G66xluL4qIdEYHrPQQ" + "x1cztTzZTuUD+P/8fJmmnIONQOeJZptdAmB7ySwZcZOIV4An/rzu5X4klyMY/EAYV" + "DHPKOK1/8Wsv1LRYYplvKp4YPPJ4FnU0si5qI45HIsZJbh24csM3vwSawmfCqDaAl" + "CZFJoPgE1kyO1t+IVxIv1TDhdAVOxa6BQMRjUBThzmDXWeHMfMGL2ow63kPOtlCkP" + "iPSADYs4ekeGg52DVm4esZAAAAAAAAAAAAAAABAAAADmxpYnNzaF90b3J0dXJlAAA" + "AAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRp" + "bmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" + "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdX" + "Nlci1yYwAAAAAAAAAAAAABFwAAAAdzc2gtcnNhAAAAAwEAAQAAAQEAoowcv2Gn8tO" + "eDyw/lgdMpoBsLtHTTdVVOOo5HwMFvj/lFkbZlb6J2n9GIE64HNPE45vSnIdJZwz4" + "UYfTvtnNKNHp1MgMrjK1Z6EjyZsGqDZ+BhmvcKA6IckkhBJnDV7U9dMrovAWha61Z" + "9GpDqB1naRfbwqJQwSRHF1p71Cnf0fZKxOhAVx0ophmYGz3x3qq4PeOZv3Yl0AHTV" + "dRmqmeELDUxeuXN2bgSyb881zEgdaKHH5oWySykP4uwjn6T7ETuL2MsDdG3HZHDhn" + "LzLmfzOZ/cNadMCrgauMluQKc5dYF2TSeDaUxwun/NPMQBVZdETHLAMBgkGmhRUku" + "flVDIQAAAQ8AAAAHc3NoLXJzYQAAAQADSp4b/Zta8zs6v47iwmxV2Gbucvt1kDrvT" + "vKAKSbGN0+zoMyXiNfMHM/OvZObDS/WWGs4GMRqbJavwO3ja/dQY17oJss23lZ+Rc" + "Lw4Rqsi3/ZEPCnX6ficiRS/yRN/LAkoXvx9vBx9QHfxlzF6JXq07wTt21zxW0tntd" + "8dL+JI9ZZ9YylnxF3gHqfRFe2ahJpiywmxm0yOZgDmimOhep59i6BH5zHiPALvpge" + "Mbk075oA5K9XKsHTflCcsQRQH+pXqaNQGL37z2CFz9oezxQYvIqqKF0w/eeRIARoA" + "neB6OdgTpKFsmgPZVtqrvhjw+b5T8a4W4iWSl+6wg6gowAm " + "rsa_privkey.pub\n"; + +/**************************************************************************** + * DSA KEYS + ****************************************************************************/ + +static const char torture_dsa_private_testkey[] = + "-----BEGIN DSA PRIVATE KEY-----\n" + "MIIBuwIBAAKBgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" + "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" + "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwIVAK84\n" + "j20GmU+zewjQwsIXuVb6C/PHAoGAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHOzMB1\n" + "xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzPiXDu\n" + "l0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GSBZPX\n" + "MpUdng8CgYB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7\n" + "n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2u\n" + "ADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgIVAI1Hd8/i\n" + "Pzsg7bTzoNvjQL+Noyiy\n" + "-----END DSA PRIVATE KEY-----\n"; + +static const char torture_dsa_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIIBSwIBADCCASsGByqGSM44BAEwggEeAoGBAJTK9U8SSfdSdkOPMLNLNIelOW3O\n" + "vQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fAC7ltzHDxOQhKrthE5Dj\n" + "T0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5qX3HUnFOZzpPQYGrW\n" + "XK74JNRTKHblo0MXAhUArziPbQaZT7N7CNDCwhe5VvoL88cCgYBeG4hWwnFRAnmd\n" + "ZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWTMN4BlTJzPfVaoYvzJ\n" + "ev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+UGeAruNBEhVSDcX\n" + "fXTh9tVravBqeIuXgZIFk9cylR2eDwQXAhUAjUd3z+I/OyDttPOg2+NAv42jKLI=\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_dsa_private_testkey_passphrase[] = + "-----BEGIN DSA PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,266023B64B1B814BCD0D0E477257F06D\n" + "\n" + "QJQErZrvYsfeMNMnU+6yVHH5Zze/zUFdPip7Bon4T1wCGlVasn4x/GQcMm1+mgmb\n" + "PCK/qJ5qw9nCepLYJq2xh8gohbwF/XKxeaNGcRA2+ancTooDUjeRTlk1WRtS1+bq\n" + "LBkwhxLXW26lIuQUHzfi93rRqQI2LC4McngY7L7WVJer7sH7hk5//4Gf6zHtPEl+\n" + "Tr2ub1zNrVbh6e1Bitw7DaGZNX6XEWpyTTsAd42sQWh6o23MC6GyfS1YFsPGHzGe\n" + "WYQbWn2AZ1mK32z2mLZfVg41qu9RKG20iCyaczZ2YmuYyOkoLHijOAHC8vZbHwYC\n" + "+lN9Yc8/BoMuMMwDTMDaJD0TsBX02hi9YI7Gu88PMCJO+SRe5400MonUMXTwCa91\n" + "Tt3RhYpBzx2XGOq5199+oLdTJAaXHJcuB6viKNdSLBuhx6RAEJXZnVexchaHs4Q6\n" + "HweIv6Et8MjVoqwkaQDmcIGA73qZ0lbUJFZAu2YDJ6TpHc1lHZes763HoMYfuvkX\n" + "HTSuHZ7edjoWqwnl/vkc3+nG//IEj8LqAacx0i4krDcQpGuQ6BnPfwPFco2NQQpw\n" + "wHBOL6HrOnD+gGs6DUFwzA==\n" + "-----END DSA PRIVATE KEY-----\n"; + +static const char torture_dsa_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBrTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQI8001emUNAOECAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBDgXXvQsVxY6zaAQVwzUwvDBIIB\n" + "UOBQqqJs4rYK6R0rXFitkdUodOK3CdFAKodyCkSC5cgoW2+ht2ndRCepxuKB2X14\n" + "Lvt1CIxPvu1k7bGnd25kePmNF85cJxG9wf0/+6vpptO3fTUdsUKyLcRKDqvxxOMB\n" + "OSqQK1MLgvUxB5uBSGCsKqFkVUPYs46uihfozjqHH2IghHSQr+VczhFDoWtzgcgp\n" + "nRNZiyXN5Thob5WOrL849TSlcaMyI3ssErEVP1G2t3ax5bLQ4AqDddumoRBed/XY\n" + "lad5QGAS2XlwMFj8tR/Spi1fEWfamIsvh23ba5ksb35TT3SUJd2gf2NC7QEz3dUK\n" + "YDSSeRSF24c4nXBsJ94TkVuUujo4X3QSaWQ2anYYBBwfQtrddVNVu95QS2sQGLov\n" + "UWIhq1xXbnL/SGC6E5T1VGnAx3qwfDEZX5tTNzkwqeTZfkrb6vRk+O+Lxt67iP+n\n" + "nw==\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_dsa_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABBC\n" + "UZK61oXs3uKMs4l7G0cpAAAAEAAAAAEAAAGxAAAAB3NzaC1kc3MAAACBAJTK9U8S\n" + "SfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+f\n" + "AC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hod\n" + "Qn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW\n" + "+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8c\n" + "NlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0\n" + "kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQ\n" + "Bfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv\n" + "1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/\n" + "iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIgAAAeAtGFEW6JZTeSumizZJI4T2\n" + "Kha05Ze3juTeW+BMjqTcf77yAL2jvsljogCtu4+5CWWO4g+cr80vyVytji6IYTNM\n" + "MPn1qe6dHXnfmgtiegHXxrjr5v5/i1cvD32Bxffy+yjR9kbV9GJYF+K5pfYVpQBa\n" + "XVmq6AJUPd/yxKw6jRGZJi8GTcrKbCZAL+VYSPwc0veCrmGPjeeMCgYcEXPvhSui\n" + "P0JnG1Ap12FeK+61rIbZBAr7qbTGJi5Z5HlDlgon2tmMZOkIuL1Oytgut4MpmYjP\n" + "ph+qrzgwfSwOsjVIuHlb1L0phWRlgbT8lmysEE7McGKWiCOabxgl3NF9lClhDBb9\n" + "nzupkK1cg/4p17USYMOdeNhTmJ0DkQT+8UenfBOmzV7kamLlEYXJdDZBN//dZ8UR\n" + "KEzAzpaAVIyJQ+wvCUIh/VO8sJP+3q4XQUkv0QcIRlc0+r9qbW2Tqv3vajFcFtK6\n" + "nrTmIJVL0pG+z/93Ncpy5susD+JvhJ4yfl7Jet3jy4fWwm3qkLl0WsobJ7Om+GyH\n" + "DzHH9RgDk3XuUHS/fz+kTwmtyIH/Rq1jIt+s+T8iA9CzKSX6sBu2yfMo1w2/LbCx\n" + "Xy1rHS42TePw28m1cQuUfjqdOC3IBgQ1m3x2f1on7hk=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_dsa_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAABsQAAAAdz\n" + "c2gtZHNzAAAAgQCUyvVPEkn3UnZDjzCzSzSHpTltzr0Ec+1mz/JACjHMBJ9C/W/P\n" + "wvH3yjkfoFhhREvoY7IPnwAu5bcxw8TkISq7YROQ409PqwwPvy0N3GUp/+kKS268\n" + "BIJ+VKN513XRf7eL1e4aHUJ+al9x1JxTmc6T0GBq1lyu+CTUUyh25aNDFwAAABUA\n" + "rziPbQaZT7N7CNDCwhe5VvoL88cAAACAXhuIVsJxUQJ5nWQRLf7o3XEGQ+EcVmHO\n" + "zMB1xCsHjYnpEhhco+r/HDZSD31kzDeAZUycz31WqGL8yXr+OZRLqEsGC7dwEAzP\n" + "iXDul0zHcl0yiKPrRrLgNJHeKcT6JflBngK7jQRIVUg3F3104fbVa2rwaniLl4GS\n" + "BZPXMpUdng8AAACAeK6A0AX4H/AKEgCQG+8vDlb8beXE+4qmkTEF9l4LVtPTbvUW\n" + "1CFUu59psN4qmRtMZxt4r9aC/YMDVzC5X/oC5rKlN+/PI2hGfSIWfFXYBwKB5ulF\n" + "jO29rgA5oYqyOnUWVFTvv4jBlLw8WuujiOIQ1dOeaW8EIbcbqVGrU4cg8yIAAAHY\n" + "tbI937WyPd8AAAAHc3NoLWRzcwAAAIEAlMr1TxJJ91J2Q48ws0s0h6U5bc69BHPt\n" + "Zs/yQAoxzASfQv1vz8Lx98o5H6BYYURL6GOyD58ALuW3McPE5CEqu2ETkONPT6sM\n" + "D78tDdxlKf/pCktuvASCflSjedd10X+3i9XuGh1CfmpfcdScU5nOk9BgatZcrvgk\n" + "1FModuWjQxcAAAAVAK84j20GmU+zewjQwsIXuVb6C/PHAAAAgF4biFbCcVECeZ1k\n" + "ES3+6N1xBkPhHFZhzszAdcQrB42J6RIYXKPq/xw2Ug99ZMw3gGVMnM99Vqhi/Ml6\n" + "/jmUS6hLBgu3cBAMz4lw7pdMx3JdMoij60ay4DSR3inE+iX5QZ4Cu40ESFVINxd9\n" + "dOH21Wtq8Gp4i5eBkgWT1zKVHZ4PAAAAgHiugNAF+B/wChIAkBvvLw5W/G3lxPuK\n" + "ppExBfZeC1bT0271FtQhVLufabDeKpkbTGcbeK/Wgv2DA1cwuV/6AuaypTfvzyNo\n" + "Rn0iFnxV2AcCgebpRYztva4AOaGKsjp1FlRU77+IwZS8PFrro4jiENXTnmlvBCG3\n" + "G6lRq1OHIPMiAAAAFQCNR3fP4j87IO2086Db40C/jaMosgAAAAABAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_dsa_public_testkey[] = + "ssh-dss AAAAB3NzaC1kc3MAAACBAJTK9U8SSfdSdkOPMLNLNIelOW3OvQRz7WbP8k" + "AKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fAC7ltzHDxOQhKrthE5DjT0+rDA+/LQ3c" + "ZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5qX3HUnFOZzpPQYGrWXK74JNRTKHblo0" + "MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzxwAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD" + "4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWTMN4BlTJzPfVaoYvzJev45lEuoSwYLt3" + "AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+UGeAruNBEhVSDcXfXTh9tVravBqeIuX" + "gZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJAb7y8OVvxt5cT7iqaRMQX2XgtW09Nu9R" + "bUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM" + "7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hDV055pbwQhtxupUatThyDzIg==\n"; + +static const char torture_dsa_testkey_cert[] = + "ssh-dss-cert-v01@openssh.com AAAAHHNzaC1kc3MtY2VydC12MDFAb3BlbnNza" + "C5jb20AAAAgKAd9MpIBrzctQyJvCYYJ2WUD5fyWlXMSv1G/3VihbCAAAACBAJTK9U8" + "SSfdSdkOPMLNLNIelOW3OvQRz7WbP8kAKMcwEn0L9b8/C8ffKOR+gWGFES+hjsg+fA" + "C7ltzHDxOQhKrthE5DjT0+rDA+/LQ3cZSn/6QpLbrwEgn5Uo3nXddF/t4vV7hodQn5" + "qX3HUnFOZzpPQYGrWXK74JNRTKHblo0MXAAAAFQCvOI9tBplPs3sI0MLCF7lW+gvzx" + "wAAAIBeG4hWwnFRAnmdZBEt/ujdcQZD4RxWYc7MwHXEKweNiekSGFyj6v8cNlIPfWT" + "MN4BlTJzPfVaoYvzJev45lEuoSwYLt3AQDM+JcO6XTMdyXTKIo+tGsuA0kd4pxPol+" + "UGeAruNBEhVSDcXfXTh9tVravBqeIuXgZIFk9cylR2eDwAAAIB4roDQBfgf8AoSAJA" + "b7y8OVvxt5cT7iqaRMQX2XgtW09Nu9RbUIVS7n2mw3iqZG0xnG3iv1oL9gwNXMLlf+" + "gLmsqU3788jaEZ9IhZ8VdgHAoHm6UWM7b2uADmhirI6dRZUVO+/iMGUvDxa66OI4hD" + "V055pbwQhtxupUatThyDzIgAAAAAAAAAAAAAAAQAAAA5saWJzc2hfdG9ydHVyZQAAA" + "AAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVcGVybWl0LVgxMS1mb3J3YXJkaW5" + "nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGluZwAAAAAAAAAWcGVybWl0LXBvc" + "nQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0eQAAAAAAAAAOcGVybWl0LXVzZXI" + "tcmMAAAAAAAAAAAAAARcAAAAHc3NoLXJzYQAAAAMBAAEAAAEBAKKMHL9hp/LTng8sP" + "5YHTKaAbC7R003VVTjqOR8DBb4/5RZG2ZW+idp/RiBOuBzTxOOb0pyHSWcM+FGH077" + "ZzSjR6dTIDK4ytWehI8mbBqg2fgYZr3CgOiHJJIQSZw1e1PXTK6LwFoWutWfRqQ6gd" + "Z2kX28KiUMEkRxdae9Qp39H2SsToQFcdKKYZmBs98d6quD3jmb92JdAB01XUZqpnhC" + "w1MXrlzdm4Esm/PNcxIHWihx+aFskspD+LsI5+k+xE7i9jLA3Rtx2Rw4Zy8y5n8zmf" + "3DWnTAq4GrjJbkCnOXWBdk0ng2lMcLp/zTzEAVWXRExywDAYJBpoUVJLn5VQyEAAAE" + "PAAAAB3NzaC1yc2EAAAEAAt4V9aGqeahOfUvhG7M8/Mn26aLB/HXbICYFJF7dY6urm" + "SIoS2KBqISCFGXTituiwGlZeAJ+pVgCMYo07Nxtd6oqIjsgKfJqDNx7e4pGw/YJnkm" + "BqMO/k/ygu2mLmQF0lnpmG2KyjKEljMibHaKlFkcVNbwfOb4p8N3OHm66g5mbCUTRZ" + "DHqMSJb3YtnObLexD13RydwxkG5AfCnOWxy5O4agXGEYwr/48AQBHYg9obGtpD1qyF" + "4mMXgzaLViFtcwah6wHGlW0UPQMvrq/RqigAkyUszSccfibkIXJ+wGAgsRYhVAMwME" + "JqPZ6GHOEIjLBKUegsclHb7Pk0YO8Auaw== " + "aris@aris-air\n"; + +/**************************************************************************** + * ECDSA KEYS + ****************************************************************************/ + +static const char torture_ecdsa256_private_testkey[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "MHcCAQEEIBCDeeYYAtX3EnsP0ratwVpNTaA/4K1N6VvHMiUZlVdhoAoGCCqGSM49\n" + "AwEHoUQDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7VtFHA0EbNHqBUQxoh+Ik\n" + "89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgEIN55hgC1fcSew/S\n" + "tq3BWk1NoD/grU3pW8cyJRmVV2GhRANCAATH7253zxDkZa0t37Iy1hpqkLzmD7Zk\n" + "untW0UcDQRs0eoFRDGiH4iTz0yWvsBTFwU936QKj40TrKr+Y1BwvsTqJ\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_testkey_passphrase[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" + "\n" + "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" + "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" + "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIHsMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAhvndbkbElTnAICCAAw\n" + "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEEOu4ierPcQpcA9RJNHUbTCoEgZBe\n" + "iusOkUYp4JZJEIpi98VlqnROzDXHpTTpEGiUDC/k+cuKvoPop5+Jx0qXp+A1NJxu\n" + "kx3j+U0ISGY7J6b2Pqt1msC/FzqpeFM7ybuHDRz+c5ZBONTp8wrs52d5NdjrYguz\n" + "UO6n9+yydSsO0FqbwPaqNZ6goBN0TfhYnToG4ZPJxlHa7gf7Su4KSMYKZdOtfx4=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAaAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwMjU2AAAACG5pc3RwMjU2AAAAQQTH7253zxDkZa0t37Iy\n" + "1hpqkLzmD7ZkuntW0UcDQRs0eoFRDGiH4iTz0yWvsBTFwU936QKj40TrKr+Y1Bwv\n" + "sTqJAAAAmOuDchHrg3IRAAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAy\n" + "NTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPT\n" + "Ja+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAgEIN55hgC1fcSew/Stq3BWk1NoD/g\n" + "rU3pW8cyJRmVV2EAAAAA\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_private_openssh_testkey_pasphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABA+\n" + "O0w3yPZF2q0FjVBhQjn2AAAAEAAAAAEAAABoAAAAE2VjZHNhLXNoYTItbmlzdHAy\n" + "NTYAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNB\n" + "GzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAACghvb4EX8M06UB\n" + "zigxOn9bg5cZkZ2yWY8jzxtOWH4YJXsuhON/jePDJuI2ro5u4iKFD1u2JLfcshdh\n" + "vKZyjixU9KdewykQQt/wFkrCfNUyCH8jFiQsAqhBfopRFyDJV9pmcUBL/3fJqwut\n" + "ZeBSfA7tXORp3xrwFI1tXiiUCM+/nhxiCsFaCJXeiM3tN+kFtwQ8kamINqwaC8Vj\n" + "lFLKHDfwJQ==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa256_public_testkey[] = + "ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNT" + "YAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPtmS6e1bRRwNBGzR6gVEMaIfiJPPTJa+w" + "FMXBT3fpAqPjROsqv5jUHC+xOok= aris@kalix86\n"; + +static const char torture_ecdsa256_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEx+9ud88Q5GWtLd+yMtYaapC85g+2\n" + "ZLp7VtFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQ==\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_ecdsa256_testkey_cert[] = + "ecdsa-sha2-nistp256-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HAyNTYtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgHvXWcdSrQeZL2/Z68V8ntbL7rDo" + "Qwrsc+ps6HbMGZrkAAAAIbmlzdHAyNTYAAABBBMfvbnfPEORlrS3fsjLWGmqQvOYPt" + "mS6e1bRRwNBGzR6gVEMaIfiJPPTJa+wFMXBT3fpAqPjROsqv5jUHC+xOokAAAAAAAA" + "AAAAAAAEAAAAHbXlpZGVudAAAAAAAAAAAAAAAAP//////////AAAAAAAAAIIAAAAVc" + "GVybWl0LVgxMS1mb3J3YXJkaW5nAAAAAAAAABdwZXJtaXQtYWdlbnQtZm9yd2FyZGl" + "uZwAAAAAAAAAWcGVybWl0LXBvcnQtZm9yd2FyZGluZwAAAAAAAAAKcGVybWl0LXB0e" + "QAAAAAAAAAOcGVybWl0LXVzZXItcmMAAAAAAAAAAAAAAGgAAAATZWNkc2Etc2hhMi1" + "uaXN0cDI1NgAAAAhuaXN0cDI1NgAAAEEEx+9ud88Q5GWtLd+yMtYaapC85g+2ZLp7V" + "tFHA0EbNHqBUQxoh+Ik89Mlr7AUxcFPd+kCo+NE6yq/mNQcL7E6iQAAAGQAAAATZWN" + "kc2Etc2hhMi1uaXN0cDI1NgAAAEkAAAAhALDSBnmFF59tgTKDQ4meTJEI7/BP2Zgf1" + "AKg1H3kIijQAAAAIFYrqSg6GI03ohXqUVsZ3lCB/XIism2aV5Vz2bg1d9zo " + "./ec256.pub"; + +static const char torture_ecdsa384_private_testkey[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "MIGkAgEBBDBY8jEa5DtRy4AVeTWhPJ/TK257behiC3uafEi6YA2oHORibqX55EDN\n" + "wz29MT40mQSgBwYFK4EEACKhZANiAARXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1P\n" + "bJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYC\n" + "jkXlc5YbYHjueBbp0oeNXqsXHNAWQZo=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDBY8jEa5DtRy4AVeTWh\n" + "PJ/TK257behiC3uafEi6YA2oHORibqX55EDNwz29MT40mQShZANiAARXc4BN6BrV\n" + "o1QMi3+i/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdzt\n" + "J0bYoyT4V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXHNAWQZo=\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_testkey_passphrase[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,5C825E6FE821D0DE99D8403F4B4020CB\n" + "\n" + "TaUq8Qenb52dKAYcQGIYfdT7Z2DroySk38w51kw/gd8o79ZHaAQv60GtaNoy0203\n" + "2X1o29E6c0WsY9DKhSHKm/zzvZmL+ChZYqqh3sd1gp55aJsHNN4axiIu2YCbCavh\n" + "8VZn2VJDaitLy8ARqA/lMGQfqHSa3EOqti9FzWG/P6s=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBHDBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIEuMnFkuHkDkCAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBA/fjhqXxV/Dk7cg8XgPxzuBIHA\n" + "TbiloDCPfKKlkm9ZguahtfJOxcVBbMtrFAK2vA/jMXGnbB9Qe13uLl8fTd6QB4tE\n" + "Zbyucq4OA0L2HyhuEsJiLvf0ICX8APrBajNv3B8F7ZStrXx7hcJUg8qTlsbdovYq\n" + "nCjOKoq/F6ax/r1F9Rr5PlXQDoSKDJ3mQkZc4n8VNKFfXOPQ7C4rEYzglSyzGwyQ\n" + "2EwRwnkkJqcYotRyH4JWtXCRak7znLVDeGbavhpP6paSVsK8OpycAoJstfQb0L4q\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAiAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwMzg0AAAAYQRXc4BN6BrVo1QMi3+i\n" + "/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+WOdLAd/w7p7DJLdztJ0bYoyT4\n" + "V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXHNAWQZoAAADIITfDfiE3w34A\n" + "AAATZWNkc2Etc2hhMi1uaXN0cDM4NAAAAAhuaXN0cDM4NAAAAGEEV3OATega1aNU\n" + "DIt/ovwfOS7u0jLswYvtT2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG\n" + "2KMk+Fdwd2akfUchqupmAo5F5XOWG2B47ngW6dKHjV6rFxzQFkGaAAAAMFjyMRrk\n" + "O1HLgBV5NaE8n9Mrbntt6GILe5p8SLpgDagc5GJupfnkQM3DPb0xPjSZBAAAAAA=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABB4N\n" + "dKGEoxFeg6dqiR2vTl6AAAAEAAAAAEAAACIAAAAE2VjZHNhLXNoYTItbmlzdHAzOD\n" + "QAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5\n" + "LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtg\n" + "eO54FunSh41eqxcc0BZBmgAAANDOL7sWcylFf8SsjGVFvr36mpyUBpAJ/e7o4RbQg\n" + "H8FDu1IxscOfbLDoB3CV7UEIgG58nVsDamfL6rXV/tzWnPxYxi6jUHcKT1BugO/Jt\n" + "/ncelMeoAS6MAZhElaGKzU1cJMlMTV9ofmuKuAwllQULG7L8lwHs9whBK4JmWPaGL\n" + "pU3i9ZoT33/g6pcvA83vicCNqj7ggl6Vb9MeO/zGW1+oV2HC3WiLTqBsYxEJu4YCM\n" + "ewfx9pWeWaCllNy/F1rCBu3cxqzcge9hqIlNtpT7Dq3k\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa384_public_testkey[] = + "ecdsa-sha2-nistp384 AAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAAAIbmlzdHAzOD" + "QAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y5" + "0sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIarqZgKOReVzlhtgeO54FunSh41eqxcc0B" + "ZBmg== aris@kalix86"; + +static const char torture_ecdsa384_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEV3OATega1aNUDIt/ovwfOS7u0jLswYvt\n" + "T2ybYvMc/ks4KuuIGgRjivaPljnSwHf8O6ewyS3c7SdG2KMk+Fdwd2akfUchqupm\n" + "Ao5F5XOWG2B47ngW6dKHjV6rFxzQFkGa\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_ecdsa384_testkey_cert[] = + "ecdsa-sha2-nistp384-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HAzODQtY2VydC12MDFAb3BlbnNzaC5jb20AAAAgvggfi3v98HjOiqVi1O5aPy7JvMd" + "rTZe68GZ0qCaAN5MAAAAIbmlzdHAzODQAAABhBFdzgE3oGtWjVAyLf6L8Hzku7tIy7" + "MGL7U9sm2LzHP5LOCrriBoEY4r2j5Y50sB3/DunsMkt3O0nRtijJPhXcHdmpH1HIar" + "qZgKOReVzlhtgeO54FunSh41eqxcc0BZBmgAAAAAAAAAAAAAAAQAAAAdteWlkZW50A" + "AAAAAAAAAAAAAAA//////////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmR" + "pbmcAAAAAAAAAF3Blcm1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtc" + "G9ydC1mb3J3YXJkaW5nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXN" + "lci1yYwAAAAAAAAAAAAAAiAAAABNlY2RzYS1zaGEyLW5pc3RwMzg0AAAACG5pc3RwM" + "zg0AAAAYQRXc4BN6BrVo1QMi3+i/B85Lu7SMuzBi+1PbJti8xz+Szgq64gaBGOK9o+" + "WOdLAd/w7p7DJLdztJ0bYoyT4V3B3ZqR9RyGq6mYCjkXlc5YbYHjueBbp0oeNXqsXH" + "NAWQZoAAACEAAAAE2VjZHNhLXNoYTItbmlzdHAzODQAAABpAAAAMQD5f0pF6U6eeBO" + "PrOV7Y3w5NuTzvuyDAq0kTv6VYNMp83TYpIJw16+tMAplOSzPTvwAAAAwWD9StvMEP" + "b+SDH2G5qqkMk+F5IaHI9fev8zcFzzdOlilLc/+CFM0NKMAFtOrrhv0 " + "./ec384.pub"; + +static const char torture_ecdsa521_private_testkey[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "MIHbAgEBBEG83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrIaTUmNzk+60f0sCCm\n" + "Gll0dgrZLmeIw+TtnG1E20VZflCKq+IdkaAHBgUrgQQAI6GBiQOBhgAEAc6D728d\n" + "baQkHnSPtztaRwJw63CBl15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9d\n" + "Qnuwn8KQUh2T/z+BARjWPKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJ\n" + "KATU3h8gzTMkbrx8DJTFHEIjXBShs44HsSYVl3Xy\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MIHuAgEAMBAGByqGSM49AgEGBSuBBAAjBIHWMIHTAgEBBEIAvN50idki6IgbxJLt\n" + "SbXkClsf17ekx5LIAu66yGk1Jjc5PutH9LAgphpZdHYK2S5niMPk7ZxtRNtFWX5Q\n" + "iqviHZGhgYkDgYYABAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT\n" + "8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsM\n" + "zGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd1\n" + "8g==\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_testkey_passphrase[] = + "-----BEGIN EC PRIVATE KEY-----\n" + "Proc-Type: 4,ENCRYPTED\n" + "DEK-Info: AES-128-CBC,24C4F383915BC07D9C63209BF6AD3DEE\n" + "\n" + "M+JGfpGfoH3Wn6XWSoHrGGevaS6p2vJGQdkFEIgUfh16s+U/LcRhAhRnhX/MV6Ds\n" + "OZTpusrjInlZXNUR97fJbmjr/600qUlh4y3U9ikiX3IXE+RI80TPNdishOOjKRF7\n" + "aWDW8UxTlFfU2Zc1Ew0pTvMXXcuTpozW1NNVY+6S9uWfHwq1/EcR35dbnEmG0gId\n" + "qsiEdVKh7p+9Qto8jcVWzMh7ANMcIwmxQ4zbvnqypwgAgpMbamWqBZ9q4egsVZKd\n" + "uRzL95L05ctOBGYNYqpPNIX3UdQU07kzwNC+yaHOb2s=\n" + "-----END EC PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIIBXTBXBgkqhkiG9w0BBQ0wSjApBgkqhkiG9w0BBQwwHAQIY6X14D05Q7gCAggA\n" + "MAwGCCqGSIb3DQIJBQAwHQYJYIZIAWUDBAECBBCmngDUX2/kg+45m4qoCBLiBIIB\n" + "ANHV+GC6Hnend9cVScT5oNtOS2a/TD82N1h+9cYmxn953IRNk2rF7LFYFFeZzcZi\n" + "e840YFYFRiTScm1GbKgwyFLYzYguvpUpS3qz3yZMygoX3xlvFw0l8FWsfeUmOzG1\n" + "uQQPGeoFCus43D3k1iQCOafEe0DPbyfcF/IxajZ+P0N8A5ikgPsOfpTLAdWiYgFt\n" + "wkafVfXx5ZH1u8S34+kmoKRhf5zBFQI1BHD6bCQDANPBkbP4KEjH5mHRO99nHK9r\n" + "EhdLDBEXRo9xb1BhgPLdQA0AdPPqZ6Wugy3KyxkEiH/GB/oBoIpg0oALnowL129g\n" + "BV6jZHwXHuO4/CLJ9rN2tdE=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAArAAAABNl\n" + "Y2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1tpCQedI+3\n" + "O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBS\n" + "HZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDN\n" + "MyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAAEAt6sYz7erGM8AAAATZWNkc2Etc2hh\n" + "Mi1uaXN0cDUyMQAAAAhuaXN0cDUyMQAAAIUEAc6D728dbaQkHnSPtztaRwJw63CB\n" + "l15cykB4SXXuwWdNOtPzBijUULMTTvBXbra8gL4ATd9dQnuwn8KQUh2T/z+BARjW\n" + "PKhcHcGx57XpXCEkawzMYaHUUnRdeFEmNRsbXypsf0mJKATU3h8gzTMkbrx8DJTF\n" + "HEIjXBShs44HsSYVl3XyAAAAQgC83nSJ2SLoiBvEku1JteQKWx/Xt6THksgC7rrI\n" + "aTUmNzk+60f0sCCmGll0dgrZLmeIw+TtnG1E20VZflCKq+IdkQAAAAABAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAj\n" + "9WBFa/piJcPFEE4CGZTKAAAAEAAAAAEAAACsAAAAE2VjZHNhLXNoYTItbmlzdHA1\n" + "MjEAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl1\n" + "7sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee1\n" + "6VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOO\n" + "B7EmFZd18gAAAQDLjaKp+DLEHFb98f5WnVFg6LgDN847sfeuPZVfVjeSAiIv016O\n" + "ld7DXb137B2xYVsuce6sHbypr10dJOvgMTLdzTl+crYNJL+8UufJP0rOIFaDenzQ\n" + "RW8wydwiQxwt1ZqtD8ASqFmadxngufJKZzPLGfjCbCz3uATKa2sXN66nRXRZJbVA\n" + "IlNYDY8ivAStNhfItUMqyM6PkYlKJECtJw7w7TYKpvts7t72JmtgqVjS45JI/YZ+\n" + "kitIG0YmG8rzL9d1vBB5m+MH/fnFz2uJqbQYCH9Ctc8HZodAVoTNDzXHU2mYF9PE\n" + "Z6+gi3jd+kOyUk3NifHcre9K6ie7LL33JayM\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa521_public_testkey[] = + "ecdsa-sha2-nistp521 AAAAE2VjZHNhLXNoYTItbmlzdHA1MjEAAAAIbmlzdHA1Mj" + "EAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwgZdeXMpAeEl17sFnTTrT8wYo1FCzE07w" + "V262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jyoXB3Bsee16VwhJGsMzGGh1FJ0XXhRJj" + "UbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI1wUobOOB7EmFZd18g== aris@kalix86"; + +static const char torture_ecdsa521_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MIGbMBAGByqGSM49AgEGBSuBBAAjA4GGAAQBzoPvbx1tpCQedI+3O1pHAnDrcIGX\n" + "XlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce7CfwpBSHZP/P4EBGNY8\n" + "qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNTeHyDNMyRuvHwMlMUc\n" + "QiNcFKGzjgexJhWXdfI=\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_ecdsa521_testkey_cert[] = + "ecdsa-sha2-nistp521-cert-v01@openssh.com AAAAKGVjZHNhLXNoYTItbmlzd" + "HA1MjEtY2VydC12MDFAb3BlbnNzaC5jb20AAAAggFIwlsx63C++kmCBDF4O14fvu5j" + "Icsm8uMbMp0smOVwAAAAIbmlzdHA1MjEAAACFBAHOg+9vHW2kJB50j7c7WkcCcOtwg" + "ZdeXMpAeEl17sFnTTrT8wYo1FCzE07wV262vIC+AE3fXUJ7sJ/CkFIdk/8/gQEY1jy" + "oXB3Bsee16VwhJGsMzGGh1FJ0XXhRJjUbG18qbH9JiSgE1N4fIM0zJG68fAyUxRxCI" + "1wUobOOB7EmFZd18gAAAAAAAAAAAAAAAQAAAAdteWlkZW50AAAAAAAAAAAAAAAA///" + "///////8AAAAAAAAAggAAABVwZXJtaXQtWDExLWZvcndhcmRpbmcAAAAAAAAAF3Blc" + "m1pdC1hZ2VudC1mb3J3YXJkaW5nAAAAAAAAABZwZXJtaXQtcG9ydC1mb3J3YXJkaW5" + "nAAAAAAAAAApwZXJtaXQtcHR5AAAAAAAAAA5wZXJtaXQtdXNlci1yYwAAAAAAAAAAA" + "AAArAAAABNlY2RzYS1zaGEyLW5pc3RwNTIxAAAACG5pc3RwNTIxAAAAhQQBzoPvbx1" + "tpCQedI+3O1pHAnDrcIGXXlzKQHhJde7BZ0060/MGKNRQsxNO8FdutryAvgBN311Ce" + "7CfwpBSHZP/P4EBGNY8qFwdwbHntelcISRrDMxhodRSdF14USY1GxtfKmx/SYkoBNT" + "eHyDNMyRuvHwMlMUcQiNcFKGzjgexJhWXdfIAAACnAAAAE2VjZHNhLXNoYTItbmlzd" + "HA1MjEAAACMAAAAQgCJzTxw/hz2qE8Qkd4XW9Qn7fPxML6Ebtttg9C18AguyGyE6Nk" + "YH1NcToYxwQxrgzDXowXYm9eCbq9JEvaXDEtIfAAAAEIBk06LmKAYR2HDwwt4f5wVI" + "PKJ0pHVLZEx3FMZI3SfwS9mVm+oojLkZ2hr8X0xn28zbN045d8daB7BB1mHMGNT+YA" + "= ./ec521.pub"; + +static const char torture_ecdsa_sk_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAfwAAACJzay1lY2\n" + "RzYS1zaGEyLW5pc3RwMjU2QG9wZW5zc2guY29tAAAACG5pc3RwMjU2AAAAQQRUfa1IVvak\n" + "xFQZudDtXVlTtw6SiuAgfTpqZBuMdcK55kgy3o7V2z02/XClN1zpvSydzdjGWVgLj6WE9Q\n" + "6xEOhQAAAABHNzaDoAAADoWSfkhlkn5IYAAAAic2stZWNkc2Etc2hhMi1uaXN0cDI1NkBv\n" + "cGVuc3NoLmNvbQAAAAhuaXN0cDI1NgAAAEEEVH2tSFb2pMRUGbnQ7V1ZU7cOkorgIH06am\n" + "QbjHXCueZIMt6O1ds9Nv1wpTdc6b0snc3YxllYC4+lhPUOsRDoUAAAAARzc2g6AQAAAEBS\n" + "Smuf/sZP2WxVdlqgSMN7E8VLFdZI717mTi/svHahGy3wcFp2tPPylCaIG9aKAQrfVt+pOJ\n" + "U+OPsm8rphRRM1AAAAAAAAABJwaG9lbml4QHBob2VuaXgtcGMBAg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa_sk_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABCzS672qr\n" + "+0DRopx7VjkjCnAAAAGAAAAAEAAAB/AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3Bl\n" + "bnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBFR9rUhW9qTEVBm50O1dWVO3DpKK4CB9OmpkG4\n" + "x1wrnmSDLejtXbPTb9cKU3XOm9LJ3N2MZZWAuPpYT1DrEQ6FAAAAAEc3NoOgAAAPDoWSmM\n" + "ki/XGLXidNxyLy2uRGejaZTOI3Ran10b7UF2ddRCrmBc6eVEXzgJ+BzB0sO0/uc1Q7QJhy\n" + "fGR9bz1rvwJd5RpLLw9cSoTHbDiap4tkQu2snQt7AF/E6MOgQ3mvdhDDYoTYvxNIiwZTH1\n" + "/Cxl2ZcRBKwSl6yp3JOxIVgttmJmNTqpt2U/uYwag9N1o6wxhWy1aamKZd1qHtPVC7MPL8\n" + "/Q96mBlCEIe3vd4Hge4wgDa24F4Lwat7IA0/NGNFISIQH7x4VaGHAiTeMFL1NOVyw52xWr\n" + "aAgXfkyplffxlB7ZfCf7RLsiCZDinMCE9y8=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ecdsa_sk_public_testkey[] = + "sk-ecdsa-sha2-nistp256@openssh.com " + "AAAAInNrLWVjZHNhLXNoYTItbmlzdHAyNTZAb3BlbnNzaC5jb20AAAAIbmlzdHAyNTYAAABBBF" + "R9rUhW9qTEVBm50O1dWVO3DpKK4CB9OmpkG4x1wrnmSDLejtXbPTb9cKU3XOm9LJ3N2MZZWAuP" + "pYT1DrEQ6FAAAAAEc3NoOg== phoenix@phoenix-pc"; + +/**************************************************************************** + * ED25519 KEYS + ****************************************************************************/ + +static const char torture_ed25519_private_pkcs8_testkey[] = + "-----BEGIN PRIVATE KEY-----\n" + "MC4CAQAwBQYDK2VwBCIEIGBhcqLe61tkqVjIHKEzwB3oINasSHWGbIWXQWcLPmGN\n" + "-----END PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAAMwAAAAtzc2gtZW\n" + "QyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlwAAAJBzsDN1c7Az\n" + "dQAAAAtzc2gtZWQyNTUxOQAAACAVlp8bgmIjsrzGC7ZIKBMhCpS1fpJTPgVOjYdz5gIqlw\n" + "AAAEBgYXKi3utbZKlYyByhM8Ad6CDWrEh1hmyFl0FnCz5hjRWWnxuCYiOyvMYLtkgoEyEK\n" + "lLV+klM+BU6Nh3PmAiqXAAAADGFyaXNAa2FsaXg4NgE=\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczEyOC1jYmMAAAAGYmNyeXB0AAAAGAAAABDYuz+a8i\n" + "nb/BgGjQjQtvkUAAAAEAAAAAEAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYL\n" + "tkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAkOBxqvzvPSns3TbhjkCayvANI66100OELnpDOm\n" + "JBGgXr5q846NkAovH3pmJ4O7qzPLTQ/cm0+959VUODRhM1i96qBg5MTNtV33lf5Y57Klzu\n" + "JegbiexcqkHIzriH42K0XSOEpfW8f/rTH7ffjbE/7l8HRNwf7AmcnxLx/d8J8FTBr+8aU7\n" + "qMU3xAJ4ixnwhYFg==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_private_pkcs8_testkey_passphrase[] = + "-----BEGIN ENCRYPTED PRIVATE KEY-----\n" + "MIGbMFcGCSqGSIb3DQEFDTBKMCkGCSqGSIb3DQEFDDAcBAie1RBk/ub+EwICCAAw\n" + "DAYIKoZIhvcNAgkFADAdBglghkgBZQMEAQIEECRLkPChQx/sZPYLdNJhxMUEQFLj\n" + "7nelAdOx3WXIBbCOfOqg3aAn8C5cXPtIQ+fiui1V8wlXXV8RBiuDCC97ScLs91D5\n" + "qQhQtw0vgfnq1um/izg=\n" + "-----END ENCRYPTED PRIVATE KEY-----\n"; + +static const char torture_ed25519_public_testkey[] = + "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+" + "BU6Nh3PmAiqX aris@kalix86"; + +static const char torture_ed25519_public_testkey_pem[] = + "-----BEGIN PUBLIC KEY-----\n" + "MCowBQYDK2VwAyEAFZafG4JiI7K8xgu2SCgTIQqUtX6SUz4FTo2Hc+YCKpc=\n" + "-----END PUBLIC KEY-----\n"; + +static const char torture_ed25519_testkey_cert[] = + "ssh-ed25519-cert-v01@openssh.com AAAAIHNzaC1lZDI1NTE5LWNlcnQtdjAxQ" + "G9wZW5zc2guY29tAAAAILrR4sPB+b6BRId/OkQha9nWwoACXqUTILz1TrmG4R9CAAA" + "AIBWWnxuCYiOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAAAAAAAAAAAABAAAAB" + "215aWRlbnQAAAAAAAAAAAAAAAD//////////wAAAAAAAACCAAAAFXBlcm1pdC1YMTE" + "tZm9yd2FyZGluZwAAAAAAAAAXcGVybWl0LWFnZW50LWZvcndhcmRpbmcAAAAAAAAAF" + "nBlcm1pdC1wb3J0LWZvcndhcmRpbmcAAAAAAAAACnBlcm1pdC1wdHkAAAAAAAAADnB" + "lcm1pdC11c2VyLXJjAAAAAAAAAAAAAAAzAAAAC3NzaC1lZDI1NTE5AAAAIBWWnxuCY" + "iOyvMYLtkgoEyEKlLV+klM+BU6Nh3PmAiqXAAAAUwAAAAtzc2gtZWQyNTUxOQAAAEB" + "d8AogGWM6njfejbazFVyfnjNiWqatx6IV3Nnqc3LjCiPY19fqIPe2YJSzytHwLTD5X" + "IjD2bJpq2ZfjQwXpO0J ./ed.pub"; + +static const char torture_ed25519_sk_private_openssh_testkey[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAABG5vbmUAAAAEbm9uZQAAAAAAAAABAAAASgAAABpzay1zc2\n" + "gtZWQyNTUxOUBvcGVuc3NoLmNvbQAAACCihqLlueARJOQCZMYRHefNkQ3WBHlhlUOuG7a/\n" + "ivCkaQAAAARzc2g6AAAA+OivRKLor0SiAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY2\n" + "9tAAAAIKKGouW54BEk5AJkxhEd582RDdYEeWGVQ64btr+K8KRpAAAABHNzaDoBAAAAgNxc\n" + "Q6pfw2S2fpCEB1UGO4Fy8O5gXZDbw3Vj8EHTcUDucNmk/iaI/GTPcUQK5cgPJH8AaB+lIZ\n" + "GasyHd28mghgpaztG2cYmxrF3ZuvNdEZJecflgMOJDXZwoYvKpb7rZWjQgf8AeDy2u2dpl\n" + "XCKHH8/LkJHdo4MABojarKofgaGzAAAAAAAAABJwaG9lbml4QHBob2VuaXgtcGMBAgMEBQ\n" + "YH\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_sk_private_openssh_testkey_passphrase[] = + "-----BEGIN OPENSSH PRIVATE KEY-----\n" + "b3BlbnNzaC1rZXktdjEAAAAACmFlczI1Ni1jdHIAAAAGYmNyeXB0AAAAGAAAABAkfYBhph\n" + "EvYRpuOO6V4wihAAAAGAAAAAEAAABKAAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29t\n" + "AAAAIKKGouW54BEk5AJkxhEd582RDdYEeWGVQ64btr+K8KRpAAAABHNzaDoAAAEA2WmpuB\n" + "2ip0Bq4XJ9c2C33fE5twVYvK3WrJfAJKzih7bFXxbt5NmUFs121SD/x+3xZLwBJWGOIhdf\n" + "idOD4gy9VWWAGCdJ0v87T/WaBYzEACr32hd99cD+Ki7VmmAxOKxx2/+/gg+WkbgygNns3c\n" + "7YoYW5SSJm7WlhtmHFCKHtSh0fd8X1Q7gLHWTdd4B+3U9PyGpVgCKe2s2IOoTIcWOHlDW3\n" + "KbEdlKELKCUEb0kof5m3hu8cktn0J/YIe1Y98YVjv472P6CO0Jw92jHSEPiTGn8JdSPkBY\n" + "Qcoq18tszucoR2gp+sf5UvQhW8iOALDxO72Yq6HINAXNbpCB22U++GJw==\n" + "-----END OPENSSH PRIVATE KEY-----\n"; + +static const char torture_ed25519_sk_public_testkey[] = + "sk-ssh-ed25519@openssh.com " + "AAAAGnNrLXNzaC1lZDI1NTE5QG9wZW5zc2guY29tAAAAIKKGouW54BEk5AJkxhEd582RDdYEeW" + "GVQ64btr+K8KRpAAAABHNzaDo= phoenix@phoenix-pc"; + +static const char * +torture_get_testkey_public_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + return torture_dsa_public_testkey; + case SSH_KEYTYPE_RSA: + if (format == FORMAT_OPENSSH) { + return torture_rsa_public_testkey; + } + return torture_rsa_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P521: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa521_public_testkey; + } + return torture_ecdsa521_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P384: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa384_public_testkey; + } + return torture_ecdsa384_public_testkey_pem; + case SSH_KEYTYPE_ECDSA_P256: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa256_public_testkey; + } + return torture_ecdsa256_public_testkey_pem; + case SSH_KEYTYPE_ED25519: + if (format == FORMAT_OPENSSH) { + return torture_ed25519_public_testkey; + } + return torture_ed25519_public_testkey_pem; + case SSH_KEYTYPE_DSS_CERT01: + return torture_dsa_testkey_cert; + case SSH_KEYTYPE_RSA_CERT01: + return torture_rsa_testkey_cert; + case SSH_KEYTYPE_ECDSA_P256_CERT01: + return torture_ecdsa256_testkey_cert; + case SSH_KEYTYPE_ECDSA_P384_CERT01: + return torture_ecdsa384_testkey_cert; + case SSH_KEYTYPE_ECDSA_P521_CERT01: + return torture_ecdsa521_testkey_cert; + case SSH_KEYTYPE_ED25519_CERT01: + return torture_ed25519_testkey_cert; + case SSH_KEYTYPE_SK_ECDSA: + if (format == FORMAT_OPENSSH) { + return torture_ecdsa_sk_public_testkey; + } + return NULL; + case SSH_KEYTYPE_SK_ED25519: + if (format == FORMAT_OPENSSH) { + return torture_ed25519_sk_public_testkey; + } + return NULL; + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +static const char * +torture_get_testkey_encrypted_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + switch (format) { + case FORMAT_OPENSSH: + return torture_dsa_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_dsa_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_dsa_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_RSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_rsa_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_rsa_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_rsa_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P521: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa521_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ecdsa521_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa521_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P384: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa384_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ecdsa384_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa384_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P256: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa256_private_openssh_testkey_pasphrase; + case FORMAT_PKCS8: + return torture_ecdsa256_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + return torture_ecdsa256_private_testkey_passphrase; + } + return NULL; + case SSH_KEYTYPE_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + return torture_ed25519_private_pkcs8_testkey_passphrase; + case FORMAT_PEM: + /* ed25519 keys are not available in legacy PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ECDSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa_sk_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + case FORMAT_PEM: + /* SK keys are not available in PKCS8 or PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_sk_private_openssh_testkey_passphrase; + case FORMAT_PKCS8: + case FORMAT_PEM: + /* SK keys are not available in PKCS8 or PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +static const char * +torture_get_testkey_internal(enum ssh_keytypes_e type, + enum torture_format_e format) +{ + switch (type) { + case SSH_KEYTYPE_DSS: + switch (format) { + case FORMAT_OPENSSH: + return torture_dsa_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_dsa_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_dsa_private_testkey; + } + return NULL; + case SSH_KEYTYPE_RSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_rsa_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_rsa_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_rsa_private_testkey; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P521: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa521_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa521_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_ecdsa521_private_testkey; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P384: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa384_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa384_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_ecdsa384_private_testkey; + } + return NULL; + case SSH_KEYTYPE_ECDSA_P256: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa256_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ecdsa256_private_pkcs8_testkey; + case FORMAT_PEM: + return torture_ecdsa256_private_testkey; + } + return NULL; + case SSH_KEYTYPE_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_private_openssh_testkey; + case FORMAT_PKCS8: + return torture_ed25519_private_pkcs8_testkey; + case FORMAT_PEM: + /* ed25519 keys are not available in legacy PEM format */ + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ECDSA: + switch (format) { + case FORMAT_OPENSSH: + return torture_ecdsa_sk_private_openssh_testkey; + case FORMAT_PKCS8: + case FORMAT_PEM: + return NULL; + } + return NULL; + case SSH_KEYTYPE_SK_ED25519: + switch (format) { + case FORMAT_OPENSSH: + return torture_ed25519_sk_private_openssh_testkey; + case FORMAT_PKCS8: + case FORMAT_PEM: + return NULL; + } + return NULL; + case SSH_KEYTYPE_DSS_CERT01: + case SSH_KEYTYPE_RSA_CERT01: + case SSH_KEYTYPE_ECDSA_P256_CERT01: + case SSH_KEYTYPE_ECDSA_P384_CERT01: + case SSH_KEYTYPE_ECDSA_P521_CERT01: + case SSH_KEYTYPE_ED25519_CERT01: + case SSH_KEYTYPE_RSA1: + case SSH_KEYTYPE_ECDSA: + case SSH_KEYTYPE_SK_ECDSA_CERT01: + case SSH_KEYTYPE_SK_ED25519_CERT01: + case SSH_KEYTYPE_UNKNOWN: + return NULL; + } + + return NULL; +} + +/* Return the encrypted private key in a new OpenSSH format */ +const char * +torture_get_openssh_testkey(enum ssh_keytypes_e type, bool with_passphrase) +{ + if (with_passphrase) { + return torture_get_testkey_encrypted_internal(type, FORMAT_OPENSSH); + } else { + return torture_get_testkey_internal(type, FORMAT_OPENSSH); + } +} + +/* Return the private key in PEM format */ +const char * +torture_get_testkey(enum ssh_keytypes_e type, bool with_passphrase) +{ + enum torture_format_e format = FORMAT_PEM; + + if (with_passphrase) { +/* This is the new PKCS8 PEM format, which works only in OpenSSL */ +#if defined(HAVE_LIBCRYPTO) + format = FORMAT_PKCS8; +#endif + return torture_get_testkey_encrypted_internal(type, format); + } else { +/* The unencrypted format works also in mbedTLS */ +#if defined(HAVE_LIBCRYPTO) || defined(HAVE_LIBMBEDCRYPTO) + format = FORMAT_PKCS8; +#endif + return torture_get_testkey_internal(type, format); + } +} + +const char * +torture_get_testkey_pub(enum ssh_keytypes_e type) +{ + return torture_get_testkey_public_internal(type, FORMAT_OPENSSH); +} + +const char * +torture_get_testkey_pub_pem(enum ssh_keytypes_e type) +{ + return torture_get_testkey_public_internal(type, FORMAT_PEM); +} + +const char * +torture_get_testkey_passphrase(void) +{ + return TORTURE_TESTKEY_PASSWORD; +} diff --git a/src/libs/libssh-0.12.2/tests/torture_key.h b/src/libs/libssh-0.12.2/tests/torture_key.h new file mode 100644 index 000000000000..5eacdab95f87 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_key.h @@ -0,0 +1,44 @@ +/* + * torture_key.h - torture library for testing libssh + * + * This file is part of the SSH Library + * + * Copyright (c) 2008-2009 by Andreas Schneider + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _TORTURE_KEY_H +#define _TORTURE_KEY_H + +#include + +#define TORTURE_TESTKEY_PASSWORD "libssh-rocks" + +/* Return the encrypted private key in a new OpenSSH format */ +const char *torture_get_openssh_testkey(enum ssh_keytypes_e type, + bool with_passphrase); + +/* Return the private key in the legacy PEM format */ +const char *torture_get_testkey(enum ssh_keytypes_e type, + bool with_passphrase); +const char *torture_get_testkey_passphrase(void); + +const char *torture_get_testkey_pub(enum ssh_keytypes_e type); + +const char *torture_get_testkey_pub_pem(enum ssh_keytypes_e type); + +#endif /* _TORTURE_KEY_H */ diff --git a/src/libs/libssh-0.12.2/tests/torture_pki.c b/src/libs/libssh-0.12.2/tests/torture_pki.c new file mode 100644 index 000000000000..f54209469a85 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_pki.c @@ -0,0 +1,97 @@ +#include "config.h" + +#include +#include +#include +#include +#include +#include + +#ifdef HAVE_UNISTD_H +#include +#endif + +#include "torture_pki.h" +#include + +char *torture_pki_read_file(const char *filename) +{ + char *key; + int fd; + int size; + int rc; + struct stat sb; + + if (filename == NULL || filename[0] == '\0') { + return NULL; + } + + fd = open(filename, O_RDONLY); + if (fd < 0) { + return NULL; + } + + rc = fstat(fd, &sb); + if (rc != 0) { + close(fd); + return NULL; + } + + key = malloc(sb.st_size + 1); + if (key == NULL) { + close(fd); + return NULL; + } + + size = read(fd, key, sb.st_size); + close(fd); + if (size != sb.st_size) { + free(key); + return NULL; + } + + key[size] = '\0'; + return key; +} + +int torture_read_one_line(const char *filename, char *buffer, size_t len) +{ + FILE *fp; + size_t nmemb; + + fp = fopen(filename, "r"); + if (fp == NULL) { + return -1; + } + + nmemb = fread(buffer, len - 2, 1, fp); + if (nmemb != 0 || ferror(fp)) { + fclose(fp); + return -1; + } + buffer[len - 1] = '\0'; + + fclose(fp); + + return 0; +} + +/** + * @internal + * + * Returns the character len of a public key string, omitting the comment part + */ +size_t torture_pubkey_len(const char *pubkey) +{ + const char *ptr; + + ptr = strchr(pubkey, ' '); + if (ptr != NULL) { + ptr = strchr(ptr + 1, ' '); + if (ptr != NULL) { + return ptr - pubkey; + } + } + + return 0; +} diff --git a/src/libs/libssh-0.12.2/tests/torture_pki.h b/src/libs/libssh-0.12.2/tests/torture_pki.h new file mode 100644 index 000000000000..460cc91f2405 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_pki.h @@ -0,0 +1,3 @@ +char *torture_pki_read_file(const char *filename); +int torture_read_one_line(const char *filename, char *buffer, size_t len); +size_t torture_pubkey_len(const char *pubkey); diff --git a/src/libs/libssh-0.12.2/tests/torture_sk.c b/src/libs/libssh-0.12.2/tests/torture_sk.c new file mode 100644 index 000000000000..fdb2a815b7ed --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_sk.c @@ -0,0 +1,395 @@ +/* + * torture_sk.c - torture library for testing security keys + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "torture_sk.h" +#include "libssh/pki.h" +#include "libssh/pki_priv.h" +#include "libssh/sk_api.h" /* For SSH_SK_* flag definitions */ + +void assert_sk_key_valid(ssh_key key, + enum ssh_keytypes_e expected_type, + bool private) +{ + char *app_str = NULL; + const char *expected_type_str = NULL; + + assert_non_null(key); + assert_true(is_sk_key_type(expected_type)); + assert_int_equal(key->type, expected_type); + + if (private) { + assert_int_equal(key->flags, + SSH_KEY_FLAG_PRIVATE | SSH_KEY_FLAG_PUBLIC); + } else { + assert_int_equal(key->flags, SSH_KEY_FLAG_PUBLIC); + } + + expected_type_str = ssh_key_type_to_char(expected_type); + assert_non_null(expected_type_str); + + assert_non_null(key->type_c); + assert_string_equal(key->type_c, expected_type_str); + + /* Validate security key specific fields */ + assert_non_null(key->sk_application); + + /* Validate application string format and content */ + app_str = ssh_string_to_char(key->sk_application); + assert_non_null(app_str); + + assert_true(ssh_string_len(key->sk_application) >= 4); + assert_true(strncmp(app_str, "ssh:", 4) == 0); + ssh_string_free_char(app_str); + + if (private) { + assert_non_null(key->sk_key_handle); + assert_true(ssh_string_len(key->sk_key_handle) > 0); + } + + const uint8_t allowed_flags = SSH_SK_USER_PRESENCE_REQD | + SSH_SK_USER_VERIFICATION_REQD | + SSH_SK_RESIDENT_KEY | SSH_SK_FORCE_OPERATION; + + /* Validate sk_flags contain only allowed bits */ + uint8_t flags = key->sk_flags; + assert_int_equal(flags & ~allowed_flags, 0); + + /* Validate underlying cryptographic key exists based on type */ + switch (expected_type) { + case SSH_KEYTYPE_SK_ECDSA: +#if defined(HAVE_LIBGCRYPT) + assert_non_null(key->ecdsa); +#elif defined(HAVE_LIBMBEDCRYPTO) + assert_non_null(key->ecdsa); +#elif defined(HAVE_LIBCRYPTO) + assert_non_null(key->key); +#endif + break; + + case SSH_KEYTYPE_SK_ED25519: +#if defined(HAVE_LIBCRYPTO) + assert_non_null(key->key); +#elif !defined(HAVE_LIBCRYPTO) + assert_non_null(key->ed25519_pubkey); +#endif + break; + + default: + /* Should not reach here */ + assert_true(0); + break; + } +} + +void assert_sk_signature_valid(ssh_signature signature, + enum ssh_keytypes_e expected_type, + ssh_key signing_key, + const uint8_t *data, + size_t data_len) +{ + uint8_t valid_flags; + const char *expected_type_str = NULL; + ssh_string sig_blob = NULL; + ssh_signature reconstructed = NULL; + ssh_buffer sk_sig_buffer = NULL; + int rc; + + /* Basic null and type validation */ + assert_non_null(signature); + assert_int_equal(signature->type, expected_type); + + /* Validate hash type is appropriate for security keys */ + switch (expected_type) { + case SSH_KEYTYPE_SK_ECDSA: + assert_int_equal(signature->hash_type, SSH_DIGEST_SHA256); + break; + case SSH_KEYTYPE_SK_ED25519: + assert_int_equal(signature->hash_type, SSH_DIGEST_AUTO); + break; + default: + /* Should not reach here */ + assert_true(0); + break; + } + + expected_type_str = ssh_key_type_to_char(expected_type); + assert_non_null(signature->type_c); + assert_string_equal(signature->type_c, expected_type_str); + + /* Check that only valid SK flags are set */ + valid_flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_USER_VERIFICATION_REQD; + assert_int_equal(signature->sk_flags & ~valid_flags, 0); + + assert_true(signature->sk_flags & SSH_SK_USER_PRESENCE_REQD); + assert_true(signature->sk_counter > 0); + + assert_non_null(signature->raw_sig); + assert_true(ssh_string_len(signature->raw_sig) > 0); + + rc = ssh_pki_export_signature_blob(signature, &sig_blob); + assert_int_equal(rc, SSH_OK); + assert_non_null(sig_blob); + + assert_non_null(signing_key); + rc = ssh_pki_import_signature_blob(sig_blob, signing_key, &reconstructed); + assert_int_equal(rc, SSH_OK); + assert_non_null(reconstructed); + + rc = pki_sk_signature_buffer_prepare(signing_key, + reconstructed, + data, + data_len, + &sk_sig_buffer); + assert_int_equal(rc, SSH_OK); + assert_non_null(sk_sig_buffer); + + rc = pki_verify_data_signature(reconstructed, + signing_key, + ssh_buffer_get(sk_sig_buffer), + ssh_buffer_get_len(sk_sig_buffer)); + assert_int_equal(rc, SSH_OK); + + SSH_BUFFER_FREE(sk_sig_buffer); + + ssh_signature_free(reconstructed); + ssh_string_free(sig_blob); +} + +ssh_pki_ctx +torture_create_sk_pki_ctx(const char *application, + uint8_t flags, + const void *challenge_data, + size_t challenge_len, + ssh_auth_callback pin_callback, + const char *device_path, + const char *user_id, + const struct ssh_sk_callbacks_struct *sk_callbacks) +{ + ssh_pki_ctx ctx = NULL; + ssh_buffer challenge_buffer = NULL; + int rc; + + ctx = ssh_pki_ctx_new(); + assert_non_null(ctx); + + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_SK_APPLICATION, + application); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_SK_FLAGS, &flags); + assert_int_equal(rc, SSH_OK); + + if (challenge_data != NULL && challenge_len > 0) { + challenge_buffer = ssh_buffer_new(); + assert_non_null(challenge_buffer); + + rc = ssh_buffer_add_data(challenge_buffer, + challenge_data, + challenge_len); + assert_int_equal(rc, SSH_OK); + } + + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_SK_CHALLENGE, + challenge_buffer); + assert_int_equal(rc, SSH_OK); + + SSH_BUFFER_FREE(challenge_buffer); + + rc = ssh_pki_ctx_set_sk_pin_callback(ctx, pin_callback, NULL); + assert_int_equal(rc, SSH_OK); + + if (device_path != NULL) { + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + device_path, + false); + assert_int_equal(rc, SSH_OK); + } + if (user_id != NULL) { + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_USER_ID, + user_id, + false); + assert_int_equal(rc, SSH_OK); + } + + if (sk_callbacks != NULL) { + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + sk_callbacks); + assert_int_equal(rc, SSH_OK); + } + + return ctx; +} + +void assert_sk_enroll_response(struct sk_enroll_response *response, int flags) +{ + assert_non_null(response); + + assert_non_null(response->public_key); + assert_true(response->public_key_len > 0); + + assert_non_null(response->key_handle); + assert_true(response->key_handle_len > 0); + + assert_non_null(response->signature); + assert_true(response->signature_len > 0); + + /* + * This check might fail for some authenticators, as returning an + * attestation certificate as part of the attestation statement is not + * mandated by the FIDO2 standard. + */ + assert_non_null(response->attestation_cert); + assert_true(response->attestation_cert_len > 0); + + assert_non_null(response->authdata); + assert_true(response->authdata_len > 0); + + assert_int_equal(response->flags, flags); +} + +void assert_sk_sign_response(struct sk_sign_response *response, + enum ssh_keytypes_e key_type) +{ + assert_non_null(response); + + assert_non_null(response->sig_r); + assert_true(response->sig_r_len > 0); + + /* sig_s is NULL for Ed25519, present for ECDSA */ + switch (key_type) { + case SSH_SK_ECDSA: + assert_non_null(response->sig_s); + assert_true(response->sig_s_len > 0); + break; + case SSH_SK_ED25519: + assert_null(response->sig_s); + assert_int_equal(response->sig_s_len, 0); + break; + default: + /* Should not reach here */ + assert_true(0); + break; + } +} + +void assert_sk_resident_key(struct sk_resident_key *resident_key) +{ + assert_non_null(resident_key); + + assert_non_null(resident_key->application); + assert_true(strlen(resident_key->application) > 0); + + assert_non_null(resident_key->user_id); + assert_true(resident_key->user_id_len > 0); + + assert_non_null(resident_key->key.public_key); + assert_true(resident_key->key.public_key_len > 0); + + assert_non_null(resident_key->key.key_handle); + assert_true(resident_key->key.key_handle_len > 0); +} + +const char *torture_get_sk_pin(void) +{ + const char *pin = getenv("TORTURE_SK_PIN"); + return (pin != NULL && pin[0] != '\0') ? pin : NULL; +} + +#ifdef HAVE_SK_DUMMY + +/* External declarations for sk-dummy library functions + * These match the signatures in openssh sk-api.h */ +extern uint32_t sk_api_version(void); + +extern int sk_enroll(uint32_t alg, + const uint8_t *challenge, + size_t challenge_len, + const char *application, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_enroll_response **enroll_response); + +extern int sk_sign(uint32_t alg, + const uint8_t *data, + size_t data_len, + const char *application, + const uint8_t *key_handle, + size_t key_handle_len, + uint8_t flags, + const char *pin, + struct sk_option **options, + struct sk_sign_response **sign_response); + +extern int sk_load_resident_keys(const char *pin, + struct sk_option **options, + struct sk_resident_key ***resident_keys, + size_t *num_keys_found); + +static struct ssh_sk_callbacks_struct sk_dummy_callbacks = { + .api_version = sk_api_version, + .enroll = sk_enroll, + .sign = sk_sign, + .load_resident_keys = sk_load_resident_keys, +}; + +#endif /* HAVE_SK_DUMMY */ + +#ifdef WITH_FIDO2 + +const struct ssh_sk_callbacks_struct *torture_get_sk_dummy_callbacks(void) +{ +#ifdef HAVE_SK_DUMMY + ssh_callbacks_init(&sk_dummy_callbacks); + return &sk_dummy_callbacks; +#else + return NULL; +#endif /* HAVE_SK_DUMMY */ +} + +const struct ssh_sk_callbacks_struct *torture_get_sk_callbacks(void) +{ + const char *env = getenv("TORTURE_SK_USBHID"); + bool torture_sk_usbhid = (env != NULL && env[0] != '\0'); + + if (torture_sk_usbhid) { + return ssh_sk_get_default_callbacks(); + } else { + return torture_get_sk_dummy_callbacks(); + } +} + +#endif /* WITH_FIDO2 */ + +bool torture_sk_is_using_sk_dummy(void) +{ + const char *env = getenv("TORTURE_SK_USBHID"); + /* Return true if using sk-dummy callbacks (when TORTURE_SK_USBHID is NOT + * set) */ + return (env == NULL || env[0] == '\0'); +} diff --git a/src/libs/libssh-0.12.2/tests/torture_sk.h b/src/libs/libssh-0.12.2/tests/torture_sk.h new file mode 100644 index 000000000000..d63b02a06013 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/torture_sk.h @@ -0,0 +1,167 @@ +/* + * torture_sk.h - torture library for testing security keys + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#ifndef _TORTURE_SK_H +#define _TORTURE_SK_H + +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/callbacks.h" +#include "libssh/pki.h" +#include "torture.h" +#include "torture_pki.h" + +/** + * @brief Validate a security key (ssh_key) structure + * + * Checks that the provided key is not NULL, matches the expected key type, + * and other internal fields. + * + * @param[in] key The key to validate + * @param[in] expected_type The expected key type (e.g., SSH_KEYTYPE_SK_ECDSA) + * @param[in] private true if key should be private, false for public + */ +void assert_sk_key_valid(ssh_key key, + enum ssh_keytypes_e expected_type, + bool private); + +/** + * @brief Validate a security key signature structure + * + * Checks that the signature is not NULL, matches the expected key type, and + * other internal fields. Also verifies that the signature was produced by the + * given signing key. + * + * @param[in] signature The signature to validate + * @param[in] expected_type The expected key type (e.g., SSH_KEYTYPE_SK_ECDSA) + * @param[in] signing_key The key that should have produced the signature + * @param[in] data The signed data buffer + * @param[in] data_len Length of the signed data + */ +void assert_sk_signature_valid(ssh_signature signature, + enum ssh_keytypes_e expected_type, + ssh_key signing_key, + const uint8_t *data, + size_t data_len); + +/** + * @brief Create and initialize a PKI context configured for security key + * operations. + * + * Parameters: + * @param[in] application Application string + * @param[in] flags SK flags + * @param[in] challenge_data Optional challenge bytes (may be NULL) + * @param[in] challenge_len Length of challenge_data + * @param[in] pin_callback Callback used to obtain the PIN (may be NULL) + * @param[in] device_path Optional device path (may be NULL) + * @param[in] user_id Optional user_id string (may be NULL) + * @param[in] sk_callbacks Pointer to SK callbacks (may be NULL) + * + * @return A configured ssh_pki_ctx on success, or NULL on allocation failure. + */ +ssh_pki_ctx +torture_create_sk_pki_ctx(const char *application, + uint8_t flags, + const void *challenge_data, + size_t challenge_len, + ssh_auth_callback pin_callback, + const char *device_path, + const char *user_id, + const struct ssh_sk_callbacks_struct *sk_callbacks); + +/** + * @brief Validate a security key enrollment response structure + * + * Validates that an sk_enroll_response contains valid data from a FIDO2 + * enrollment operation, including public key, key handle, signature, + * attestation certificate, and authenticator data. + * + * @param[in] response The enrollment response to validate + * @param[in] flags The expected flags that should match the response flags + */ +void assert_sk_enroll_response(struct sk_enroll_response *response, int flags); + +/** + * @brief Validate a security key sign response structure + * + * Validates that an sk_sign_response contains valid signature data from + * a FIDO2 sign operation. + * + * @param[in] response The sign response to validate + * @param[in] key_type The key type (e.g., SSH_SK_ECDSA, SSH_SK_ED25519) + */ +void assert_sk_sign_response(struct sk_sign_response *response, + enum ssh_keytypes_e key_type); + +/** + * @brief Validate a security key resident key structure + * + * Validates that an sk_resident_key contains valid data including application + * identifier, user ID, public key, and key handle. + * + * @param[in] resident_key The resident key to validate + */ +void assert_sk_resident_key(struct sk_resident_key *resident_key); + +/** + * @brief Get security key PIN from environment variable + * + * Reads the TORTURE_SK_PIN environment variable and returns its value. + * + * @return Pointer to PIN string if set and non-empty, NULL otherwise + */ +const char *torture_get_sk_pin(void); + +/** + * @brief Get dummy security key callbacks for testing + * + * Returns dummy security key callbacks from openssh's sk-dummy + * if available, or NULL if not. + * + * @return Pointer to ssh_sk_callbacks_struct or NULL if unavailable. + * + */ +const struct ssh_sk_callbacks_struct *torture_get_sk_dummy_callbacks(void); + +/** + * @brief Get security key callbacks for testing + * + * Returns the default sk callbacks if TORTURE_SK_USBHID is set, + * otherwise returns dummy callbacks from openssh sk-dummy, or NULL if + * unavailable. + * + * @return Pointer to ssh_sk_callbacks_struct or NULL if unavailable + */ +const struct ssh_sk_callbacks_struct *torture_get_sk_callbacks(void); + +/** + * @brief Check if using sk-dummy callbacks for testing + * + * @return true if using sk-dummy callbacks, false otherwise + */ +bool torture_sk_is_using_sk_dummy(void); + +#endif /* _TORTURE_SK_H */ diff --git a/src/libs/libssh-0.12.2/tests/unittests/CMakeLists.txt b/src/libs/libssh-0.12.2/tests/unittests/CMakeLists.txt new file mode 100644 index 000000000000..79f6b218a518 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/CMakeLists.txt @@ -0,0 +1,161 @@ +project(unittests C) + +set(LIBSSH_UNIT_TESTS + torture_bignum + torture_buffer + torture_bytearray + torture_callbacks + torture_crypto + torture_init + torture_list + torture_misc + torture_config + torture_options + torture_isipaddr + torture_knownhosts_parsing + torture_hashes + torture_packet_filter + torture_temp_dir + torture_temp_file + torture_push_pop_dir + torture_session_keys + torture_string + torture_tokens +) + +set(LIBSSH_THREAD_UNIT_TESTS + torture_rand + torture_threads_init + torture_threads_buffer + torture_threads_crypto +) + +set(TORTURE_UNIT_ENVIRONMENT + "LSAN_OPTIONS=suppressions=${libssh-tests_SOURCE_DIR}/suppressions/lsan.supp;") +if (OPENSSL_FOUND) + list(APPEND TORTURE_UNIT_ENVIRONMENT OPENSSL_ENABLE_SHA1_SIGNATURES=1) +endif (OPENSSL_FOUND) + +if (UNIX AND NOT WIN32) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + # this uses a socketpair + torture_packet + # requires ssh-keygen + torture_keyfiles + torture_pki + torture_pki_rsa + torture_pki_dsa + torture_pki_ed25519 + torture_pki_sk_ed25519 + torture_pki_sshsig + # requires /dev/null + torture_channel + ) + if (HAVE_IFADDRS_H) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + # requires some non-standard API from netdb.h, in.h + # and arpa/inet.h for handling IP addresses + torture_config_match_localnetwork + ) + endif (HAVE_IFADDRS_H) + if (WITH_SERVER) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_bind_config) + + if (WITH_GEX) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_moduli) + endif() + endif() + + if (WITH_PKCS11_URI) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_rsa_uri + torture_pki_ecdsa_uri + ) + if (WITH_PKCS11_PROVIDER) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_ed25519_uri + ) + list(APPEND TORTURE_UNIT_ENVIRONMENT + PKCS11_PROVIDER_DEBUG=file:/tmp/p11prov-debug.log) + endif() + endif() + + if (WITH_FIDO2) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_sk + ) + endif (WITH_FIDO2) + + if (HAVE_ECC) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_pki_ecdsa + torture_pki_sk_ecdsa + ) + endif() + + set(LIBSSH_THREAD_UNIT_TESTS + ${LIBSSH_THREAD_UNIT_TESTS} + # requires pthread + torture_threads_pki_rsa + ) + if (WITH_SERVER) + set(LIBSSH_THREAD_UNIT_TESTS + ${LIBSSH_THREAD_UNIT_TESTS} + torture_unit_server + torture_server_x11 + torture_forwarded_tcpip_callback + torture_server_direct_tcpip + ) + endif (WITH_SERVER) +endif (UNIX AND NOT WIN32) + +if (HAVE_LIBFIDO2) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_sk_usbhid + ) +endif (HAVE_LIBFIDO2) + +if (WITH_SFTP) + set(LIBSSH_UNIT_TESTS + ${LIBSSH_UNIT_TESTS} + torture_unit_sftp + ) +endif (WITH_SFTP) + +foreach(_UNIT_TEST ${LIBSSH_UNIT_TESTS}) + add_cmocka_test(${_UNIT_TEST} + SOURCES ${_UNIT_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} + ) + + set_property(TEST ${_UNIT_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_UNIT_ENVIRONMENT}) +endforeach() + +if (CMAKE_USE_PTHREADS_INIT) + foreach(_UNIT_TEST ${LIBSSH_THREAD_UNIT_TESTS}) + add_cmocka_test(${_UNIT_TEST} + SOURCES ${_UNIT_TEST}.c + COMPILE_OPTIONS ${DEFAULT_C_COMPILE_FLAGS} + LINK_LIBRARIES ${TEST_TARGET_LIBRARIES} Threads::Threads + ) + + set_property(TEST ${_UNIT_TEST} + PROPERTY + ENVIRONMENT ${TORTURE_UNIT_ENVIRONMENT}) + endforeach() +endif () + diff --git a/src/libs/libssh-0.12.2/tests/unittests/hello world.sh b/src/libs/libssh-0.12.2/tests/unittests/hello world.sh new file mode 100755 index 000000000000..8f6870284f2c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/hello world.sh @@ -0,0 +1,2 @@ +#!/bin/sh +printf '%s' "$1" 2>&1 diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_bignum.c b/src/libs/libssh-0.12.2/tests/unittests/torture_bignum.c new file mode 100644 index 000000000000..6f679946a263 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_bignum.c @@ -0,0 +1,176 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/bignum.h" +#include "libssh/string.h" + +static void check_str(int n, ssh_string str) +{ + if (n > 0 && n <= 127) { + assert_int_equal(1, ntohl(str->size)); + assert_int_equal(n, str->data[0]); + } else if (n > 127 && n <= 255) { + assert_int_equal(2, ntohl(str->size)); + assert_int_equal(0, str->data[0]); + assert_int_equal(n, str->data[1]); + } else if (n > 255 && n <= 32767) { + assert_int_equal(2, ntohl(str->size)); + assert_int_equal(n >> 8, str->data[0]); + assert_int_equal(n & 0xFF, str->data[1]); + } else { + assert_int_equal(3, ntohl(str->size)); + assert_int_equal(n >> 16, str->data[0]); + assert_int_equal((n >> 8) & 0xFF, str->data[1]); + assert_int_equal(n & 0xFF, str->data[2]); + } +} + +static void check_padded_str(int n, ssh_string str) +{ + assert_int_equal(4, ntohl(str->size)); + if (n > 0 && n <= 255) { + assert_int_equal(0, str->data[0]); + assert_int_equal(0, str->data[1]); + assert_int_equal(0, str->data[2]); + assert_int_equal(n, str->data[3]); + } else if (n > 255 && n <= 65535) { + assert_int_equal(0, str->data[0]); + assert_int_equal(0, str->data[1]); + assert_int_equal(n >> 8, str->data[2]); + assert_int_equal(n & 0xFF, str->data[3]); + } else { + assert_int_equal(0, str->data[0]); + assert_int_equal(n >> 16, str->data[1]); + assert_int_equal((n >> 8) & 0xFF, str->data[2]); + assert_int_equal(n & 0xFF, str->data[3]); + } +} + +static void check_bignum(int n, const char *nstr) +{ + bignum num = NULL, num2 = NULL; + bignum num3 = NULL; + ssh_string str = NULL; + char *dec = NULL; + int rc; + + num = bignum_new(); + assert_non_null(num); + + rc = bignum_set_word(num, n); + assert_int_equal(rc, 1); + + ssh_print_bignum("num", num); + + dec = bignum_bn2dec (num); + assert_non_null (dec); + assert_string_equal (nstr, dec); + ssh_crypto_free(dec); + + /* ssh_make_bignum_string */ + + str = ssh_make_bignum_string(num); + assert_non_null(str); + + check_str (n, str); + + /* ssh_make_string_bn */ + + num2 = ssh_make_string_bn(str); + ssh_string_free (str); + assert_non_null(num2); + + ssh_print_bignum("num2", num2); + + assert_int_equal (0, bignum_cmp (num, num2)); + + dec = bignum_bn2dec (num2); + assert_non_null (dec); + assert_string_equal (nstr, dec); + ssh_crypto_free(dec); + + bignum_dup(num, &num3); + assert_non_null(num3); + assert_int_equal(0, bignum_cmp(num, num3)); + + bignum_safe_free(num2); + + /* ssh_make_padded_bignum_string */ + + str = ssh_make_padded_bignum_string(num, 4); + assert_non_null(str); + + check_padded_str(n, str); + + num2 = ssh_make_string_bn(str); + ssh_string_free(str); + assert_non_null(num2); + + ssh_print_bignum("num2", num2); + + assert_true(bignum_cmp(num, num2) == 0); + + dec = bignum_bn2dec(num2); + assert_non_null(dec); + assert_string_equal(nstr, dec); + ssh_crypto_free(dec); + + /* negative test */ + str = ssh_make_padded_bignum_string(num, 2); + if (n > 65535) { + /* larger values need larger padding! */ + assert_null(str); + } else { + assert_non_null(str); + assert_int_equal(2, ntohl(str->size)); + if (n > 0 && n <= 255) { + assert_int_equal(0, str->data[0]); + assert_int_equal(n, str->data[1]); + } else { + assert_int_equal(n >> 8, str->data[0]); + assert_int_equal(n & 0xFF, str->data[1]); + } + ssh_string_free(str); + } + + bignum_safe_free(num); + bignum_safe_free(num2); + bignum_safe_free(num3); +} + + +static void torture_bignum(void **state) { + (void) state; /* unused */ + + ssh_set_log_level(SSH_LOG_TRACE); + + check_bignum (1, "1"); + check_bignum (17, "17"); + check_bignum (42, "42"); + check_bignum (127, "127"); + check_bignum (128, "128"); + check_bignum (254, "254"); + check_bignum (255, "255"); + check_bignum (256, "256"); + check_bignum (257, "257"); + check_bignum (300, "300"); + check_bignum (32767, "32767"); + check_bignum (32768, "32768"); + check_bignum (65535, "65535"); + check_bignum (65536, "65536"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_bignum), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_bind_config.c b/src/libs/libssh-0.12.2/tests/unittests/torture_bind_config.c new file mode 100644 index 000000000000..514727e52f76 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_bind_config.c @@ -0,0 +1,1888 @@ +/* + * torture_bind_config.c - Tests for server side configuration + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" + +#include +#include + +extern LIBSSH_THREAD int ssh_log_level; + +#define LOGLEVEL "verbose" +#define LOGLEVEL2 "fatal" +#define LOGLEVEL3 "DEBUG1" +#define LOGLEVEL4 "DEBUG2" +#define LISTEN_ADDRESS "::1" +#define LISTEN_ADDRESS2 "::2" +#define KEXALGORITHMS "ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha1" +#define KEXALGORITHMS2 "ecdh-sha2-nistp521" +#define CIPHERS "aes128-ctr,aes192-ctr,aes256-ctr" +#define CIPHERS2 "aes256-ctr" +#define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa" +#define HOSTKEYALGORITHMS_UNKNOWN "ssh-ed25519,ecdsa-sha2-nistp521,unknown,ssh-rsa" +#define HOSTKEYALGORITHMS2 "rsa-sha2-256" +#define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521" +#define PUBKEYACCEPTEDTYPES_UNKNOWN "rsa-sha2-512,ssh-rsa,unknown,ecdsa-sha2-nistp521" +#define PUBKEYACCEPTEDTYPES2 "rsa-sha2-256,ssh-rsa" +#define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com" +#define MACS2 "hmac-sha1" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#ifdef HAVE_ECC +#define LIBSSH_ECDSA_521_TESTKEY "libssh_testkey.id_ecdsa521" +#endif + +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS "libssh_test_bind_config_listenaddress" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING "ListenAddress "LISTEN_ADDRESS"\n" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2 "libssh_test_bind_config_listenaddress2" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING "ListenAddress "LISTEN_ADDRESS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE "libssh_test_bind_config_listenaddress_twice" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING \ + "ListenAddress "LISTEN_ADDRESS"\n" \ + "ListenAddress "LISTEN_ADDRESS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC "libssh_test_bind_config_listenaddress_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING \ + "ListenAddress "LISTEN_ADDRESS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_PORT "libssh_test_bind_config_port" +#define LIBSSH_TEST_BIND_CONFIG_PORT_STRING "Port 123\n" +#define LIBSSH_TEST_BIND_CONFIG_PORT2 "libssh_test_bind_config_port2" +#define LIBSSH_TEST_BIND_CONFIG_PORT2_STRING "Port 456\n" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE "libssh_test_bind_config_port_twice" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING \ + "Port 123\n" \ + "Port 456\n" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC "libssh_test_bind_config_port_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING \ + "Port 123\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_PORT2"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY "libssh_test_bind_config_hostkey" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY2 "libssh_test_bind_config_hostkey2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY2_STRING "HostKey "LIBSSH_RSA_TESTKEY"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE "libssh_test_bind_config_hostkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING \ + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" \ + "HostKey "LIBSSH_RSA_TESTKEY"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC "libssh_test_bind_config_hostkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING \ + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL "libssh_test_bind_config_loglevel" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING "LogLevel "LOGLEVEL"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1 "libssh_test_bind_config_loglevel2" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING "LogLevel "LOGLEVEL2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE "libssh_test_bind_config_loglevel_twice" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING \ + "LogLevel "LOGLEVEL"\n" \ + "LogLevel "LOGLEVEL2"\n" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC "libssh_test_bind_config_loglevel_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING \ + "LogLevel "LOGLEVEL"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1"\n" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS "libssh_test_bind_config_ciphers" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING "Ciphers "CIPHERS"\n" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS2 "libssh_test_bind_config_ciphers2" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING "Ciphers "CIPHERS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE "libssh_test_bind_config_ciphers_twice" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING \ + "Ciphers "CIPHERS"\n" \ + "Ciphers "CIPHERS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC "libssh_test_bind_config_ciphers_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING \ + "Ciphers "CIPHERS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_MACS "libssh_test_bind_config_macs" +#define LIBSSH_TEST_BIND_CONFIG_MACS_STRING "MACs "MACS"\n" +#define LIBSSH_TEST_BIND_CONFIG_MACS2 "libssh_test_bind_config_macs2" +#define LIBSSH_TEST_BIND_CONFIG_MACS2_STRING "MACs "MACS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE "libssh_test_bind_config_macs_twice" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING \ + "MACs "MACS"\n" \ + "MACs "MACS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC "libssh_test_bind_config_macs_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING \ + "MACs "MACS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_MACS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS "libssh_test_bind_config_kexalgorithms" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING "KexAlgorithms "KEXALGORITHMS"\n" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2 "libssh_test_bind_config_kexalgorithms2" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING "KexAlgorithms "KEXALGORITHMS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE "libssh_test_bind_config_kexalgorithms_twice" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING \ + "KexAlgorithms "KEXALGORITHMS"\n" \ + "KexAlgorithms "KEXALGORITHMS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC "libssh_test_bind_config_kexalgorithms_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING \ + "KexAlgorithms "KEXALGORITHMS"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2"\n" + +#define LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE "libssh_test_bind_config_required_rsa_size" +#define LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE_STRING "RequiredRsaSize 2233\n" + +#define LIBSSH_TEST_BIND_CONFIG_FULL "libssh_test_bind_config_full" +#define LIBSSH_TEST_BIND_CONFIG_INCLUDE "libssh_test_bind_config_include" +#define LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE "libssh_test_bind_config_include_recursive" +#define LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP "libssh_test_bind_config_include_recursive_loop" +#define LIBSSH_TEST_BIND_CONFIG_CORNER_CASES "libssh_test_bind_config_corner_cases" + +#define LIBSSH_TEST_BIND_CONFIG_MATCH_ALL "libssh_test_bind_config_match_all" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_TWICE "libssh_test_bind_config_match_twice" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_UNSUPPORTED "libssh_test_bind_config_match_unsupported" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_NOT_ALLOWED "libssh_test_bind_config_match_not_allowed" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_CORNER_CASES "libssh_test_bind_config_match_corner_cases" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID "libssh_test_bind_config_match_invalid" +#define LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2 "libssh_test_bind_config_match_invalid2" + +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED "libssh_test_bind_config_pubkey" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2 "libssh_test_bind_config_pubkey2" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE "libssh_test_bind_config_pubkey_twice" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC "libssh_test_bind_config_pubkey_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES2"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN "libssh_test_bind_config_pubkey_unknown" +#define LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES_UNKNOWN"\n" + +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS "libssh_test_bind_config_hostkey_alg" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2 "libssh_test_bind_config_hostkey_alg2" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE "libssh_test_bind_config_hostkey_alg_twice" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS"\n" \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC "libssh_test_bind_config_hostkey_alg_twice_rec" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING \ + "HostKeyAlgorithms "HOSTKEYALGORITHMS2"\n" \ + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN "libssh_test_bind_config_hostkey_alg_unknown" +#define LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING "HostKeyAlgorithms "HOSTKEYALGORITHMS_UNKNOWN"\n" + +const char template[] = "temp_dir_XXXXXX"; + +struct bind_st { + char *cwd; + char *temp_dir; + ssh_bind bind; +}; + +static int setup_config_files(void **state) +{ + struct bind_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct bind_st *)malloc(sizeof(struct bind_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + /* For ed25519 the test keys are not available in legacy PEM format. Using + * the new OpenSSH format for all algorithms */ + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + + torture_write_file(LIBSSH_ED25519_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); +#ifdef HAVE_ECC + torture_write_file(LIBSSH_ECDSA_521_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); +#endif + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT, + LIBSSH_TEST_BIND_CONFIG_PORT_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT2, + LIBSSH_TEST_BIND_CONFIG_PORT2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY2, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS2, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS, + LIBSSH_TEST_BIND_CONFIG_MACS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS2, + LIBSSH_TEST_BIND_CONFIG_MACS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE, + LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_FULL, + "ListenAddress "LISTEN_ADDRESS"\n" + "Port 123\n" + "HostKey "LIBSSH_ECDSA_521_TESTKEY"\n" + "LogLevel "LOGLEVEL"\n" + "Ciphers "CIPHERS"\n" + "MACs "MACS"\n" + "KexAlgorithms "KEXALGORITHMS"\n" + "RequiredRsaSize 2233\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE, + "Include "LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_PORT"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_HOSTKEY"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_LOGLEVEL"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_CIPHERS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_MACS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS"\n" + "Include "LIBSSH_TEST_BIND_CONFIG_REQUIRED_RSA_SIZE"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE, + "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP, + "Include "LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP"\n"); + + /* Unsupported options and corner cases */ + torture_write_file(LIBSSH_TEST_BIND_CONFIG_CORNER_CASES, + "\n" /* empty line */ + "# comment line\n" + " # comment line not starting with hash\n" + "UnknownConfigurationOption yes\n" + "Ciphers "CIPHERS2"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_ALL, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_TWICE, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_UNSUPPORTED, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match User alice\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Group sftp_users\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Host 192.168.0.*\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match LocalAddress 172.30.1.5\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match LocalPort 42\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Rdomain 4\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match Address 10.0.0.10\n" + "\tLogLevel "LOGLEVEL2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_NOT_ALLOWED, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All\n" + "\tListenAddress "LISTEN_ADDRESS2"\n" + "\tPort 456\n" + "\tHostKey "LIBSSH_RSA_TESTKEY"\n" + "\tCiphers "CIPHERS2"\n" + "\tMACs "MACS2"\n" + "\tKexAlgorithms "KEXALGORITHMS2"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_CORNER_CASES, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match User alice\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match User alice All\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL4"\n"); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2, + "Include "LIBSSH_TEST_BIND_CONFIG_FULL"\n" + "Match All User alice\n" + "\tLogLevel "LOGLEVEL2"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL3"\n" + "Match All\n" + "\tLogLevel "LOGLEVEL4"\n"); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING); + + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING); + torture_write_file(LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING); + return 0; +} + +static int sshbind_setup(void **state) +{ + int rc; + struct bind_st *test_state = NULL; + + rc = setup_config_files((void **)&test_state); + assert_int_equal(rc, 0); + assert_non_null(test_state); + + test_state->bind = ssh_bind_new(); + assert_non_null(test_state->bind); + + *state = test_state; + + return 0; +} + +static int sshbind_teardown(void **state) +{ + struct bind_st *test_state = NULL; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + assert_non_null(test_state->bind); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + ssh_bind_free(test_state->bind); + SAFE_FREE(test_state); + + return 0; +} + +/** + * @brief helper function loading configuration from either file or string + */ +static void +_parse_config(ssh_bind bind, + const char *file, + const char *string, + int expected) +{ + int ret = -1; + + /* make sure either config file or config string is given, + * not both */ + assert_int_not_equal(file == NULL, string == NULL); + + if (file != NULL) { + ret = ssh_bind_config_parse_file(bind, file); + } else if (string != NULL) { + ret = ssh_bind_config_parse_string(bind, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_return_code(ret, expected); +} + + +static void +torture_bind_config_listen_address(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, expect); +} + +static void torture_bind_config_listen_address_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS, + NULL, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_STRING, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address2_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2, + NULL, + LISTEN_ADDRESS2); +} + +static void torture_bind_config_listen_address2_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS2_STRING, + LISTEN_ADDRESS2); +} + +static void torture_bind_config_listen_address_twice_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE, + NULL, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_twice_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_STRING, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_twice_rec_file(void **state) +{ + torture_bind_config_listen_address(state, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC, + NULL, + LISTEN_ADDRESS); +} + +static void torture_bind_config_listen_address_twice_rec_string(void **state) +{ + torture_bind_config_listen_address(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LISTENADDRESS_TWICE_REC_STRING, + LISTEN_ADDRESS); +} + +static void +torture_bind_config_port(void **state, + const char *file, + const char *string, + int expect) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_int_equal(bind->bindport, expect); +} + +static void torture_bind_config_port_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT, + NULL, + 123); +} + +static void torture_bind_config_port_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_STRING, + 123); +} + +static void torture_bind_config_port2_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT2, + NULL, + 456); +} + +static void torture_bind_config_port2_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT2_STRING, + 456); +} + +static void torture_bind_config_port_twice_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE, + NULL, + 123); +} + +static void torture_bind_config_port_twice_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_STRING, + 123); +} + +static void torture_bind_config_port_twice_rec_file(void **state) +{ + torture_bind_config_port(state, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC, + NULL, + 123); +} + +static void torture_bind_config_port_twice_rec_string(void **state) +{ + torture_bind_config_port(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PORT_TWICE_REC_STRING, + 123); +} + +static void +torture_bind_config_hostkey(void **state, + const char *file, + const char *string) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +} + +static void +torture_bind_config_hostkey2(void **state, + const char *file, + const char *string) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); +} + +static void torture_bind_config_hostkey_file(void **state) +{ + torture_bind_config_hostkey(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY, NULL); +} + +static void torture_bind_config_hostkey_string(void **state) +{ + torture_bind_config_hostkey(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_STRING); +} + +static void torture_bind_config_hostkey_twice_file(void **state) +{ + torture_bind_config_hostkey2(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE, NULL); +} + +static void torture_bind_config_hostkey_twice_string(void **state) +{ + torture_bind_config_hostkey2(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_STRING); +} + +static void torture_bind_config_hostkey_twice_rec_file(void **state) +{ + torture_bind_config_hostkey2(state, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC, NULL); +} + +static void torture_bind_config_hostkey_twice_rec_string(void **state) +{ + torture_bind_config_hostkey2(state, NULL, LIBSSH_TEST_BIND_CONFIG_HOSTKEY_TWICE_REC_STRING); +} + +static void torture_bind_config_hostkey_separately(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_HOSTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_HOSTKEY2); + assert_int_equal(rc, 0); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +} + +static void +torture_bind_config_loglevel(void **state, + const char *file, + const char *string, + int expect) +{ + struct bind_st *test_state; + ssh_bind bind; + int previous_level, new_level, rc; + + previous_level = ssh_get_log_level(); + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, expect); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_loglevel_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL, + NULL, + 2); +} + +static void torture_bind_config_loglevel_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_STRING, + 2); +} + +static void torture_bind_config_loglevel1_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1, + NULL, + 1); +} + +static void torture_bind_config_loglevel1_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL1_STRING, + 1); +} + +static void torture_bind_config_loglevel_twice_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE, + NULL, + 2); +} + +static void torture_bind_config_loglevel_twice_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_STRING, + 2); +} + +static void torture_bind_config_loglevel_twice_rec_file(void **state) +{ + torture_bind_config_loglevel(state, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC, + NULL, + 2); +} + +static void torture_bind_config_loglevel_twice_rec_string(void **state) +{ + torture_bind_config_loglevel(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_LOGLEVEL_TWICE_REC_STRING, + 2); +} + +static void +torture_bind_config_ciphers(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_ciphers = NULL; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, expect); + assert_non_null(fips_ciphers); + } + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); + SAFE_FREE(fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], expect); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], expect); + } +} + +static void torture_bind_config_ciphers_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS, + NULL, + CIPHERS); +} + +static void torture_bind_config_ciphers_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_STRING, + CIPHERS); +} + +static void torture_bind_config_ciphers2_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2, + NULL, + CIPHERS2); +} + +static void torture_bind_config_ciphers2_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS2_STRING, + CIPHERS2); +} + +static void torture_bind_config_ciphers_twice_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE, + NULL, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_STRING, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_rec_file(void **state) +{ + torture_bind_config_ciphers(state, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC, + NULL, + CIPHERS); +} + +static void torture_bind_config_ciphers_twice_rec_string(void **state) +{ + torture_bind_config_ciphers(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_CIPHERS_TWICE_REC_STRING, + CIPHERS); +} + +static void +torture_bind_config_macs(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], expect); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], expect); +} + +static void torture_bind_config_macs_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS, + NULL, + MACS); +} + +static void torture_bind_config_macs_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_STRING, + MACS); +} + +static void torture_bind_config_macs2_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS2, + NULL, + MACS2); +} + +static void torture_bind_config_macs2_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS2_STRING, + MACS2); +} + +static void torture_bind_config_macs_twice_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE, + NULL, + MACS); +} + +static void torture_bind_config_macs_twice_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_STRING, + MACS); +} + +static void torture_bind_config_macs_twice_rec_file(void **state) +{ + torture_bind_config_macs(state, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC, + NULL, + MACS); +} + +static void torture_bind_config_macs_twice_rec_string(void **state) +{ + torture_bind_config_macs(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_MACS_TWICE_REC_STRING, + MACS); +} + +static void +torture_bind_config_kexalgorithms(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_kex = NULL; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + if (ssh_fips_mode()) { + fips_kex = ssh_keep_fips_algos(SSH_KEX, expect); + assert_non_null(fips_kex); + } + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); + SAFE_FREE(fips_kex); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], expect); + } +} + +static void torture_bind_config_kexalgorithms_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS, + NULL, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_STRING, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms2_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2, + NULL, + KEXALGORITHMS2); +} + +static void torture_bind_config_kexalgorithms2_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS2_STRING, + KEXALGORITHMS2); +} + +static void torture_bind_config_kexalgorithms_twice_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE, + NULL, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_STRING, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_rec_file(void **state) +{ + torture_bind_config_kexalgorithms(state, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC, + NULL, + KEXALGORITHMS); +} + +static void torture_bind_config_kexalgorithms_twice_rec_string(void **state) +{ + torture_bind_config_kexalgorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_KEXALGORITHMS_TWICE_REC_STRING, + KEXALGORITHMS); +} + +static void +torture_bind_config_pubkey_accepted(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_pubkeys = NULL; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + if (ssh_fips_mode()) { + fips_pubkeys = ssh_keep_fips_algos(SSH_HOSTKEYS, expect); + assert_non_null(fips_pubkeys); + } + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, fips_pubkeys); + SAFE_FREE(fips_pubkeys); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, expect); + } +} + +static void torture_bind_config_pubkey_accepted_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED, + NULL, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_STRING, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_twice_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE, + NULL, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_twice_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_STRING, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_twice_rec_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC, + NULL, + PUBKEYACCEPTEDTYPES2); +} + +static void torture_bind_config_pubkey_accepted_twice_rec_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_TWICE_REC_STRING, + PUBKEYACCEPTEDTYPES2); +} + +static void torture_bind_config_pubkey_accepted_unknown_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN, + NULL, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted_unknown_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED_UNKNOWN_STRING, + PUBKEYACCEPTEDTYPES); +} + +static void torture_bind_config_pubkey_accepted2_file(void **state) +{ + torture_bind_config_pubkey_accepted(state, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2, + NULL, + PUBKEYACCEPTEDTYPES2); +} + +static void torture_bind_config_pubkey_accepted2_string(void **state) +{ + torture_bind_config_pubkey_accepted(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_PUBKEY_ACCEPTED2_STRING, + PUBKEYACCEPTEDTYPES2); +} + +static void +torture_bind_config_hostkey_algorithms(void **state, + const char *file, + const char *string, + const char *expect) +{ + struct bind_st *test_state; + ssh_bind bind; + char *fips_hostkey = NULL; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + if (ssh_fips_mode()) { + fips_hostkey = ssh_keep_fips_algos(SSH_HOSTKEYS, expect); + assert_non_null(fips_hostkey); + } + + _parse_config(bind, file, string, SSH_OK); + + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], fips_hostkey); + SAFE_FREE(fips_hostkey); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], expect); + } +} + +static void torture_bind_config_hostkey_algorithms_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_STRING, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_STRING, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_twice_rec_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC, + NULL, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms_twice_rec_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_TWICE_REC_STRING, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms2_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2, + NULL, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms2_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS2_STRING, + HOSTKEYALGORITHMS2); +} + +static void torture_bind_config_hostkey_algorithms_unknown_file(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN, + NULL, + HOSTKEYALGORITHMS); +} + +static void torture_bind_config_hostkey_algorithms_unknown_string(void **state) +{ + torture_bind_config_hostkey_algorithms(state, + NULL, + LIBSSH_TEST_BIND_CONFIG_HOSTKEY_ALGORITHMS_UNKNOWN_STRING, + HOSTKEYALGORITHMS); +} + +static int assert_full_bind_config(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int new_level; + + char *fips_ciphers = NULL; + char *fips_kex = NULL; + + if (ssh_fips_mode()) { + fips_ciphers = ssh_keep_fips_algos(SSH_CRYPT_C_S, CIPHERS); + assert_non_null(fips_ciphers); + fips_kex = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS); + assert_non_null(fips_kex); + } + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 2); + + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, LISTEN_ADDRESS); + + assert_int_equal(bind->bindport, 123); + + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS); + } + + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], fips_ciphers); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS); + } + + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], MACS); + + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], MACS); + + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], fips_kex); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], KEXALGORITHMS); + } + + assert_int_equal(bind->rsa_min_size, 2233); + + SAFE_FREE(fips_ciphers); + SAFE_FREE(fips_kex); + + return 0; +} + +static void torture_bind_config_full(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_FULL); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_include(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_INCLUDE); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_include_recursive(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_include_recursive_loop(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_INCLUDE_RECURSIVE_LOOP); + assert_int_equal(rc, 0); +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options + */ +static void torture_bind_config_corner_cases(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_config_parse_file(bind, LIBSSH_TEST_BIND_CONFIG_CORNER_CASES); + assert_int_equal(rc, 0); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], CIPHERS2); + + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], CIPHERS2); +} + +static void torture_bind_config_match_all(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_ALL); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_twice(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_TWICE); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_unsupported(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_UNSUPPORTED); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_not_allowed(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_NOT_ALLOWED); + assert_int_equal(rc, 0); + + rc = assert_full_bind_config(state); + assert_int_equal(rc, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_corner_cases(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_CORNER_CASES); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 3); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_config_match_invalid(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID); + assert_int_equal(rc, -1); + + rc = ssh_bind_config_parse_file(bind, + LIBSSH_TEST_BIND_CONFIG_MATCH_INVALID2); + assert_int_equal(rc, -1); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_listen_address_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_port_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_separately, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel1_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel1_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_loglevel_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_ciphers_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_macs_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_kexalgorithms_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_full, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_include, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_include_recursive, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_include_recursive_loop, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_corner_cases, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_all, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_twice, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_unsupported, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_not_allowed, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_corner_cases, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_match_invalid, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_unknown_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_pubkey_accepted_unknown_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_rec_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_twice_rec_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms2_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms2_string, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_unknown_file, + sshbind_setup, sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_config_hostkey_algorithms_unknown_string, + sshbind_setup, sshbind_teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_buffer.c b/src/libs/libssh-0.12.2/tests/unittests/torture_buffer.c new file mode 100644 index 000000000000..028645fa3edc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_buffer.c @@ -0,0 +1,404 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#define DEBUG_BUFFER +#include "buffer.c" + +#include + +#define LIMIT (8*1024*1024) + +static int setup(void **state) { + ssh_buffer buffer; + + buffer = ssh_buffer_new(); + if (buffer == NULL) { + return -1; + } + ssh_buffer_set_secure(buffer); + *state = (void *) buffer; + + return 0; +} + +static int teardown(void **state) { + SSH_BUFFER_FREE(*state); + + return 0; +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity + */ +static void torture_growing_buffer(void **state) { + ssh_buffer buffer = *state; + int i; + + for(i=0;iused >= 128){ + if(ssh_buffer_get_len(buffer) * 2 < buffer->allocated){ + assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated); + } + } + } +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity, when we remove 1 byte after each call (sliding window) + */ +static void torture_growing_buffer_shifting(void **state) { + ssh_buffer buffer = *state; + int i; + unsigned char c; + for(i=0; i<1024;++i){ + ssh_buffer_add_data(buffer,"S",1); + } + for(i=0;iused >= 128){ + if(ssh_buffer_get_len(buffer) * 4 < buffer->allocated){ + assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated); + return; + } + } + } +} + +/* + * Test the behavior of ssh_buffer_prepend_data + */ +static void torture_buffer_prepend(void **state) { + ssh_buffer buffer = *state; + uint32_t v; + ssh_buffer_add_data(buffer,"abcdef",6); + ssh_buffer_prepend_data(buffer,"xyz",3); + assert_int_equal(ssh_buffer_get_len(buffer),9); + assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9); + + /* Now remove 4 bytes and see if we can replace them */ + ssh_buffer_get_u32(buffer,&v); + assert_int_equal(ssh_buffer_get_len(buffer),5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer,"aris",4); + assert_int_equal(ssh_buffer_get_len(buffer),9); + assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9); + + /* same thing but we add 5 bytes now */ + ssh_buffer_get_u32(buffer,&v); + assert_int_equal(ssh_buffer_get_len(buffer),5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer,"12345",5); + assert_int_equal(ssh_buffer_get_len(buffer),10); + assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10); +} + +/* + * Test the behavior of ssh_buffer_get_ssh_string with invalid data + */ +static void torture_ssh_buffer_get_ssh_string(void **state) { + ssh_buffer buffer; + int i,j,k,l, rc; + /* some values that can go wrong */ + uint32_t values[] = {0xffffffff, 0xfffffffe, 0xfffffffc, 0xffffff00, + 0x80000000, 0x80000004, 0x7fffffff}; + char data[128]; + (void)state; + memset(data,'X',sizeof(data)); + for(i=0; i < (int)(sizeof(values)/sizeof(values[0]));++i){ + for(j=0; j< (int)sizeof(data);++j){ + for(k=1;k<5;++k){ + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + for(l=0;lsecure); + SSH_BUFFER_FREE(dup_buffer); + + /* test buffer with data */ + rc = ssh_buffer_add_data(buffer, test_data, test_data_len); + assert_int_equal(rc, SSH_OK); + dup_buffer = ssh_buffer_dup(buffer); + assert_non_null(dup_buffer); + assert_int_equal(ssh_buffer_get_len(dup_buffer), test_data_len); + assert_memory_equal(ssh_buffer_get(dup_buffer), test_data, test_data_len); + assert_true(dup_buffer->secure); + + /* test independence of buffers - modify original buffer */ + rc = ssh_buffer_add_data(buffer, " more data", 10); + assert_int_equal(rc, SSH_OK); + assert_int_equal(ssh_buffer_get_len(buffer), test_data_len + 10); + assert_int_equal(ssh_buffer_get_len(dup_buffer), test_data_len); + + /* test independence of buffers - modify duplicated buffer */ + rc = ssh_buffer_add_data(dup_buffer, " different", 10); + assert_int_equal(rc, SSH_OK); + assert_int_equal(ssh_buffer_get_len(dup_buffer), test_data_len + 10); + assert_int_equal(ssh_buffer_get_len(buffer), test_data_len + 10); + + assert_memory_not_equal(ssh_buffer_get(buffer), + ssh_buffer_get(dup_buffer), + test_data_len + 10); + + SSH_BUFFER_FREE(dup_buffer); + + /* test duplicating non-secure buffer */ + null_buffer = ssh_buffer_new(); + assert_non_null(null_buffer); + rc = ssh_buffer_add_data(null_buffer, "non-secure data", 15); + assert_int_equal(rc, SSH_OK); + + dup_buffer = ssh_buffer_dup(null_buffer); + assert_non_null(dup_buffer); + assert_int_equal(ssh_buffer_get_len(dup_buffer), 15); + assert_memory_equal(ssh_buffer_get(dup_buffer), "non-secure data", 15); + assert_false(dup_buffer->secure); + + SSH_BUFFER_FREE(dup_buffer); + SSH_BUFFER_FREE(null_buffer); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_growing_buffer, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_growing_buffer_shifting, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_buffer_prepend, + setup, + teardown), + cmocka_unit_test(torture_ssh_buffer_get_ssh_string), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_add_format, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_get_format, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_get_format_error, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_buffer_pack_badformat, + setup, + teardown), + cmocka_unit_test(torture_ssh_buffer_bignum), + cmocka_unit_test_setup_teardown(torture_ssh_buffer_dup, + setup, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_bytearray.c b/src/libs/libssh-0.12.2/tests/unittests/torture_bytearray.c new file mode 100644 index 000000000000..5bb862476717 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_bytearray.c @@ -0,0 +1,410 @@ +#include "config.h" + +#include + +#include "torture.h" +#include "libssh/bytearray.h" + +static void torture_pull_le_u8(void **state) +{ + uint8_t data[2] = {0}; + uint8_t result; + + (void)state; + + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 42); + + + data[0] = 0xf; + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 0xf); + + data[0] = 0xff; + result = PULL_LE_U8(data, 0); + assert_int_equal(result, 0xff); + + data[1] = 0x2a; + result = PULL_LE_U8(data, 1); + assert_int_equal(result, 42); +} + +static void torture_pull_le_u16(void **state) +{ + uint8_t data[2] = {0, 0}; + uint16_t result; + + (void)state; + + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + data[1] = 0x00; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 42); + + data[0] = 0xff; + data[1] = 0x00; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0x00; + data[1] = 0xff; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0xff; + data[1] = 0xff; + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 0xffff); +} + +static void torture_pull_le_u32(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint32_t result; + + (void)state; + + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 42); + + data[0] = 0xff; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0x00; + data[1] = 0xff; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0xff; + data[3] = 0x00; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xff0000); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0xff; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xff000000); + + data[0] = 0xff; + data[1] = 0xff; + data[2] = 0xff; + data[3] = 0xff; + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xffffffff); +} + +static void torture_push_le_u8(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {42, 42, 42, 42}; + + (void)state; + + PUSH_LE_U8(data, 0, 42); + PUSH_LE_U8(data, 1, 42); + PUSH_LE_U8(data, 2, 42); + PUSH_LE_U8(data, 3, 42); + assert_memory_equal(data, data2, sizeof(data)); +} + +static void torture_push_le_u16(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {0xa6, 0x7f, 0x2a, 0x00}; + uint16_t result; + + (void)state; + + PUSH_LE_U16(data, 0, 32678); + PUSH_LE_U16(data, 2, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_LE_U16(data, 2); + assert_int_equal(result, 42); + + result = PULL_LE_U16(data, 0); + assert_int_equal(result, 32678); +} + +static void torture_push_le_u32(void **state) +{ + uint8_t data[8] = {0}; + uint8_t data2[8] = {0xa6, 0x7f, 0x00, 0x00, 0x2a, 0x00, 0x00, 0x00}; + uint32_t result; + + (void)state; + + PUSH_LE_U32(data, 0, 32678); + PUSH_LE_U32(data, 4, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_LE_U32(data, 4); + assert_int_equal(result, 42); + + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 32678); + + PUSH_LE_U32(data, 0, 0xfffefffe); + result = PULL_LE_U32(data, 0); + assert_int_equal(result, 0xfffefffe); +} + +static void torture_push_le_u64(void **state) +{ + uint8_t data[16] = {0}; + uint64_t result; + + (void)state; + + PUSH_LE_U64(data, 0, 32678); + + result = PULL_LE_U64(data, 0); + assert_int_equal(result, 32678); + + PUSH_LE_U64(data, 0, 0xfffefffefffefffeUL); + + result = PULL_LE_U64(data, 0); + assert_int_equal(result, 0xfffefffefffefffeUL); +} + +/****************** BIG ENDIAN ********************/ + +static void torture_pull_be_u8(void **state) +{ + uint8_t data[2] = {0}; + uint8_t result; + + (void)state; + + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x2a; + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 42); + + + data[0] = 0xf; + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 0xf); + + data[0] = 0xff; + result = PULL_BE_U8(data, 0); + assert_int_equal(result, 0xff); + + data[1] = 0x2a; + result = PULL_BE_U8(data, 1); + assert_int_equal(result, 42); +} + +static void torture_pull_be_u16(void **state) +{ + uint8_t data[2] = {0, 0}; + uint16_t result; + + (void)state; + + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x00; + data[1] = 0x2a; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 42); + + data[0] = 0x00; + data[1] = 0xff; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0xff; + data[1] = 0x00; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0xff; + data[1] = 0xff; + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 0xffff); +} + +static void torture_pull_be_u32(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint32_t result; + + (void)state; + + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x2a; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 42); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0xff; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0x00ff); + + data[0] = 0x00; + data[1] = 0x00; + data[2] = 0xff; + data[3] = 0x00; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xff00); + + data[0] = 0x00; + data[1] = 0xff; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xff0000); + + data[0] = 0xff; + data[1] = 0x00; + data[2] = 0x00; + data[3] = 0x00; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xff000000); + + data[0] = 0xff; + data[1] = 0xff; + data[2] = 0xff; + data[3] = 0xff; + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xffffffff); +} + +static void torture_push_be_u8(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {42, 42, 42, 42}; + + (void)state; + + PUSH_BE_U8(data, 0, 42); + PUSH_BE_U8(data, 1, 42); + PUSH_BE_U8(data, 2, 42); + PUSH_BE_U8(data, 3, 42); + assert_memory_equal(data, data2, sizeof(data)); +} + +static void torture_push_be_u16(void **state) +{ + uint8_t data[4] = {0, 0, 0, 0}; + uint8_t data2[4] = {0x7f, 0xa6, 0x00, 0x2a}; + uint16_t result; + + (void)state; + + PUSH_BE_U16(data, 0, 32678); + PUSH_BE_U16(data, 2, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_BE_U16(data, 2); + assert_int_equal(result, 42); + + result = PULL_BE_U16(data, 0); + assert_int_equal(result, 32678); +} + +static void torture_push_be_u32(void **state) +{ + uint8_t data[8] = {0}; + uint8_t data2[8] = {0x00, 0x00, 0x7f, 0xa6, 0x00, 0x00, 0x00, 0x2a}; + uint32_t result; + + (void)state; + + PUSH_BE_U32(data, 0, 32678); + PUSH_BE_U32(data, 4, 42); + assert_memory_equal(data, data2, sizeof(data)); + + result = PULL_BE_U32(data, 4); + assert_int_equal(result, 42); + + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 32678); + + PUSH_BE_U32(data, 0, 0xfffefffe); + result = PULL_BE_U32(data, 0); + assert_int_equal(result, 0xfffefffe); +} + +static void torture_push_be_u64(void **state) +{ + uint8_t data[16] = {0}; + uint64_t result; + + (void)state; + + PUSH_BE_U64(data, 0, 32678); + + result = PULL_BE_U64(data, 0); + assert_int_equal(result, 32678); + + PUSH_LE_U64(data, 8, 0xfffefffe); + + result = PULL_LE_U64(data, 8); + assert_int_equal(result, 0xfffefffe); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pull_le_u8), + cmocka_unit_test(torture_pull_le_u16), + cmocka_unit_test(torture_pull_le_u32), + + cmocka_unit_test(torture_push_le_u8), + cmocka_unit_test(torture_push_le_u16), + cmocka_unit_test(torture_push_le_u32), + cmocka_unit_test(torture_push_le_u64), + + /* BIG ENDIAN */ + cmocka_unit_test(torture_pull_be_u8), + cmocka_unit_test(torture_pull_be_u16), + cmocka_unit_test(torture_pull_be_u32), + + cmocka_unit_test(torture_push_be_u8), + cmocka_unit_test(torture_push_be_u16), + cmocka_unit_test(torture_push_be_u32), + cmocka_unit_test(torture_push_be_u64), + }; + + torture_filter_tests(tests); + + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_callbacks.c b/src/libs/libssh-0.12.2/tests/unittests/torture_callbacks.c new file mode 100644 index 000000000000..ee8b03835350 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_callbacks.c @@ -0,0 +1,268 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include +#include +#include +#include + +static int myauthcallback (const char *prompt, char *buf, size_t len, + int echo, int verify, void *userdata) { + (void) prompt; + (void) buf; + (void) len; + (void) echo; + (void) verify; + (void) userdata; + return 0; +} + +static int setup(void **state) +{ + struct ssh_callbacks_struct *cb = NULL; + + cb = calloc(1, sizeof(struct ssh_callbacks_struct)); + assert_non_null(cb); + + cb->userdata = (void *) 0x0badc0de; + cb->auth_function = myauthcallback; + + ssh_callbacks_init(cb); + *state = cb; + + return 0; +} + +static int teardown(void **state) +{ + free(*state); + + return 0; +} + +static void torture_callbacks_size(void **state) { + struct ssh_callbacks_struct *cb = *state; + + assert_int_not_equal(cb->size, 0); +} + +static void torture_callbacks_exists(void **state) { + struct ssh_callbacks_struct *cb = *state; + + assert_int_not_equal(ssh_callbacks_exists(cb, auth_function), 0); + assert_int_equal(ssh_callbacks_exists(cb, log_function), 0); + + /* + * We redefine size so auth_function is outside the range of + * callbacks->size. + */ + cb->size = (unsigned char *) &cb->auth_function - (unsigned char *) cb; + assert_int_equal(ssh_callbacks_exists(cb, auth_function), 0); + + /* Now make it one pointer bigger so we spill over the auth_function slot */ + cb->size += sizeof(void *); + assert_int_not_equal(ssh_callbacks_exists(cb, auth_function), 0); +} + +struct test_mock_state { + int executed; +}; + +static void test_mock_ssh_logging_callback(int priority, + const char *function, + const char *buffer, + void *userdata) +{ + struct test_mock_state *t = (struct test_mock_state *)userdata; + + check_expected(priority); + check_expected(function); + check_expected(buffer); + + t->executed++; +} + +static void torture_log_callback(void **state) +{ + struct test_mock_state t = { + .executed = 0, + }; + + (void)state; /* unused */ + + ssh_set_log_callback(test_mock_ssh_logging_callback); + ssh_set_log_userdata(&t); + ssh_set_log_level(1); + + expect_value(test_mock_ssh_logging_callback, priority, 1); + expect_string(test_mock_ssh_logging_callback, function, "torture_log_callback"); + expect_string(test_mock_ssh_logging_callback, buffer, "torture_log_callback: test"); + + SSH_LOG(SSH_LOG_WARN, "test"); + + assert_int_equal(t.executed, 1); +} + +static void cb1(ssh_session session, ssh_channel channel, void *userdata){ + int *v = userdata; + (void) session; + (void) channel; + *v += 1; +} + +static void cb2(ssh_session session, ssh_channel channel, int status, void *userdata){ + int *v = userdata; + (void) session; + (void) channel; + (void) status; + *v += 10; +} + +static void torture_callbacks_execute_list(void **state){ + struct ssh_list *list = ssh_list_new(); + int v = 0, w = 0; + struct ssh_channel_callbacks_struct c1 = { + .channel_eof_function = cb1, + .userdata = &v + }; + struct ssh_channel_callbacks_struct c2 = { + .channel_exit_status_function = cb2, + .userdata = &v + }; + struct ssh_channel_callbacks_struct c3 = { + .channel_eof_function = cb1, + .channel_exit_status_function = cb2, + .userdata = &w + }; + + (void)state; + + assert_non_null(list); + + ssh_callbacks_init(&c1); + ssh_callbacks_init(&c2); + ssh_callbacks_init(&c3); + + ssh_list_append(list, &c1); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_eof_function, + NULL, + NULL); + assert_int_equal(v, 1); + + v = 0; + ssh_list_append(list, &c2); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_eof_function, + NULL, + NULL); + assert_int_equal(v, 1); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_exit_status_function, + NULL, + NULL, + 0); + assert_int_equal(v, 11); + + v = 0; + w = 0; + ssh_list_append(list, &c3); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_eof_function, + NULL, + NULL); + assert_int_equal(v, 1); + assert_int_equal(w, 1); + ssh_callbacks_execute_list(list, + ssh_channel_callbacks, + channel_exit_status_function, + NULL, + NULL, + 0); + assert_int_equal(v, 11); + assert_int_equal(w, 11); + + ssh_list_free(list); + +} + +static int cb3(ssh_session session, ssh_channel channel, void *userdata){ + int *v = userdata; + (void)session; + (void)channel; + *v = 1; + return 10; +} + +static void torture_callbacks_iterate(void **state){ + struct ssh_list *list = ssh_list_new(); + int v = 0, w = 0; + struct ssh_channel_callbacks_struct c1 = { + .channel_eof_function = cb1, + .channel_shell_request_function = cb3, + .userdata = &v + }; + struct ssh_channel_callbacks_struct c2 = { + .channel_eof_function = cb1, + .channel_shell_request_function = cb3, + .userdata = &v + }; + + (void)state; /* unused */ + + assert_non_null(list); + + ssh_callbacks_init(&c1); + ssh_callbacks_init(&c2); + + ssh_list_append(list, &c1); + ssh_list_append(list, &c2); + + ssh_callbacks_iterate(list, ssh_channel_callbacks, channel_eof_function){ + ssh_callbacks_iterate_exec(channel_eof_function, NULL, NULL); + } + ssh_callbacks_iterate_end(); + + assert_int_equal(v, 2); + + v = 0; + ssh_callbacks_iterate(list, ssh_channel_callbacks, channel_shell_request_function){ + w = ssh_callbacks_iterate_exec(channel_shell_request_function, NULL, NULL); + if (w) { + break; + } + } + ssh_callbacks_iterate_end(); + + assert_int_equal(w, 10); + assert_int_equal(v, 1); + + ssh_list_free(list); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_callbacks_size, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_callbacks_exists, + setup, + teardown), + cmocka_unit_test(torture_log_callback), + cmocka_unit_test(torture_callbacks_execute_list), + cmocka_unit_test(torture_callbacks_iterate), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_channel.c b/src/libs/libssh-0.12.2/tests/unittests/torture_channel.c new file mode 100644 index 000000000000..23defc0ffa6c --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_channel.c @@ -0,0 +1,198 @@ +#include "config.h" + +#define LIBSSH_STATIC +#include + +#include +#include +#include + +#include "torture.h" +#include "channels.c" + +#include + +static void torture_channel_select(void **state) +{ + fd_set readfds; + int fd; + int rc; + int i; + + (void)state; /* unused */ + + ZERO_STRUCT(readfds); + + fd = open("/dev/null", 0); + assert_true(fd > 2); + + FD_ZERO(&readfds); + FD_SET(fd, &readfds); + + for (i = 0; i < 10; i++) { + ssh_channel cin[1] = { NULL, }; + ssh_channel cout[1] = { NULL, }; + struct timeval tv = { .tv_sec = 0, .tv_usec = 1000 }; + + rc = ssh_select(cin, cout, fd + 1, &readfds, &tv); + assert_int_equal(rc, SSH_OK); + } + + close(fd); +} + +static void torture_channel_null_session(void **state) +{ + ssh_channel channel = NULL; + + (void)state; + + channel = calloc(1, sizeof(struct ssh_channel_struct)); + + assert_non_null(channel); + + channel->state = SSH_CHANNEL_STATE_OPEN; + channel->session = NULL; + + assert_int_equal(ssh_channel_is_open(channel), 0); + + free(channel); +} + +/* Feed a fabricated SSH2_MSG_CHANNEL_OPEN_CONFIRMATION with the given + * maximum packet size to a channel in OPENING state and return its + * resulting state. */ +static enum ssh_channel_state_e +channel_open_conf_maxpacket(uint32_t maxpacket) +{ + ssh_session session = NULL; + ssh_channel channel = NULL; + ssh_buffer packet = NULL; + enum ssh_channel_state_e result; + int rc; + + session = ssh_new(); + assert_non_null(session); + session->flags |= SSH_SESSION_FLAG_AUTHENTICATED; + + channel = ssh_channel_new(session); + assert_non_null(channel); + channel->local_channel = ssh_channel_new_id(session); + channel->state = SSH_CHANNEL_STATE_OPENING; + + packet = ssh_buffer_new(); + assert_non_null(packet); + rc = ssh_buffer_pack(packet, + "dddd", + channel->local_channel, + (uint32_t)42, /* sender channel */ + (uint32_t)64000, /* initial window size */ + maxpacket); + assert_int_equal(rc, SSH_OK); + + rc = ssh_packet_channel_open_conf(session, + SSH2_MSG_CHANNEL_OPEN_CONFIRMATION, + packet, + NULL); + assert_int_equal(rc, SSH_PACKET_USED); + + result = channel->state; + + SSH_BUFFER_FREE(packet); + ssh_free(session); + + return result; +} + +static void torture_channel_open_conf(void **state) +{ + (void)state; /* unused */ + + assert_int_equal(channel_open_conf_maxpacket(32768), + SSH_CHANNEL_STATE_OPEN); +} + +/* CVE-2026-59843: a maximum packet size of 0 in CHANNEL_OPEN_CONFIRMATION + * must not open the channel, as it would cause an infinite loop in + * channel_write_common(). */ +static void torture_channel_open_conf_zero_maxpacket(void **state) +{ + (void)state; /* unused */ + + assert_int_not_equal(channel_open_conf_maxpacket(0), + SSH_CHANNEL_STATE_OPEN); +} + +/* Feed a fabricated SSH2_MSG_CHANNEL_OPEN with the given maximum packet + * size to an unauthenticated session and return the resulting session + * error string via a static buffer. */ +static const char * +channel_open_maxpacket_error(uint32_t maxpacket) +{ + static char error[256]; + ssh_session session = NULL; + ssh_buffer packet = NULL; + int rc; + + session = ssh_new(); + assert_non_null(session); + + packet = ssh_buffer_new(); + assert_non_null(packet); + rc = ssh_buffer_pack(packet, + "sddd", + "session", + (uint32_t)42, /* sender channel */ + (uint32_t)64000, /* initial window size */ + maxpacket); + assert_int_equal(rc, SSH_OK); + + rc = ssh_packet_channel_open(session, + SSH2_MSG_CHANNEL_OPEN, + packet, + NULL); + assert_int_equal(rc, SSH_PACKET_USED); + + snprintf(error, sizeof(error), "%s", ssh_get_error(session)); + + SSH_BUFFER_FREE(packet); + ssh_free(session); + + return error; +} + +/* CVE-2026-59843: a maximum packet size of 0 in CHANNEL_OPEN must be + * rejected before any further processing. The control case with a valid + * size proceeds to the session state check, proving the zero case failed + * on the packet size specifically. */ +static void torture_channel_open_zero_maxpacket(void **state) +{ + (void)state; /* unused */ + + assert_string_equal( + channel_open_maxpacket_error(0), + "Invalid maximum packet size 0 in SSH2_MSG_CHANNEL_OPEN"); + + assert_string_equal( + channel_open_maxpacket_error(32768), + "Invalid state when receiving channel open request " + "(must be authenticated)"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_channel_select), + cmocka_unit_test(torture_channel_null_session), + cmocka_unit_test(torture_channel_open_conf), + cmocka_unit_test(torture_channel_open_conf_zero_maxpacket), + cmocka_unit_test(torture_channel_open_zero_maxpacket), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_config.c b/src/libs/libssh-0.12.2/tests/unittests/torture_config.c new file mode 100644 index 000000000000..e82aa5f5d496 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_config.c @@ -0,0 +1,3260 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#ifndef _WIN32 +#define _POSIX_PTHREAD_SEMANTICS +#include +#endif + +#include "torture.h" +#include "libssh/options.h" +#include "libssh/session.h" +#include "libssh/config_parser.h" +#include "match.c" +#include "config.c" +#include "libssh/socket.h" +#include "libssh/misc.h" + +extern LIBSSH_THREAD int ssh_log_level; + +#define USERNAME "testuser" +#define PROXYCMD "ssh -q -W %h:%p gateway.example.com" +#define ID_FILE "/etc/xxx" +#define KEXALGORITHMS "ecdh-sha2-nistp521,diffie-hellman-group16-sha512,diffie-hellman-group18-sha512,diffie-hellman-group14-sha1" +#define HOSTKEYALGORITHMS "ssh-ed25519,ecdsa-sha2-nistp521,ssh-rsa" +#define PUBKEYACCEPTEDTYPES "rsa-sha2-512,ssh-rsa,ecdsa-sha2-nistp521" +#define MACS "hmac-sha1,hmac-sha2-256,hmac-sha2-512,hmac-sha1-etm@openssh.com,hmac-sha2-256-etm@openssh.com,hmac-sha2-512-etm@openssh.com" +#define USER_KNOWN_HOSTS "%d/.ssh/my_known_hosts" +#define GLOBAL_KNOWN_HOSTS "/etc/ssh/my_ssh_known_hosts" +#define BIND_ADDRESS "::1" + + +#define LIBSSH_TESTCONFIG1 "libssh_testconfig1.tmp" +#define LIBSSH_TESTCONFIG2 "libssh_testconfig2.tmp" +#define LIBSSH_TESTCONFIG3 "libssh_testconfig3.tmp" +#define LIBSSH_TESTCONFIG4 "libssh_testconfig4.tmp" +#define LIBSSH_TESTCONFIG5 "libssh_testconfig5.tmp" +#define LIBSSH_TESTCONFIG6 "libssh_testconfig6.tmp" +#define LIBSSH_TESTCONFIG7 "libssh_testconfig7.tmp" +#define LIBSSH_TESTCONFIG8 "libssh_testconfig8.tmp" +#define LIBSSH_TESTCONFIG9 "libssh_testconfig9.tmp" +#define LIBSSH_TESTCONFIG10 "libssh_testconfig10.tmp" +#define LIBSSH_TESTCONFIG11 "libssh_testconfig11.tmp" +#define LIBSSH_TESTCONFIG12 "libssh_testconfig12.tmp" +#define LIBSSH_TESTCONFIG14 "libssh_testconfig14.tmp" +#define LIBSSH_TESTCONFIG15 "libssh_testconfig15.tmp" +#define LIBSSH_TESTCONFIG16 "libssh_testconfig16.tmp" +#define LIBSSH_TESTCONFIG17 "libssh_testconfig17.tmp" +#define LIBSSH_TESTCONFIG18 "libssh_testconfig18.tmp" +#define LIBSSH_TESTCONFIGGLOB "libssh_testc*[36].tmp" +#define LIBSSH_TEST_PUBKEYTYPES "libssh_test_PubkeyAcceptedKeyTypes.tmp" +#define LIBSSH_TEST_PUBKEYALGORITHMS "libssh_test_PubkeyAcceptedAlgorithms.tmp" +#define LIBSSH_TEST_NONEWLINEEND "libssh_test_NoNewLineEnd.tmp" +#define LIBSSH_TEST_NONEWLINEONELINE "libssh_test_NoNewLineOneline.tmp" +#define LIBSSH_TEST_RECURSIVE_INCLUDE "libssh_test_recursive_include.tmp" +#define LIBSSH_TESTCONFIG_MATCH_COMPLEX "libssh_test_match_complex.tmp" +#define LIBSSH_TESTCONFIG_LOGLEVEL_MISSING "libssh_test_loglevel_missing.tmp" +#define LIBSSH_TESTCONFIG_JUMP "libssh_test_jump.tmp" + +#define LIBSSH_TESTCONFIG_STRING1 \ + "User "USERNAME"\nInclude "LIBSSH_TESTCONFIG2"\n\n" + +#define LIBSSH_TESTCONFIG_STRING2 \ + "Include "LIBSSH_TESTCONFIG3"\n" \ + "ProxyCommand "PROXYCMD"\n\n" + +#define LIBSSH_TESTCONFIG_STRING3 \ + "\n\nIdentityFile "ID_FILE"\n" \ + "\n\nKexAlgorithms "KEXALGORITHMS"\n" \ + "\n\nHostKeyAlgorithms "HOSTKEYALGORITHMS"\n" \ + "\n\nPubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" \ + "\n\nMACs "MACS"\n" + +/* Multiple Port settings -> parsing returns early. */ +#define LIBSSH_TESTCONFIG_STRING4 \ + "Port 123\nPort 456\n" + +/* Testing glob include */ +#define LIBSSH_TESTCONFIG_STRING5 \ + "User "USERNAME"\nInclude "LIBSSH_TESTCONFIGGLOB"\n\n" \ + +#define LIBSSH_TESTCONFIG_STRING6 \ + "ProxyCommand "PROXYCMD"\n\n" + +/* new options */ +#define LIBSSH_TESTCONFIG_STRING7 \ + "\tBindAddress "BIND_ADDRESS"\n" \ + "\tConnectTimeout 30\n" \ + "\tLogLevel DEBUG3\n" \ + "\tGlobalKnownHostsFile "GLOBAL_KNOWN_HOSTS"\n" \ + "\tCompression yes\n" \ + "\tStrictHostkeyChecking no\n" \ + "\tGSSAPIDelegateCredentials yes\n" \ + "\tGSSAPIServerIdentity example.com\n" \ + "\tGSSAPIClientIdentity home.sweet\n" \ + "\tUserKnownHostsFile "USER_KNOWN_HOSTS"\n" \ + "\tRequiredRSASize 2233\n" \ + "\tGSSAPIKeyExchange yes\n" \ + "\tGSSAPIKexAlgorithms gss-group14-sha256-\n" + +/* authentication methods */ +#define LIBSSH_TESTCONFIG_STRING8 \ + "Host gss\n" \ + "\tGSSAPIAuthentication yes\n" \ + "Host kbd\n" \ + "\tKbdInteractiveAuthentication yes\n" \ + "Host pass\n" \ + "\tPasswordAuthentication yes\n" \ + "Host pubkey\n" \ + "\tPubkeyAuthentication yes\n" \ + "Host nogss\n" \ + "\tGSSAPIAuthentication no\n" \ + "Host nokbd\n" \ + "\tKbdInteractiveAuthentication no\n" \ + "Host nopass\n" \ + "\tPasswordAuthentication no\n" \ + "Host nopubkey\n" \ + "\tPubkeyAuthentication no\n" + +/* unsupported options and corner cases */ +#define LIBSSH_TESTCONFIG_STRING9 \ + "\n" /* empty line */ \ + "# comment line\n" \ + " # comment line not starting with hash\n" \ + "UnknownConfigurationOption yes\n" \ + "GSSAPIKexAlgorithms yes\n" \ + "ControlMaster auto\n" /* SOC_NA */ \ + "VisualHostkey yes\n" /* SOC_UNSUPPORTED */ \ + "HostName =equal.sign\n" /* valid */ \ + "ProxyJump = many-spaces.com\n" /* valid */ + +/* Match keyword */ +#define LIBSSH_TESTCONFIG_STRING10 \ + "Match host example\n" \ + "\tHostName example.com\n" \ + "Match host example1,example2\n" \ + "\tHostName exampleN\n" \ + "Match user guest\n" \ + "\tHostName guest.com\n" \ + "Match user tester host testhost\n" \ + "\tHostName testhost.com\n" \ + "Match !user tester host testhost\n" \ + "\tHostName nonuser-testhost.com\n" \ + "Match all\n" \ + "\tHostName all-matched.com\n" \ + /* Unsupported options */ \ + "Match originalhost example\n" \ + "\tHostName original-example.com\n" \ + "Match localuser guest\n" \ + "\tHostName local-guest.com\n" + +/* ProxyJump */ +#define LIBSSH_TESTCONFIG_STRING11 \ + "Host simple\n" \ + "\tProxyJump jumpbox\n" \ + "Host user\n" \ + "\tProxyJump user@jumpbox\n" \ + "Host port\n" \ + "\tProxyJump jumpbox:2222\n" \ + "Host two-step\n" \ + "\tProxyJump u1@first:222,u2@second:33\n" \ + "Host three-step\n" \ + "\tProxyJump u1@first:222,u2@second:33,u3@third:444\n" \ + "Host none\n" \ + "\tProxyJump none\n" \ + "Host only-command\n" \ + "\tProxyCommand "PROXYCMD"\n" \ + "\tProxyJump jumpbox\n" \ + "Host only-jump\n" \ + "\tProxyJump jumpbox\n" \ + "\tProxyCommand "PROXYCMD"\n" \ + "Host ipv6\n" \ + "\tProxyJump [2620:52:0::fed]\n" + +/* RekeyLimit combinations */ +#define LIBSSH_TESTCONFIG_STRING12 \ + "Host default\n" \ + "\tRekeyLimit default none\n" \ + "Host data1\n" \ + "\tRekeyLimit 42G\n" \ + "Host data2\n" \ + "\tRekeyLimit 31M\n" \ + "Host data3\n" \ + "\tRekeyLimit 521K\n" \ + "Host time1\n" \ + "\tRekeyLimit default 3D\n" \ + "Host time2\n" \ + "\tRekeyLimit default 2h\n" \ + "Host time3\n" \ + "\tRekeyLimit default 160m\n" \ + "Host time4\n" \ + "\tRekeyLimit default 9600\n" + +/* Multiple IdentityFile settings all are applied */ +#define LIBSSH_TESTCONFIG_STRING13 \ + "IdentityFile id_rsa_one\n" \ + "CertificateFile id_rsa_one-cert.pub\n" \ + "IdentityFile id_ecdsa_two\n" \ + "CertificateFile id_ecdsa_two-cert.pub\n" \ + +/* +,-,^ features for all supported list */ +/* kex won't work in fips */ +#define LIBSSH_TESTCONFIG_STRING14 \ + "HostKeyAlgorithms +ssh-rsa\n" \ + "Ciphers +aes128-cbc,aes256-cbc\n" \ + "KexAlgorithms +diffie-hellman-group14-sha1,diffie-hellman-group1-sha1\n" \ + "MACs +hmac-sha1,hmac-sha1-etm@openssh.com\n" + +/* have to be algorithms which are in the default list */ +#define LIBSSH_TESTCONFIG_STRING15 \ + "HostKeyAlgorithms -rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers -aes256-ctr\n" \ + "KexAlgorithms -diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs -hmac-sha2-256-etm@openssh.com\n" + +#define LIBSSH_TESTCONFIG_STRING16 \ + "HostKeyAlgorithms ^rsa-sha2-512,rsa-sha2-256\n" \ + "Ciphers ^aes256-cbc\n" \ + "KexAlgorithms ^diffie-hellman-group18-sha512,diffie-hellman-group16-sha512\n" \ + "MACs ^hmac-sha1\n" + +/* Connection Multiplexing */ +#define LIBSSH_TESTCONFIG_STRING17 \ + "Host simple\n" \ + "\tControlMaster auto\n" \ + "\tControlPath /tmp/ssh-%r@%h:%p\n" \ + "Host none\n" \ + "\tControlMaster yes\n" \ + "\tControlPath none\n" + +#define LIBSSH_TESTCONFIG_STRING18 \ + "Host simple\n" \ + "Host af\n" \ + "\tAddressFamily any\n" \ + "Host af4\n" \ + "\tAddressFamily inet\n" \ + "Host af6\n" \ + "\tAddressFamily inet6\n" + +#define LIBSSH_TEST_PUBKEYTYPES_STRING \ + "PubkeyAcceptedKeyTypes "PUBKEYACCEPTEDTYPES"\n" + +#define LIBSSH_TEST_PUBKEYALGORITHMS_STRING \ + "PubkeyAcceptedAlgorithms "PUBKEYACCEPTEDTYPES"\n" + +#define LIBSSH_TEST_NONEWLINEEND_STRING \ + "ConnectTimeout 30\n" \ + "LogLevel DEBUG3" + +#define LIBSSH_TEST_NONEWLINEONELINE_STRING \ + "ConnectTimeout 30" + +#define LIBSSH_TEST_RECURSIVE_INCLUDE_STRING \ + "Include " LIBSSH_TEST_RECURSIVE_INCLUDE + +/* Complex match cases */ +#define LIBSSH_TESTCONFIG_MATCH_COMPLEX_STRING \ + "Match originalhost \"Foo,Bar\" exec \"[ \\\"$(ps h o comm p $(ps h o ppid p $PPID))\\\" != \\\"rsync\\\" ]\"\n" \ + "Match exec \"[ \\\"$(ps h o comm p $(ps h o ppid p $PPID))\\\" != \\\"rsync\\\" ]\"\n" \ + "\tForwardAgent yes\n" \ + "\tHostName complex-match\n" + +#define LIBSSH_TESTCONFIG_LOGLEVEL_MISSING_STRING "LogLevel\n" +#define LIBSSH_TESTCONFIG_JUMP_STRING \ + "# The jump host\n" \ + "Host ub-jumphost\n" \ + " HostName 1xxxxxx\n" \ + " User ubuntu\n" \ + " IdentityFile ~/of/temp-libssh.pem\n" \ + " Port 23\n" \ + " LogLevel DEBUG3\n" \ + "\n" \ + "# Cisco Router through Jump Host\n" \ + "Host cisco-router\n" \ + " HostName xx.xxxxxxxxx\n" \ + " User username\n" \ + " ProxyJump ub-jumphost\n" \ + " Port 5555\n" \ + " #RequiredRSASize 512\n" \ + " PasswordAuthentication yes\n" \ + " LogLevel DEBUG3\n" + +/** + * @brief helper function loading configuration from either file or string + */ +static void _parse_config(ssh_session session, + const char *file, const char *string, int expected) +{ + int ret = -1; + + /* make sure either config file or config string is given, + * not both */ + assert_int_not_equal(file == NULL, string == NULL); + + if (file != NULL) { + ret = ssh_config_parse_file(session, file); + } else if (string != NULL) { + ret = ssh_config_parse_string(session, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_ssh_return_code_equal(session, ret, expected); +} + +static int setup_config_files(void **state) +{ + (void) state; /* unused */ + + unlink(LIBSSH_TESTCONFIG1); + unlink(LIBSSH_TESTCONFIG2); + unlink(LIBSSH_TESTCONFIG3); + unlink(LIBSSH_TESTCONFIG4); + unlink(LIBSSH_TESTCONFIG5); + unlink(LIBSSH_TESTCONFIG6); + unlink(LIBSSH_TESTCONFIG7); + unlink(LIBSSH_TESTCONFIG8); + unlink(LIBSSH_TESTCONFIG9); + unlink(LIBSSH_TESTCONFIG10); + unlink(LIBSSH_TESTCONFIG11); + unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); + unlink(LIBSSH_TESTCONFIG17); + unlink(LIBSSH_TESTCONFIG18); + unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); + unlink(LIBSSH_TEST_NONEWLINEEND); + unlink(LIBSSH_TEST_NONEWLINEONELINE); + unlink(LIBSSH_TESTCONFIG_MATCH_COMPLEX); + unlink(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING); + unlink(LIBSSH_TESTCONFIG_JUMP); + + torture_write_file(LIBSSH_TESTCONFIG1, + LIBSSH_TESTCONFIG_STRING1); + torture_write_file(LIBSSH_TESTCONFIG2, + LIBSSH_TESTCONFIG_STRING2); + torture_write_file(LIBSSH_TESTCONFIG3, + LIBSSH_TESTCONFIG_STRING3); + + /* Multiple Port settings -> parsing returns early. */ + torture_write_file(LIBSSH_TESTCONFIG4, + LIBSSH_TESTCONFIG_STRING4); + + /* Testing glob include */ + torture_write_file(LIBSSH_TESTCONFIG5, + LIBSSH_TESTCONFIG_STRING5); + + torture_write_file(LIBSSH_TESTCONFIG6, + LIBSSH_TESTCONFIG_STRING6); + + /* new options */ + torture_write_file(LIBSSH_TESTCONFIG7, + LIBSSH_TESTCONFIG_STRING7); + + /* authentication methods */ + torture_write_file(LIBSSH_TESTCONFIG8, + LIBSSH_TESTCONFIG_STRING8); + + /* unsupported options and corner cases */ + torture_write_file(LIBSSH_TESTCONFIG9, + LIBSSH_TESTCONFIG_STRING9); + + /* Match keyword */ + torture_write_file(LIBSSH_TESTCONFIG10, + LIBSSH_TESTCONFIG_STRING10); + + /* ProxyJump */ + torture_write_file(LIBSSH_TESTCONFIG11, + LIBSSH_TESTCONFIG_STRING11); + + /* RekeyLimit combinations */ + torture_write_file(LIBSSH_TESTCONFIG12, + LIBSSH_TESTCONFIG_STRING12); + + /* +,-,^ feature */ + torture_write_file(LIBSSH_TESTCONFIG14, + LIBSSH_TESTCONFIG_STRING14); + torture_write_file(LIBSSH_TESTCONFIG15, + LIBSSH_TESTCONFIG_STRING15); + torture_write_file(LIBSSH_TESTCONFIG16, + LIBSSH_TESTCONFIG_STRING16); + torture_write_file(LIBSSH_TESTCONFIG17, + LIBSSH_TESTCONFIG_STRING17); + torture_write_file(LIBSSH_TESTCONFIG18, + LIBSSH_TESTCONFIG_STRING18); + + torture_write_file(LIBSSH_TEST_PUBKEYTYPES, + LIBSSH_TEST_PUBKEYTYPES_STRING); + + torture_write_file(LIBSSH_TEST_PUBKEYALGORITHMS, + LIBSSH_TEST_PUBKEYALGORITHMS_STRING); + + torture_write_file(LIBSSH_TEST_NONEWLINEEND, + LIBSSH_TEST_NONEWLINEEND_STRING); + + torture_write_file(LIBSSH_TEST_NONEWLINEONELINE, + LIBSSH_TEST_NONEWLINEONELINE_STRING); + + /* Match complex combinations */ + torture_write_file(LIBSSH_TESTCONFIG_MATCH_COMPLEX, + LIBSSH_TESTCONFIG_MATCH_COMPLEX_STRING); + torture_write_file(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING, + LIBSSH_TESTCONFIG_LOGLEVEL_MISSING_STRING); + torture_write_file(LIBSSH_TESTCONFIG_JUMP, + LIBSSH_TESTCONFIG_JUMP_STRING); + + return 0; +} + +static int teardown_config_files(void **state) +{ + (void) state; /* unused */ + + unlink(LIBSSH_TESTCONFIG1); + unlink(LIBSSH_TESTCONFIG2); + unlink(LIBSSH_TESTCONFIG3); + unlink(LIBSSH_TESTCONFIG4); + unlink(LIBSSH_TESTCONFIG5); + unlink(LIBSSH_TESTCONFIG6); + unlink(LIBSSH_TESTCONFIG7); + unlink(LIBSSH_TESTCONFIG8); + unlink(LIBSSH_TESTCONFIG9); + unlink(LIBSSH_TESTCONFIG10); + unlink(LIBSSH_TESTCONFIG11); + unlink(LIBSSH_TESTCONFIG12); + unlink(LIBSSH_TESTCONFIG14); + unlink(LIBSSH_TESTCONFIG15); + unlink(LIBSSH_TESTCONFIG16); + unlink(LIBSSH_TESTCONFIG17); + unlink(LIBSSH_TESTCONFIG18); + unlink(LIBSSH_TEST_PUBKEYTYPES); + unlink(LIBSSH_TEST_PUBKEYALGORITHMS); + unlink(LIBSSH_TEST_NONEWLINEEND); + unlink(LIBSSH_TEST_NONEWLINEONELINE); + unlink(LIBSSH_TESTCONFIG_MATCH_COMPLEX); + unlink(LIBSSH_TESTCONFIG_LOGLEVEL_MISSING); + unlink(LIBSSH_TESTCONFIG_JUMP); + + return 0; +} + +static int setup(void **state) +{ + ssh_session session = NULL; + char *wd = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + wd = torture_get_current_working_dir(); + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, wd); + free(wd); + + *state = session; + + return 0; +} + +static int setup_no_sshdir(void **state) +{ + ssh_session session = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +/** + * @brief tests ssh config parsing with Include directives + */ +static void torture_config_include(void **state, + const char *file, const char *string) +{ + int ret; + char *v = NULL; + char *fips_algos = NULL; + ssh_session session = *state; + + _parse_config(session, file, string, SSH_OK); + + /* Test the variable presence */ + ret = ssh_options_get(session, SSH_OPTIONS_PROXYCOMMAND, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, PROXYCMD); + SSH_STRING_FREE_CHAR(v); + + ret = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, ID_FILE); + SSH_STRING_FREE_CHAR(v); + + ret = ssh_options_get(session, SSH_OPTIONS_USER, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, USERNAME); + SSH_STRING_FREE_CHAR(v); + + if (ssh_fips_mode()) { + fips_algos = ssh_keep_fips_algos(SSH_KEX, KEXALGORITHMS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, HOSTKEYALGORITHMS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); + assert_non_null(fips_algos); + assert_string_equal(session->opts.pubkey_accepted_types, fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_MAC_C_S, MACS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], + fips_algos); + SAFE_FREE(fips_algos); + fips_algos = ssh_keep_fips_algos(SSH_MAC_S_C, MACS); + assert_non_null(fips_algos); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + fips_algos); + SAFE_FREE(fips_algos); + } else { + assert_non_null(session->opts.wanted_methods[SSH_KEX]); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + KEXALGORITHMS); + assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + HOSTKEYALGORITHMS); + assert_non_null(session->opts.pubkey_accepted_types); + assert_string_equal(session->opts.pubkey_accepted_types, + PUBKEYACCEPTEDTYPES); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], MACS); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], MACS); + } +} + +/** + * @brief tests ssh_config_parse_file with Include directives from file + */ +static void torture_config_include_file(void **state) +{ + torture_config_include(state, LIBSSH_TESTCONFIG1, NULL); +} + +/** + * @brief tests ssh_config_parse_string with Include directives from string + */ +static void torture_config_include_string(void **state) +{ + torture_config_include(state, NULL, LIBSSH_TESTCONFIG_STRING1); +} + +/** + * @brief tests ssh_config_parse_file with recursive Include directives from file + */ +static void torture_config_include_recursive_file(void **state) +{ + _parse_config(*state, LIBSSH_TEST_RECURSIVE_INCLUDE, NULL, SSH_OK); +} + +/** + * @brief tests ssh_config_parse_string with Include directives from string + */ +static void torture_config_include_recursive_string(void **state) +{ + _parse_config(*state, NULL, LIBSSH_TEST_RECURSIVE_INCLUDE_STRING, SSH_OK); +} + +/** + * @brief tests ssh_config_parse_file with multiple Port settings. + */ +static void torture_config_double_ports_file(void **state) +{ + _parse_config(*state, LIBSSH_TESTCONFIG4, NULL, SSH_OK); +} + +/** + * @brief tests ssh_config_parse_string with multiple Port settings. + */ +static void torture_config_double_ports_string(void **state) +{ + _parse_config(*state, NULL, LIBSSH_TESTCONFIG_STRING4, SSH_OK); +} + +static void torture_config_glob(void **state, + const char *file, const char *string) +{ +#if defined(HAVE_GLOB) && defined(HAVE_GLOB_GL_FLAGS_MEMBER) + int ret; + char *v; + ssh_session session = *state; + + _parse_config(session, file, string, SSH_OK); + + /* Test the variable presence */ + + ret = ssh_options_get(session, SSH_OPTIONS_PROXYCOMMAND, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, PROXYCMD); + SSH_STRING_FREE_CHAR(v); + + ret = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &v); + assert_true(ret == 0); + assert_non_null(v); + + assert_string_equal(v, ID_FILE); + SSH_STRING_FREE_CHAR(v); +#endif /* HAVE_GLOB && HAVE_GLOB_GL_FLAGS_MEMBER */ +} + +static void torture_config_glob_file(void **state) +{ + torture_config_glob(state, LIBSSH_TESTCONFIG5, NULL); +} + +static void torture_config_glob_string(void **state) +{ + torture_config_glob(state, NULL, LIBSSH_TESTCONFIG_STRING5); +} + +/** + * @brief Verify the new options are passed from configuration + */ +static void torture_config_new(void ** state, + const char *file, const char *string) +{ + ssh_session session = *state; + + _parse_config(session, file, string, SSH_OK); + + assert_string_equal(session->opts.knownhosts, USER_KNOWN_HOSTS); + assert_string_equal(session->opts.global_knownhosts, GLOBAL_KNOWN_HOSTS); + assert_int_equal(session->opts.timeout, 30); + assert_string_equal(session->opts.bindaddr, BIND_ADDRESS); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); +#endif /* WITH_ZLIB */ + assert_int_equal(session->opts.StrictHostKeyChecking, 0); + assert_int_equal(session->opts.gss_delegate_creds, 1); + assert_string_equal(session->opts.gss_server_identity, "example.com"); + assert_string_equal(session->opts.gss_client_identity, "home.sweet"); +#ifdef WITH_GSSAPI + assert_true(session->opts.gssapi_key_exchange); + assert_string_equal(session->opts.gssapi_key_exchange_algs, + "gss-group14-sha256-"); +#endif /* WITH_GSSAPI */ + + assert_int_equal(ssh_get_log_level(), SSH_LOG_TRACE); + assert_int_equal(session->common.log_verbosity, SSH_LOG_TRACE); + assert_int_equal(session->opts.rsa_min_size, 2233); +} + +static void torture_config_new_file(void **state) +{ + torture_config_new(state, LIBSSH_TESTCONFIG7, NULL); +} + +static void torture_config_new_string(void **state) +{ + torture_config_new(state, NULL, LIBSSH_TESTCONFIG_STRING7); +} + +/** + * @brief Verify the authentication methods from configuration are effective + */ +static void torture_config_auth_methods(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + /* gradually disable all the methods based on different hosts */ + ssh_options_set(session, SSH_OPTIONS_HOST, "nogss"); + _parse_config(session, file, string, SSH_OK); + assert_false(session->opts.flags & SSH_OPT_FLAG_GSSAPI_AUTH); + assert_true(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "nokbd"); + _parse_config(session, file, string, SSH_OK); + assert_false(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "nopass"); + _parse_config(session, file, string, SSH_OK); + assert_false(session->opts.flags & SSH_OPT_FLAG_PASSWORD_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "nopubkey"); + _parse_config(session, file, string, SSH_OK); + assert_false(session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH); + + /* no method should be left enabled */ + assert_int_equal(session->opts.flags, 0); + + /* gradually enable them again */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "gss"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.flags & SSH_OPT_FLAG_GSSAPI_AUTH); + assert_false(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "kbd"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.flags & SSH_OPT_FLAG_KBDINT_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "pass"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.flags & SSH_OPT_FLAG_PASSWORD_AUTH); + + ssh_options_set(session, SSH_OPTIONS_HOST, "pubkey"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.flags & SSH_OPT_FLAG_PUBKEY_AUTH); +} + +/** + * @brief Verify the authentication methods from configuration file + * are effective + */ +static void torture_config_auth_methods_file(void **state) +{ + torture_config_auth_methods(state, LIBSSH_TESTCONFIG8, NULL); +} + +/** + * @brief Verify the authentication methods from configuration string + * are effective + */ +static void torture_config_auth_methods_string(void **state) +{ + torture_config_auth_methods(state, NULL, LIBSSH_TESTCONFIG_STRING8); +} + +/** + * @brief Helper for checking hostname, username and port of ssh_jump_info_struct + */ +static void +helper_proxy_jump_check(struct ssh_iterator *jump, + const char *hostname, + const char *username, + const char *port) +{ + struct ssh_jump_info_struct *jis = + ssh_iterator_value(struct ssh_jump_info_struct *, jump); + + assert_string_equal(jis->hostname, hostname); + + if (username != NULL) { + assert_string_equal(jis->username, username); + } else { + assert_null(jis->username); + } + + if (port != NULL) { + int iport = strtol(port, NULL, 10); + assert_int_equal(jis->port, iport); + } else { + /* No port in the ProxyJump spec: left unset for the jump host's own + * configuration to supply. */ + assert_int_equal(jis->port, 0); + } +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options + */ +static void torture_config_unknown(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + int ret = 0; + + /* test corner cases */ + /* Without libssh proxy jump */ + torture_setenv("OPENSSH_PROXYJUMP", "1"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -W '[%h]:%p' many-spaces.com"); + assert_string_equal(session->opts.host, "equal.sign"); + + ret = ssh_config_parse_file(session, "/etc/ssh/ssh_config"); + assert_true(ret == 0); + ret = ssh_config_parse_file(session, GLOBAL_CLIENT_CONFIG); + assert_true(ret == 0); + torture_unsetenv("OPENSSH_PROXYJUMP"); +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options in configuration file + */ +static void torture_config_unknown_file(void **state) +{ + torture_config_unknown(state, LIBSSH_TESTCONFIG9, NULL); +} + +/** + * @brief Verify the configuration parser does not choke on unknown + * or unsupported configuration options in configuration string + */ +static void torture_config_unknown_string(void **state) +{ + torture_config_unknown(state, NULL, LIBSSH_TESTCONFIG_STRING9); +} + +/** + * @brief Verify the configuration parser accepts Match keyword with + * full OpenSSH syntax. + */ +static void torture_config_match(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + char *localuser = NULL; + const char *config = NULL; + char config_string[1024]; + + /* Without any settings we should get all-matched.com hostname */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "all-matched.com"); + + /* Hostname example does simple hostname matching */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "example.com"); + + /* We can match also both hosts from a comma separated list */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example1"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "exampleN"); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example2"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "exampleN"); + + /* We can match by user */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_USER, "guest"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "guest.com"); + + /* We can combine two options on a single line to match both of them */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_USER, "tester"); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "testhost.com"); + + /* We can also negate conditions */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_USER, "not-tester"); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "nonuser-testhost.com"); + + /* In this part, we try various other config files and strings. */ + + /* Match final is not completely supported, but should do quite much the + * same as "match all". The trailing "all" is not mandatory. */ + config = "Match final all\n" + "\tHostName final-all.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "final-all.com"); + + config = "Match final\n" + "\tHostName final.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "final.com"); + + /* Match canonical is not completely supported, but should do quite + * much the same as "match all". The trailing "all" is not mandatory. */ + config = "Match canonical all\n" + "\tHostName canonical-all.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "canonical-all.com"); + + config = "Match canonical all\n" + "\tHostName canonical.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "canonical.com"); + + localuser = ssh_get_local_username(); + assert_non_null(localuser); + snprintf(config_string, sizeof(config_string), + "Match localuser %s\n" + "\tHostName otherhost\n", + localuser); + config = config_string; + free(localuser); + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "otherhost"); + + config = "Match exec true\n" + "\tHostName execed-true.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_string_equal(session->opts.host, "otherhost"); +#else + assert_string_equal(session->opts.host, "execed-true.com"); +#endif + + config = "Match !exec false\n" + "\tHostName execed-false.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_string_equal(session->opts.host, "otherhost"); +#else + assert_string_equal(session->opts.host, "execed-false.com"); +#endif + + config = "Match exec \"test 1 -eq 1\"\n" + "\tHostName execed-arguments.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_string_equal(session->opts.host, "otherhost"); +#else + assert_string_equal(session->opts.host, "execed-arguments.com"); +#endif + + /* Try to create some invalid configurations */ + /* Missing argument to Match*/ + config = "Match\n" + "\tHost missing.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "unmatched"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "unmatched"); + + /* Missing argument to unsupported option originalhost */ + config = "Match originalhost\n" + "\tHost originalhost.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing argument to option localuser */ + config = "Match localuser\n" + "\tUser localuser2\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing argument to option user */ + config = "Match user\n" + "\tUser user2\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing argument to option host */ + config = "Match host\n" + "\tUser host2\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing argument to option exec */ + config = "Match exec\n" + "\tUser exec\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_ERROR); + + /* Unknown argument to Match keyword */ + config = "Match tagged tag_name\n" + "\tHostName never-matched.com\n" + "Match all\n" + "\tHostName config-host.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "config-host.com"); + + /* Missing argument to Match keyword */ + config = "Match\n" + "\tHostName never-matched.com\n" + "Match all\n" + "\tHostName config-host.com\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "example.com"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "config-host.com"); +} + +/** + * @brief Verify the configuration parser accepts Match keyword with + * full OpenSSH syntax through configuration file. + */ +static void torture_config_match_file(void **state) +{ + torture_config_match(state, LIBSSH_TESTCONFIG10, NULL); +} + +/** + * @brief Verify the configuration parser accepts Match keyword with + * full OpenSSH syntax through configuration string. + */ +static void torture_config_match_string(void **state) +{ + torture_config_match(state, NULL, LIBSSH_TESTCONFIG_STRING10); +} + +/** + * @brief Verify we can parse ProxyJump configuration option + */ +static void torture_config_proxyjump(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + const char *config = NULL; + + + /* Tests for libssh based proxyjump */ + /* Simplest version with just a hostname */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + NULL, + NULL); + + /* With username */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "user"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + "user", + NULL); + + /* With port */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "port"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + NULL, + "2222"); + + /* Two step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "second", + "u2", + "33"); + helper_proxy_jump_check(session->opts.proxy_jumps->root->next, + "first", + "u1", + "222"); + + /* Three step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "three-step"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "third", + "u3", + "444"); + helper_proxy_jump_check(session->opts.proxy_jumps->root->next, + "second", + "u2", + "33"); + helper_proxy_jump_check(session->opts.proxy_jumps->root->next->next, + "first", + "u1", + "222"); + + /* none */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 0); + + /* If also ProxyCommand is specified, the first is applied */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-command"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, PROXYCMD); + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 0); + + /* If also ProxyCommand is specified, the first is applied */ + torture_reset_config(session); + SAFE_FREE(session->opts.ProxyCommand); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); + _parse_config(session, file, string, SSH_OK); + assert_null(session->opts.ProxyCommand); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + NULL, + NULL); + + /* IPv6 address */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "2620:52:0::fed", + NULL, + NULL); + + torture_reset_config(session); + + /* Tests for proxycommand based proxyjump */ + torture_setenv("OPENSSH_PROXYJUMP", "1"); + + /* Simplest version with just a hostname */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, "ssh -W '[%h]:%p' jumpbox"); + + /* With username */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "user"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l user -W '[%h]:%p' jumpbox"); + + /* With port */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "port"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -p 2222 -W '[%h]:%p' jumpbox"); + + /* Two step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "two-step"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l u1 -p 222 -J u2@second:33 -W '[%h]:%p' first"); + + /* Three step jump */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "three-step"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l u1 -p 222 -J u2@second:33,u3@third:444 -W '[%h]:%p' first"); + + /* none */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_true(session->opts.ProxyCommand == NULL); + + /* If also ProxyCommand is specified, the first is applied */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-command"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, PROXYCMD); + + /* If also ProxyCommand is specified, the first is applied */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "only-jump"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -W '[%h]:%p' jumpbox"); + + /* IPv6 address */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "ipv6"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -W '[%h]:%p' 2620:52:0::fed"); + + + /* Multiple @ is allowed in second jump */ + config = "Host allowed-hostname\n" + "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -J user@principal.com@jumpbox:22 -W '[%h]:%p' localhost"); + + /* Multiple @ is allowed */ + config = "Host allowed-hostname\n" + "\tProxyJump user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.ProxyCommand, + "ssh -l user@principal.com -p 22 -W '[%h]:%p' jumpbox"); + torture_unsetenv("OPENSSH_PROXYJUMP"); + + /* Tests for libssh based proxyjump */ + /* Multiple @ is allowed in second jump */ + config = "Host allowed-hostname\n" + "\tProxyJump localhost,user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + "user@principal.com", + "22"); + + /* Multiple @ is allowed */ + config = "Host allowed-hostname\n" + "\tProxyJump user@principal.com@jumpbox:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "allowed-hostname"); + _parse_config(session, file, string, SSH_OK); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "jumpbox", + "user@principal.com", + "22"); + torture_reset_config(session); + + /* In this part, we try various other config files and strings. */ + torture_setenv("OPENSSH_PROXYJUMP", "1"); + + /* Try to create some invalid configurations */ + /* Non-numeric port */ + config = "Host bad-port\n" + "\tProxyJump jumpbox:22bad22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port"); + _parse_config(session, file, string, SSH_ERROR); + + /* Braces mismatch in hostname */ + config = "Host mismatch\n" + "\tProxyJump [::1\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "mismatch"); + _parse_config(session, file, string, SSH_ERROR); + + /* Bad host-port separator */ + config = "Host beef\n" + "\tProxyJump [dead::beef]::22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "beef"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing hostname */ + config = "Host no-host\n" + "\tProxyJump user@:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-host"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing user */ + config = "Host no-user\n" + "\tProxyJump @host:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-user"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing port */ + config = "Host no-port\n" + "\tProxyJump host:\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-port"); + _parse_config(session, file, string, SSH_ERROR); + + /* Non-numeric port in second jump */ + config = "Host bad-port-2\n" + "\tProxyJump localhost,jumpbox:22bad22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "bad-port-2"); + _parse_config(session, file, string, SSH_ERROR); + + /* Braces mismatch in second jump */ + config = "Host mismatch\n" + "\tProxyJump localhost,[::1:20\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "mismatch"); + _parse_config(session, file, string, SSH_ERROR); + + /* Bad host-port separator in second jump */ + config = "Host beef\n" + "\tProxyJump localhost,[dead::beef]::22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "beef"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing hostname in second jump */ + config = "Host no-host\n" + "\tProxyJump localhost,user@:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-host"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing user in second jump */ + config = "Host no-user\n" + "\tProxyJump localhost,@host:22\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-user"); + _parse_config(session, file, string, SSH_ERROR); + + /* Missing port in second jump */ + config = "Host no-port\n" + "\tProxyJump localhost,host:\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "no-port"); + _parse_config(session, file, string, SSH_ERROR); + + torture_unsetenv("OPENSSH_PROXYJUMP"); +} + +/** + * @brief Verify we can parse ProxyJump configuration option from file + */ +static void torture_config_proxyjump_file(void **state) +{ + torture_config_proxyjump(state, LIBSSH_TESTCONFIG11, NULL); +} + +/** + * @brief Verify we can parse ProxyJump configuration option from string + */ +static void torture_config_proxyjump_string(void **state) +{ + torture_config_proxyjump(state, NULL, LIBSSH_TESTCONFIG_STRING11); +} + +/** + * @brief Verify we can parse ControlPath configuration option + */ +static void torture_config_control_path(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_null(session->opts.control_path); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_null(session->opts.control_path); +} + +/** + * @brief Verify we can parse ControlPath configuration option from string + */ +static void torture_config_control_path_string(void **state) +{ + torture_config_control_path(state, NULL, LIBSSH_TESTCONFIG_STRING17); +} + +/** + * @brief Verify we can parse ControlPath configuration option from file + */ +static void torture_config_control_path_file(void **state) +{ + torture_config_control_path(state, LIBSSH_TESTCONFIG17, NULL); +} + +/** + * @brief Verify we can parse ControlMaster configuration option + */ +static void torture_config_control_master(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_NO); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "none"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_NO); +} + +/** + * @brief Verify we can parse ControlMaster configuration option from string + */ +static void torture_config_control_master_string(void **state) +{ + torture_config_control_master(state, NULL, LIBSSH_TESTCONFIG_STRING17); +} + +/** + * @brief Verify we can parse ControlMaster configuration option from file + */ +static void torture_config_control_master_file(void **state) +{ + torture_config_control_master(state, LIBSSH_TESTCONFIG17, NULL); +} + +/** + * @brief Verify we can parse AdressFamily configuration option + */ +static void torture_config_address_family(void **state, + const char *file, + const char *string) +{ + ssh_session session = *state; + + const char *config = NULL; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "simple"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_ANY); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "af"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_ANY); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "af4"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_INET); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "af6"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.address_family, SSH_ADDRESS_FAMILY_INET6); + + /* test for parsing failures */ + config = "Host afmissing\n" + "\tAddressFamily\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "afmissing"); + _parse_config(session, file, string, SSH_ERROR); + + config = "Host afinvalid\n" + "\tAddressFamily wurstkäse\n"; + if (file != NULL) { + torture_write_file(file, config); + } else { + string = config; + } + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "afinvalid"); + _parse_config(session, file, string, SSH_ERROR); +} + +/** + * @brief Verify we can parse AdressFamily configuration option from string + */ +static void torture_config_address_family_string(void **state) +{ + torture_config_address_family(state, NULL, LIBSSH_TESTCONFIG_STRING18); +} + +/** + * @brief Verify we can parse AdressFamily configuration option from file + */ +static void torture_config_address_family_file(void **state) +{ + torture_config_address_family(state, LIBSSH_TESTCONFIG18, NULL); +} + +/** + * @brief Verify the configuration parser handles all the possible + * versions of RekeyLimit configuration option. + */ +static void torture_config_rekey(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + + /* Default values */ + ssh_options_set(session, SSH_OPTIONS_HOST, "default"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 0); + + /* 42 GB */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "data1"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, + (uint64_t) 42 * 1024 * 1024 * 1024); + assert_int_equal(session->opts.rekey_time, 0); + + /* 41 MB */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "data2"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 31 * 1024 * 1024); + assert_int_equal(session->opts.rekey_time, 0); + + /* 521 KB */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "data3"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 521 * 1024); + assert_int_equal(session->opts.rekey_time, 0); + + /* default 3D */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time1"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 3 * 24 * 60 * 60 * 1000); + + /* default 2h */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time2"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 2 * 60 * 60 * 1000); + + /* default 160m */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time3"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 160 * 60 * 1000); + + /* default 9600 [s] */ + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "time4"); + _parse_config(session, file, string, SSH_OK); + assert_int_equal(session->opts.rekey_data, 0); + assert_int_equal(session->opts.rekey_time, 9600 * 1000); + +} + +/** + * @brief Verify the configuration parser handles all the possible + * versions of RekeyLimit configuration option in file + */ +static void torture_config_rekey_file(void **state) +{ + torture_config_rekey(state, LIBSSH_TESTCONFIG12, NULL); +} + +/** + * @brief Verify the configuration parser handles all the possible + * versions of RekeyLimit configuration option in string + */ +static void torture_config_rekey_string(void **state) +{ + torture_config_rekey(state, NULL, LIBSSH_TESTCONFIG_STRING12); +} + +/** + * @brief Remove substring from a string + * + * @param occurrence 0 means "remove the first occurrence" + * 1 means "remove the second occurrence" and so on + */ +static void helper_remove_substring(char *s, const char *subs, int occurrence) { + char *p; + /* remove the substring from the defaults */ + p = strstr(s, subs); + assert_non_null(p); + /* look for second occurrence */ + for (int i = 0; i < occurrence; i++) { + p = strstr(p + 1, subs); + assert_non_null(p); + } + memmove(p, p + strlen(subs), strlen(p + strlen(subs)) + 1); +} + +/** + * @brief test that openssh style '+' feature works + */ +static void torture_config_plus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_added = ",ssh-rsa"; + const char *ciphers_added = ",aes128-cbc,aes256-cbc"; + const char *kex_added = ",diffie-hellman-group14-sha1,diffie-hellman-group1-sha1"; + const char *mac_added = ",hmac-sha1,hmac-sha1-etm@openssh.com"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + /* ssh-rsa is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], fips_hostkeys); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_added) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + strlen(hostkeys_added) + 1, + "%s%s", def_hostkeys, hostkeys_added); + assert_int_equal(rc, strlen(def_hostkeys) + strlen(hostkeys_added)); + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + } + + /* check ciphers */ + if (ssh_fips_mode()) { + /* already all supported is in the list */ + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], fips_ciphers); + } else { + awaited = calloc(strlen(def_ciphers) + strlen(ciphers_added) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + strlen(ciphers_added) + 1, + "%s%s", def_ciphers, ciphers_added); + assert_int_equal(rc, strlen(def_ciphers) + strlen(ciphers_added)); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + } + + /* check kex */ + if (ssh_fips_mode()) { + /* sha1 is disabled in fips */ + assert_string_equal(session->opts.wanted_methods[SSH_KEX], fips_kex); + } else { + awaited = calloc(strlen(def_kex) + strlen(kex_added) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + strlen(kex_added) + 1, + "%s%s", def_kex, kex_added); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_added)); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + } + + /* check mac */ + if (ssh_fips_mode()) { + /* the added algos are already in the fips_methods */ + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], fips_mac); + } else { + awaited = calloc(strlen(def_mac) + strlen(mac_added) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + strlen(mac_added) + 1, + "%s%s", def_mac, mac_added); + assert_int_equal(rc, strlen(def_mac) + strlen(mac_added)); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); + } +} + +/** + * @brief test that openssh style '+' feature works from file + */ +static void torture_config_plus_file(void **state) +{ + torture_config_plus(state, LIBSSH_TESTCONFIG14, NULL); +} + +/** + * @brief test that openssh style '+' feature works from string + */ +static void torture_config_plus_string(void **state) +{ + torture_config_plus(state, NULL, LIBSSH_TESTCONFIG_STRING14); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_removed = ",rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_removed = ",aes256-ctr"; + const char *kex_removed = ",diffie-hellman-group18-sha512,diffie-hellman-group16-sha512"; + const char *fips_kex_removed = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_removed = "hmac-sha2-256-etm@openssh.com,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(fips_hostkeys) + 1, "%s", fips_hostkeys); + assert_int_equal(rc, strlen(fips_hostkeys)); + } else { + awaited = calloc(strlen(def_hostkeys) + 1, 1); + rc = snprintf(awaited, strlen(def_hostkeys) + 1, "%s", def_hostkeys); + assert_int_equal(rc, strlen(def_hostkeys)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(fips_ciphers) + 1, "%s", fips_ciphers); + assert_int_equal(rc, strlen(fips_ciphers)); + } else { + awaited = calloc(strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(def_ciphers) + 1, "%s", def_ciphers); + assert_int_equal(rc, strlen(def_ciphers)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(fips_kex) + 1, "%s", fips_kex); + assert_int_equal(rc, strlen(fips_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, fips_kex_removed, 0); + } else { + awaited = calloc(strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(def_kex) + 1, "%s", def_kex); + assert_int_equal(rc, strlen(def_kex)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_removed, 0); + } + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(fips_mac) + 1, "%s", fips_mac); + assert_int_equal(rc, strlen(fips_mac)); + } else { + awaited = calloc(strlen(def_mac) + 1, 1); + rc = snprintf(awaited, strlen(def_mac) + 1, "%s", def_mac); + assert_int_equal(rc, strlen(def_mac)); + } + /* remove the substring from the defaults */ + helper_remove_substring(awaited, mac_removed, 0); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '-' feature works from file + */ +static void torture_config_minus_file(void **state) +{ + torture_config_minus(state, LIBSSH_TESTCONFIG15, NULL); +} + +/** + * @brief test that openssh style '-' feature works from string + */ +static void torture_config_minus_string(void **state) +{ + torture_config_minus(state, NULL, LIBSSH_TESTCONFIG_STRING15); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + const char *def_hostkeys = ssh_kex_get_default_methods(SSH_HOSTKEYS); + const char *fips_hostkeys = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + const char *def_ciphers = ssh_kex_get_default_methods(SSH_CRYPT_C_S); + const char *fips_ciphers = ssh_kex_get_fips_methods(SSH_CRYPT_C_S); + const char *def_kex = ssh_kex_get_default_methods(SSH_KEX); + const char *fips_kex = ssh_kex_get_fips_methods(SSH_KEX); + const char *def_mac = ssh_kex_get_default_methods(SSH_MAC_C_S); + const char *fips_mac = ssh_kex_get_fips_methods(SSH_MAC_C_S); + const char *hostkeys_prio = "rsa-sha2-512,rsa-sha2-256"; + const char *ciphers_prio = "aes256-cbc,"; + const char *kex_prio = "diffie-hellman-group18-sha512,diffie-hellman-group16-sha512,"; + const char *fips_kex_prio = ",diffie-hellman-group16-sha512,diffie-hellman-group18-sha512"; + const char *mac_prio = "hmac-sha1,"; + char *awaited = NULL; + int rc; + + _parse_config(session, file, string, SSH_OK); + + /* check hostkeys */ + /* +2 for the added comma and the \0 */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 2, + "%s,%s", hostkeys_prio, fips_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(fips_hostkeys) + 1); + } else { + awaited = calloc(strlen(def_hostkeys) + strlen(hostkeys_prio) + 2, 1); + rc = snprintf(awaited, strlen(hostkeys_prio) + strlen(def_hostkeys) + 2, + "%s,%s", hostkeys_prio, def_hostkeys); + assert_int_equal(rc, strlen(hostkeys_prio) + strlen(def_hostkeys) + 1); + } + + /* remove the substring from the defaults */ + helper_remove_substring(awaited, hostkeys_prio, 1); + /* remove the comma at the end of the list */ + awaited[strlen(awaited) - 1] = '\0'; + + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], awaited); + free(awaited); + + /* check ciphers */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(ciphers_prio) + strlen(fips_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(fips_ciphers) + 1, + "%s%s", ciphers_prio, fips_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(fips_ciphers)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, ciphers_prio, 1); + } else { + /* + 2 because the '\0' and the comma */ + awaited = calloc(strlen(ciphers_prio) + strlen(def_ciphers) + 1, 1); + rc = snprintf(awaited, strlen(ciphers_prio) + strlen(def_ciphers) + 1, + "%s%s", ciphers_prio, def_ciphers); + assert_int_equal(rc, strlen(ciphers_prio) + strlen(def_ciphers)); + } + + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], awaited); + free(awaited); + + /* check kex */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(kex_prio) + strlen(fips_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(fips_kex) + 1, + "%s%s", kex_prio, fips_kex); + assert_int_equal(rc, strlen(kex_prio) + strlen(fips_kex)); + /* remove the substring from the defaults */ + /* the default list has different order of these two algos than the fips + * and because here is a braindead string substitution being done, + * change the order and remove the first occurrence of it */ + helper_remove_substring(awaited, fips_kex_prio, 0); + } else { + awaited = calloc(strlen(kex_prio) + strlen(def_kex) + 1, 1); + rc = snprintf(awaited, strlen(kex_prio) + strlen(def_kex) + 1, + "%s%s", kex_prio, def_kex); + assert_int_equal(rc, strlen(def_kex) + strlen(kex_prio)); + /* remove the substring from the defaults */ + helper_remove_substring(awaited, kex_prio, 1); + } + + assert_string_equal(session->opts.wanted_methods[SSH_KEX], awaited); + free(awaited); + + /* check mac */ + if (ssh_fips_mode()) { + awaited = calloc(strlen(mac_prio) + strlen(fips_mac) + 1, 1); + rc = snprintf(awaited, strlen(mac_prio) + strlen(fips_mac) + 1, "%s%s", mac_prio, fips_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(fips_mac)); + /* the fips list contains hmac-sha1 algo */ + helper_remove_substring(awaited, mac_prio, 1); + } else { + awaited = calloc(strlen(mac_prio) + strlen(def_mac) + 1, 1); + /* the mac is not in default; it is added to the list */ + rc = snprintf(awaited, strlen(mac_prio) + strlen(def_mac) + 1, "%s%s", mac_prio, def_mac); + assert_int_equal(rc, strlen(mac_prio) + strlen(def_mac)); + } + assert_string_equal(session->opts.wanted_methods[SSH_MAC_C_S], awaited); + free(awaited); +} + +/** + * @brief test that openssh style '^' feature works from file + */ +static void torture_config_caret_file(void **state) +{ + torture_config_caret(state, LIBSSH_TESTCONFIG16, NULL); +} + +/** + * @brief test that openssh style '^' feature works from string + */ +static void torture_config_caret_string(void **state) +{ + torture_config_caret(state, NULL, LIBSSH_TESTCONFIG_STRING16); +} + +/** + * @brief test PubkeyAcceptedKeyTypes helper function + */ +static void torture_config_pubkeytypes(void **state, + const char *file, const char *string) +{ + ssh_session session = *state; + char *fips_algos; + + _parse_config(session, file, string, SSH_OK); + + if (ssh_fips_mode()) { + fips_algos = ssh_keep_fips_algos(SSH_HOSTKEYS, PUBKEYACCEPTEDTYPES); + assert_non_null(fips_algos); + assert_string_equal(session->opts.pubkey_accepted_types, fips_algos); + SAFE_FREE(fips_algos); + } else { + assert_string_equal(session->opts.pubkey_accepted_types, + PUBKEYACCEPTEDTYPES); + } +} + +/** + * @brief test parsing PubkeyAcceptedKeyTypes from file + */ +static void torture_config_pubkeytypes_file(void **state) +{ + torture_config_pubkeytypes(state, LIBSSH_TEST_PUBKEYTYPES, NULL); +} + +/** + * @brief test parsing PubkeyAcceptedKeyTypes from string + */ +static void torture_config_pubkeytypes_string(void **state) +{ + torture_config_pubkeytypes(state, NULL, LIBSSH_TEST_PUBKEYTYPES_STRING); +} + +/** + * @brief test parsing PubkeyAcceptedKAlgorithms from file + */ +static void torture_config_pubkeyalgorithms_file(void **state) +{ + torture_config_pubkeytypes(state, LIBSSH_TEST_PUBKEYALGORITHMS, NULL); +} + +/** + * @brief test parsing PubkeyAcceptedAlgorithms from string + */ +static void torture_config_pubkeyalgorithms_string(void **state) +{ + torture_config_pubkeytypes(state, NULL, LIBSSH_TEST_PUBKEYALGORITHMS_STRING); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end + */ +static void torture_config_nonewlineend(void **state, + const char *file, const char *string) +{ + _parse_config(*state, file, string, SSH_OK); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of file + */ +static void torture_config_nonewlineend_file(void **state) +{ + torture_config_nonewlineend(state, LIBSSH_TEST_NONEWLINEEND, NULL); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of string + */ +static void torture_config_nonewlineend_string(void **state) +{ + torture_config_nonewlineend(state, NULL, LIBSSH_TEST_NONEWLINEEND_STRING); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end + */ +static void torture_config_nonewlineoneline(void **state, + const char *file, + const char *string) +{ + _parse_config(*state, file, string, SSH_OK); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of file + */ +static void torture_config_nonewlineoneline_file(void **state) +{ + torture_config_nonewlineend(state, LIBSSH_TEST_NONEWLINEONELINE, NULL); +} + +/** + * @brief Verify the configuration parser handles + * missing newline in the end of string + */ +static void torture_config_nonewlineoneline_string(void **state) +{ + torture_config_nonewlineoneline(state, + NULL, LIBSSH_TEST_NONEWLINEONELINE_STRING); +} + +/* ssh_config_get_cmd() does these two things: + * * Strips leading whitespace + * * Terminate on the end of line + */ +static void torture_config_parser_get_cmd(void **state) +{ + char *p = NULL, *tok = NULL; + char data[256]; +#ifdef WITH_EXEC + FILE *outfile = NULL, *infile = NULL; + int pid; + char buffer[256] = {0}; +#endif + (void)state; + + /* Ignore leading whitespace */ + strncpy(data, " \t\t string\n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + /* but keeps the trailing whitespace */ + strncpy(data, "string \t\t \n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "string \t\t "); + assert_int_equal(*p, '\0'); + + /* should not drop the quotes and not split them into separate arguments */ + strncpy(data, "\"multi string\" something\n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "\"multi string\" something"); + assert_int_equal(*p, '\0'); + + /* But it does not split tokens by whitespace + * if they are not quoted, which is weird */ + strncpy(data, "multi string something\n", sizeof(data)); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, "multi string something"); + assert_int_equal(*p, '\0'); + + /* Commands in quotes are not treated special */ + sprintf(data, "%s%s%s%s", "\"", SOURCEDIR "/tests/unittests/hello world.sh", "\" ", "\"hello libssh\"\n"); + printf("%s\n", data); + p = data; + tok = ssh_config_get_cmd(&p); + assert_string_equal(tok, data); + assert_int_equal(*p, '\0'); + +#ifdef WITH_EXEC + /* Check if the command would get correctly executed + * Use the script file "hello world.sh" to echo the first argument + * Run as <= "/workdir/hello world.sh" "hello libssh" => */ + + /* output to file and check wrong */ + outfile = fopen("output.log", "a+"); + assert_non_null(outfile); + printf("the tok is %s\n", tok); + + pid = fork(); + if (pid == -1) { + perror("fork"); + } else if (pid == 0) { + ssh_execute_command(tok, fileno(outfile), fileno(outfile)); + /* Does not return */ + } else { + /* parent + * wait child process */ + wait(NULL); + infile = fopen("output.log", "r"); + assert_non_null(infile); + p = fgets(buffer, sizeof(buffer), infile); + fclose(infile); + remove("output.log"); + assert_non_null(p); + } + + fclose(outfile); + assert_string_equal(buffer, "hello libssh"); +#endif /* WITH_EXEC */ +} + +/* ssh_config_get_token() should behave as expected + * * Strip leading whitespace + * * Return first token separated by whitespace or equal sign, + * respecting quotes! + * * Correctly treat escaped quotes inside of quotes. + */ +static void torture_config_parser_get_token(void **state) +{ + char *p = NULL, *tok = NULL; + char data[256]; + + (void) state; + + /* Ignore leading whitespace (from get_cmd() already */ + strncpy(data, " \t\t string\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + strncpy(data, " \t\t string", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + /* drops trailing whitespace */ + strncpy(data, "string \t\t \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + strncpy(data, "string \t\t ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, '\0'); + + /* Correctly handles tokens in quotes */ + strncpy(data, "\"multi string\" something\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + strncpy(data, "\"multi string\" something", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + /* Consistently splits unquoted strings */ + strncpy(data, "multi string something\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + strncpy(data, "multi string something", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "multi"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "string"); + assert_int_equal(*p, 's'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "something"); + assert_int_equal(*p, '\0'); + + /* It is made to parse also option=value pairs as well */ + strncpy(data, " key=value \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, " key=value ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* spaces are allowed also around the equal sign */ + strncpy(data, " key = value \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, " key = value ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* correctly parses even key=value pairs with either one in quotes */ + strncpy(data, " key=\"value with spaces\" \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '\"'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value with spaces"); + assert_int_equal(*p, '\0'); + + strncpy(data, " key=\"value with spaces\" ", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '\"'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value with spaces"); + assert_int_equal(*p, '\0'); + + /* Only one equal sign is allowed */ + strncpy(data, "key==value\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '='); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, ""); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, "key==value", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "key"); + assert_int_equal(*p, '='); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, ""); + assert_int_equal(*p, 'v'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* Unmatched quotes */ + strncpy(data, " \"value\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + strncpy(data, " \"value", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value"); + assert_int_equal(*p, '\0'); + + /* Escaped quotes */ + strncpy(data, " \"value with \\\"escaped\\\" quotes\" \n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "value with \"escaped\" quotes"); + assert_int_equal(*p, '\0'); + + strncpy(data, "\\\"value with \\\"escaped\\\" quotes\\\"\n", sizeof(data)); + p = data; + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "\\\"value"); + assert_int_equal(*p, 'w'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "with"); + assert_int_equal(*p, '\\'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "\\\"escaped\\\""); + assert_int_equal(*p, 'q'); + tok = ssh_config_get_token(&p); + assert_string_equal(tok, "quotes\\\""); + assert_int_equal(*p, '\0'); +} + +/* match_pattern() sanity tests + */ +static void torture_config_match_pattern(void **state) +{ + int rv = 0; + + (void) state; + + /* Simple test "a" matches "a" */ + rv = match_pattern("a", "a"); + assert_int_equal(rv, 1); + + /* Simple test "a" does not match "b" */ + rv = match_pattern("a", "b"); + assert_int_equal(rv, 0); + + /* NULL arguments are correctly handled */ + rv = match_pattern("a", NULL); + assert_int_equal(rv, 0); + rv = match_pattern(NULL, "a"); + assert_int_equal(rv, 0); + + /* Simple wildcard ? is handled in pattern */ + rv = match_pattern("a", "?"); + assert_int_equal(rv, 1); + rv = match_pattern("aa", "?"); + assert_int_equal(rv, 0); + /* Wildcard in search string */ + rv = match_pattern("?", "a"); + assert_int_equal(rv, 0); + rv = match_pattern("?", "?"); + assert_int_equal(rv, 1); + + /* Simple wildcard * is handled in pattern */ + rv = match_pattern("a", "*"); + assert_int_equal(rv, 1); + rv = match_pattern("aa", "*"); + assert_int_equal(rv, 1); + /* Wildcard in search string */ + rv = match_pattern("*", "a"); + assert_int_equal(rv, 0); + rv = match_pattern("*", "*"); + assert_int_equal(rv, 1); + + /* More complicated patterns */ + rv = match_pattern("a", "*a"); + assert_int_equal(rv, 1); + rv = match_pattern("a", "a*"); + assert_int_equal(rv, 1); + rv = match_pattern("abababc", "*abc"); + assert_int_equal(rv, 1); + rv = match_pattern("ababababca", "*abc"); + assert_int_equal(rv, 0); + rv = match_pattern("ababababca", "*abc*"); + assert_int_equal(rv, 1); + + /* Multiple wildcards in row */ + rv = match_pattern("aa", "??"); + assert_int_equal(rv, 1); + rv = match_pattern("bba", "??a"); + assert_int_equal(rv, 1); + rv = match_pattern("aaa", "**a"); + assert_int_equal(rv, 1); + rv = match_pattern("bbb", "**a"); + assert_int_equal(rv, 0); + + /* Consecutive asterisks do not make sense and do not need to recurse */ + rv = match_pattern("hostname", "**********pattern"); + assert_int_equal(rv, 0); + rv = match_pattern("hostname", "pattern**********"); + assert_int_equal(rv, 0); + rv = match_pattern("pattern", "***********pattern"); + assert_int_equal(rv, 1); + rv = match_pattern("pattern", "pattern***********"); + assert_int_equal(rv, 1); + + rv = match_pattern("hostname", "*p*a*t*t*e*r*n*"); + assert_int_equal(rv, 0); + rv = match_pattern("pattern", "*p*a*t*t*e*r*n*"); + assert_int_equal(rv, 1); + + /* Regular Expression Denial of Service */ + rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a"); + assert_int_equal(rv, 1); + rv = match_pattern("ababababababababababababababababababababab", + "*a*b*a*b*a*b*a*b*a*b*a*b*a*b*a*b"); + assert_int_equal(rv, 1); + + /* A lot of backtracking */ + rv = match_pattern("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax", + "a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*a*ax"); + assert_int_equal(rv, 1); + + /* Test backtracking: *a matches first 'a', fails on 'b', must backtrack */ + rv = match_pattern("axaxaxb", "*a*b"); + assert_int_equal(rv, 1); + + /* Test greedy consumption with suffix */ + rv = match_pattern("foo_bar_baz_bar", "*bar"); + assert_int_equal(rv, 1); + + /* Test exact suffix requirement (ensure no partial match acceptance) */ + rv = match_pattern("foobar_extra", "*bar"); + assert_int_equal(rv, 0); + + /* Test multiple distinct wildcards */ + rv = match_pattern("a_very_long_string_with_a_pattern", "*long*pattern"); + assert_int_equal(rv, 1); + + /* ? inside a * sequence */ + rv = match_pattern("abcdefg", "a*c?e*g"); + assert_int_equal(rv, 1); + + /* Consecutive mixed wildcards */ + rv = match_pattern("abc", "*?c"); + assert_int_equal(rv, 1); + + /* ? at the very end after * */ + rv = match_pattern("abc", "ab?"); + assert_int_equal(rv, 1); + rv = match_pattern("abc", "ab*?"); + assert_int_equal(rv, 1); + + /* Consecutive stars should be collapsed or handled gracefully */ + rv = match_pattern("abc", "a**c"); + assert_int_equal(rv, 1); + rv = match_pattern("abc", "***"); + assert_int_equal(rv, 1); + + /* Empty string handling */ + rv = match_pattern("", "*"); + assert_int_equal(rv, 1); + rv = match_pattern("", "?"); + assert_int_equal(rv, 0); + rv = match_pattern("", ""); + assert_int_equal(rv, 1); + + /* Pattern longer than string */ + rv = match_pattern("short", "short_but_longer"); + assert_int_equal(rv, 0); +} + +/* Identity file can be specified multiple times in the configuration + */ +static void torture_config_identity(void **state) +{ + const char *id = NULL; + const char *cert = NULL; + struct ssh_iterator *it = NULL; + ssh_session session = *state; + + _parse_config(session, NULL, LIBSSH_TESTCONFIG_STRING13, SSH_OK); + + /* The identities are first added to this temporary list before expanding */ + it = ssh_list_get_iterator(session->opts.identity_non_exp); + assert_non_null(it); + id = it->data; + /* The identities are prepended to the list so we start with second one */ + assert_string_equal(id, "id_ecdsa_two"); + + it = it->next; + assert_non_null(it); + id = it->data; + assert_string_equal(id, "id_rsa_one"); + + /* The certs are first added to this temporary list before expanding */ + it = ssh_list_get_iterator(session->opts.certificate_non_exp); + assert_non_null(it); + cert = it->data; + /* The certs are coming as listed in the configuration file */ + assert_string_equal(cert, "id_rsa_one-cert.pub"); + + it = it->next; + assert_non_null(it); + cert = it->data; + assert_string_equal(cert, "id_ecdsa_two-cert.pub"); + /* and that is all */ + assert_null(it->next); +} + +/* Make absolute path for config include + */ +static void torture_config_make_absolute_int(void **state, bool no_sshdir_fails) +{ + ssh_session session = *state; + char *result = NULL; +#ifndef _WIN32 + char h[256] = {0}; + char *user = NULL; + char *home = NULL; + struct passwd *pw = getpwuid(getuid()); + assert_non_null(pw); + user = strdup(pw->pw_name); + assert_non_null(user); + home = strdup(pw->pw_dir); + assert_non_null(home); +#endif + + /* Absolute path already -- should not change in any case */ + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + result = ssh_config_make_absolute(session, "/etc/ssh/ssh_config.d/*.conf", 0); + assert_string_equal(result, "/etc/ssh/ssh_config.d/*.conf"); + free(result); + + /* Global is relative to /etc/ssh/ */ + result = ssh_config_make_absolute(session, "ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/ssh_config.d/test.conf"); + free(result); + result = ssh_config_make_absolute(session, "./ssh_config.d/test.conf", 1); + assert_string_equal(result, "/etc/ssh/./ssh_config.d/test.conf"); + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + result = ssh_config_make_absolute(session, "my_config", 0); + if (no_sshdir_fails) { + assert_null(result); + } else { + /* The path depends on the PWD so lets skip checking the actual path here */ + assert_non_null(result); + } + free(result); + + /* User config is relative to sshdir -- here faked to /tmp/ssh/ */ + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, "/tmp/ssh"); + result = ssh_config_make_absolute(session, "my_config", 0); + assert_string_equal(result, "/tmp/ssh/my_config"); + free(result); + +#ifndef _WIN32 + /* Tilde expansion works only in user config */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 0); + snprintf(h, 256 - 1, "%s/.ssh/config.d/*.conf", home); + assert_string_equal(result, h); + free(result); + + /* in global config its just prefixed without expansion */ + result = ssh_config_make_absolute(session, "~/.ssh/config.d/*.conf", 1); + assert_string_equal(result, "/etc/ssh/~/.ssh/config.d/*.conf"); + free(result); + snprintf(h, 256 - 1, "~%s/.ssh/config.d/*.conf", user); + result = ssh_config_make_absolute(session, h, 1); + snprintf(h, 256 - 1, "/etc/ssh/~%s/.ssh/config.d/*.conf", user); + assert_string_equal(result, h); + free(result); + free(home); + free(user); +#endif +} + +static void torture_config_make_absolute(void **state) +{ + torture_config_make_absolute_int(state, 0); +} + +static void torture_config_make_absolute_no_sshdir(void **state) +{ + torture_config_make_absolute_int(state, 1); +} + +static void torture_config_parse_uri(void **state) +{ + char *username = NULL; + char *hostname = NULL; + char *port = NULL; + int rc; + + (void)state; /* unused */ + + rc = ssh_config_parse_uri("localhost", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "localhost"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_null(port); + + rc = ssh_config_parse_uri("1.2.3.4:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1.2.3.4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + rc = ssh_config_parse_uri("[1:2:3::4]:2222", &username, &hostname, &port, false); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); + assert_string_equal(port, "2222"); + SAFE_FREE(port); + + /* do not want port */ + rc = ssh_config_parse_uri("1:2:3::4", &username, &hostname, NULL, true); + assert_return_code(rc, errno); + assert_null(username); + assert_string_equal(hostname, "1:2:3::4"); + SAFE_FREE(hostname); + + rc = ssh_config_parse_uri("user -name@", &username, NULL, NULL, true); + assert_int_equal(rc, SSH_ERROR); +} + +/* Complex ssh match configurations + */ +static void torture_config_match_complex(void **state) +{ + ssh_session session = *state; + char *v = NULL; + int ret; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + _parse_config(session, LIBSSH_TESTCONFIG_MATCH_COMPLEX, NULL, SSH_OK); + + /* Test the variable presence */ + ret = ssh_options_get(session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_non_null(v); +#ifndef WITH_EXEC + assert_string_equal(session->opts.host, "Bar"); +#else + assert_string_equal(v, "complex-match"); +#endif + ssh_string_free_char(v); +} + +/* Missing value to LogLevel configuration option + */ +static void torture_config_loglevel_missing_value(void **state) +{ + ssh_session session = *state; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + _parse_config(session, LIBSSH_TESTCONFIG_LOGLEVEL_MISSING, NULL, SSH_OK); +} + +static int before_connection(ssh_session jump_session, void *user) +{ + char *v = NULL; + int ret; + + (void)user; + + /* During the connection, we force parsing the same configuration file + * (would be normally parsed automatically during the connection itself) + */ + ret = ssh_config_parse_file(jump_session, LIBSSH_TESTCONFIG_JUMP); + assert_return_code(ret, errno); + + /* Test the variable presence */ + ret = ssh_options_get(jump_session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "1xxxxxx"); + ssh_string_free_char(v); + + ret = ssh_options_get(jump_session, SSH_OPTIONS_USER, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "ubuntu"); + ssh_string_free_char(v); + + assert_int_equal(jump_session->opts.port, 23); + + /* Fail the connection -- we are in unit tests so it would fail anyway */ + return 1; +} + +static int verify_knownhost(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} + +static int authenticate(ssh_session jump_session, void *user) +{ + (void)jump_session; + (void)user; + + return 0; +} +/* Reproducer for complex proxy jump + */ +static void torture_config_jump(void **state) +{ + ssh_session session = *state; + struct ssh_jump_callbacks_struct c = { + .before_connection = before_connection, + .verify_knownhost = verify_knownhost, + .authenticate = authenticate, + }; + char *v = NULL; + int ret; + + ssh_options_set(session, SSH_OPTIONS_HOST, "cisco-router"); + + _parse_config(session, LIBSSH_TESTCONFIG_JUMP, NULL, SSH_OK); + + /* Test the variable presence */ + ret = ssh_options_get(session, SSH_OPTIONS_HOST, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "xx.xxxxxxxxx"); + ssh_string_free_char(v); + + ret = ssh_options_get(session, SSH_OPTIONS_USER, &v); + assert_return_code(ret, errno); + assert_string_equal(v, "username"); + ssh_string_free_char(v); + + assert_int_equal(session->opts.port, 5555); + + /* At this point, the configuration file is not parsed for the jump host so + * we are getting just the the hostname -- the port and username will get + * pulled during the session connecting to this host */ + assert_int_equal(ssh_list_count(session->opts.proxy_jumps), 1); + helper_proxy_jump_check(session->opts.proxy_jumps->root, + "ub-jumphost", + NULL, + NULL); + + /* Set up the callbacks -- they should verify we are going to connect to the + * right host */ + ret = ssh_options_set(session, SSH_OPTIONS_PROXYJUMP_CB_LIST_APPEND, &c); + assert_ssh_return_code(session, ret); + + ret = ssh_connect(session); + assert_ssh_return_code_equal(session, ret, SSH_ERROR); + + printf("%s: EOF\n", __func__); +} + +/* Invalid configuration files + */ +static void torture_config_invalid(void **state) +{ + ssh_session session = *state; + + ssh_options_set(session, SSH_OPTIONS_HOST, "Bar"); + + /* non-regular file -- ignored (or missing on non-unix) so OK */ + _parse_config(session, "/dev/random", NULL, SSH_OK); + +#ifndef _WIN32 + /* huge file -- ignored (or missing on non-unix) so OK */ + _parse_config(session, "/proc/kcore", NULL, SSH_OK); +#endif +} + +/* Issue #365: a value set via ssh_options_set() before config parsing must + * NOT be overridden by the config file. */ +static void torture_config_user_not_overridden(void **state) +{ + ssh_session session = *state; + char *user = NULL; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "appuser"); + assert_ssh_return_code(session, rc); + + _parse_config(session, NULL, "User configuser\n", SSH_OK); + + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_ssh_return_code(session, rc); + assert_non_null(user); + assert_string_equal(user, "appuser"); + SSH_STRING_FREE_CHAR(user); +} + +/* When the application did NOT set User, the config value still applies. */ +static void torture_config_user_from_config_applies(void **state) +{ + ssh_session session = *state; + char *user = NULL; + int rc; + + _parse_config(session, NULL, "User configuser\n", SSH_OK); + + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_ssh_return_code(session, rc); + assert_non_null(user); + assert_string_equal(user, "configuser"); + SSH_STRING_FREE_CHAR(user); +} + +/* Protection is general, not User-specific: an app-set Port survives config. */ +static void torture_config_port_not_overridden(void **state) +{ + ssh_session session = *state; + unsigned int port = 2020; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &port); + assert_ssh_return_code(session, rc); + + _parse_config(session, NULL, "Port 2222\n", SSH_OK); + assert_int_equal(session->opts.port, 2020); +} + +/* The host match key is NOT protected: config HostName still resolves an + * app-set alias to the real hostname. */ +static void torture_config_hostname_still_resolves(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "myalias"); + assert_ssh_return_code(session, rc); + + _parse_config(session, + NULL, + "Host myalias\n\tHostName real.example.com\n", + SSH_OK); + + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "real.example.com"); +} + +/* HostName keeps its own "first obtained value wins" precedence between config + * entries, independently of the application-set SSH_OPTIONS_HOST lookup key. + * Every target below resolves to the first HostName, matching OpenSSH: + * + * $ ssh -F config -G test | grep ^hostname -> hostname test + * $ ssh -F config -G test2 | grep ^hostname -> hostname test + * $ ssh -F config -G test3 | grep ^hostname -> hostname test + */ +static void torture_config_hostname_first_wins(void **state) +{ + ssh_session session = *state; + const char *config = "HostName test\n" + "HostName test2\n" + "Match host test2\n" + "\tHostName test3\n"; + const char *targets[] = {"test", "test2", "test3"}; + size_t i; + int rc; + + (void)session; + + for (i = 0; i < ARRAY_SIZE(targets); i++) { + ssh_session s = ssh_new(); + assert_non_null(s); + + rc = ssh_options_set(s, SSH_OPTIONS_HOST, targets[i]); + assert_ssh_return_code(s, rc); + + _parse_config(s, NULL, config, SSH_OK); + + assert_non_null(s->opts.host); + assert_string_equal(s->opts.host, "test"); + + ssh_free(s); + } +} + +/* Operational options like log verbosity are NOT protected: config LogLevel + * still applies even if the application set verbosity beforehand. */ +static void torture_config_loglevel_not_overridden(void **state) +{ + ssh_session session = *state; + int level = SSH_LOG_NOLOG; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &level); + assert_ssh_return_code(session, rc); + + _parse_config(session, NULL, "LogLevel DEBUG3\n", SSH_OK); + + assert_int_equal(session->common.log_verbosity, SSH_LOG_TRACE); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_config_include_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_include_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_include_recursive_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_double_ports_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_double_ports_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_glob_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_glob_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_new_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_new_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_auth_methods_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_auth_methods_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_unknown_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_unknown_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_proxyjump_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_proxyjump_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_control_path_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_control_path_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_control_master_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_control_master_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_address_family_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_address_family_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_rekey_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_rekey_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_plus_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_minus_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_caret_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeytypes_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_pubkeyalgorithms_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineend_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineend_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineoneline_file, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_nonewlineoneline_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_parser_get_cmd, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_parser_get_token, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_pattern, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_make_absolute_no_sshdir, + setup_no_sshdir, + teardown), + cmocka_unit_test_setup_teardown(torture_config_parse_uri, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_complex, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_loglevel_missing_value, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_jump, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_invalid, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_user_not_overridden, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_user_from_config_applies, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_port_not_overridden, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_hostname_still_resolves, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_hostname_first_wins, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_loglevel_not_overridden, + setup, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_config_files, teardown_config_files); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_config_match_localnetwork.c b/src/libs/libssh-0.12.2/tests/unittests/torture_config_match_localnetwork.c new file mode 100644 index 000000000000..4c20db08ee5a --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_config_match_localnetwork.c @@ -0,0 +1,752 @@ +#include "config.h" +#include "torture.h" +#include "libssh/options.h" +#include "libssh/session.h" +#include "match.c" +#ifdef HAVE_IFADDRS_H +#include +#endif +#include +#include + +/* This list contains common local subnet addresses and more generic ones */ +#define IPV4_LIST \ + "158.46.192.0/18,213.86.215.224/27,61.67.54.0/23,164.155.128.0/21," \ + "171.10.0.0/16,205.59.221.0/24,122.105.209.48/28,10.0.1.0/24," \ + "130.192.28.0/22,172.16.16.0/16,192.168.0.0/24,169.254.0.0/16" + +#define IPV6_LIST "fe80::/64" + +static int +setup(void **state) +{ + ssh_session session = NULL; + char *wd = NULL; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + wd = torture_get_current_working_dir(); + ssh_options_set(session, SSH_OPTIONS_SSH_DIR, wd); + free(wd); + + *state = session; + + return 0; +} + +static int +teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +/** + * @brief helper function loading configuration from either file or string + */ +static void +_parse_config(ssh_session session, + const char *file, + const char *string, + int expected) +{ + /* + * Initialisation of ret is not needed, but the compiler is not able to + * understand fail() so it will complain about uninitialised use of ret + * below in assert_ssh_return_code_equal() + */ + int ret = -1; + + /* + * make sure either config file or config string is given, + * not both + */ + assert_int_not_equal(file == NULL, string == NULL); + + if (file != NULL) { + ret = ssh_config_parse_file(session, file); + } else if (string != NULL) { + ret = ssh_config_parse_string(session, string); + } else { + /* should not happen */ + fail(); + } + + /* make sure parsing went as expected */ + assert_ssh_return_code_equal(session, ret, expected); +} + +/** + * @brief converts subnet mask to prefix length (IPv4) + */ +static int +subnet_mask_to_prefix_length_4(struct in_addr subnet_mask) +{ + uint32_t mask; + int prefix_length = 0; + + mask = ntohl(subnet_mask.s_addr); + + /* Count the number of consecutive 1 bits */ + while (mask & 0x80000000) { + prefix_length++; + mask <<= 1; + } + return prefix_length; +} + +/** + * @brief converts subnet mask to prefix length (IPv6) + */ +static int +subnet_mask_to_prefix_length_6(struct in6_addr subnet_mask) +{ + uint8_t *mask = NULL, chunk; + int i, j, prefix_length = 0; + + mask = subnet_mask.s6_addr; + + /* Count the number of consecutive 1 bits in each byte chunk */ + for (i = 0; i < 16; i++) { + chunk = mask[i]; + while (chunk) { + for (j = 0; j < 8; j++) { + if (chunk & 0x80) { + prefix_length++; + chunk <<= 1; + } else { + break; + } + } + } + } + return prefix_length; +} + +/** + * @brief helper function returning the IPv4 and IPv6 network ID + * (in CIDR format) corresponding to any of the running local interfaces. + * The network interface corresponding to IPv4 and IPv6 network ID may be + * different. + * + * @note If no non-loopback network interfaces are found for IPv4 or + * IPv6, the function will fall back to using the loopback addresses. + */ +static int +get_network_id(char *net_id_4, char *net_id_6) +{ + struct ifaddrs *ifa = NULL, *ifaddrs = NULL; + struct in_addr addr, network_id_4, subnet_mask_4; + struct in6_addr addr6, network_id_6, subnet_mask_6; + struct sockaddr_in netmask; + struct sockaddr_in6 netmask6; + char address[NI_MAXHOST], *a = NULL; + char *network_id_str = NULL, network_id_str6[INET6_ADDRSTRLEN], + lo_net_id_4[NI_MAXHOST], lo_net_id_6[NI_MAXHOST]; + int i, prefix_length, rc; + int found_4 = 0, found_lo_4 = 0, found_6 = 0, found_lo_6 = 0; + socklen_t sa_len; + + ZERO_STRUCT(addr); + ZERO_STRUCT(network_id_4); + ZERO_STRUCT(subnet_mask_4); + + ZERO_STRUCT(addr6); + ZERO_STRUCT(network_id_6); + ZERO_STRUCT(subnet_mask_6); + + if (getifaddrs(&ifaddrs) != 0) { + goto out; + } + + for (ifa = ifaddrs; ifa != NULL; ifa = ifa->ifa_next) { + if (found_4 && found_6) { + break; + } + + if (ifa->ifa_addr == NULL || (ifa->ifa_flags & IFF_UP) == 0) { + continue; + } + + switch (ifa->ifa_addr->sa_family) { + case AF_INET: + if (found_4) { + continue; + } + sa_len = sizeof(struct sockaddr_in); + break; + case AF_INET6: + if (found_6) { + continue; + } + sa_len = sizeof(struct sockaddr_in6); + break; + default: + continue; + } + + rc = getnameinfo(ifa->ifa_addr, + sa_len, + address, + sizeof(address), + NULL, + 0, + NI_NUMERICHOST); + if (rc != 0) { + continue; + } + + if (ifa->ifa_addr->sa_family == AF_INET) { + + /* Extract subnet mask */ + memcpy(&netmask, ifa->ifa_netmask, sizeof(struct sockaddr_in)); + subnet_mask_4 = netmask.sin_addr; + + rc = inet_pton(AF_INET, address, &addr); + if (rc == 0) { + continue; + } + + /* Calculate the network ID */ + network_id_4.s_addr = addr.s_addr & subnet_mask_4.s_addr; + + /* Convert network ID to string and compute prefix length */ + network_id_str = inet_ntoa(network_id_4); + if (network_id_str == NULL) { + continue; + } + prefix_length = subnet_mask_to_prefix_length_4(subnet_mask_4); + if (prefix_length > 32) { + continue; + } + + if (strcmp(ifa->ifa_name, "lo") == 0) { + /* Store it temporarily in case needed for fallback */ + snprintf(lo_net_id_4, + NI_MAXHOST, + "%s/%u", + network_id_str, + prefix_length); + found_lo_4 = 1; + } else { + snprintf(net_id_4, + NI_MAXHOST, + "%s/%u", + network_id_str, + prefix_length); + found_4 = 1; + } + } else if (ifa->ifa_addr->sa_family == AF_INET6) { + + /* Remove interface in case of IPv6 address: addr%interface */ + a = strchr(address, '%'); + if (a != NULL) { + *a = '\0'; + } + + /* Extract subnet mask */ + memcpy(&netmask6, ifa->ifa_netmask, sizeof(struct sockaddr_in6)); + subnet_mask_6 = netmask6.sin6_addr; + + rc = inet_pton(AF_INET6, address, &addr6); + if (rc == 0) { + continue; + } + + /* Calculate the network ID */ + for (i = 0; i < 16; i++) { + network_id_6.s6_addr[i] = + addr6.s6_addr[i] & subnet_mask_6.s6_addr[i]; + } + + /* Convert network ID to string and compute prefix length */ + if (inet_ntop(AF_INET6, + &network_id_6, + network_id_str6, + INET6_ADDRSTRLEN) == NULL) { + continue; + } + prefix_length = subnet_mask_to_prefix_length_6(subnet_mask_6); + if (prefix_length > 128) { + continue; + } + + if (strcmp(ifa->ifa_name, "lo") == 0) { + /* Store it temporarily in case needed for fallback */ + snprintf(lo_net_id_6, + NI_MAXHOST, + "%s/%u", + network_id_str6, + prefix_length); + found_lo_6 = 1; + } else { + snprintf(net_id_6, + NI_MAXHOST, + "%s/%u", + network_id_str6, + prefix_length); + found_6 = 1; + } + } + } + + /* + * Fallback to the loopback network ID (127.0.0.0/8) if no other + * IPv4 network ID has been found. + */ + if (!found_4 && found_lo_4) { + snprintf(net_id_4, NI_MAXHOST, "%s", lo_net_id_4); + found_4 = 1; + } + + /* + * Fallback to the loopback network ID (::1/128) if no other + * IPv6 network ID has been found. + */ + if (!found_6 && found_lo_6) { + snprintf(net_id_6, NI_MAXHOST, "%s", lo_net_id_6); + found_6 = 1; + } + + freeifaddrs(ifaddrs); + +out: + /* if both net_id_4 and net_id_6 are not set then we should fail */ + return (found_4 && found_6) ? 0 : -1; +} + +/** + * @brief Verify the match between a IPv4/IPv6 address and a IPv4/IPv6 subnet + */ +static void +assert_true_match_cidr(const char *try, + const char *match, + unsigned int mask_len, + int af, + int rv) +{ + struct in_addr try_addr, match_addr; + struct in6_addr try_addr6, match_addr6; + int r1, r2; + + switch (af) { + case AF_INET: + ZERO_STRUCT(try_addr); + ZERO_STRUCT(match_addr); + + r1 = inet_pton(AF_INET, try, &try_addr); + r2 = inet_pton(AF_INET, match, &match_addr); + if (r1 == 0 || r2 == 0) { + fail(); + } + assert_int_equal(cidr_match_4(&try_addr, &match_addr, mask_len), rv); + break; + case AF_INET6: + ZERO_STRUCT(try_addr6); + ZERO_STRUCT(match_addr6); + + r1 = inet_pton(AF_INET6, try, &try_addr6); + r2 = inet_pton(AF_INET6, match, &match_addr6); + if (r1 == 0 || r2 == 0) { + fail(); + } + assert_int_equal(cidr_match_6(&try_addr6, &match_addr6, mask_len), rv); + break; + default: + fail(); + } +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + */ +static void +torture_config_match_localnetwork(void **state, bool use_file) +{ + ssh_session session = *state; + const char *config = NULL; + char config_string[2048]; + char network_id_4[NI_MAXHOST], network_id_6[NI_MAXHOST]; + const char *file = NULL, *string = NULL; + + if (use_file == true) { + file = "libssh_testconfig_localnetwork.tmp"; + } + + if (get_network_id(network_id_4, network_id_6) == -1) { + fail(); + } + + /* IPv4 test */ + snprintf(config_string, + sizeof(config_string), + "Match localnetwork %s\n" + "\tHostName expected.com\n", + network_id_4); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "expected.com"); + + /* IPv6 test */ + snprintf(config_string, + sizeof(config_string), + "Match localnetwork %s\n" + "\tHostName expected.com\n", + network_id_6); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "expected.com"); + + /* Test negate condition */ + snprintf(config_string, + sizeof(config_string), + "Match Host station !localnetwork %s\n" + "\tHostName expected.com\n" + "Host station\n" + "\tHostName negate.com\n", + network_id_4); + config = config_string; + + if (use_file == true) { + torture_write_file(file, config); + } else { + string = config; + } + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "station"); + _parse_config(session, file, string, SSH_OK); + assert_string_equal(session->opts.host, "negate.com"); +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + * through configuration file. + */ +static void +torture_config_match_localnetwork_file(void **state) +{ + torture_config_match_localnetwork(state, true); +} + +/** + * @brief Verify the configuration parser accepts Match localnetwork keyword + * through configuration string. + */ +static void +torture_config_match_localnetwork_string(void **state) +{ + torture_config_match_localnetwork(state, false); +} + +/** + * @brief Verify the cidr matching function works correctly + * with IPv4 addresses + */ +static void +torture_match_cidr_address_list_ipv4(void **state) +{ + int rc; + (void)state; + + /* Test some valid IPv4 addresses */ + rc = match_cidr_address_list("192.158.50.5", "192.158.50.0/28", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.2.200.200", "10.2.128.0/17", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("192.168.175.40", "192.168.175.0/26", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("172.31.140.100", "172.31.128.0/19", AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.3.9.50", "10.3.8.0/23", AF_INET); + assert_int_equal(rc, 1); + + /* Test positive match with unknown host address family */ + rc = match_cidr_address_list("158.15.96.13", "158.12.30.0/12", -1); + assert_int_equal(rc, 1); + + /* Test some valid IPv4 addresses against IPV4_LIST */ + rc = match_cidr_address_list("164.155.128.15", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("158.46.223.71", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("205.59.221.160", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("10.0.1.254", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("172.16.58.1", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + rc = match_cidr_address_list("169.254.20.28", IPV4_LIST, AF_INET); + assert_int_equal(rc, 1); + + rc = match_cidr_address_list("255.255.255.255", "0.0.0.0/0", AF_INET); + assert_int_equal(rc, 1); + + /* Test some not matching IPv4 addresses */ + rc = match_cidr_address_list("172.21.0.200", "172.20.240.0/20", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("10.10.14.100", "10.10.10.0/22", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("192.168.150.8", "192.168.150.0/29", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("10.238.16.50", "10.255.0.0/12", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("172.31.160.100", "172.31.128.0/19", AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("192.168.4.98", IPV4_LIST, AF_INET); + assert_int_equal(rc, 0); + rc = match_cidr_address_list("0.0.0.0", IPV4_LIST, AF_INET); + assert_int_equal(rc, 0); + + /* Test negative match with unknown host address family */ + rc = match_cidr_address_list("122.105.210.57", IPV4_LIST, -1); + assert_int_equal(rc, 0); + + /* Test some invalid input */ + rc = match_cidr_address_list("192.168.1.x", "192.168.1.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("0.168.f2.b8", "172.0.0.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("10.0.1.2/22", "10.0.1.0/22", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("10.0.1.2/", "10.0.1.0/22", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("172.16.16.5/abc1", "172.16.16.0/24", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("172.16.18.251", "172.16.16.0", AF_INET); + assert_int_equal(rc, -1); + + /* Test invalid input with unknown host address family */ + rc = match_cidr_address_list("172.67.3.x", IPV4_LIST, -1); + assert_int_equal(rc, -1); + + /* Test invalid CIDR list */ + rc = match_cidr_address_list(NULL, "192.168.1.0/33", AF_INET); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, "", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, ",", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, ",192.168.1.0/24", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list(NULL, "10.0.0.0/24 , 192.168.1.0/24", -1); + assert_int_equal(rc, -1); + rc = match_cidr_address_list( + NULL, + "ffff:ffff:ffff:ffff:ffff:ffff:255.255.255.255/128junkdata", + -1); + assert_int_equal(rc, -1); +} + +/** + * @brief Verify the cidr matching function works correctly + * with IPv6 addresses + */ +static void +torture_match_cidr_address_list_ipv6(void **state) +{ + /* Test link-local addresses against fe80::/64 */ + int i, rc, valid_addr_len, invalid_addr_len; + const char *valid_addr[] = {"fe80::aadf:b119:507a:986a%abcdef", + "fe80::0000:b418:efd4:5160:0a25%abcdef", + "fe80::c7f5:7f94:4bd9:c35c%abcdef", + "fe80::321f:46c2:0cea:ec54%abcdef", + "fe80::906d:b670:86a2:fd68%abc", + "fe80::b1c2:0000:0039:b598%", + "fe80::07e8:39e6:cb49:9cd4", + "fe80::1%abcdef", + "fe80:0:0:0:202:b3ff:fe1e:8329%abcdef", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329"}; + + const char *invalid_addr[] = {"fe80::8d1d:4d88:68a8:44f8:f3e7%abcdef", + "2001:0db8:85a3::8a2e:0370:7334%abcdef", + "fd00::adf8:7c21:147c:6c97", + "::1%lo", + "fe80::1:4d88:68a8:1200:f3e7%abcdef"}; + + (void)state; + + /* Test valid link-local addresses */ + valid_addr_len = sizeof(valid_addr) / sizeof(valid_addr[0]); + for (i = 0; i < valid_addr_len; i++) { + rc = match_cidr_address_list(valid_addr[i], IPV6_LIST, AF_INET6); + assert_int_equal(rc, 1); + } + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8328/127", + AF_INET6); + assert_int_equal(rc, 1); + + /* Test positive match with unknown host address family */ + rc = match_cidr_address_list("fe80::aadf:b119:507a:986a%abcdef", + IPV6_LIST, + -1); + assert_int_equal(rc, 1); + + /* Test some invalid input */ + invalid_addr_len = sizeof(invalid_addr) / sizeof(invalid_addr[0]); + for (i = 0; i < invalid_addr_len; i++) { + rc = match_cidr_address_list(invalid_addr[i], IPV6_LIST, AF_INET6); + assert_int_equal(rc, 0); + } + + /* Test negative match with unknown host address family */ + rc = match_cidr_address_list("fe80::8d1d:4d88:68a8:44f8:f3e7%abcdef", + IPV6_LIST, + -1); + assert_int_equal(rc, 0); + + /* Test errors */ + rc = match_cidr_address_list("fe80::be50:09ca::2be3", IPV6_LIST, AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:x:202:b3ff:fe1e:8329", + IPV6_LIST, + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80::202:ghfc:zzzz:1a49", + IPV6_LIST, + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329/131", + AF_INET6); + assert_int_equal(rc, -1); + rc = match_cidr_address_list("fe80:0000:0000:0000:0202:b3ff:fe1e:8329", + "fe80:0000:0000:0000:0202:b3ff:fe1e:8329//127", + AF_INET6); + assert_int_equal(rc, -1); + + /* Test invalid input with unknown host address family */ + rc = match_cidr_address_list("fe80::ba67:1002:gffx:zz32", IPV6_LIST, -1); + assert_int_equal(rc, -1); +} + +/** + * @brief Verify the cidr_match_4 function works correctly + */ +static void +torture_match_cidr_v4(void **state) +{ + int af = AF_INET; + (void)state; + + /* Test some matching input */ + assert_true_match_cidr("192.168.1.20", "192.168.1.0", 24, af, 1); + assert_true_match_cidr("172.31.5.128", "172.31.0.0", 16, af, 1); + assert_true_match_cidr("10.0.0.158", "10.0.0.128", 25, af, 1); + assert_true_match_cidr("192.168.255.250", "192.168.255.248", 29, af, 1); + assert_true_match_cidr("122.105.209.57", "122.105.209.48", 28, af, 1); + assert_true_match_cidr("192.168.100.150", "192.168.64.0", 18, af, 1); + + /* Test some not matching input */ + assert_true_match_cidr("172.16.56.30", "172.16.48.0", 21, af, 0); + assert_true_match_cidr("10.18.5.5", "10.10.4.0", 23, af, 0); + assert_true_match_cidr("172.16.32.50", "172.16.0.0", 19, af, 0); + assert_true_match_cidr("203.0.120.10", "203.0.112.0", 21, af, 0); + assert_true_match_cidr("172.31.112.150", "172.31.96.0", 20, af, 0); + assert_true_match_cidr("198.52.20.200", "198.48.0.0", 14, af, 0); +} + +/** + * @brief Verify the cidr_match_6 function works correctly + */ +static void +torture_match_cidr_v6(void **state) +{ + int af = AF_INET6; + (void)state; + + /* Test some matching input */ + assert_true_match_cidr("2001:0db8:85a3:0000:0000:8a2e:0370:7334", + "2001:0db8:85a3:0000::", + 64, + af, + 1); + assert_true_match_cidr("2001:0db8:0000:0042:0000:8a2e:0370:7334", + "2001:0db8:0000::", + 48, + af, + 1); + assert_true_match_cidr("fe80::8a2e:0370:7334", "fe80::", 64, af, 1); + assert_true_match_cidr("fd00::8a2e:0370:7334", "fd00::", 56, af, 1); + assert_true_match_cidr("fe80:0000:0000:0000:0000:0000:fe1e:32ff", + "fe80::", + 96, + af, + 1); + assert_true_match_cidr("2001:0db8:1a2b:3c4d:5e6f:7a8b::18", + "2001:0db8:1a2b:3c4d:5e6f:7a8b::", + 120, + af, + 1); + + /* Test some not matching input */ + assert_true_match_cidr("2001:0db8:1234:5678:9abc:def0:1234:5678", + "2001:0db8:1234:5678::", + 96, + af, + 0); + assert_true_match_cidr("2001:3858:accd::", + "2001:3858:abcd:eaa1::", + 48, + af, + 0); + assert_true_match_cidr("2001:0db8:1234:5678::ff4c", + "2001:0db8:1234:5600::", + 110, + af, + 0); + assert_true_match_cidr("fe80::0001:af12:a1b2:c3d4:e5f7", + "fe80::", + 64, + af, + 0); + assert_true_match_cidr("2001:0db8:84ff:ffff:ffff:ffff:ffff:fffa", + "2001:0db8:8500::", + 80, + af, + 0); + assert_true_match_cidr("::3", "::", 127, af, 0); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_config_match_localnetwork_string, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_config_match_localnetwork_file, + setup, + teardown), + cmocka_unit_test(torture_match_cidr_address_list_ipv4), + cmocka_unit_test(torture_match_cidr_address_list_ipv6), + cmocka_unit_test(torture_match_cidr_v4), + cmocka_unit_test(torture_match_cidr_v6), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, setup, teardown); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_crypto.c b/src/libs/libssh-0.12.2/tests/unittests/torture_crypto.c new file mode 100644 index 000000000000..3f84e19c24a0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_crypto.c @@ -0,0 +1,337 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/crypto.h" +#include "libssh/chacha20-poly1305-common.h" + +uint8_t key[32] = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" + "\x1e\x1f"; + +uint8_t IV[16] = + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e" + "\x1f"; + +uint8_t cleartext[144] = + "\xb4\xfc\x5d\xc2\x49\x8d\x2c\x29\x4a\xc9\x9a\xb0\x1b\xf8\x29" + "\xee\x85\x6d\x8c\x04\x34\x7c\x65\xf4\x89\x97\xc5\x71\x70\x41" + "\x91\x40\x19\x60\xe1\xf1\x8f\x4d\x8c\x17\x51\xd6\xbc\x69\x6e" + "\xf2\x21\x87\x18\x6c\xef\xc4\xf4\xd9\xe6\x1b\x94\xf7\xd8\xb2" + "\xe9\x24\xb9\xe7\xe6\x19\xf5\xec\x55\x80\x9a\xc8\x7d\x70\xa3" + "\x50\xf8\x03\x10\x35\x49\x9b\x53\x58\xd7\x4c\xfc\x5f\x02\xd6" + "\x28\xea\xcc\x43\xee\x5e\x2b\x8a\x7a\x66\xf7\x00\xee\x09\x18" + "\x30\x1b\x47\xa2\x16\x69\xc4\x6e\x44\x3f\xbd\xec\x52\xce\xe5" + "\x41\xf2\xe0\x04\x4f\x5a\x55\x58\x37\xba\x45\x8d\x15\x53\xf6" + "\x31\x91\x13\x8c\x51\xed\x08\x07\xdb"; + +uint8_t aes256_cbc_encrypted[144] = + "\x7f\x1b\x92\xac\xc5\x16\x05\x55\x74\xac\xb4\xe0\x91\x8c\xf8" + "\x0d\xa9\x72\xa5\x09\xb8\x44\xee\x55\x02\x13\xb7\x52\x0a\xf0" + "\xac\xd0\x21\x0e\x58\x7b\x34\xfe\xdb\x36\x01\x60\x7d\x18\x3a" + "\xa9\x15\x18\x5b\x13\xca\xdd\x77\x7d\xdf\x64\xc6\xd5\x75\x4b" + "\x02\x02\x37\xb1\xf4\x33\xff\x93\xe6\x32\x08\xda\xcb\x5d\xa2" + "\x8f\x17\x1f\x99\x92\x60\x22\x9d\x6b\xe6\xb2\x5e\xb0\x5d\x26" + "\x3f\xde\xb8\xc1\xb0\x70\x80\x1c\x00\xd0\x93\x2b\xeb\x0f\xd7" + "\x70\x7a\x9a\x7a\xa6\x21\x23\x2c\x02\xb7\xcd\x88\x10\x9c\x2d" + "\x0c\xd3\xfa\xc1\x33\x5b\xe1\xa1\xd4\x3d\x8f\xb8\x50\xc5\xb5" + "\x72\xdd\x6d\x32\x1f\x58\x00\x48\xbe"; + +static int get_cipher(struct ssh_cipher_struct *cipher, const char *ciphername) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + size_t i; + int cmp; + + assert_non_null(cipher); + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0){ + memcpy(cipher, &ciphers[i], sizeof(*cipher)); + return SSH_OK; + } + } + + return SSH_ERROR; +} + +static void torture_crypto_aes256_cbc(void **state) +{ + uint8_t output[sizeof(cleartext)] = {0}; + uint8_t iv[16] = {0}; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.encrypt); + + memcpy(iv, IV, sizeof(IV)); + cipher.set_encrypt_key(&cipher, + key, + iv + ); + + cipher.encrypt(&cipher, + cleartext, + output, + sizeof(cleartext) + ); + + assert_memory_equal(output, aes256_cbc_encrypted, sizeof(aes256_cbc_encrypted)); + ssh_cipher_clear(&cipher); + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + + assert_non_null(cipher.set_decrypt_key); + assert_non_null(cipher.decrypt); + + memcpy(iv, IV, sizeof(IV)); + cipher.set_decrypt_key(&cipher, + key, + iv + ); + + memset(output, '\0', sizeof(output)); + cipher.decrypt(&cipher, + aes256_cbc_encrypted, + output, + sizeof(aes256_cbc_encrypted) + ); + + assert_memory_equal(output, cleartext, sizeof(cleartext)); + + ssh_cipher_clear(&cipher); +} + +uint8_t chacha20poly1305_key[CHACHA20_KEYLEN*2] = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" + "\x1e\x1f\x20\x21\x22\x23\x24\x25\x26\x27\x28\x29\x2a\x2b\x2c" + "\x2d\x2e\x2f\x30\x31\x32\x33\x34\x35\x36\x37\x38\x39\x3a\x3b" + "\x3c\x3d\x3e\x3f"; + +#define CLEARTEXT_LENGTH 144 +uint8_t chacha20poly1305_cleartext[CLEARTEXT_LENGTH] = + "\xb4\xfc\x5d\xc2\x49\x8d\x2c\x29\x4a\xc9\x9a\xb0\x1b\xf8\x29" + "\xee\x85\x6d\x8c\x04\x34\x7c\x65\xf4\x89\x97\xc5\x71\x70\x41" + "\x91\x40\x19\x60\xe1\xf1\x8f\x4d\x8c\x17\x51\xd6\xbc\x69\x6e" + "\xf2\x21\x87\x18\x6c\xef\xc4\xf4\xd9\xe6\x1b\x94\xf7\xd8\xb2" + "\xe9\x24\xb9\xe7\xe6\x19\xf5\xec\x55\x80\x9a\xc8\x7d\x70\xa3" + "\x50\xf8\x03\x10\x35\x49\x9b\x53\x58\xd7\x4c\xfc\x5f\x02\xd6" + "\x28\xea\xcc\x43\xee\x5e\x2b\x8a\x7a\x66\xf7\x00\xee\x09\x18" + "\x30\x1b\x47\xa2\x16\x69\xc4\x6e\x44\x3f\xbd\xec\x52\xce\xe5" + "\x41\xf2\xe0\x04\x4f\x5a\x55\x58\x37\xba\x45\x8d\x15\x53\xf6" + "\x31\x91\x13\x8c\x51\xed\x08\x07\xdb"; + +uint64_t chacha20poly1305_seq = (uint64_t)1234567890 * 98765431; + +uint8_t chacha20poly1305_encrypted[sizeof(uint32_t) + CLEARTEXT_LENGTH + POLY1305_TAGLEN] = + "\xac\x2e\x4c\x54\xf6\x97\x75\xb4\x3b\x8f\xb0\x8e\xb0\x0a\x8e" + "\xb3\x90\x21\x0d\x7a\xb6\xd3\x03\xf6\xbc\x6e\x3a\x32\x67\xe1" + "\x13\x65\x43\x3b\x34\x9d\xcb\x62\x7e\x0a\x80\xb0\x45\x87\x07" + "\x85\x49\x8d\x23\x5f\xac\x9c\x8b\xa8\xd5\x01\x12\xfe\x52\xc6" + "\x99\xb4\xf2\xde\x12\x78\x79\xea\x1c\x5f\x45\xcd\xf7\xe4\xa0" + "\x66\x15\x7f\xe3\xf4\x73\x3b\xe0\x52\xac\x2a\x00\x73\xd0\xd7" + "\x95\xa9\xb9\x3a\xe0\x50\x13\xf4\xdc\xfc\x2a\x64\xb5\xcf\x29" + "\x88\xef\x4c\x56\x10\x30\x28\xbb\x59\xb8\x23\x58\xab\x01\xa2" + "\xab\x6b\xdd\xee\x20\x43\xe1\xec\x7a\xe1\xaa\x8b\x60\x19\xde" + "\x3a\xd1\xd6\x80\x49\x7d\x5c\x81\xb8\x96\xad\x62\x32\xe5\x25" + "\x72\xe9\x63\x96\xa1\x44\x25\x91\xe1\xdc\x01\xc7\x5c\xa9"; + +static void torture_crypto_chacha20poly1305(void **state) +{ + uint8_t input[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)]; + uint8_t output[sizeof(input) + POLY1305_TAGLEN] = {0}; + uint8_t *outtag = output + sizeof(input); + struct ssh_cipher_struct cipher = {0}; + uint32_t in_length; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + assert_int_equal(sizeof(output), sizeof(chacha20poly1305_encrypted)); + + in_length = htonl(sizeof(chacha20poly1305_cleartext)); + memcpy(input, &in_length, sizeof(uint32_t)); + memcpy(input + sizeof(uint32_t), chacha20poly1305_cleartext, + sizeof(chacha20poly1305_cleartext)); + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(sizeof(chacha20poly1305_key) * 8, cipher.keysize); + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.aead_encrypt); + + rc = cipher.set_encrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + cipher.aead_encrypt(&cipher, input, output, sizeof(input), outtag, + chacha20poly1305_seq); + assert_memory_equal(output, chacha20poly1305_encrypted, + sizeof(chacha20poly1305_encrypted)); + ssh_cipher_clear(&cipher); + + memset(output, '\0', sizeof(output)); + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + assert_non_null(cipher.set_decrypt_key); + assert_non_null(cipher.aead_decrypt); + assert_non_null(cipher.aead_decrypt_length); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, chacha20poly1305_encrypted, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, chacha20poly1305_encrypted, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + assert_memory_equal(output, input, sizeof(input)); + + ssh_cipher_clear(&cipher); +} + +static void torture_crypto_chacha20poly1305_bad_packet_length(void **state) +{ + uint8_t output[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)] = {0}; + uint8_t encrypted_bad[sizeof(chacha20poly1305_encrypted)]; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + /* Test corrupted packet length */ + memcpy(encrypted_bad, chacha20poly1305_encrypted, sizeof(encrypted_bad)); + encrypted_bad[1] ^= 1; + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, encrypted_bad, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, encrypted_bad, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_ERROR); + + ssh_cipher_clear(&cipher); +} + +static void torture_crypto_chacha20poly1305_bad_data(void **state) +{ + uint8_t output[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)] = {0}; + uint8_t encrypted_bad[sizeof(chacha20poly1305_encrypted)]; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + /* Test corrupted data */ + memcpy(encrypted_bad, chacha20poly1305_encrypted, sizeof(encrypted_bad)); + encrypted_bad[100] ^= 1; + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, encrypted_bad, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, encrypted_bad, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_ERROR); + + ssh_cipher_clear(&cipher); +} + +static void torture_crypto_chacha20poly1305_bad_tag(void **state) +{ + uint8_t output[sizeof(uint32_t) + sizeof(chacha20poly1305_cleartext)] = {0}; + uint8_t encrypted_bad[sizeof(chacha20poly1305_encrypted)]; + struct ssh_cipher_struct cipher = {0}; + int rc; + (void)state; + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + /* Test corrupted tag */ + assert_int_equal(sizeof(encrypted_bad), sizeof(chacha20poly1305_encrypted)); + memcpy(encrypted_bad, chacha20poly1305_encrypted, sizeof(encrypted_bad)); + encrypted_bad[sizeof(encrypted_bad) - 1] ^= 1; + + rc = get_cipher(&cipher, "chacha20-poly1305@openssh.com"); + assert_int_equal(rc, SSH_OK); + + rc = cipher.set_decrypt_key(&cipher, chacha20poly1305_key, NULL); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt_length(&cipher, encrypted_bad, + output, sizeof(uint32_t), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_OK); + + rc = cipher.aead_decrypt(&cipher, encrypted_bad, + output + sizeof(uint32_t), sizeof(cleartext), + chacha20poly1305_seq); + assert_int_equal(rc, SSH_ERROR); + + ssh_cipher_clear(&cipher); +} + +int torture_run_tests(void) { + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_crypto_aes256_cbc), + cmocka_unit_test(torture_crypto_chacha20poly1305), + cmocka_unit_test(torture_crypto_chacha20poly1305_bad_packet_length), + cmocka_unit_test(torture_crypto_chacha20poly1305_bad_data), + cmocka_unit_test(torture_crypto_chacha20poly1305_bad_tag), + }; + + ssh_init(); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_forwarded_tcpip_callback.c b/src/libs/libssh-0.12.2/tests/unittests/torture_forwarded_tcpip_callback.c new file mode 100644 index 000000000000..cf80c87b85f4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_forwarded_tcpip_callback.c @@ -0,0 +1,336 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" + +#include + +#define TEST_SERVER_HOST "127.0.0.1" +#define TEST_SERVER_PORT 2222 +#define TEST_DEST_HOST "127.0.0.1" +#define TEST_DEST_PORT 12345 +#define TEST_ORIG_HOST "127.0.0.1" +#define TEST_ORIG_PORT 54321 + +struct hostkey_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +struct server_thread_args { + struct hostkey_state *h; + bool should_accept; +}; + +static bool is_server_ready = false; +static pthread_mutex_t server_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t server_cond = PTHREAD_COND_INITIALIZER; + +static bool client_callbacks_initialised = false; +static pthread_mutex_t client_mutex = PTHREAD_MUTEX_INITIALIZER; +static pthread_cond_t client_cond = PTHREAD_COND_INITIALIZER; + +static int setup(void **state) +{ + struct hostkey_state *h = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + h = (struct hostkey_state *)malloc(sizeof(struct hostkey_state)); + assert_non_null(h); + + h->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + assert_non_null(h->hostkey_path); + + mask = umask(S_IRWXO | S_IRWXG); + h->fd = mkstemp(h->hostkey_path); + umask(mask); + assert_return_code(h->fd, errno); + close(h->fd); + + h->key_type = SSH_KEYTYPE_ECDSA_P256; + h->hostkey = torture_get_testkey(h->key_type, 0); + + torture_write_file(h->hostkey_path, h->hostkey); + + *state = h; + + /* Reset before every test */ + is_server_ready = false; + client_callbacks_initialised = false; + + return 0; +} + +static int teardown(void **state) +{ + struct hostkey_state *h = (struct hostkey_state *)*state; + + unlink(h->hostkey_path); + free(h->hostkey_path); + free(h); + + ssh_finalize(); + + return 0; +} + +static int auth_password_accept(ssh_session session, + const char *user, + const char *password, + void *userdata) +{ + /* unused */ + (void)session; + (void)user; + (void)password; + (void)userdata; + + return SSH_AUTH_SUCCESS; +} + +static void *server_thread(void *arg) +{ + struct server_thread_args *args = (struct server_thread_args *)arg; + struct hostkey_state *h = args->h; + bool should_accept = args->should_accept; + ssh_bind sshbind = NULL; + ssh_session server = NULL; + ssh_channel channel = NULL; + ssh_event event = NULL; + int rc; + + struct ssh_server_callbacks_struct server_cb = { + .auth_password_function = auth_password_accept, + }; + ssh_callbacks_init(&server_cb); + + /* Create server */ + sshbind = torture_ssh_bind(TEST_SERVER_HOST, + TEST_SERVER_PORT, + h->key_type, + h->hostkey_path); + assert_non_null(sshbind); + + server = ssh_new(); + assert_non_null(server); + + rc = ssh_set_server_callbacks(server, &server_cb); + assert_int_equal(rc, SSH_OK); + + /* Signal that the server is ready */ + pthread_mutex_lock(&server_mutex); + is_server_ready = true; + pthread_cond_signal(&server_cond); + pthread_mutex_unlock(&server_mutex); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + rc = ssh_handle_key_exchange(server); + assert_int_equal(rc, SSH_OK); + + /* Handle client connection */ + event = ssh_event_new(); + assert_non_null(event); + + rc = ssh_event_add_session(event, server); + assert_int_equal(rc, SSH_OK); + + /* Poll until authentication is complete */ + while (server->session_state != SSH_SESSION_STATE_AUTHENTICATED) { + rc = ssh_event_dopoll(event, -1); + if (rc == SSH_ERROR) { + break; + } + } + + /* Cleanup the event */ + ssh_event_free(event); + + /* Wait for client callbacks to be initialized before proceeding */ + pthread_mutex_lock(&client_mutex); + while (!client_callbacks_initialised) { + pthread_cond_wait(&client_cond, &client_mutex); + } + pthread_mutex_unlock(&client_mutex); + + channel = ssh_channel_new(server); + assert_non_null(channel); + + rc = ssh_channel_open_reverse_forward(channel, + TEST_DEST_HOST, + TEST_DEST_PORT, + TEST_ORIG_HOST, + TEST_ORIG_PORT); + if (should_accept) { + assert_int_equal(rc, SSH_OK); + } else { + assert_int_equal(rc, SSH_ERROR); + } + + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_bind_free(sshbind); + ssh_free(server); + + return NULL; +} + +struct channel_data { + /* Whether the callback should accept the channel open request */ + bool should_accept; + + int req_seen; + char *dest_host; + uint32_t dest_port; + char *orig_host; + uint32_t orig_port; +}; + +static ssh_channel channel_forwarded_tcpip_callback(ssh_session session, + const char *dest_host, + int dest_port, + const char *orig_host, + int orig_port, + void *userdata) +{ + struct channel_data *channel_data = (struct channel_data *)userdata; + ssh_channel channel = NULL; + + /* Record that we've seen a forwarded-tcpip request and store the parameters + */ + channel_data->req_seen = 1; + channel_data->dest_host = strdup(dest_host); + channel_data->dest_port = dest_port; + channel_data->orig_host = strdup(orig_host); + channel_data->orig_port = orig_port; + + /* Create and return a new channel for this request */ + if (channel_data->should_accept) { + channel = ssh_channel_new(session); + } + + return channel; +} + +static void torture_forwarded_tcpip_callback(void **state, bool should_accept) +{ + int rc, event_rc; + pthread_t server_pthread; + ssh_session session = NULL; + ssh_event event = NULL; + struct channel_data channel_data; + unsigned int server_port = TEST_SERVER_PORT; + + struct server_thread_args args = { + .h = (struct hostkey_state *)*state, + .should_accept = should_accept, + }; + + struct ssh_callbacks_struct client_cb = { + .userdata = &channel_data, + .channel_open_request_forwarded_tcpip_function = + channel_forwarded_tcpip_callback, + }; + ssh_callbacks_init(&client_cb); + + memset(&channel_data, 0, sizeof(channel_data)); + channel_data.should_accept = should_accept; + + rc = pthread_create(&server_pthread, NULL, server_thread, &args); + assert_return_code(rc, errno); + + /* Wait for the server to be ready using condition variable */ + pthread_mutex_lock(&server_mutex); + while (!is_server_ready) { + pthread_cond_wait(&server_cond, &server_mutex); + } + pthread_mutex_unlock(&server_mutex); + + session = + torture_ssh_session(NULL, "127.0.0.1", &server_port, "foo", "bar"); + assert_non_null(session); + + rc = ssh_set_callbacks(session, &client_cb); + assert_int_equal(rc, SSH_OK); + + event = ssh_event_new(); + assert_non_null(event); + + rc = ssh_event_add_session(event, session); + assert_int_equal(rc, SSH_OK); + + /* Signal that client callbacks are initialized */ + pthread_mutex_lock(&client_mutex); + client_callbacks_initialised = true; + pthread_cond_signal(&client_cond); + pthread_mutex_unlock(&client_mutex); + + event_rc = SSH_OK; + while (channel_data.req_seen != 1 && event_rc == SSH_OK) { + event_rc = ssh_event_dopoll(event, -1); + } + + /* Cleanup */ + ssh_event_free(event); + ssh_free(session); + + rc = pthread_join(server_pthread, NULL); + assert_int_equal(rc, 0); + + /* Verify forwarded-tcpip request parameters */ + assert_true(channel_data.req_seen); + assert_string_equal(channel_data.dest_host, TEST_DEST_HOST); + assert_int_equal(channel_data.dest_port, TEST_DEST_PORT); + assert_string_equal(channel_data.orig_host, TEST_ORIG_HOST); + assert_int_equal(channel_data.orig_port, TEST_ORIG_PORT); + + /* Free allocated memory */ + free(channel_data.dest_host); + free(channel_data.orig_host); +} + +static void torture_forwarded_tcpip_callback_success(void **state) +{ + torture_forwarded_tcpip_callback(state, true); +} + +static void torture_forwarded_tcpip_callback_failure(void **state) +{ + torture_forwarded_tcpip_callback(state, false); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_forwarded_tcpip_callback_success, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_forwarded_tcpip_callback_failure, + setup, + teardown), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_hashes.c b/src/libs/libssh-0.12.2/tests/unittests/torture_hashes.c new file mode 100644 index 000000000000..a243471a4184 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_hashes.c @@ -0,0 +1,161 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" +#include "legacy.c" +#include "dh.c" + +static int setup_rsa_key(void **state) +{ + int rc=0; + enum ssh_keytypes_e type; + char *b64_key, *p; + ssh_key key; + + const char *q; + + b64_key = strdup(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_non_null(b64_key); + + q = p = b64_key; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_RSA); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + + free(b64_key); + *state = key; + + return 0; +} + +static int teardown(void **state) +{ + SSH_KEY_FREE(*state); + return 0; +} + +static void torture_md5_hash(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *hexa = NULL; + size_t hlen; + int rc = 0; + +#if defined(HAVE_LIBCRYPTO) && OPENSSL_VERSION_NUMBER < 0x30000000L + /* In FIPS mode without OpenSSL providers, we cannot use MD5 */ + if (ssh_fips_mode()) { + skip(); + } +#endif + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_MD5, + (unsigned char **)&hash, &hlen); + assert_int_equal(rc, SSH_OK); + + hexa = ssh_get_hexa((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(hexa, + "50:15:a0:9b:92:bf:33:1c:01:c5:8c:fe:18:fa:ce:78"); + SSH_STRING_FREE_CHAR(hexa); +} + +static void torture_sha1_hash(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *sha1 = NULL; + int rc = 0; + size_t hlen; + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA1, + (unsigned char **)&hash, &hlen); + assert_true(rc == 0); + + sha1 = ssh_get_b64_unpadded((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(sha1, "6wP+houujQmxLBiFugTcoeoODCM"); + + SSH_STRING_FREE_CHAR(sha1); +} + +static void torture_sha256_hash(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *sha256 = NULL; + int rc = 0; + size_t hlen; + + rc = ssh_get_publickey_hash(pubkey, SSH_PUBLICKEY_HASH_SHA256, + (unsigned char **)&hash, &hlen); + assert_true(rc == 0); + + sha256 = ssh_get_b64_unpadded((unsigned char *)hash, hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(sha256, "jXstVLLe84fSDo1kEYGn6iumnPCSorhaiWxnJz8VTII"); + + SSH_STRING_FREE_CHAR(sha256); + +} + +static void torture_sha256_fingerprint(void **state) +{ + ssh_key pubkey = *state; + char *hash = NULL; + char *sha256 = NULL; + int rc = 0; + size_t hlen; + + rc = ssh_get_publickey_hash(pubkey, + SSH_PUBLICKEY_HASH_SHA256, + (unsigned char **)&hash, + &hlen); + assert_true(rc == 0); + + sha256 = ssh_get_fingerprint_hash(SSH_PUBLICKEY_HASH_SHA256, + (unsigned char *)hash, + hlen); + SSH_STRING_FREE_CHAR(hash); + assert_string_equal(sha256, + "SHA256:jXstVLLe84fSDo1kEYGn6iumnPCSorhaiWxnJz8VTII"); + + SSH_STRING_FREE_CHAR(sha256); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_md5_hash, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_sha1_hash, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_sha256_hash, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_sha256_fingerprint, + setup_rsa_key, + teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_init.c b/src/libs/libssh-0.12.2/tests/unittests/torture_init.c new file mode 100644 index 000000000000..f719fc999500 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_init.c @@ -0,0 +1,69 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include "torture.h" +#include "libssh/libssh.h" + +static void torture_ssh_init(void **state) { + int rc; + + (void) state; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); +} + +static void torture_ssh_init_after_finalize(void **state) { + + int rc; + + (void) state; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); +} + +static void torture_is_ssh_initialized(UNUSED_PARAM(void **state)) { + + int rc; + bool initialized = false; + + /* Make sure the library is not initialized */ + while (is_ssh_initialized()) { + rc = ssh_finalize(); + assert_return_code(rc, errno); + } + + rc = ssh_init(); + assert_return_code(rc, errno); + initialized = is_ssh_initialized(); + assert_true(initialized); + rc = ssh_finalize(); + assert_return_code(rc, errno); + initialized = is_ssh_initialized(); + assert_false(initialized); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_init), + cmocka_unit_test(torture_ssh_init_after_finalize), + cmocka_unit_test(torture_is_ssh_initialized), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_isipaddr.c b/src/libs/libssh-0.12.2/tests/unittests/torture_isipaddr.c new file mode 100644 index 000000000000..91fd5a153909 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_isipaddr.c @@ -0,0 +1,66 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" + +#include "misc.c" +#include "error.c" + +/* + * Test the behavior of ssh_is_ipaddr() + */ +static void torture_ssh_is_ipaddr(void **state) +{ + (void)state; + + assert_int_equal(ssh_is_ipaddr("127.0.0.1"),1); + assert_int_equal(ssh_is_ipaddr("0.0.0.0"),1); + assert_int_equal(ssh_is_ipaddr("1.1.1.1"),1); + assert_int_equal(ssh_is_ipaddr("255.255.255.255"),1); + assert_int_equal(ssh_is_ipaddr("128.128.128.128"),1); + assert_int_equal(ssh_is_ipaddr("1.10.100.1"),1); + assert_int_equal(ssh_is_ipaddr("0.1.10.100"),1); + + assert_int_equal(ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334"),1); + assert_int_equal(ssh_is_ipaddr("fe80:0000:0000:0000:0202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("fe80:0:0:0:202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("fe80::202:b3ff:fe1e:8329"),1); + assert_int_equal(ssh_is_ipaddr("::1"),1); + + assert_int_equal(ssh_is_ipaddr("::ffff:192.0.2.128"),1); + + assert_int_equal(ssh_is_ipaddr("0.0.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr("0.0.0.0.a"),0); + assert_int_equal(ssh_is_ipaddr("a.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr("0a.0.0.0.0"),0); + assert_int_equal(ssh_is_ipaddr(""),0); + assert_int_equal(ssh_is_ipaddr("0.0.0."),0); + assert_int_equal(ssh_is_ipaddr("0.0"),0); + assert_int_equal(ssh_is_ipaddr("0"),0); + + /* + * FIXME: Temporary workaround for Wine bug + */ +#ifndef _WIN32 + assert_int_equal(ssh_is_ipaddr("255.255.255"),0); +#endif + + assert_int_equal(ssh_is_ipaddr("2001:0db8:85a3:0000:0000:8a2e:0370:7334:1002"), 0); + assert_int_equal(ssh_is_ipaddr("fe80:x:202:b3ff:fe1e:8329"), 0); + assert_int_equal(ssh_is_ipaddr("fe80:x:202:b3ff:fe1e:8329"), 0); + assert_int_equal(ssh_is_ipaddr(":1"), 0); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_is_ipaddr) + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_keyfiles.c b/src/libs/libssh-0.12.2/tests/unittests/torture_keyfiles.c new file mode 100644 index 000000000000..2ce47b72bedd --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_keyfiles.c @@ -0,0 +1,256 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "torture_key.h" +#include "legacy.c" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" + +static int setup_rsa_key(void **state) +{ + ssh_session session; + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + session = ssh_new(); + *state = session; + + return 0; +} + +static int setup_both_keys(void **state) { + int rc; + + rc = setup_rsa_key(state); + if (rc != 0) { + return rc; + } + + return rc; +} + +static int setup_both_keys_passphrase(void **state) +{ + ssh_session session; + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + session = ssh_new(); + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + + unlink(LIBSSH_RSA_TESTKEY); + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + ssh_free(*state); + + return 0; +} + +static void torture_pubkey_from_file(void **state) { + ssh_session session = *state; + ssh_string pubkey = NULL; + int type, rc; + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + + assert_true(rc == 0); + + SSH_STRING_FREE(pubkey); + + /* test if it returns 1 if pubkey doesn't exist */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + assert_true(rc == 1); + + /* This free is unnecessary, but the static analyser does not know */ + SSH_STRING_FREE(pubkey); + + /* test if it returns -1 if privkey doesn't exist */ + unlink(LIBSSH_RSA_TESTKEY); + + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey, &type); + assert_true(rc == -1); + + /* This free is unnecessary, but the static analyser does not know */ + SSH_STRING_FREE(pubkey); +} + +static int torture_read_one_line(const char *filename, char *buffer, size_t len) +{ + FILE *fp; + size_t nmemb; + + fp = fopen(filename, "r"); + if (fp == NULL) { + return -1; + } + + nmemb = fread(buffer, len - 2, 1, fp); + if (nmemb != 0 || ferror(fp)) { + fclose(fp); + return -1; + } + buffer[len - 1] = '\0'; + + fclose(fp); + + return 0; +} + +static void torture_pubkey_generate_from_privkey(void **state) { + ssh_session session = *state; + ssh_private_key privkey = NULL; + ssh_public_key pubkey = NULL; + ssh_string pubkey_orig = NULL; + ssh_string pubkey_new = NULL; + char pubkey_line_orig[512] = {0}; + char pubkey_line_new[512] = {0}; + char *p; + int type_orig = 0; + int type_new = 0; + int rc; + + /* read the publickey */ + rc = ssh_try_publickey_from_file(session, LIBSSH_RSA_TESTKEY, &pubkey_orig, + &type_orig); + assert_true(rc == 0); + assert_non_null(pubkey_orig); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_line_orig, + sizeof(pubkey_line_orig)); + assert_true(rc == 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + privkey = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, NULL); + assert_non_null(privkey); + + pubkey = publickey_from_privatekey(privkey); + assert_non_null(pubkey); + type_new = privkey->type; + privatekey_free(privkey); + + pubkey_new = publickey_to_string(pubkey); + publickey_free(pubkey); + + assert_non_null(pubkey_new); + + assert_true(ssh_string_len(pubkey_orig) == ssh_string_len(pubkey_new)); + assert_memory_equal(ssh_string_data(pubkey_orig), + ssh_string_data(pubkey_new), + ssh_string_len(pubkey_orig)); + + rc = ssh_publickey_to_file(session, LIBSSH_RSA_TESTKEY ".pub", pubkey_new, type_new); + assert_true(rc == 0); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", pubkey_line_new, + sizeof(pubkey_line_new)); + assert_true(rc == 0); + + /* do not compare hostname */ + p = strrchr(pubkey_line_orig, ' '); + if (p != NULL) { + *p = '\0'; + } + p = strrchr(pubkey_line_new, ' '); + if (p != NULL) { + *p = '\0'; + } + + assert_string_equal(pubkey_line_orig, pubkey_line_new); + + SSH_STRING_FREE(pubkey_orig); + SSH_STRING_FREE(pubkey_new); +} + +/** + * @brief tests the privatekey_from_file function without passphrase + */ +static void torture_privatekey_from_file(void **state) { + ssh_session session = *state; + ssh_private_key key = NULL; + + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, SSH_KEYTYPE_RSA, NULL); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + /* Test the automatic type discovery */ + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, NULL); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + +} + +/** + * @brief tests the privatekey_from_file function with passphrase + */ +static void torture_privatekey_from_file_passphrase(void **state) { + ssh_session session = *state; + ssh_private_key key = NULL; + + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, SSH_KEYTYPE_RSA, TORTURE_TESTKEY_PASSWORD); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + + /* Test the automatic type discovery */ + key = privatekey_from_file(session, LIBSSH_RSA_TESTKEY, 0, TORTURE_TESTKEY_PASSWORD); + assert_non_null(key); + if (key != NULL) { + privatekey_free(key); + key = NULL; + } + +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pubkey_from_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pubkey_generate_from_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_privatekey_from_file, + setup_both_keys, + teardown), + cmocka_unit_test_setup_teardown(torture_privatekey_from_file_passphrase, + setup_both_keys_passphrase, + teardown), + }; + + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_knownhosts_parsing.c b/src/libs/libssh-0.12.2/tests/unittests/torture_knownhosts_parsing.c new file mode 100644 index 000000000000..e815ae1d1794 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_knownhosts_parsing.c @@ -0,0 +1,828 @@ +#include "config.h" + +#include + +#define LIBSSH_STATIC +#include + +#include "knownhosts.c" + +#include "torture.h" +#if (defined _WIN32) || (defined _WIN64) +#ifndef S_IRWXO +#define S_IRWXO 0 +#endif +#ifndef S_IRWXG +#define S_IRWXG 0 +#endif +#endif + +#define LOCALHOST_DSS_LINE "localhost,127.0.0.1 ssh-dss AAAAB3NzaC1kc3MAAACBAIK3RTEWBw+rAPcYUM2Qq4kEw59gXpUQ/WvkdeY7QDO64MHaaorySj8xsraNudmQFh4xb/i5Q1EMnNchOFxtilfU5bUJgdTvetyZEWFL+2HxqBs8GaWRyB1vtSFAw3GO8VUEnjF844N3dNyLoc0NX8IvzwNIaQho6KTsueQlG1X9AAAAFQCXUl4a5UvElL4thi/8QlxR5PtEewAAAIBqNpl5MTBxKQu5jT0+WASa7pAqwT53ofv7ZTDIEokYRb57/nwzDgkcs1fsBRrI6eczJ/VlXWwKbsgkx2Nh3ZiWYwC+HY5uqRpDaj3HERC6LMn4dzdcl29fYeziEibCbRjJX5lZF2vIaA1Ewv8yT0UlunyHZRiyw4WlEglkf/NITAAAAIBxLsdBBXn+8qEYwWK9KT+arRqNXC/lrl0Fp5YyxGNGCv82JcnuOShGGTzhYf8AtTCY1u5oixiW9kea6KXGAKgTjfJShr7n47SZVfOPOrBT3VLhRdGGO3GblDUppzfL8wsEdoqXjzrJuxSdrGnkFu8S9QjkPn9dCtScvWEcluHqMw==" +#define LOCALHOST_RSA_LINE "localhost,127.0.0.1 ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDD7g+vV5cvxxGN0Ldmda4WZCPgRaxV1tV+1KRZoGUNUI61h0X4bmmGaAPRQBCz4G1d9bawqDqEqnpFWazrxBU5cQtISSjzuDJKovLGliky/ShTszee1Thszg3qVNk9gGOWj7jn/HDaOxRlp003Bp47MOdnMnK/oftllFDfY2fF5IRpE6sSIGtg2ZDtF95TV5/9W2oMOIAy8u/83tuibYlNPa1X/von5LgdaPLn6Bk16bQKIhAhlMtFZH8MBYEWe4ZtOGaSWKOsK9MM/RTMlwPi6PkfoHNl4MCMupjx+CdLXwbQEt9Ww+bBIaCui2VWBEiruVbIgJh0W2Tal0e2BzYZ What a Wurst!" +#define LOCALHOST_ECDSA_SHA1_NISTP256_LINE "localhost ecdsa-sha2-nistp256 AAAAE2VjZHNhLXNoYTItbmlzdHAyNTYAAAAIbmlzdHAyNTYAAABBBFWmI0n0Tn5+zR7pPGcKYszRbJ/T0T3QfzRBSMMiyebGKRY8tjkU5h2l/UMugzOrOyWqMGQDgQn+a0aMunhKMg0=" +#define LOCALHOST_DEFAULT_ED25519 "localhost ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_PORT_ED25519 "[localhost]:2222 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_PATTERN_ED25519 "local* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_HASHED_ED25519 "|1|ayWjmTf9mYgj7PuQNVOa7Lqkj5s=|hkbEh8FN6IkLo6t6GQGuBwamgsM= ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_PORT_WILDCARD "[localhost]:* ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" +#define LOCALHOST_STANDARD_PORT "[localhost]:22 ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIA7M22fXD7OiS7kGMXP+OoIjCa+J+5sq8SgAZfIOmDgM" + +#define TMP_FILE_NAME "/tmp/known_hosts_XXXXXX" + +const char template[] = "temp_dir_XXXXXX"; + +static int setup_knownhosts_file(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_PATTERN_ED25519, + sizeof(char), + strlen(LOCALHOST_PATTERN_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_PATTERN_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite(LOCALHOST_RSA_LINE, + sizeof(char), + strlen(LOCALHOST_RSA_LINE), + fp); + if (nwritten != strlen(LOCALHOST_RSA_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static int setup_knownhosts_file_duplicate(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + /* ed25519 key */ + nwritten = fwrite(LOCALHOST_PATTERN_ED25519, + sizeof(char), + strlen(LOCALHOST_PATTERN_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_PATTERN_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + + /* RSA key */ + nwritten = fwrite(LOCALHOST_RSA_LINE, + sizeof(char), + strlen(LOCALHOST_RSA_LINE), + fp); + if (nwritten != strlen(LOCALHOST_RSA_LINE)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + + /* ed25519 key again */ + nwritten = fwrite(LOCALHOST_PATTERN_ED25519, + sizeof(char), + strlen(LOCALHOST_PATTERN_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_PATTERN_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static int setup_knownhosts_file_unsupported_type(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_DSS_LINE, + sizeof(char), + strlen(LOCALHOST_DSS_LINE), + fp); + if (nwritten != strlen(LOCALHOST_DSS_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static int teardown_knownhosts_file(void **state) +{ + char *tmp_file = *state; + + if (tmp_file == NULL) { + return -1; + } + + unlink(tmp_file); + SAFE_FREE(tmp_file); + + return 0; +} + +static void torture_knownhosts_parse_line_rsa(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_RSA_LINE, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_RSA); + assert_string_equal(entry->comment, "What a Wurst!"); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); + + rc = ssh_known_hosts_parse_line("127.0.0.1", + LOCALHOST_RSA_LINE, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "127.0.0.1"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_RSA); + assert_string_equal(entry->comment, "What a Wurst!"); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_ecdsa(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_ECDSA_SHA1_NISTP256_LINE, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ECDSA_P256); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_default_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_DEFAULT_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_port_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("[localhost]:2222", + LOCALHOST_PORT_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "[localhost]:2222"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_port_wildcard(void **state) +{ + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_PORT_WILDCARD, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_standard_port(void **state) +{ + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_STANDARD_PORT, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_pattern_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_PATTERN_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_parse_line_hashed_ed25519(void **state) { + struct ssh_knownhosts_entry *entry = NULL; + int rc; + + (void) state; + + rc = ssh_known_hosts_parse_line("localhost", + LOCALHOST_HASHED_ED25519, + &entry); + assert_int_equal(rc, SSH_OK); + + assert_string_equal(entry->hostname, "localhost"); + assert_non_null(entry->unparsed); + assert_non_null(entry->publickey); + assert_int_equal(ssh_key_type(entry->publickey), SSH_KEYTYPE_ED25519); + + SSH_KNOWNHOSTS_ENTRY_FREE(entry); +} + +static void torture_knownhosts_read_file(void **state) +{ + const char *knownhosts_file = *state; + struct ssh_list *entry_list = NULL; + struct ssh_iterator *it = NULL; + struct ssh_knownhosts_entry *entry = NULL; + enum ssh_keytypes_e type; + int rc; + + rc = ssh_known_hosts_read_entries("localhost", + knownhosts_file, + &entry_list); + assert_int_equal(rc, SSH_OK); + assert_non_null(entry_list); + it = ssh_list_get_iterator(entry_list); + assert_non_null(it); + + /* First key in known hosts file is ED25519 */ + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + assert_non_null(entry); + + assert_string_equal(entry->hostname, "localhost"); + type = ssh_key_type(entry->publickey); + assert_int_equal(type, SSH_KEYTYPE_ED25519); + assert_non_null(it->next); + + it = it->next; + + /* Second key in known hosts file is RSA */ + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + assert_non_null(entry); + + assert_string_equal(entry->hostname, "localhost"); + type = ssh_key_type(entry->publickey); + assert_int_equal(type, SSH_KEYTYPE_RSA); + assert_null(it->next); + + it = ssh_list_get_iterator(entry_list); + for (;it != NULL; it = it->next) { + entry = ssh_iterator_value(struct ssh_knownhosts_entry *, it); + SSH_KNOWNHOSTS_ENTRY_FREE(entry); + } + ssh_list_free(entry_list); +} + +static void torture_knownhosts_get_algorithms_names(void **state) +{ + const char *knownhosts_file = *state; + ssh_session session; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256,ssh-rsa"; + char *names = NULL; + bool process_config = false; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + names = ssh_known_hosts_get_algorithms_names(session); + assert_non_null(names); + assert_string_equal(names, expect); + + SAFE_FREE(names); + ssh_free(session); +} + +/* Do not remove this test if we completely remove DSA support! */ +static void torture_knownhosts_get_algorithms_names_unsupported(void **state) +{ + const char *knownhosts_file = *state; + ssh_session session; + char *names = NULL; + bool process_config = false; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + names = ssh_known_hosts_get_algorithms_names(session); + assert_null(names); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms_wanted(void **state) +{ + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; + bool process_config = false; + const char *wanted = "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256," + "rsa-sha2-256,ecdsa-sha2-nistp521"; + const char *expect = "rsa-sha2-256,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256,ecdsa-sha2-nistp521"; + int verbose = 4; + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + /* Set the wanted list of hostkeys, ordered by preference */ + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + assert_string_equal(algo_list, expect); + free(algo_list); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms_negative(UNUSED_PARAM(void **state)) +{ + const char *wanted = NULL; + const char *expect = NULL; + + char *algo_list = NULL; + + char *cwd = NULL; + char *tmp_dir = NULL; + + bool process_config = false; + int verbose = 4; + int rc = 0; + + ssh_session session; + /* Create temporary directory */ + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbose); + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + + /* Test with unknown key type in known_hosts */ + wanted = "rsa-sha2-256"; + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + torture_write_file("unknown_key_type", "localhost unknown AAAABBBBCCCC"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "unknown_key_type"); + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + assert_string_equal(algo_list, wanted); + SAFE_FREE(algo_list); + + /* Test with unsupported, but existing types */ + wanted = "rsa-sha2-256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com"; + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + algo_list = ssh_client_select_hostkeys(session); + assert_null(algo_list); + + /* In FIPS mode, test filtering keys not allowed */ + if (ssh_fips_mode()) { + wanted = "ssh-ed25519,rsa-sha2-256,ssh-rsa"; + expect = "rsa-sha2-256"; + ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, wanted); + torture_write_file("no_fips", LOCALHOST_DEFAULT_ED25519); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "no_fips"); + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + assert_string_equal(algo_list, expect); + SAFE_FREE(algo_list); + } + + ssh_free(session); + + /* Teardown */ + rc = torture_change_dir(cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(tmp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(tmp_dir); + SAFE_FREE(cwd); +} + +#ifndef _WIN32 /* There is no /dev/null on Windows */ +static void torture_knownhosts_host_exists(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + + /* This makes sure the system's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "/dev/null"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will not fail when the system's known_hosts is + * not accessible*/ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "./unaccessible"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will fail for an unknown host */ + ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + ssh_free(session); +} + +static void torture_knownhosts_host_exists_global(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + + session = ssh_new(); + assert_non_null(session); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + /* This makes sure the user's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will not fail when the user's known_hosts is + * not accessible*/ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "./unaccessible"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_OK); + + /* This makes sure the check will fail for an unknown host */ + ssh_options_set(session, SSH_OPTIONS_HOST, "wurstbrot"); + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_UNKNOWN); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms(void **state) +{ + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; + bool process_config = false; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256," + "sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com"; + const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, knownhosts_file); + /* This makes sure the system's known_hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, "/dev/null"); + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + if (ssh_fips_mode()) { + assert_string_equal(algo_list, expect_fips); + } else { + assert_string_equal(algo_list, expect); + } + free(algo_list); + + ssh_free(session); +} + +static void torture_knownhosts_algorithms_global(void **state) +{ + const char *knownhosts_file = *state; + char *algo_list = NULL; + ssh_session session; + bool process_config = false; + const char *expect = "ssh-ed25519,rsa-sha2-512,rsa-sha2-256," + "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256," + "sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com"; + const char *expect_fips = "rsa-sha2-512,rsa-sha2-256,ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384,ecdsa-sha2-nistp256"; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + /* This makes sure the current-user's known hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + algo_list = ssh_client_select_hostkeys(session); + assert_non_null(algo_list); + if (ssh_fips_mode()) { + assert_string_equal(algo_list, expect_fips); + } else { + assert_string_equal(algo_list, expect); + } + free(algo_list); + + ssh_free(session); +} + +static int setup_bad_knownhosts_file(void **state) +{ + char *tmp_file = NULL; + size_t nwritten; + FILE *fp = NULL; + int rc = 0; + + tmp_file = torture_create_temp_file(TMP_FILE_NAME); + assert_non_null(tmp_file); + + *state = tmp_file; + + fp = fopen(tmp_file, "w"); + assert_non_null(fp); + + nwritten = fwrite(LOCALHOST_DEFAULT_ED25519, + sizeof(char), + strlen(LOCALHOST_DEFAULT_ED25519), + fp); + if (nwritten != strlen(LOCALHOST_DEFAULT_ED25519)) { + rc = -1; + goto close_fp; + } + + nwritten = fwrite("\n", sizeof(char), 1, fp); + if (nwritten != 1) { + rc = -1; + goto close_fp; + } + +#define LOCALHOST_BAD_LINE "localhost \n" + nwritten = fwrite(LOCALHOST_BAD_LINE, + sizeof(char), + strlen(LOCALHOST_BAD_LINE), + fp); + if (nwritten != strlen(LOCALHOST_BAD_LINE)) { + rc = -1; + goto close_fp; + } + +close_fp: + fclose(fp); + + return rc; +} + +static void torture_knownhosts_has_entry(void **state) +{ + const char *knownhosts_file = *state; + enum ssh_known_hosts_e found; + ssh_session session; + bool process_config = false; + struct ssh_knownhosts_entry *entry = NULL; + + session = ssh_new(); + assert_non_null(session); + + /* This makes sure the global configuration file is not processed */ + ssh_options_set(session, SSH_OPTIONS_PROCESS_CONFIG, &process_config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + /* This makes sure the current-user's known hosts are not used */ + ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, "/dev/null"); + ssh_options_set(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, knownhosts_file); + + /* Error is expected -- this tests the memory is not leaked from this + * test case */ + found = ssh_session_has_known_hosts_entry(session); + assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR); + + found = ssh_session_get_known_hosts_entry(session, &entry); + assert_int_equal(found, SSH_KNOWN_HOSTS_ERROR); + assert_null(entry); + + ssh_free(session); +} +#endif /* _WIN32 There is no /dev/null on Windows */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_knownhosts_parse_line_rsa), + cmocka_unit_test(torture_knownhosts_parse_line_ecdsa), + cmocka_unit_test(torture_knownhosts_parse_line_default_ed25519), + cmocka_unit_test(torture_knownhosts_parse_line_port_ed25519), + cmocka_unit_test(torture_knownhosts_parse_line_port_wildcard), + cmocka_unit_test(torture_knownhosts_parse_line_standard_port), + cmocka_unit_test(torture_knownhosts_parse_line_pattern_ed25519), + cmocka_unit_test(torture_knownhosts_parse_line_hashed_ed25519), + cmocka_unit_test_setup_teardown(torture_knownhosts_read_file, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_read_file, + setup_knownhosts_file_duplicate, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_get_algorithms_names_unsupported, + setup_knownhosts_file_unsupported_type, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_wanted, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test(torture_knownhosts_algorithms_negative), +#ifndef _WIN32 + cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_host_exists_global, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_algorithms_global, + setup_knownhosts_file, + teardown_knownhosts_file), + cmocka_unit_test_setup_teardown(torture_knownhosts_has_entry, + setup_bad_knownhosts_file, + teardown_knownhosts_file), +#endif + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_list.c b/src/libs/libssh-0.12.2/tests/unittests/torture_list.c new file mode 100644 index 000000000000..663c5516b743 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_list.c @@ -0,0 +1,131 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "error.c" +#include "misc.c" + +static void torture_ssh_list_new(void **state) { + struct ssh_list *xlist; + + (void) state; + + xlist = ssh_list_new(); + + assert_non_null(xlist); + assert_null(xlist->root); + assert_null(xlist->end); + + assert_int_equal(ssh_list_count(xlist), 0); + + ssh_list_free(xlist); +} + +static void torture_ssh_list_append(void **state) { + struct ssh_list *xlist; + int rc; + + (void) state; + + xlist = ssh_list_new(); + assert_non_null(xlist); + + rc = ssh_list_append(xlist, "item1"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item1"); + + rc = ssh_list_append(xlist, "item2"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + rc = ssh_list_append(xlist, "item3"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->root->next); + assert_non_null(xlist->root->next->data); + assert_non_null(xlist->root->next->next); + assert_non_null(xlist->root->next->next->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->root->next->data, "item2"); + assert_string_equal((const char *) xlist->root->next->next->data, "item3"); + assert_string_equal((const char *) xlist->end->data, "item3"); + + assert_int_equal(ssh_list_count(xlist), 3); + + ssh_list_free(xlist); +} + +static void torture_ssh_list_prepend(void **state) { + struct ssh_list *xlist; + int rc; + + (void) state; + + xlist = ssh_list_new(); + assert_non_null(xlist); + + rc = ssh_list_prepend(xlist, "item1"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item1"); + + rc = ssh_list_append(xlist, "item2"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item1"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + rc = ssh_list_prepend(xlist, "item3"); + assert_true(rc == 0); + assert_non_null(xlist->root); + assert_non_null(xlist->root->data); + assert_non_null(xlist->root->next); + assert_non_null(xlist->root->next->data); + assert_non_null(xlist->root->next->next); + assert_non_null(xlist->end); + assert_non_null(xlist->end->data); + assert_string_equal((const char *) xlist->root->data, "item3"); + assert_string_equal((const char *) xlist->root->next->data, "item1"); + assert_string_equal((const char *) xlist->root->next->next->data, "item2"); + assert_string_equal((const char *) xlist->end->data, "item2"); + + assert_int_equal(ssh_list_count(xlist), 3); + + ssh_list_free(xlist); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_list_new), + cmocka_unit_test(torture_ssh_list_append), + cmocka_unit_test(torture_ssh_list_prepend), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_misc.c b/src/libs/libssh-0.12.2/tests/unittests/torture_misc.c new file mode 100644 index 000000000000..88bb73741d11 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_misc.c @@ -0,0 +1,1329 @@ +#include "config.h" + +#ifdef HAVE_UNISTD_H +#include +#endif +#include +#include + +#ifndef _WIN32 +#define _POSIX_PTHREAD_SEMANTICS +#include +#endif + +#define LIBSSH_STATIC +#include + +#include "misc.c" +#include "torture.h" +#include "error.c" + +#ifdef _WIN32 +#include +#else +#include +#endif + +#define TORTURE_TEST_DIR "/usr/local/bin/truc/much/.." +#define TORTURE_IPV6_LOCAL_LINK "fe80::98e1:82ff:fe8d:28b3%%%s" + +const char template[] = "temp_dir_XXXXXX"; + +static int setup(void **state) +{ + ssh_session session = ssh_new(); + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +static void torture_get_user_home_dir(void **state) { +#ifndef _WIN32 + struct passwd *pwd = getpwuid(getuid()); +#endif /* _WIN32 */ + char *user; + + (void) state; + + user = ssh_get_user_home_dir(NULL); + assert_non_null(user); +#ifndef _WIN32 + assert_string_equal(user, pwd->pw_dir); +#endif /* _WIN32 */ + + SAFE_FREE(user); +} + +static void torture_basename(void **state) { + char *path; + + (void) state; + + path=ssh_basename(TORTURE_TEST_DIR "/test"); + assert_non_null(path); + assert_string_equal(path, "test"); + SAFE_FREE(path); + path=ssh_basename(TORTURE_TEST_DIR "/test/"); + assert_non_null(path); + assert_string_equal(path, "test"); + SAFE_FREE(path); +} + +static void torture_dirname(void **state) { + char *path; + + (void) state; + + path=ssh_dirname(TORTURE_TEST_DIR "/test"); + assert_non_null(path); + assert_string_equal(path, TORTURE_TEST_DIR ); + SAFE_FREE(path); + path=ssh_dirname(TORTURE_TEST_DIR "/test/"); + assert_non_null(path); + assert_string_equal(path, TORTURE_TEST_DIR); + SAFE_FREE(path); +} + +static void torture_ntohll(void **state) { + uint64_t value = 0x0123456789abcdef; + uint32_t sample = 1; + unsigned char *ptr = (unsigned char *) &sample; + uint64_t check; + + (void) state; + + if (ptr[0] == 1){ + /* we're in little endian */ + check = 0xefcdab8967452301; + } else { + /* big endian */ + check = value; + } + value = ntohll(value); + assert_true(value == check); +} + +/** + * @brief Compare fields of two (struct tm) type structures. + * + * @param[in] a Pointer to the first structure to compare + * + * @param[in] b Pointer to the second structure to compare + * + * @returns -1 on error + * @returns 0 if the fields of the structures are the same + * @returns 1 if the fields of the structures are not the same + */ +static int tm_cmp(const struct tm *a, const struct tm *b) +{ + if (a == NULL || b == NULL) { + return -1; + } + + return !(a->tm_sec == b->tm_sec && + a->tm_min == b->tm_min && + a->tm_hour == b->tm_hour && + a->tm_mday == b->tm_mday && + a->tm_mon == b->tm_mon && + a->tm_year == b->tm_year && + a->tm_wday == b->tm_wday && + a->tm_yday == b->tm_yday && + a->tm_isdst == b->tm_isdst); +} + +/** + * @brief Validate that localtime_r() works properly. + * + * This test is mainly to check that the libssh implementation of + * localtime_r() on Windows works properly (Windows does not provide + * localtime_r()) + */ +static void torture_localtime_r(UNUSED_PARAM(void **state)) +{ + /* + * The tm_wday and tm_yday fields of tm1 and tm2 would be filled + * appropriately due to the mktime() call further in the test. + */ + + /* Linux release date: 17/09/1991 (random time: 02:01:00) */ + struct tm tm1 = {.tm_sec = 0, + .tm_min = 1, + .tm_hour = 2, + .tm_mday = 17, + .tm_mon = 9 - 1, + .tm_year = 1991 - 1900, + .tm_isdst = 0}; + + /* Windows release date: 20/11/1985 (random time 05:04:03) */ + struct tm tm2 = {.tm_sec = 3, + .tm_min = 4, + .tm_hour = 5, + .tm_mday = 20, + .tm_mon = 11 - 1, + .tm_year = 1985 - 1900, + .tm_isdst = 0}; + + time_t t1, t2; + struct tm *static_tm_ptr = NULL, *tm_ptr = NULL; + struct tm our_tm = {0}; + int cmp; + + /* + * Convert time represented as (struct tm) to time represented as + * a (time_t) + */ + t1 = mktime(&tm1); + assert_int_not_equal(t1, (time_t)-1); + + t2 = mktime(&tm2); + assert_int_not_equal(t2, (time_t)-1); + + /* Test that localtime_r() gives the correct broken down time */ + tm_ptr = localtime_r(&t1, &our_tm); + assert_ptr_equal(tm_ptr, &our_tm); + + cmp = tm_cmp(&our_tm, &tm1); + assert_int_equal(cmp, 0); + + /* + * Test that localtime_r() does not modify the static structure used by + * localtime(). (This is an attempt to test that the localtime_r() + * implementation does not use localtime() internally) + * + * To test this, we first use localtime() on some time, then use + * localtime_r() on another time and then validate that the time + * corresponding to the pointer (to the static structure) returned by + * the first localtime() call does not change. + */ + static_tm_ptr = localtime(&t1); + assert_non_null(static_tm_ptr); + + cmp = tm_cmp(static_tm_ptr, &tm1); + assert_int_equal(cmp, 0); + + tm_ptr = localtime_r(&t2, &our_tm); + assert_ptr_equal(tm_ptr, &our_tm); + + cmp = tm_cmp(static_tm_ptr, &tm1); + assert_int_equal(cmp, 0); + + /* + * Ideally, it should be checked that the localtime_r() implementation + * is thread safe by testing it under multiple threads, but we are not + * checking that as of now. This is because we trust localtime_r() provided + * by POSIX systems to be thread safe and the libssh implementation of + * localtime_r() on Windows should be a simple wrapper around Windows's + * localtime_s() which should also be thread safe. + */ +} + +#ifdef _WIN32 + +static void torture_path_expand_tilde_win(void **state) { + char *d; + + (void) state; + + d = ssh_path_expand_tilde("~\\.ssh"); + assert_non_null(d); + print_message("Expanded path: %s\n", d); + free(d); + + d = ssh_path_expand_tilde("/guru/meditation"); + assert_string_equal(d, "/guru/meditation"); + free(d); +} + +#else /* _WIN32 */ + +static void torture_path_expand_tilde_unix(void **state) { + char h[256] = {0}; + char *d = NULL; + char *user = NULL; + char *home = NULL; + struct passwd *pw = NULL; + + (void) state; + + pw = getpwuid(getuid()); + assert_non_null(pw); + + user = pw->pw_name; + assert_non_null(user); + home = pw->pw_dir; + assert_non_null(home); + + snprintf(h, 256 - 1, "%s/.ssh", home); + + d = ssh_path_expand_tilde("~/.ssh"); + assert_non_null(d); + assert_string_equal(d, h); + free(d); + + d = ssh_path_expand_tilde("/guru/meditation"); + assert_non_null(d); + assert_string_equal(d, "/guru/meditation"); + free(d); + + snprintf(h, 256 - 1, "~%s/.ssh", user); + d = ssh_path_expand_tilde(h); + assert_non_null(d); + + snprintf(h, 256 - 1, "%s/.ssh", home); + assert_string_equal(d, h); + free(d); +} + +#endif /* _WIN32 */ + +static void torture_path_expand_escape(void **state) { + ssh_session session = *state; + const char *s = "%d/%h/%p/by/%r"; + char *e; + + /* Set the homedir here to prevent querying the NSS DB */ + session->opts.homedir = strdup("guru"); + session->opts.host = strdup("meditation"); + session->opts.port = 0; + session->opts.username = strdup("root"); + + e = ssh_path_expand_escape(session, s); + assert_non_null(e); + assert_string_equal(e, "guru/meditation/22/by/root"); + ssh_string_free_char(e); + + session->opts.port = 222; + + e = ssh_path_expand_escape(session, s); + assert_non_null(e); + assert_string_equal(e, "guru/meditation/222/by/root"); + ssh_string_free_char(e); +} + +static void torture_path_expand_known_hosts(void **state) { + ssh_session session = *state; + char *tmp; + + /* Set the homedir here to prevent querying the NSS DB */ + session->opts.homedir = strdup("/home/guru"); + + tmp = ssh_path_expand_escape(session, "%d/.ssh/known_hosts"); + assert_non_null(tmp); + assert_string_equal(tmp, "/home/guru/.ssh/known_hosts"); + free(tmp); +} + +static void torture_path_expand_percent(void **state) { + ssh_session session = *state; + char *tmp; + + /* Set the homedir here to prevent querying the NSS DB */ + session->opts.homedir = strdup("/home/guru"); + + tmp = ssh_path_expand_escape(session, "%d/.ssh/config%%1"); + assert_non_null(tmp); + assert_string_equal(tmp, "/home/guru/.ssh/config%1"); + free(tmp); +} + +static void torture_timeout_elapsed(void **state){ + struct ssh_timestamp ts; + (void) state; + ssh_timestamp_init(&ts); + usleep(30000); + + assert_true(ssh_timeout_elapsed(&ts,25)); + assert_false(ssh_timeout_elapsed(&ts,30000)); + assert_false(ssh_timeout_elapsed(&ts,300)); + assert_true(ssh_timeout_elapsed(&ts,0)); + assert_false(ssh_timeout_elapsed(&ts,-1)); +} + +static void torture_timeout_update(void **state){ + struct ssh_timestamp ts; + (void) state; + ssh_timestamp_init(&ts); + usleep(50000); + assert_int_equal(ssh_timeout_update(&ts,25), 0); + assert_in_range(ssh_timeout_update(&ts,30000),29000,29960); + assert_in_range(ssh_timeout_update(&ts,500),1,460); + assert_int_equal(ssh_timeout_update(&ts,0),0); + assert_int_equal(ssh_timeout_update(&ts,-1),-1); +} + +static void torture_ssh_analyze_banner(void **state) { + int rc = 0; + ssh_session session = NULL; + (void) state; + +#define reset_banner_test() \ + do { \ + rc = 0; \ + ssh_free(session); \ + session = ssh_new(); \ + assert_non_null(session); \ + } while (0) + +#define assert_banner_rejected(is_server) \ + do { \ + rc = ssh_analyze_banner(session, is_server); \ + assert_int_not_equal(0, rc); \ + } while (0); + +#define assert_client_banner_rejected(banner) \ + do { \ + reset_banner_test(); \ + session->clientbanner = strdup(banner); \ + assert_non_null(session->clientbanner); \ + assert_banner_rejected(1 /*server*/); \ + SAFE_FREE(session->clientbanner); \ + } while (0) + +#define assert_server_banner_rejected(banner) \ + do { \ + reset_banner_test(); \ + session->serverbanner = strdup(banner); \ + assert_non_null(session->serverbanner); \ + assert_banner_rejected(0 /*client*/); \ + SAFE_FREE(session->serverbanner); \ + } while (0) + +#define assert_banner_accepted(is_server) \ + do { \ + rc = ssh_analyze_banner(session, is_server); \ + assert_int_equal(0, rc); \ + } while (0) + +#define assert_client_banner_accepted(banner) \ + do { \ + reset_banner_test(); \ + session->clientbanner = strdup(banner); \ + assert_non_null(session->clientbanner); \ + assert_banner_accepted(1 /*server*/); \ + SAFE_FREE(session->clientbanner); \ + } while (0) + +#define assert_server_banner_accepted(banner) \ + do { \ + reset_banner_test(); \ + session->serverbanner = strdup(banner); \ + assert_non_null(session->serverbanner); \ + assert_banner_accepted(0 /*client*/); \ + SAFE_FREE(session->serverbanner); \ + } while (0) + + /* no banner is set */ + reset_banner_test(); + assert_banner_rejected(0 /*client*/); + reset_banner_test(); + assert_banner_rejected(1 /*server*/); + + /* banner is too short */ + assert_client_banner_rejected("abc"); + assert_server_banner_rejected("abc"); + + /* banner doesn't start "SSH-" */ + assert_client_banner_rejected("abc-2.0"); + assert_server_banner_rejected("abc-2.0"); + + /* SSH v1 */ + assert_client_banner_rejected("SSH-1.0"); + assert_server_banner_rejected("SSH-1.0"); + + /* SSH v1.9 gets counted as both v1 and v2 */ + assert_client_banner_accepted("SSH-1.9"); + assert_server_banner_accepted("SSH-1.9"); + + /* SSH v2 */ + assert_client_banner_accepted("SSH-2.0"); + assert_server_banner_accepted("SSH-2.0"); + + /* OpenSSH banners: too short to extract major and minor versions */ + assert_client_banner_accepted("SSH-2.0-OpenSSH"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH"); + assert_int_equal(0, session->openssh); + + + /* OpenSSH banners: big enough to extract major and minor versions */ + assert_client_banner_accepted("SSH-2.0-OpenSSH_5.9p1"); + assert_int_equal(SSH_VERSION_INT(5, 9, 0), session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_5.9p1"); + assert_int_equal(SSH_VERSION_INT(5, 9, 0), session->openssh); + + assert_client_banner_accepted("SSH-2.0-OpenSSH_1.99"); + assert_int_equal(SSH_VERSION_INT(1, 99, 0), session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_1.99"); + assert_int_equal(SSH_VERSION_INT(1, 99, 0), session->openssh); + + /* OpenSSH banners: major, minor version limits result in zero */ + assert_client_banner_accepted("SSH-2.0-OpenSSH_0.99p1"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_0.99p1"); + assert_int_equal(0, session->openssh); + assert_client_banner_accepted("SSH-2.0-OpenSSH_1.101p1"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_1.101p1"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: bogus major results in zero */ + assert_client_banner_accepted("SSH-2.0-OpenSSH_X.9p1"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH_X.9p1"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: bogus minor results in zero */ + assert_server_banner_accepted("SSH-2.0-OpenSSH_5.Yp1"); + assert_int_equal(0, session->openssh); + assert_client_banner_accepted("SSH-2.0-OpenSSH_5.Yp1"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: ssh-keyscan(1) */ + assert_client_banner_accepted("SSH-2.0-OpenSSH-keyscan"); + assert_int_equal(0, session->openssh); + assert_server_banner_accepted("SSH-2.0-OpenSSH-keyscan"); + assert_int_equal(0, session->openssh); + + /* OpenSSH banners: Double digit in major version */ + assert_server_banner_accepted("SSH-2.0-OpenSSH_10.0p1"); + assert_int_equal(SSH_VERSION_INT(10, 0, 0), session->openssh); + + ssh_free(session); +} + +static void torture_ssh_dir_writeable(UNUSED_PARAM(void **state)) +{ + char *tmp_dir = NULL; + int rc = 0; + FILE *file = NULL; + char buffer[256]; + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + rc = ssh_dir_writeable(tmp_dir); + assert_int_equal(rc, 1); + + /* Create a file */ + snprintf(buffer, sizeof(buffer), "%s/a", tmp_dir); + + file = fopen(buffer, "w"); + assert_non_null(file); + + fprintf(file, "Hello world!\n"); + fclose(file); + + /* Negative test for checking a normal file */ + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 0); + + /* Negative test for non existent file */ + snprintf(buffer, sizeof(buffer), "%s/b", tmp_dir); + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 0); + +#ifndef _WIN32 + /* Negative test for directory without write permission */ + rc = ssh_mkdir(buffer, 0400); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 0); +#endif + + torture_rmdirs(tmp_dir); + + SAFE_FREE(tmp_dir); +} + +static void torture_ssh_mkdirs(UNUSED_PARAM(void **state)) +{ + char *tmp_dir = NULL; + char *cwd = NULL; + char buffer[256]; + + ssize_t count = 0; + + int rc; + + /* Get current working directory */ + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + /* Create a base disposable directory */ + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + /* Create a single directory */ + count = snprintf(buffer, sizeof(buffer), "%s/a", tmp_dir); + assert_return_code(count, errno); + + rc = ssh_mkdirs(buffer, 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 1); + + /* Create directories recursively */ + count = snprintf(buffer, sizeof(buffer), "%s/b/c/d", tmp_dir); + assert_return_code(count, errno); + + rc = ssh_mkdirs(buffer, 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable(buffer); + assert_int_equal(rc, 1); + + /* Change directory */ + rc = torture_change_dir(tmp_dir); + assert_return_code(rc, errno); + + /* Create single local directory */ + rc = ssh_mkdirs("e", 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable("e"); + assert_int_equal(rc, 1); + + /* Create local directories recursively */ + rc = ssh_mkdirs("f/g/h", 0700); + assert_return_code(rc, errno); + + rc = ssh_dir_writeable("f/g/h"); + assert_int_equal(rc, 1); + + /* Negative test for creating "." directory */ + rc = ssh_mkdirs(".", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating "/" directory */ + rc = ssh_mkdirs("/", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating "" directory */ + rc = ssh_mkdirs("", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating NULL directory */ + rc = ssh_mkdirs(NULL, 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EINVAL); + + /* Negative test for creating existing directory */ + rc = ssh_mkdirs("a", 0700); + assert_int_equal(rc, -1); + assert_int_equal(errno, EEXIST); + + /* Return to original directory */ + rc = torture_change_dir(cwd); + assert_return_code(rc, errno); + + /* Cleanup */ + torture_rmdirs(tmp_dir); + + SAFE_FREE(tmp_dir); + SAFE_FREE(cwd); +} + +static void torture_ssh_quote_file_name(UNUSED_PARAM(void **state)) +{ + char buffer[2048]; + int rc; + + /* Only ordinary chars */ + rc = ssh_quote_file_name("a b", buffer, 2048); + assert_int_equal(rc, 5); + assert_string_equal(buffer, "'a b'"); + + /* Single quote in file name */ + rc = ssh_quote_file_name("a'b", buffer, 2048); + assert_int_equal(rc, 9); + assert_string_equal(buffer, "'a'\"'\"'b'"); + + /* Exclamation in file name */ + rc = ssh_quote_file_name("a!b", buffer, 2048); + assert_int_equal(rc, 8); + assert_string_equal(buffer, "'a'\\!'b'"); + + /* All together */ + rc = ssh_quote_file_name("'a!b'", buffer, 2048); + assert_int_equal(rc, 14); + assert_string_equal(buffer, "\"'\"'a'\\!'b'\"'\""); + + rc = ssh_quote_file_name("a'!b", buffer, 2048); + assert_int_equal(rc, 11); + assert_string_equal(buffer, "'a'\"'\"\\!'b'"); + + rc = ssh_quote_file_name("a'$b", buffer, 2048); + assert_int_equal(rc, 10); + assert_string_equal(buffer, "'a'\"'\"'$b'"); + + rc = ssh_quote_file_name("a'`b", buffer, 2048); + assert_int_equal(rc, 10); + assert_string_equal(buffer, "'a'\"'\"'`b'"); + + + rc = ssh_quote_file_name(" ", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "' '"); + + rc = ssh_quote_file_name(" ", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "' '"); + + + rc = ssh_quote_file_name("\r", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\r'"); + + rc = ssh_quote_file_name("\n", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\n'"); + + rc = ssh_quote_file_name("\r\n", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\r\n'"); + + + rc = ssh_quote_file_name("\\r", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\\r'"); + + rc = ssh_quote_file_name("\\n", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\\n'"); + + rc = ssh_quote_file_name("\\r\\n", buffer, 2048); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "'\\r\\n'"); + + + rc = ssh_quote_file_name("\t", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\t'"); + + rc = ssh_quote_file_name("\v", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\v'"); + + rc = ssh_quote_file_name("\t\v", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\t\v'"); + + + rc = ssh_quote_file_name("'", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "\"'\""); + + rc = ssh_quote_file_name("''", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "\"''\""); + + + rc = ssh_quote_file_name("\"", buffer, 2048); + assert_int_equal(rc, 3); + assert_string_equal(buffer, "'\"'"); + + rc = ssh_quote_file_name("\"\"", buffer, 2048); + assert_int_equal(rc, 4); + assert_string_equal(buffer, "'\"\"'"); + + rc = ssh_quote_file_name("'\"", buffer, 2048); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "\"'\"'\"'"); + + rc = ssh_quote_file_name("\"'", buffer, 2048); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "'\"'\"'\""); + + + /* Worst case */ + rc = ssh_quote_file_name("a'b'", buffer, 3 * 4 + 1); + assert_int_equal(rc, 12); + assert_string_equal(buffer, "'a'\"'\"'b'\"'\""); + + /* Negative tests */ + + /* NULL params */ + rc = ssh_quote_file_name(NULL, buffer, 3 * 4 + 1); + assert_int_equal(rc, SSH_ERROR); + + /* NULL params */ + rc = ssh_quote_file_name("a b", NULL, 3 * 4 + 1); + assert_int_equal(rc, SSH_ERROR); + + /* Small buffer size */ + rc = ssh_quote_file_name("a b", buffer, 0); + assert_int_equal(rc, SSH_ERROR); + + /* Worst case and small buffer size */ + rc = ssh_quote_file_name("a'b'", buffer, 3 * 4); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_ssh_newline_vis(UNUSED_PARAM(void **state)) +{ + int rc; + char buffer[1024]; + + rc = ssh_newline_vis("\n", buffer, 1024); + assert_int_equal(rc, 2); + assert_string_equal(buffer, "\\n"); + + rc = ssh_newline_vis("\n\n\n\n", buffer, 1024); + assert_int_equal(rc, 8); + assert_string_equal(buffer, "\\n\\n\\n\\n"); + + rc = ssh_newline_vis("a\nb\n", buffer, 1024); + assert_int_equal(rc, 6); + assert_string_equal(buffer, "a\\nb\\n"); +} + +static void torture_ssh_strreplace(void **state) +{ + char test_string1[] = "this;is;a;test"; + char test_string2[] = "test;is;a;this"; + char test_string3[] = "this;test;is;a"; + char *replaced_string = NULL; + + (void) state; + + /* pattern and replacement are of the same size */ + replaced_string = ssh_strreplace(test_string1, "test", "kiwi"); + assert_string_equal(replaced_string, "this;is;a;kiwi"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string2, "test", "kiwi"); + assert_string_equal(replaced_string, "kiwi;is;a;this"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string3, "test", "kiwi"); + assert_string_equal(replaced_string, "this;kiwi;is;a"); + free(replaced_string); + + /* replacement is greater than pattern */ + replaced_string = ssh_strreplace(test_string1, "test", "an;apple"); + assert_string_equal(replaced_string, "this;is;a;an;apple"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string2, "test", "an;apple"); + assert_string_equal(replaced_string, "an;apple;is;a;this"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string3, "test", "an;apple"); + assert_string_equal(replaced_string, "this;an;apple;is;a"); + free(replaced_string); + + /* replacement is less than pattern */ + replaced_string = ssh_strreplace(test_string1, "test", "an"); + assert_string_equal(replaced_string, "this;is;a;an"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string2, "test", "an"); + assert_string_equal(replaced_string, "an;is;a;this"); + free(replaced_string); + + replaced_string = ssh_strreplace(test_string3, "test", "an"); + assert_string_equal(replaced_string, "this;an;is;a"); + free(replaced_string); + + /* pattern not found in teststring */ + replaced_string = ssh_strreplace(test_string1, "banana", "an"); + assert_string_equal(replaced_string, test_string1); + free(replaced_string); + + /* pattern is NULL */ + replaced_string = ssh_strreplace(test_string1, NULL , "an"); + assert_string_equal(replaced_string, test_string1); + free(replaced_string); + + /* replacement is NULL */ + replaced_string = ssh_strreplace(test_string1, "test", NULL); + assert_string_equal(replaced_string, test_string1); + free(replaced_string); + + /* src is NULL */ + replaced_string = ssh_strreplace(NULL, "test", "kiwi"); + assert_null(replaced_string); +} + +static void torture_ssh_strerror(void **state) +{ + char buf[1024]; + size_t bufflen = sizeof(buf); + char *out = NULL; + + (void) state; + + out = ssh_strerror(ENOENT, buf, 1); /* too short */ + assert_string_equal(out, "\0"); + + out = ssh_strerror(256, buf, bufflen); /* unknown error code */ + /* This error is always different: + * Freebd: "Unknown error: 256" + * MinGW/Win: "Unknown error" + * Linux/glibc: "Unknown error 256" + * Alpine/musl: "No error information" + */ + assert_non_null(out); + + out = ssh_strerror(ENOMEM, buf, bufflen); + /* This actually differs too for glibc/musl: + * musl: "Out of memory" + * everything else: "Cannot allocate memory" + */ + assert_non_null(out); +} + +static void torture_ssh_readn(void **state) +{ + char *write_buf = NULL, *read_buf = NULL, *file_path = NULL; + size_t data_len = 10 * 1024 * 1024; + size_t read_buf_size = data_len + 1024; + size_t i, total_bytes_written = 0; + + const char *file_template = "libssh_torture_ssh_readn_test_XXXXXX"; + + off_t off; + ssize_t bytes_read, bytes_written; + int fd, rc, flags; + + (void)state; + + write_buf = malloc(data_len); + assert_non_null(write_buf); + + /* Fill the write buffer with random data */ + for (i = 0; i < data_len; ++i) { + rc = rand(); + write_buf[i] = (char)(rc & 0xff); + } + + /* + * The read buffer's size is intentionally kept larger than data_len. + * + * This is done so that we are able to test the scenario when the user + * requests ssh_readn() to read more bytes than the number of bytes present + * in the file. + * + * If the read buffer's size is kept same as data_len and if the test + * requests ssh_readn() to read more than data_len bytes starting from file + * offset 0, it will lead to a valgrind failure as in this case, ssh_readn() + * will pass an unallocated memory address in the last call it makes to + * read(). + */ + read_buf = malloc(read_buf_size); + assert_non_null(read_buf); + + file_path = torture_create_temp_file(file_template); + assert_non_null(file_path); + + /* Open a file for reading and writing */ + flags = O_RDWR; +#ifdef _WIN32 + flags |= O_BINARY; +#endif + + fd = open(file_path, flags, 0); + assert_int_not_equal(fd, -1); + + /* Write the data present in the write buffer to the file */ + do { + bytes_written = write(fd, + write_buf + total_bytes_written, + data_len - total_bytes_written); + + if (bytes_written == -1 && errno == EINTR) { + continue; + } + + assert_int_not_equal(bytes_written, -1); + total_bytes_written += bytes_written; + } while (total_bytes_written < data_len); + + /* Seek to the start of the file */ + off = lseek(fd, 0, SEEK_SET); + assert_int_not_equal(off, -1); + + bytes_read = ssh_readn(fd, read_buf, data_len); + assert_int_equal(bytes_read, data_len); + + /* + * Ensure that the data stored in the read buffer is same as the data + * present in the file and not some garbage. + */ + assert_memory_equal(read_buf, write_buf, data_len); + + /* + * Ensure that the file offset is on EOF and requesting to read more leads + * to 0 bytes getting read. + */ + off = lseek(fd, 0, SEEK_CUR); + assert_int_equal(off, data_len); + + bytes_read = ssh_readn(fd, read_buf, data_len); + assert_int_equal(bytes_read, 0); + + /* Try to read more bytes than what are present in the file */ + off = lseek(fd, 0, SEEK_SET); + assert_int_not_equal(off, -1); + + bytes_read = ssh_readn(fd, read_buf, read_buf_size); + assert_int_equal(bytes_read, data_len); + + /* + * Ensure that the data stored in the read buffer is same as the data + * present in the file and not some garbage. + */ + assert_memory_equal(read_buf, write_buf, data_len); + + /* Negative tests start */ + bytes_read = ssh_readn(-2, read_buf, data_len); + assert_int_equal(bytes_read, -1); + + bytes_read = ssh_readn(fd, NULL, data_len); + assert_int_equal(bytes_read, -1); + + bytes_read = ssh_readn(fd, read_buf, 0); + assert_int_equal(bytes_read, -1); + + /* Clean up */ + rc = close(fd); + assert_int_equal(rc, 0); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(file_path); + free(read_buf); + free(write_buf); +} + +static void torture_ssh_writen(void **state) +{ + char *write_buf = NULL, *read_buf = NULL, *file_path = NULL; + const char *file_template = "libssh_torture_ssh_writen_test_XXXXXX"; + + size_t data_len = 10 * 1024 * 1024; + size_t i, total_bytes_read = 0; + ssize_t bytes_written, bytes_read; + off_t off; + int rc, fd, flags; + + (void)state; + + write_buf = malloc(data_len); + assert_non_null(write_buf); + + /* Fill the write buffer with random data */ + for (i = 0; i < data_len; ++i) { + rc = rand(); + write_buf[i] = (char)(rc & 0xff); + } + + read_buf = malloc(data_len); + assert_non_null(read_buf); + + file_path = torture_create_temp_file(file_template); + assert_non_null(file_path); + + /* Open a file for reading and writing */ + flags = O_RDWR; +#ifdef _WIN32 + flags |= O_BINARY; +#endif + + fd = open(file_path, flags, 0); + assert_int_not_equal(fd, -1); + + /* Write the data present in the write buffer to the file */ + bytes_written = ssh_writen(fd, write_buf, data_len); + assert_int_equal(bytes_written, data_len); + + /* + * Ensure that the file offset is incremented by the number of bytes + * written. + */ + off = lseek(fd, 0, SEEK_CUR); + assert_int_equal(off, data_len); + + /* + * Ensure that the data present in the write buffer has been written to the + * file and not some garbage. + */ + off = lseek(fd, 0, SEEK_SET); + assert_int_not_equal(off, -1); + + do { + bytes_read = read(fd, + read_buf + total_bytes_read, + data_len - total_bytes_read); + + if (bytes_read == -1 && errno == EINTR) { + continue; + } + + assert_int_not_equal(bytes_read, -1); + assert_int_not_equal(bytes_read, 0); + + total_bytes_read += bytes_read; + } while (total_bytes_read < data_len); + + assert_memory_equal(write_buf, read_buf, data_len); + + /* Negative tests start */ + bytes_written = ssh_writen(-3, write_buf, data_len); + assert_int_equal(bytes_written, -1); + + bytes_written = ssh_writen(fd, NULL, data_len); + assert_int_equal(bytes_written, -1); + + bytes_written = ssh_writen(fd, write_buf, 0); + assert_int_equal(bytes_written, -1); + + /* Clean up */ + rc = close(fd); + assert_int_equal(rc, 0); + + rc = unlink(file_path); + assert_int_equal(rc, 0); + + free(file_path); + free(read_buf); + free(write_buf); +} + +static void torture_ssh_check_hostname_syntax(void **state) +{ + int rc; + (void)state; + + rc = ssh_check_hostname_syntax("duckduckgo.com"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("www.libssh.org"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("Some-Thing.com"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.ok"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("amazon.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123.a23456789012345678901234567890123456789012345678901234567890123"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("lavabo-inter.innocentes-manus-meas"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("localhost"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("a"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("a-0.b-b"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_hostname_syntax("libssh."); + assert_int_equal(rc, SSH_OK); + // IDN + rc = ssh_check_hostname_syntax("xn--bcher-kva.tld"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_check_hostname_syntax(NULL); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(""); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("/"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("@"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("["); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("`"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("{"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("&"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("|"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("\""); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("`"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(" "); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("*the+giant&\"rooks\".c0m"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("!www.libssh.org"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("--.--"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh.a234567890123456789012345678901234567890123456789012345678901234.a234567890123456789012345678901234567890123456789012345678901234"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("libssh-"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("fe80::9656:d028:8652:66b6"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax("."); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_hostname_syntax(".."); + assert_int_equal(rc, SSH_ERROR); + // IDN non-encoded + rc = ssh_check_hostname_syntax("bücher.tld"); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_ssh_check_username_syntax(void **state) { + int rc; + (void)state; + + rc = ssh_check_username_syntax("username"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("Alice and Bob"); + assert_int_equal(rc, SSH_OK); + rc = ssh_check_username_syntax("n4me?"); + assert_int_equal(rc, SSH_OK); + + rc = ssh_check_username_syntax("alice&bob"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("backslash\\"); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_check_username_syntax("&var|()us\" +#endif +#include + +#include "torture.h" +#include "torture_key.h" +#include +#include +#include +#include +#include +#include +#include +#ifdef WITH_SERVER +#include +#define LIBSSH_CUSTOM_BIND_CONFIG_FILE "my_bind_config" +#endif +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#ifdef HAVE_ECC +#define LIBSSH_ECDSA_521_TESTKEY "libssh_testkey.id_ecdsa521" +#endif + +static int setup(void **state) +{ + ssh_session session; + int verbosity; + + session = ssh_new(); + + verbosity = torture_libssh_verbosity(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + session->client = 1; + + *state = session; + + return 0; +} + +static int teardown(void **state) +{ + ssh_free(*state); + + return 0; +} + +static void torture_options_set_host(void **state) { + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "localhost"); + + /* IPv4 address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "127.1.1.1"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "127.1.1.1"); + assert_null(session->opts.username); + + /* IPv6 address */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "::1"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "::1"); + assert_null(session->opts.username); + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "guru@meditation"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "meditation"); + assert_non_null(session->opts.username); + assert_string_equal(session->opts.username, "guru"); + + /* more @ in uri is OK -- it should go to the username */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "at@login@hostname"); + assert_true(rc == 0); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "hostname"); + assert_non_null(session->opts.username); + assert_string_equal(session->opts.username, "at@login"); + + /* disallow metacharacters in the username */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "shallN()tP4ss -@hostname"); + assert_string_equal(ssh_get_error(session), + "Invalid argument in ssh_options_set"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + /* IPv6 hostnames should work without square braces */ + SAFE_FREE(session->opts.username); + rc = ssh_options_set(session, + SSH_OPTIONS_HOST, + "fd4d:5449:7400:111:626d:3cff:fedf:4d39"); + assert_return_code(rc, errno); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, + "fd4d:5449:7400:111:626d:3cff:fedf:4d39"); + assert_null(session->opts.username); + + /* IPv6 hostnames should work also with square braces */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOST, + "[fd4d:5449:7400:111:626d:3cff:fedf:4d39]"); + assert_return_code(rc, errno); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, + "fd4d:5449:7400:111:626d:3cff:fedf:4d39"); + assert_null(session->opts.username); + + /* IDN need to be in punycode format */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "xn--bcher-kva.tld"); + assert_return_code(rc, errno); + assert_non_null(session->opts.host); + assert_string_equal(session->opts.host, "xn--bcher-kva.tld"); + assert_null(session->opts.username); + + /* IDN in UTF8 won't work */ + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "bücher.tld"); + assert_string_equal(ssh_get_error(session), + "Invalid argument in ssh_options_set"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); +} + +static void torture_options_set_ciphers(void **state) +{ + ssh_session session = *state; + int rc; + + /* Test known ciphers */ + rc = ssh_options_set(session, + SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes192-ctr,aes256-ctr"); + } + + /* Test one unknown cipher */ + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,unknown-crap@example.com,aes256-ctr"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_CRYPT_C_S]); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + + /* Test all unknown ciphers */ + rc = ssh_options_set(session, SSH_OPTIONS_CIPHERS_C_S, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_get_ciphers(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_CIPHERS_C_S, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "aes256-gcm@openssh.com," + "aes256-ctr," + "aes256-cbc," + "aes128-gcm@openssh.com," + "aes128-ctr," + "aes128-cbc"); + } else { + assert_string_equal(value, + "chacha20-poly1305@openssh.com," + "aes256-gcm@openssh.com," + "aes128-gcm@openssh.com," + "aes256-ctr," + "aes192-ctr," + "aes128-ctr"); + } + ssh_string_free_char(value); + + /* Test explicit ciphers */ + rc = ssh_options_set(session, + SSH_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_CIPHERS_C_S, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(value, "aes128-ctr,aes192-ctr,aes256-ctr"); + } + ssh_string_free_char(value); +} + +static void torture_options_set_key_exchange(void **state) +{ + ssh_session session = *state; + int rc; + + /* Test known kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "sntrup761x25519-sha512," + "sntrup761x25519-sha512@openssh.com," + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "sntrup761x25519-sha512," + "sntrup761x25519-sha512@openssh.com," + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + + /* Test one unknown kex */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "diffie-hellman-group16-sha512," + "unknown-crap@example.com," + "diffie-hellman-group18-sha512"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_KEX]); + assert_string_equal(session->opts.wanted_methods[SSH_KEX], + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"); + + /* Test all unknown kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_get_key_exchange(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + const char *exp_value = NULL; + + /* Test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_KEY_EXCHANGE, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + exp_value = "mlkem768nistp256-sha256," +#ifdef HAVE_MLKEM1024 + "mlkem1024nistp384-sha384," +#endif + "ecdh-sha2-nistp256," + "ecdh-sha2-nistp384," + "ecdh-sha2-nistp521," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256," + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"; + } else { + exp_value = "mlkem768x25519-sha256," + "mlkem768nistp256-sha256," +#ifdef HAVE_MLKEM1024 + "mlkem1024nistp384-sha384," +#endif + "sntrup761x25519-sha512," + "sntrup761x25519-sha512@openssh.com," + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,ecdh-sha2-nistp384," + "ecdh-sha2-nistp521,diffie-hellman-group18-sha512," + "diffie-hellman-group16-sha512," + "diffie-hellman-group-exchange-sha256," + "diffie-hellman-group14-sha256"; + } + assert_string_equal(value, exp_value); + ssh_string_free_char(value); + + /* Test explicit kexes */ + rc = ssh_options_set(session, + SSH_OPTIONS_KEY_EXCHANGE, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_KEY_EXCHANGE, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(value, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + ssh_string_free_char(value); +} + +static void torture_options_set_hostkey(void **state) +{ + ssh_session session = *state; + int rc; + + /* Test known host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + /* Test one unknown host key */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ecdsa-sha2-nistp521," + "unknown-crap@example.com," + "rsa-sha2-256"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp521," + "rsa-sha2-256"); + + /* Test all unknown host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_get_hostkey(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + rc = ssh_options_get(session, SSH_OPTIONS_HOSTKEYS, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "ecdsa-sha2-nistp521-cert-v01@openssh.com," + "ecdsa-sha2-nistp384-cert-v01@openssh.com," + "ecdsa-sha2-nistp256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com," + "rsa-sha2-256-cert-v01@openssh.com," + "ecdsa-sha2-nistp521," + "ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256," + "rsa-sha2-512," + "rsa-sha2-256"); + } else { + assert_string_equal(value, + "ssh-ed25519-cert-v01@openssh.com," + "ecdsa-sha2-nistp521-cert-v01@openssh.com," + "ecdsa-sha2-nistp384-cert-v01@openssh.com," + "ecdsa-sha2-nistp256-cert-v01@openssh.com," + "sk-ecdsa-sha2-nistp256-cert-v01@openssh.com," + "rsa-sha2-512-cert-v01@openssh.com," + "rsa-sha2-256-cert-v01@openssh.com," + "ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384," + "ecdsa-sha2-nistp256,sk-ssh-ed25519@openssh.com," + "sk-ecdsa-sha2-nistp256@openssh.com," + "rsa-sha2-512,rsa-sha2-256"); + } + ssh_string_free_char(value); + + /* Test explicit host keys */ + rc = ssh_options_set(session, + SSH_OPTIONS_HOSTKEYS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_HOSTKEYS, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(value, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + ssh_string_free_char(value); +} + +static void torture_options_set_pubkey_accepted_types(void **state) +{ + ssh_session session = *state; + int rc; + enum ssh_digest_e type; + + /* Test known public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.pubkey_accepted_types); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.pubkey_accepted_types, + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(session->opts.pubkey_accepted_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + if (!ssh_fips_mode()) { + /* Test one unknown public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,unknown-crap@example.com,ssh-rsa"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.pubkey_accepted_types); + assert_string_equal(session->opts.pubkey_accepted_types, + "ssh-ed25519,ssh-rsa"); + + /* Test all unknown public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "unknown-crap@example.com,more-crap@example.com"); + assert_false(rc == 0); + + /* Test that the option affects the algorithm selection for RSA keys */ + /* simulate the SHA2 extension was negotiated */ + session->extensions = SSH_EXT_SIG_RSA_SHA256; + + /* previous configuration did not list the SHA2 extension algorithms, so + * it should not be used */ + type = ssh_key_type_to_hash(session, SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_DIGEST_SHA1); + } + + /* now, lets allow the signature from SHA2 extension and expect + * it to be used */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "rsa-sha2-256,ssh-rsa"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.pubkey_accepted_types); + if (ssh_fips_mode()) { + assert_string_equal(session->opts.pubkey_accepted_types, + "rsa-sha2-256"); + } else { + assert_string_equal(session->opts.pubkey_accepted_types, + "rsa-sha2-256,ssh-rsa"); + } + + /* Test that the option affects the algorithm selection for RSA keys */ + /* simulate the SHA2 extension was negotiated */ + session->extensions = SSH_EXT_SIG_RSA_SHA256; + + type = ssh_key_type_to_hash(session, SSH_KEYTYPE_RSA); + assert_int_equal(type, SSH_DIGEST_SHA256); +} + +static void torture_options_get_pubkey_accepted_types(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* Test known public key algorithms */ + rc = ssh_options_set(session, + SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_get(session, SSH_OPTIONS_PUBLICKEY_ACCEPTED_TYPES, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(value, "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + ssh_string_free_char(value); +} + + +static void torture_options_set_macs(void **state) +{ + ssh_session session = *state; + int rc; + + /* Test known MACs */ + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], "hmac-sha1"); + + /* Test multiple known MACs */ + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "hmac-sha1-etm@openssh.com," + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1,hmac-sha2-256"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + "hmac-sha1-etm@openssh.com," + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1,hmac-sha2-256"); + + /* Test unknown MACs */ + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "unknown-crap@example.com,hmac-sha1-etm@openssh.com," + "unknown@example.com"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_MAC_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_MAC_S_C], + "hmac-sha1-etm@openssh.com"); + + /* Test all unknown MACs */ + rc = ssh_options_set(session, + SSH_OPTIONS_HMAC_S_C, + "unknown-crap@example.com"); + assert_false(rc == 0); +} + +static void torture_options_get_macs(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + + /* test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_HMAC_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + if (ssh_fips_mode()) { + assert_string_equal(value, + "hmac-sha2-256-etm@openssh.com," + "hmac-sha1-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com," + "hmac-sha2-256," + "hmac-sha1," + "hmac-sha2-512"); + } else { + assert_string_equal(value, + "hmac-sha2-256-etm@openssh.com," + "hmac-sha2-512-etm@openssh.com," + "hmac-sha2-256," + "hmac-sha2-512"); + } + ssh_string_free_char(value); + + /* Test known MACs */ + rc = ssh_options_set(session, SSH_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_HMAC_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + assert_string_equal(value, "hmac-sha1"); + ssh_string_free_char(value); +} + +static void torture_options_set_compression(void **state) +{ + ssh_session session = *state; + int rc; + const char *known_value; + const char *multiple; + +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + known_value = "none"; + multiple = "none,squeeze"; + } else { + known_value = "zlib"; + multiple = "zlib,squeeze"; + } +#else + known_value = "none"; + multiple = "none,squeeze"; +#endif + + /* Test known compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, known_value); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + known_value); + + /* Test multiple known compression */ + if (!ssh_fips_mode()) { + rc = ssh_options_set(session, + SSH_OPTIONS_COMPRESSION_S_C, + "none,zlib@openssh.com"); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none,zlib@openssh.com"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], "none"); +#endif + } + + /* Test unknown compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, multiple); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.wanted_methods[SSH_COMP_S_C]); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + known_value); + + /* Test all unknown compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, "squeeze"); + assert_false(rc == 0); +} + +static void torture_options_get_compression(void **state) +{ + ssh_session session = *state; + int rc; + char *value = NULL; + const char *test_value = NULL; + +#ifdef WITH_ZLIB + if (ssh_fips_mode()) { + test_value = "none"; + } else { + test_value = "zlib@openssh.com"; + } +#else + test_value = "none"; +#endif + + /* test defaults returned */ + rc = ssh_options_get(session, SSH_OPTIONS_COMPRESSION_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); +#ifdef WITH_ZLIB + assert_string_equal(value, "none,zlib@openssh.com"); +#else + assert_string_equal(value, "none"); +#endif + ssh_string_free_char(value); + + /* Test known compression */ + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_S_C, test_value); + assert_ssh_return_code(session, rc); + + value = NULL; + rc = ssh_options_get(session, SSH_OPTIONS_COMPRESSION_S_C, &value); + assert_ssh_return_code(session, rc); + assert_non_null(value); + assert_string_equal(value, test_value); + ssh_string_free_char(value); +} + +static void torture_options_get_host(void **state) +{ + ssh_session session = *state; + int rc; + char* host = NULL; + + rc = ssh_options_set(session, SSH_OPTIONS_HOST, "localhost"); + assert_true(rc == 0); + assert_string_equal(session->opts.host, "localhost"); + + assert_false(ssh_options_get(session, SSH_OPTIONS_HOST, &host)); + + assert_string_equal(host, "localhost"); + ssh_string_free_char(host); +} + +static void torture_options_set_port(void **state) +{ + ssh_session session = *state; + int rc; + unsigned int port = 42; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &port); + assert_true(rc == 0); + assert_true(session->opts.port == port); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "23"); + assert_true(rc == 0); + assert_true(session->opts.port == 23); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT_STR, "five"); + assert_true(rc == -1); + assert_int_not_equal(session->opts.port, 0); + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, NULL); + assert_true(rc == -1); +} + +static void torture_options_get_port(void **state) +{ + ssh_session session = *state; + unsigned int given_port = 1234; + unsigned int port_container; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_PORT, &given_port); + assert_true(rc == 0); + rc = ssh_options_get_port(session, &port_container); + assert_true(rc == 0); + assert_int_equal(port_container, 1234); +} + +static void torture_options_get_user(void **state) +{ + ssh_session session = *state; + char *user = NULL; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "magicaltrevor"); + assert_int_equal(rc, SSH_OK); + rc = ssh_options_get(session, SSH_OPTIONS_USER, &user); + assert_int_equal(rc, SSH_OK); + assert_non_null(user); + assert_string_equal(user, "magicaltrevor"); + ssh_string_free_char(user); +} + +static void torture_options_set_fd(void **state) +{ + ssh_session session = *state; + socket_t fd = 42; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_FD, &fd); + assert_true(rc == 0); + assert_true(session->opts.fd == fd); + + rc = ssh_options_set(session, SSH_OPTIONS_FD, NULL); + assert_true(rc == SSH_ERROR); + assert_true(session->opts.fd == SSH_INVALID_SOCKET); +} + +static void torture_options_set_user(void **state) +{ + ssh_session session = *state; + int rc; +#ifndef _WIN32 +# ifndef NSS_BUFLEN_PASSWD +# define NSS_BUFLEN_PASSWD 4096 +# endif /* NSS_BUFLEN_PASSWD */ + struct passwd pwd; + struct passwd *pwdbuf; + char buf[NSS_BUFLEN_PASSWD]; + + /* get local username */ + rc = getpwuid_r(getuid(), &pwd, buf, NSS_BUFLEN_PASSWD, &pwdbuf); + assert_true(rc == 0); +#endif /* _WIN32 */ + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "&shallN()tP4ss"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_USER, "guru"); + assert_true(rc == 0); + assert_string_equal(session->opts.username, "guru"); + + + rc = ssh_options_set(session, SSH_OPTIONS_USER, NULL); + assert_true(rc == 0); + +#ifndef _WIN32 + assert_string_equal(session->opts.username, pwd.pw_name); +#endif +} + +static void torture_options_set_identity(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); + assert_true(rc == 0); + assert_string_equal(session->opts.identity_non_exp->root->data, + "identity1"); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); + assert_true(rc == 0); + assert_string_equal(session->opts.identity_non_exp->root->data, + "identity2"); + assert_string_equal(session->opts.identity_non_exp->root->next->data, + "identity1"); +} + +static void torture_options_get_identity(void **state) +{ + ssh_session session = *state; + char *identity = NULL; + int rc; + + /* This adds an identity to the head of the list and returns */ + rc = ssh_options_set(session, SSH_OPTIONS_ADD_IDENTITY, "identity1"); + assert_true(rc == 0); + rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_non_null(identity); + assert_string_equal(identity, "identity1"); + SAFE_FREE(identity); + + rc = ssh_options_set(session, SSH_OPTIONS_IDENTITY, "identity2"); + assert_int_equal(rc, SSH_OK); + assert_string_equal(session->opts.identity_non_exp->root->data, + "identity2"); + rc = ssh_options_get(session, SSH_OPTIONS_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_non_null(identity); + assert_string_equal(identity, "identity2"); + ssh_string_free_char(identity); + + /* Iterate over all of the identities */ + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "identity2"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "identity1"); + SAFE_FREE(identity); + + /* here are the default identities */ + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ed25519"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ecdsa"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_rsa"); + ssh_string_free_char(identity); + +#ifdef WITH_FIDO2 + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ed25519_sk"); + ssh_string_free_char(identity); + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_OK); + assert_string_equal(identity, "%d/.ssh/id_ecdsa_sk"); + ssh_string_free_char(identity); +#endif /* WITH_FIDO2 */ + + rc = ssh_options_get(session, SSH_OPTIONS_NEXT_IDENTITY, &identity); + assert_int_equal(rc, SSH_EOF); +} + +static void torture_options_set_global_knownhosts(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "/etc/libssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.global_knownhosts, + "/etc/libssh/known_hosts"); +} + +static void torture_options_get_global_knownhosts(void **state) +{ + ssh_session session = *state; + char *str = NULL; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "/etc/libssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.global_knownhosts, + "/etc/libssh/known_hosts"); + + + rc = ssh_options_get(session, SSH_OPTIONS_GLOBAL_KNOWNHOSTS, &str); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.global_knownhosts, + "/etc/libssh/known_hosts"); + + SSH_STRING_FREE_CHAR(str); +} + +static void torture_options_set_knownhosts(void **state) +{ + ssh_session session = *state; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "/home/libssh/.ssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.knownhosts, + "/home/libssh/.ssh/known_hosts"); + + /* The NULL value should not crash the libssh */ + rc = ssh_options_set(session, SSH_OPTIONS_KNOWNHOSTS, NULL); + assert_ssh_return_code(session, rc); + assert_null(session->opts.knownhosts); + + /* ssh_options_apply() should set the path to correct value */ + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + assert_non_null(session->opts.knownhosts); +} + +static void torture_options_get_knownhosts(void **state) +{ + ssh_session session = *state; + char *str = NULL; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "/home/libssh/.ssh/known_hosts"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.knownhosts, + "/home/libssh/.ssh/known_hosts"); + + + rc = ssh_options_get(session, SSH_OPTIONS_KNOWNHOSTS, &str); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.knownhosts, + "/home/libssh/.ssh/known_hosts"); + + SSH_STRING_FREE_CHAR(str); +} + +static void torture_options_proxycommand(void **state) { + ssh_session session = *state; + int rc; + + /* Enable ProxyCommand */ + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "ssh -q -A -X -W %h:%p JUMPHOST"); + assert_int_equal(rc, 0); + + assert_string_equal(session->opts.ProxyCommand, "ssh -q -A -X -W %h:%p JUMPHOST"); + + /* Disable ProxyCommand */ + rc = ssh_options_set(session, SSH_OPTIONS_PROXYCOMMAND, "none"); + assert_int_equal(rc, 0); + + assert_null(session->opts.ProxyCommand); +} + +static void torture_options_control_master (void **state) +{ + ssh_session session = *state; + int rc, val = SSH_CONTROL_MASTER_NO; + + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_NO); + + val = SSH_CONTROL_MASTER_AUTO; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_AUTO); + + val = SSH_CONTROL_MASTER_YES; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_YES); + + val = SSH_CONTROL_MASTER_ASK; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_ASK); + + val = SSH_CONTROL_MASTER_AUTOASK; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_OK); + assert_int_equal(session->opts.control_master, SSH_CONTROL_MASTER_AUTOASK); + + val = 255; + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_MASTER, + &val); + assert_int_equal(rc, SSH_ERROR); +} + +static void torture_options_control_path(void **state) +{ + ssh_session session = *state; + char *str = NULL; + int rc; + + /* Set Control Path */ + rc = ssh_options_set(session, + SSH_OPTIONS_CONTROL_PATH, + "/tmp/ssh-%r@%h:%p"); + assert_int_equal(rc, 0); + + assert_string_equal(session->opts.control_path, "/tmp/ssh-%r@%h:%p"); + + rc = ssh_options_get(session, SSH_OPTIONS_CONTROL_PATH, &str); + assert_int_equal(rc, 0); + assert_string_equal(str, "/tmp/ssh-%r@%h:%p"); + + /* Disable Multiplexing */ + rc = ssh_options_set(session, SSH_OPTIONS_CONTROL_PATH, "none"); + assert_int_equal(rc, 0); + + assert_null(session->opts.control_path); + SSH_STRING_FREE_CHAR(str); +} + +static void torture_options_config_host(void **state) +{ + ssh_session session = *state; + FILE *config = NULL; + + /* create a new config file */ + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Host testhost1\nPort 42\n" + "Host testhost2,testhost3\nPort 43\n" + "Host testhost4 testhost5\nPort 44\n", + config); + fclose(config); + + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1"); + ssh_options_parse_config(session, "test_config"); + + assert_int_equal(session->opts.port, 42); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost2"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 43); + + session->opts.port = 0; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost3"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 43); + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost4"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 44); + + session->opts.port = 0; + + torture_reset_config(session); + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost5"); + ssh_options_parse_config(session, "test_config"); + assert_int_equal(session->opts.port, 44); + + unlink("test_config"); +} + +static void torture_options_config_match(void **state) +{ + ssh_session session = *state; + char *localuser = NULL; + FILE *config = NULL; + int rv; + + /* Required for options_parse_config() */ + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1"); + + /* The Match keyword requires argument */ + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_OK); + + /* The Match all keyword needs to be the only one (start) */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match all host local\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match all keyword needs to be the only one (end) */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match host local all\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match host keyword requires an argument */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match host\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match user keyword requires an argument */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match user\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_ERROR); + + /* The Match canonical keyword is the same as match all */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match canonical\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code_equal(session, rv, SSH_OK); + assert_int_equal(session->opts.port, 33); + + session->opts.port = 0; + + /* The Match originalhost keyword is ignored */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match originalhost origin\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + + session->opts.port = 0; + + /* The Match localuser keyword */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match localuser ", config); + localuser = ssh_get_local_username(); + assert_non_null(localuser); + fputs(localuser, config); + ssh_string_free_char(localuser); + fputs("\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 33); + + session->opts.port = 0; + + /* The Match exec keyword */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec true\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_int_equal(session->opts.port, 34); +#else + assert_int_equal(session->opts.port, 33); +#endif + + session->opts.port = 0; + + /* Commands containing whitespace characters must be quoted. */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec \"true 1\"\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_int_equal(session->opts.port, 34); +#else + assert_int_equal(session->opts.port, 33); +#endif + + session->opts.port = 0; + + unlink("test_config"); +} + +static void torture_options_config_match_multi(void **state) +{ + ssh_session session = *state; + FILE *config = NULL; + struct stat sb; + int rv; + + /* Required for options_parse_config() */ + ssh_options_set(session, SSH_OPTIONS_HOST, "testhost1"); + + /* Exec is not executed when it can not be matched */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match host wronghost exec \"touch test_config_wrong\"\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + assert_int_equal(stat("test_config_wrong", &sb), -1); + + session->opts.port = 0; + + /* After matching exec, other conditions can be used */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec true host testhost1\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); +#ifndef WITH_EXEC + /* The match exec is not supported on windows at this moment */ + assert_int_equal(session->opts.port, 34); +#else + assert_int_equal(session->opts.port, 33); +#endif + + /* After matching exec, other conditions can be used */ + torture_reset_config(session); + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("Match exec true host otherhost\n" + "\tPort 33\n" + "Match all\n" + "\tPort 34\n", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + assert_int_equal(session->opts.port, 34); + + unlink("test_config"); +} + +static void torture_options_copy(void **state) +{ + ssh_session session = *state, new = NULL; + struct ssh_iterator *it = NULL, *it2 = NULL; + FILE *config = NULL; + int i, level = 9; + int rv; + + /* Required for options_parse_config() */ + ssh_options_set(session, SSH_OPTIONS_HOST, "example"); + + /* Impossible to set through the configuration */ + rv = ssh_options_set(session, SSH_OPTIONS_COMPRESSION_LEVEL, &level); + assert_ssh_return_code(session, rv); + level = 1; + rv = ssh_options_set(session, SSH_OPTIONS_NODELAY, &level); + assert_ssh_return_code(session, rv); + + /* The Match keyword requires argument */ + config = fopen("test_config", "w"); + assert_non_null(config); + fputs("IdentityFile ~/.ssh/id_ecdsa\n" + "IdentityFile ~/.ssh/my_rsa\n" + "CertificateFile ~/.ssh/my_rsa-cert.pub\n" + "CertificateFile ~/.ssh/id_ecdsa-cert.pub\n" + "User tester\n" + "Hostname example.com\n" + "BindAddress 127.0.0.2\n" + "GlobalKnownHostsFile /etc/ssh/known_hosts2\n" + "UserKnownHostsFile ~/.ssh/known_hosts2\n" + "KexAlgorithms curve25519-sha256,sntrup761x25519-sha512@openssh.com,ecdh-sha2-nistp521\n" + "Ciphers aes256-ctr\n" + "MACs hmac-sha2-256\n" + "HostKeyAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" + "Compression yes\n" + "PubkeyAcceptedAlgorithms ssh-ed25519,ecdsa-sha2-nistp521\n" + "ProxyCommand nc 127.0.0.10 22\n" + "ControlMaster ask\n" + "ControlPath /tmp/ssh-%r@%h:%p\n" + /* ops.custombanner */ + "ConnectTimeout 42\n" + "Port 222\n" + "StrictHostKeyChecking no\n" + "GSSAPIServerIdentity my.example.com\n" + "GSSAPIClientIdentity home.sweet\n" + "GSSAPIDelegateCredentials yes\n" + "PubkeyAuthentication yes\n" /* sets flags */ + "GSSAPIAuthentication no\n" /* sets flags */ + "AddressFamily inet6\n" + "", + config); + fclose(config); + + rv = ssh_options_parse_config(session, "test_config"); + assert_ssh_return_code(session, rv); + + rv = ssh_options_copy(session, &new); + assert_ssh_return_code(session, rv); + assert_non_null(new); + + /* Check the identities match */ + it = ssh_list_get_iterator(session->opts.identity_non_exp); + assert_non_null(it); + it2 = ssh_list_get_iterator(new->opts.identity_non_exp); + assert_non_null(it2); + while (it != NULL && it2 != NULL) { + assert_string_equal(it->data, it2->data); + it = it->next; + it2 = it2->next; + } + assert_null(it); + assert_null(it2); + + /* Check the certificates match */ + it = ssh_list_get_iterator(session->opts.certificate_non_exp); + assert_non_null(it); + it2 = ssh_list_get_iterator(new->opts.certificate_non_exp); + assert_non_null(it2); + while (it != NULL && it2 != NULL) { + assert_string_equal(it->data, it2->data); + it = it->next; + it2 = it2->next; + } + assert_null(it); + assert_null(it2); + + assert_string_equal(session->opts.username, new->opts.username); + assert_string_equal(session->opts.host, new->opts.host); + assert_string_equal(session->opts.bindaddr, new->opts.bindaddr); + assert_string_equal(session->opts.sshdir, new->opts.sshdir); + assert_string_equal(session->opts.knownhosts, new->opts.knownhosts); + assert_string_equal(session->opts.global_knownhosts, + new->opts.global_knownhosts); + for (i = 0; i < SSH_KEX_METHODS; i++) { + if (session->opts.wanted_methods[i] == NULL) { + assert_null(new->opts.wanted_methods[i]); + } else { + assert_string_equal(session->opts.wanted_methods[i], + new->opts.wanted_methods[i]); + } + } + assert_string_equal(session->opts.pubkey_accepted_types, + new->opts.pubkey_accepted_types); + assert_string_equal(session->opts.ProxyCommand, new->opts.ProxyCommand); + assert_null(new->opts.control_path); + /* TODO custombanner */ + assert_int_equal(session->opts.timeout, new->opts.timeout); + assert_int_equal(session->opts.timeout_usec, new->opts.timeout_usec); + assert_int_equal(session->opts.port, new->opts.port); + assert_int_equal(session->opts.control_master, new->opts.control_master); + assert_int_equal(session->opts.StrictHostKeyChecking, + new->opts.StrictHostKeyChecking); + assert_int_equal(session->opts.compressionlevel, + new->opts.compressionlevel); + assert_string_equal(session->opts.gss_server_identity, + new->opts.gss_server_identity); + assert_string_equal(session->opts.gss_client_identity, + new->opts.gss_client_identity); + assert_int_equal(session->opts.gss_delegate_creds, + new->opts.gss_delegate_creds); + assert_int_equal(session->opts.flags, new->opts.flags); + assert_int_equal(session->opts.nodelay, new->opts.nodelay); + assert_true(session->opts.config_processed == new->opts.config_processed); + assert_memory_equal(session->opts.options_seen, new->opts.options_seen, + sizeof(session->opts.options_seen)); + assert_int_equal(session->opts.address_family, new->opts.address_family); + + ssh_free(new); + + /* test if ssh_options_apply was called before ssh_options_copy + * the opts.identity list gets copied (percent expanded list) */ + rv = ssh_options_apply(session); + assert_ssh_return_code(session, rv); + + rv = ssh_options_copy(session, &new); + assert_ssh_return_code(session, rv); + assert_non_null(new); + + it = ssh_list_get_iterator(session->opts.identity_non_exp); + assert_null(it); + it2 = ssh_list_get_iterator(new->opts.identity_non_exp); + assert_null(it2); + + it = ssh_list_get_iterator(session->opts.identity); + assert_non_null(it); + it2 = ssh_list_get_iterator(new->opts.identity); + assert_non_null(it2); + while (it != NULL && it2 != NULL) { + assert_string_equal(it->data, it2->data); + it = it->next; + it2 = it2->next; + } + assert_null(it); + assert_null(it2); + + ssh_free(new); +} + +#define EXECUTABLE_NAME "test-exec" +static void torture_options_getopt(void **state) +{ + ssh_session session = *state; + int rc; + int previous_level, new_level; + const char *argv[] = {EXECUTABLE_NAME, "-l", "username", "-p", "222", + "-vv", "-v", "-r", "-c", "aes128-ctr", + "-i", "id_rsa", "-C", "-2", "-1", NULL}; + int argc = sizeof(argv)/sizeof(char *) - 1; + previous_level = ssh_get_log_level(); + + /* Test with all the supported options */ + rc = ssh_options_getopt(session, &argc, (char **)argv); +#ifdef _MSC_VER + UNUSED_VAR(new_level); + + /* Not supported in windows */ + assert_ssh_return_code_equal(session, rc, -1); +#else + assert_ssh_return_code(session, rc); + + /* Restore the log level to previous value first */ + new_level = ssh_get_log_level(); + assert_int_equal(new_level, 3); /* 2 + 1 -v's */ + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); + + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.username, "username"); + assert_int_equal(session->opts.port, 222); + /* The -r (usersa) is noop */ + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr"); + assert_string_equal(session->opts.wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr"); + assert_string_equal(session->opts.identity_non_exp->root->data, "id_rsa"); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); +#endif + /* -1 and -2 are noop */ + + + /* It should ignore unknown arguments */ + argv[1] = "-F"; + argv[2] = "config_file"; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 3); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "-F"); + assert_string_equal(argv[2], "config_file"); + + + /* It should not mess with unknown arguments order */ + argv[1] = "-F"; + argv[2] = "config_file"; + argv[3] = "-M"; + argv[4] = "hmac-sha1"; + argv[5] = "-X"; + argv[6] = NULL; + argc = 6; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 6); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "-F"); + assert_string_equal(argv[2], "config_file"); + assert_string_equal(argv[3], "-M"); + assert_string_equal(argv[4], "hmac-sha1"); + assert_string_equal(argv[5], "-X"); + + + /* Trailing arguments should be passed as they are */ + argv[1] = "-F"; + argv[2] = "config_file"; + argv[3] = "-M"; + argv[4] = "hmac-sha1"; + argv[5] = "example.com"; + argv[6] = NULL; + argc = 6; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 6); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "-F"); + assert_string_equal(argv[2], "config_file"); + assert_string_equal(argv[3], "-M"); + assert_string_equal(argv[4], "hmac-sha1"); + assert_string_equal(argv[5], "example.com"); + + /* Corner case: only one argument */ + argv[1] = "-C"; + argv[2] = NULL; + argc = 2; + rc = ssh_options_set(session, SSH_OPTIONS_COMPRESSION, "no"); + assert_ssh_return_code(session, rc); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none,zlib@openssh.com"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none,zlib@openssh.com"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); +#endif + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 1); + assert_string_equal(argv[0], EXECUTABLE_NAME); +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "none"); +#endif + + /* Corner case: only hostname is not parsed */ + argv[1] = "example.com"; + argv[2] = NULL; + argc = 2; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 2); + assert_string_equal(argv[0], EXECUTABLE_NAME); + assert_string_equal(argv[1], "example.com"); + + /* Corner case: no arguments */ + argv[1] = NULL; + argc = 1; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(argc, 1); + assert_string_equal(argv[0], EXECUTABLE_NAME); + +#endif /* _NSC_VER */ +} + +static void torture_options_getopt_o_option(void **state) +{ +#ifndef _MSC_VER + ssh_session session = *state; + int rc; + enum ssh_config_opcode_e opcode = + ssh_config_get_opcode((char *)"compression"); + const char *argv[6] = {EXECUTABLE_NAME, "-o", "Compression nah", NULL}; + int argc = 3; + + // Test: -o with invalid value (e.g., "-o Compression nah") + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + session->opts.options_seen[opcode] = 0; + + // Test: -o with valid value (e.g., "-o Compression yes") + argv[1] = "-o"; + argv[2] = "compression yes"; + argv[3] = NULL; + argc = 3; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + assert_int_equal(session->opts.options_seen[opcode], 1); + +#ifdef WITH_ZLIB + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], + "zlib@openssh.com,none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], + "zlib@openssh.com,none"); +#else + assert_string_equal(session->opts.wanted_methods[SSH_COMP_C_S], "none"); + assert_string_equal(session->opts.wanted_methods[SSH_COMP_S_C], "none"); +#endif + + // Test: -o with missing value (e.g., "-o =") + argv[1] = "-o"; + argv[2] = "="; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + + // Test: -o with only option name, no value (e.g., "-o Compression") + session->opts.options_seen[opcode] = 0; + argv[1] = "-o"; + argv[2] = "Compression"; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: -o with empty string (e.g., "-o ") + argv[1] = "-o"; + argv[2] = ""; + argv[3] = NULL; + argc = 3; + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: -o with unsupported option on the cli + argv[1] = "-o"; + argv[2] = "match *"; + argv[3] = NULL; + argc = 3; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: multiple -o options together, one invalid + session->opts.options_seen[opcode] = 0; + argv[1] = "-o"; + argv[2] = "compression yes"; + argv[3] = "-o"; + argv[4] = "enablesshkeysign yes"; + argv[5] = NULL; + argc = 5; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + // Test: multiple -o options together, all valid + session->opts.options_seen[opcode] = 0; + argv[1] = "-o"; + argv[2] = "compression no"; + argv[3] = "-o"; + argv[4] = "rekeylimit 1G 1h"; + argv[5] = NULL; + argc = 5; + + rc = ssh_options_getopt(session, &argc, (char **)argv); + assert_ssh_return_code(session, rc); + + opcode = ssh_config_get_opcode((char *)"compression"); + assert_int_equal(session->opts.options_seen[opcode], 1); + + opcode = ssh_config_get_opcode((char *)"rekeylimit"); + assert_int_equal(session->opts.options_seen[opcode], 1); +#endif /* _MSC_VER */ +} + +static void torture_options_plus_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + char *awaited; + size_t alg_len, algs_len; + + if (ssh_fips_mode()) { + alg = ",rsa-sha2-512-cert-v01@openssh.com"; + algs = ",rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = ",ssh-rsa"; + algs = ",ssh-rsa,ssh-rsa-cert-v01@openssh.com"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + /* in fips mode, the default list is the available list, which means + * we can't append anything because everything enabled is already + * included */ + if (ssh_fips_mode()) { + awaited = strdup(def_host_alg); + assert_non_null(awaited); + } else { + awaited = calloc(strlen(def_host_alg) + alg_len + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + memcpy(awaited+strlen(def_host_alg), alg, alg_len); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+ssh-rsa"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + if (!ssh_fips_mode()) { + /* different algorithm list is used here */ + free(awaited); + + awaited = calloc(strlen(def_host_alg) + algs_len + 1, 1); + assert_non_null(awaited); + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + memcpy(awaited+strlen(def_host_alg), algs, algs_len); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "+rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "+ssh-rsa,ssh-rsa-cert-v01@openssh.com"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "+blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, NULL); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + free(awaited); +} + +static void torture_options_minus_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + char *awaited, *p; + size_t alg_len, algs_len; + + if (ssh_fips_mode()) { + alg = "rsa-sha2-512-cert-v01@openssh.com,"; + algs = "rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521,"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = "ssh-ed25519,"; + algs = "ecdsa-sha2-nistp521,ecdsa-sha2-nistp384,"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + awaited = calloc(strlen(def_host_alg) + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, def_host_alg, strlen(def_host_alg)); + p = strstr(awaited, alg); + assert_non_null(p); + memmove(p, p+alg_len, strlen(p + alg_len) + 1); + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-ssh-ed25519"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + p = strstr(awaited, algs); + assert_non_null(p); + memmove(p, p+algs_len, strlen(p + algs_len) + 1); + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-ssh-ed25519,ecdsa-sha2-nistp521,ecdsa-sha2-nistp384"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "-blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + free(awaited); +} + +static void torture_options_caret_sign(void **state) +{ + ssh_session session = *state; + int rc; + const char *def_host_alg, *alg, *algs; + size_t alg_len, algs_len; + char *awaited, *p; + + if (ssh_fips_mode()) { + alg = "rsa-sha2-512-cert-v01@openssh.com,"; + algs = "rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521,"; + def_host_alg = ssh_kex_get_fips_methods(SSH_HOSTKEYS); + } else { + alg = "ssh-rsa,"; + algs = "ssh-rsa,ssh-rsa-cert-v01@openssh.com,"; + def_host_alg = ssh_kex_get_default_methods(SSH_HOSTKEYS); + } + alg_len = strlen(alg); + algs_len = strlen(algs); + + awaited = calloc(strlen(def_host_alg) + alg_len + 1, 1); + assert_non_null(awaited); + + memcpy(awaited, alg, alg_len); + memcpy(awaited+alg_len, def_host_alg, strlen(def_host_alg)); + if (ssh_fips_mode()) { + p = strstr(awaited, alg); + /* look for second occurrence */ + p = strstr(p+1, algs); + memmove(p, p+alg_len, strlen(p + alg_len) + 1); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^rsa-sha2-512-cert-v01@openssh.com"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^ssh-rsa"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + /* different algorithm list is used here */ + free(awaited); + + awaited = calloc(strlen(def_host_alg) + algs_len + 1, 1); + assert_non_null(awaited); + memcpy(awaited, algs, algs_len); + memcpy(awaited+algs_len, def_host_alg, strlen(def_host_alg)); + if (ssh_fips_mode()) { + p = strstr(awaited, algs); + /* look for second occurrence */ + p = strstr(p+1, algs); + memmove(p, p+algs_len, strlen(p + algs_len) + 1); + } + + if (ssh_fips_mode()) { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "^rsa-sha2-512-cert-v01@openssh.com,rsa-sha2-256-cert-v01@openssh.com,ecdsa-sha2-nistp521"); + } else { + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, + "^ssh-rsa,ssh-rsa-cert-v01@openssh.com"); + } + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + awaited); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^"); + assert_ssh_return_code_equal(session, rc, SSH_ERROR); + + rc = ssh_options_set(session, SSH_OPTIONS_HOSTKEYS, "^blablabla"); + assert_ssh_return_code(session, rc); + assert_string_equal(session->opts.wanted_methods[SSH_HOSTKEYS], + def_host_alg); + + free(awaited); +} + +static void torture_options_apply (void **state) +{ + ssh_session session = *state; + struct ssh_list *awaited_list = NULL; + struct ssh_iterator *it1 = NULL, *it2 = NULL; + char *id = NULL; + int rc; + + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "%%d/.ssh/known_hosts"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "/etc/%%u/libssh/known_hosts"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_PROXYCOMMAND, + "exec echo \"Hello libssh %%d!\""); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "%%d/do_not_expand"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + + /* check that the values got expanded */ + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND); + assert_true(ssh_list_count(session->opts.identity_non_exp) == 0); + assert_true(ssh_list_count(session->opts.identity) > 0); + + /* should not change anything calling it again */ + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + + /* check that the expansion was done only once */ + assert_string_equal(session->opts.knownhosts, "%d/.ssh/known_hosts"); + assert_string_equal(session->opts.global_knownhosts, + "/etc/%u/libssh/known_hosts"); + /* no exec should be added if there already is one */ + assert_string_equal(session->opts.ProxyCommand, + "exec echo \"Hello libssh %d!\""); + assert_string_equal(session->opts.identity->root->data, + "%d/do_not_expand"); + + /* apply should keep the freshest setting */ + rc = ssh_options_set(session, + SSH_OPTIONS_KNOWNHOSTS, + "hello there"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_GLOBAL_KNOWNHOSTS, + "lorem ipsum"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_PROXYCOMMAND, + "mission_impossible"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "007"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "3"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "2"); + assert_ssh_return_code(session, rc); + + rc = ssh_options_set(session, + SSH_OPTIONS_ADD_IDENTITY, + "1"); + assert_ssh_return_code(session, rc); + + /* check that flags show need of escape expansion */ + assert_false(session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS); + assert_false(session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS); + assert_false(session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND); + assert_false(ssh_list_count(session->opts.identity_non_exp) == 0); + + rc = ssh_options_apply(session); + assert_ssh_return_code(session, rc); + + /* check that the values got expanded */ + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_GLOBAL_KNOWNHOSTS); + assert_true(session->opts.exp_flags & SSH_OPT_EXP_FLAG_PROXYCOMMAND); + assert_true(ssh_list_count(session->opts.identity_non_exp) == 0); + + assert_string_equal(session->opts.knownhosts, "hello there"); + assert_string_equal(session->opts.global_knownhosts, "lorem ipsum"); + /* check that the "exec " was added at the beginning */ + assert_string_equal(session->opts.ProxyCommand, "exec mission_impossible"); + assert_string_equal(session->opts.identity->root->data, "1"); + + /* check the order of the identity files after double expansion */ + awaited_list = ssh_list_new(); + /* append the new data in order */ + id = strdup("1"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("2"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("3"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("007"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + id = strdup("%d/do_not_expand"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + /* append the defaults; this list is copied from ssh_new@src/session.c */ + id = ssh_path_expand_escape(session, "%d/.ssh/id_ed25519"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#ifdef HAVE_ECC + id = ssh_path_expand_escape(session, "%d/.ssh/id_ecdsa"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#endif + id = ssh_path_expand_escape(session, "%d/.ssh/id_rsa"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#ifdef WITH_FIDO2 + /* Add security key identities */ + id = ssh_path_expand_escape(session, "%d/.ssh/id_ed25519_sk"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); + +#ifdef HAVE_ECC + id = ssh_path_expand_escape(session, "%d/.ssh/id_ecdsa_sk"); + rc = ssh_list_append(awaited_list, id); + assert_int_equal(rc, SSH_OK); +#endif /* HAVE_ECC */ +#endif /* WITH_FIDO2 */ + + assert_int_equal(ssh_list_count(awaited_list), + ssh_list_count(session->opts.identity)); + + it1 = ssh_list_get_iterator(awaited_list); + assert_non_null(it1); + it2 = ssh_list_get_iterator(session->opts.identity); + assert_non_null(it2); + while (it1 != NULL && it2 != NULL) { + assert_string_equal(it1->data, it2->data); + + free((void*)it1->data); + it1 = it1->next; + it2 = it2->next; + } + assert_null(it1); + assert_null(it2); + + ssh_list_free(awaited_list); +} + +static void torture_options_set_verbosity (void **state) +{ + ssh_session session = *state; + int rc, new_level; + + rc = ssh_options_set(session, + SSH_OPTIONS_LOG_VERBOSITY_STR, + "3"); + assert_int_equal(rc, SSH_OK); + new_level = ssh_get_log_level(); + assert_int_equal(new_level, SSH_LOG_PACKET); + + rc = ssh_options_set(session, + SSH_OPTIONS_LOG_VERBOSITY_STR, + "datsun"); + assert_int_equal(rc, -1); + new_level = ssh_get_log_level(); + assert_int_not_equal(new_level, 0); +} + +static void torture_options_set_rsa_min_size(void **state) +{ + ssh_session session = *state; + int min_allowed = RSA_MIN_KEY_SIZE, key_size, rc; + + /* Check that passing NULL leads to failure */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, NULL); + assert_int_equal(rc, -1); + + /* + * Check that supplying a value less than the allowed minimum leads + * to failure + */ + key_size = min_allowed - 2; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying a negative value leads to failure */ + key_size = -10; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying 0 succeeds (used to revert to default) */ + key_size = 0; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); + + /* Check that supplying allowed minimum succeeds */ + key_size = min_allowed; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); + + /* Check that supplying a value greater than allowed minimum succeeds */ + key_size = min_allowed + 10; + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_ssh_return_code(session, rc); +} + +#ifdef WITH_SERVER +const char template[] = "temp_dir_XXXXXX"; + +struct bind_st { + char *cwd; + char *temp_dir; + ssh_bind bind; +}; + +static int ssh_bind_setup_files(void **state) +{ + struct bind_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct bind_st *)malloc(sizeof(struct bind_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + /* For ed25519 the test keys are not available in legacy PEM format. Using + * the new OpenSSH format for all algorithms */ + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + + torture_write_file(LIBSSH_ED25519_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); +#ifdef HAVE_ECC + torture_write_file(LIBSSH_ECDSA_521_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_ECDSA_P521, 0)); +#endif + torture_write_file(LIBSSH_CUSTOM_BIND_CONFIG_FILE, + "Port 42\n"); + return 0; +} + + +/* sshbind options */ +static int sshbind_setup(void **state) +{ + int rc; + struct bind_st *test_state = NULL; + + rc = ssh_bind_setup_files((void **)&test_state); + assert_int_equal(rc, 0); + assert_non_null(test_state); + + test_state->bind = ssh_bind_new(); + assert_non_null(test_state->bind); + + *state = test_state; + + return 0; +} + +static int sshbind_teardown(void **state) +{ + struct bind_st *test_state = NULL; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + assert_non_null(test_state->bind); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + ssh_bind_free(test_state->bind); + SAFE_FREE(test_state); + + return 0; +} + +static void +torture_bind_options_import_key(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + const char *base64_key; + ssh_key key = ssh_key_new(); + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* set null */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, NULL); + assert_int_equal(rc, -1); + /* set invalid key */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); + + /* set ed25519 key */ + base64_key = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + assert_null(key); + } else { + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + } + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, 0); + } + + /* set rsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, 0); +#ifdef HAVE_ECC + /* set ecdsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0); + rc = ssh_pki_import_privkey_base64(base64_key, NULL, NULL, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY, key); + assert_int_equal(rc, 0); +#endif +} + +static void +torture_bind_options_import_key_str(void **state) +{ + struct bind_st *test_state = NULL; + ssh_bind bind = NULL; + int rc; + const char *base64_key = ""; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* set null */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, NULL); + assert_int_equal(rc, -1); + /* set invalid key */ + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, -1); + + /* set ed25519 key */ + base64_key = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + } else { + assert_int_equal(rc, 0); + } + + /* set rsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, 0); +#ifdef HAVE_ECC + /* set ecdsa key */ + base64_key = torture_get_testkey(SSH_KEYTYPE_ECDSA_P521, 0); + + rc = + ssh_bind_options_set(bind, SSH_BIND_OPTIONS_IMPORT_KEY_STR, base64_key); + assert_int_equal(rc, 0); +#endif +} + +static void torture_bind_options_hostkey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test RSA key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_RSA_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); + + /* Test ED25519 key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_ED25519_TESTKEY); + if (ssh_fips_mode()) { + assert_int_equal(rc, SSH_ERROR); + assert_null(bind->ed25519key); + } else { + assert_int_equal(rc, 0); + assert_non_null(bind->ed25519key); + assert_string_equal(bind->ed25519key, LIBSSH_ED25519_TESTKEY); + } + +#ifdef HAVE_ECC + /* Test ECDSA key */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_ECDSA_521_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +#endif +} + +static void torture_bind_options_bindaddr(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + const char *address = "127.0.0.1"; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDADDR, address); + assert_int_equal(rc, 0); + assert_non_null(bind->bindaddr); + assert_string_equal(bind->bindaddr, address); +} + +static void torture_bind_options_bindport(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + unsigned int given_port = 1234; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT, &given_port); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 1234); +} + +static void torture_bind_options_bindport_str(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "23"); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 23); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_BINDPORT_STR, "twentythree"); + assert_int_equal(rc, -1); + assert_int_not_equal(bind->bindport, 0); +} + +static void torture_bind_options_log_verbosity(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int verbosity = SSH_LOG_PACKET; + int previous_level, new_level; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY, &verbosity); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, verbosity); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_options_log_verbosity_str(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + int previous_level, new_level; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + previous_level = ssh_get_log_level(); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "3"); + assert_int_equal(rc, 0); + + new_level = ssh_get_log_level(); + assert_int_equal(new_level, SSH_LOG_PACKET); + + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_LOG_VERBOSITY_STR, "verbosity"); + assert_int_equal(rc, -1); + new_level = ssh_get_log_level(); + assert_int_not_equal(new_level, 0); + + rc = ssh_set_log_level(previous_level); + assert_int_equal(rc, SSH_OK); +} + +static void torture_bind_options_rsakey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_RSA_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->rsakey); + assert_string_equal(bind->rsakey, LIBSSH_RSA_TESTKEY); +} + +static void torture_bind_options_set_rsa_min_size(void **state) +{ + struct bind_st *test_state = NULL; + ssh_bind bind = NULL; + int rc, min_allowed = RSA_MIN_KEY_SIZE, key_size; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Check that passing NULL leads to failure */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, NULL); + assert_int_equal(rc, -1); + + /* + * Check that supplying a value less than the allowed minimum leads + * to failure + */ + key_size = min_allowed - 2; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying a negative value leads to failure */ + key_size = -10; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, -1); + + /* Check that supplying 0 succeeds (used to revert to default) */ + key_size = 0; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); + + /* Check that supplying allowed minimum succeeds */ + key_size = min_allowed; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); + + /* Check that supplying a value greater than allowed minimum succeeds */ + key_size = min_allowed + 10; + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_RSA_MIN_SIZE, &key_size); + assert_int_equal(rc, 0); +} + +#ifdef HAVE_ECC +static void torture_bind_options_ecdsakey(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY, + LIBSSH_ECDSA_521_TESTKEY); + assert_int_equal(rc, 0); + assert_non_null(bind->ecdsakey); + assert_string_equal(bind->ecdsakey, LIBSSH_ECDSA_521_TESTKEY); +} +#endif + +static void torture_bind_options_banner(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + const char *banner = "This is the new banner"; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_BANNER, + banner); + assert_int_equal(rc, 0); + assert_non_null(bind->banner); + assert_string_equal(bind->banner, banner); +} + +static void torture_bind_options_set_ciphers(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + assert_non_null(bind->wanted_methods); + + /* Test known ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes192-ctr,aes256-ctr"); + } + + /* Test one unknown cipher */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, + "aes128-ctr,unknown-crap@example.com,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_C_S]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_C_S], + "aes128-ctr,aes256-ctr"); + + /* Test all unknown ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_C_S, + "unknown-crap@example.com,more-crap@example.com"); + assert_int_not_equal(rc, 0); + + /* Test known ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, + "aes128-ctr,aes192-ctr,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr,aes256-ctr"); + } else { + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr,aes192-ctr,aes256-ctr"); + } + + /* Test one unknown cipher */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, + "aes128-ctr,unknown-crap@example.com,aes256-ctr"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_CRYPT_S_C]); + assert_string_equal(bind->wanted_methods[SSH_CRYPT_S_C], + "aes128-ctr,aes256-ctr"); + + /* Test all unknown ciphers */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_CIPHERS_S_C, + "unknown-crap@example.com,more-crap@example.com"); + assert_int_not_equal(rc, 0); +} + +static void torture_bind_options_set_key_exchange(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + assert_non_null(bind->wanted_methods); + + /* Test known kexes */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_KEX], + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256"); + } else { + assert_string_equal(bind->wanted_methods[SSH_KEX], + "curve25519-sha256,curve25519-sha256@libssh.org," + "ecdh-sha2-nistp256,diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512," + "diffie-hellman-group14-sha256," + "diffie-hellman-group14-sha1"); + } + + /* Test one unknown kex */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + "diffie-hellman-group16-sha512," + "unknown-crap@example.com," + "diffie-hellman-group18-sha512"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_KEX]); + assert_string_equal(bind->wanted_methods[SSH_KEX], + "diffie-hellman-group16-sha512," + "diffie-hellman-group18-sha512"); + + /* Test all unknown kexes */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_KEY_EXCHANGE, + "unknown-crap@example.com,more-crap@example.com"); + assert_int_not_equal(rc, 0); +} + +static void torture_bind_options_set_macs(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + assert_non_null(bind->wanted_methods); + + /* Test known MACs */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_S_C, "hmac-sha1"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], "hmac-sha1"); + + /* Test multiple known MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "hmac-sha1,hmac-sha2-256"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], + "hmac-sha1,hmac-sha2-256"); + + /* Test unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "unknown-crap@example.com," + "hmac-sha1,unknown@example.com"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_S_C]); + assert_string_equal(bind->wanted_methods[SSH_MAC_S_C], "hmac-sha1"); + + /* Test all unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_S_C, + "unknown-crap@example.com"); + assert_int_not_equal(rc, 0); + + /* Test known MACs */ + rc = ssh_bind_options_set(bind, SSH_BIND_OPTIONS_HMAC_C_S, "hmac-sha1"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], "hmac-sha1"); + + /* Test multiple known MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "hmac-sha1,hmac-sha2-256"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], + "hmac-sha1,hmac-sha2-256"); + + /* Test unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "unknown-crap@example.com," + "hmac-sha1,unknown@example.com"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_MAC_C_S]); + assert_string_equal(bind->wanted_methods[SSH_MAC_C_S], "hmac-sha1"); + + /* Test all unknown MACs */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HMAC_C_S, + "unknown-crap@example.com"); + assert_int_not_equal(rc, 0); +} + +static void torture_bind_options_parse_config(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + char *cwd = NULL; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_CONFIG_DIR, + (const char *)cwd); + assert_int_equal(rc, 0); + assert_non_null(bind->config_dir); + assert_string_equal(bind->config_dir, cwd); + + rc = ssh_bind_options_parse_config(bind, + "%d/" LIBSSH_CUSTOM_BIND_CONFIG_FILE); + assert_int_equal(rc, 0); + assert_int_equal(bind->bindport, 42); + + SAFE_FREE(cwd); +} + +static void torture_bind_options_config_dir(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + const char *new_dir = "/new/dir/"; + const char *replacement_dir = "/replacement/dir/"; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_CONFIG_DIR, + new_dir); + assert_int_equal(rc, 0); + assert_non_null(bind->config_dir); + assert_string_equal(bind->config_dir, new_dir); + + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_CONFIG_DIR, + replacement_dir); + assert_int_equal(rc, 0); + assert_non_null(bind->config_dir); + assert_string_equal(bind->config_dir, replacement_dir); +} + +static void torture_bind_options_set_pubkey_accepted_key_types(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test known Pubkey Types */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + if (ssh_fips_mode()) { + assert_string_equal(bind->pubkey_accepted_key_types, + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(bind->pubkey_accepted_key_types, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + SAFE_FREE(bind->pubkey_accepted_key_types); + + /* Test with some unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384,unknown-type,rsa-sha2-256"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, + "ecdsa-sha2-nistp384,rsa-sha2-256"); + + SAFE_FREE(bind->pubkey_accepted_key_types); + + /* Test with only unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); + assert_int_equal(rc, -1); + assert_null(bind->pubkey_accepted_key_types); + + /* Test with something set and then try unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "ecdsa-sha2-nistp384"); + assert_int_equal(rc, 0); + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_PUBKEY_ACCEPTED_KEY_TYPES, + "unknown-type"); + assert_int_equal(rc, -1); + + /* Check that nothing changed */ + assert_non_null(bind->pubkey_accepted_key_types); + assert_string_equal(bind->pubkey_accepted_key_types, "ecdsa-sha2-nistp384"); +} + +static void torture_bind_options_set_hostkey_algorithms(void **state) +{ + struct bind_st *test_state; + ssh_bind bind; + int rc; + + assert_non_null(state); + test_state = *((struct bind_st **)state); + assert_non_null(test_state); + assert_non_null(test_state->bind); + bind = test_state->bind; + + /* Test known Pubkey Types */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + if (ssh_fips_mode()) { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + } else { + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ssh-ed25519,ecdsa-sha2-nistp384,ssh-rsa"); + } + + SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with some unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384,unknown-type"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + + SAFE_FREE(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with only unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); + assert_int_equal(rc, -1); + assert_null(bind->wanted_methods[SSH_HOSTKEYS]); + + /* Test with something set and then try unknown type */ + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "ecdsa-sha2-nistp384"); + assert_int_equal(rc, 0); + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); + rc = ssh_bind_options_set(bind, + SSH_BIND_OPTIONS_HOSTKEY_ALGORITHMS, + "unknown-type"); + assert_int_equal(rc, -1); + + /* Check that nothing changed */ + assert_non_null(bind->wanted_methods[SSH_HOSTKEYS]); + assert_string_equal(bind->wanted_methods[SSH_HOSTKEYS], + "ecdsa-sha2-nistp384"); +} + +#endif /* WITH_SERVER */ + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_options_set_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_port, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_port, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_fd, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_user, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_user, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_identity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_global_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_global_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_knownhosts, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_proxycommand, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_control_master, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_control_path, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_ciphers, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_ciphers, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_key_exchange, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_key_exchange, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_hostkey, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_hostkey, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_options_set_pubkey_accepted_types, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_options_get_pubkey_accepted_types, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_macs, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_macs, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_compression, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_get_compression, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_copy, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_config_host, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_config_match, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_config_match_multi, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_getopt, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_getopt_o_option, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_plus_sign, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_minus_sign, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_caret_sign, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_apply, setup, teardown), + cmocka_unit_test_setup_teardown(torture_options_set_verbosity, + setup, + teardown), + cmocka_unit_test_setup_teardown(torture_options_set_rsa_min_size, + setup, + teardown), + }; + +#ifdef WITH_SERVER + struct CMUnitTest sshbind_tests[] = { + cmocka_unit_test_setup_teardown(torture_bind_options_import_key, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_import_key_str, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_hostkey, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_bindaddr, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_bindport, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_bindport_str, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_log_verbosity_str, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_rsakey, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_rsa_min_size, + sshbind_setup, + sshbind_teardown), +#ifdef HAVE_ECC + cmocka_unit_test_setup_teardown(torture_bind_options_ecdsakey, + sshbind_setup, + sshbind_teardown), +#endif + cmocka_unit_test_setup_teardown(torture_bind_options_banner, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_ciphers, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_key_exchange, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_set_macs, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_parse_config, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown(torture_bind_options_config_dir, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown( + torture_bind_options_set_pubkey_accepted_key_types, + sshbind_setup, + sshbind_teardown), + cmocka_unit_test_setup_teardown( + torture_bind_options_set_hostkey_algorithms, + sshbind_setup, + sshbind_teardown), + }; +#endif /* WITH_SERVER */ + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); +#ifdef WITH_SERVER + rc += cmocka_run_group_tests(sshbind_tests, NULL, NULL); +#endif /* WITH_SERVER */ + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_packet.c b/src/libs/libssh-0.12.2/tests/unittests/torture_packet.c new file mode 100644 index 000000000000..80e96b4cbf2e --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_packet.c @@ -0,0 +1,395 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" +#include "libssh/session.h" +#include "libssh/crypto.h" +#include "libssh/buffer.h" +#include "libssh/socket.h" +#include "libssh/callbacks.h" + +#include "socket.c" + +uint8_t test_data[]="\x02" + "This is test data. Use it to check the validity of packet functions." + "This is test data. Use it to check the validity of packet functions." + "This is test data. Use it to check the validity of packet functions." + "This is test data. Use it to check the validity of packet functions."; +uint8_t key[]="iekaeshoa7ooCie2shai8shahngee3ONsee3xoishooj0ojei6aeChieth1iraPh"; +uint8_t iv[]="eixaxughoomah4ui7Aew3ohxuolaifuu"; +uint8_t mac[]="thook2Jai0ahmahyae7ChuuruoPhee8Y"; + +static uint8_t *copy_data(uint8_t *data, size_t len){ + uint8_t *ret = malloc(len); + assert_non_null(ret); + memcpy(ret, data, len); + return ret; +} + +static SSH_PACKET_CALLBACK(copy_packet_data){ + uint8_t *response = user; + size_t len = ssh_buffer_get_len(packet); + (void)type; + (void)session; + + if(len > 1024){ + len = 1024; + } + ssh_buffer_get_data(packet, response, len); + + return 0; +} + +static void +torture_packet(const char *cipher, const char *mac_type, + const char *comp_type, size_t payload_len) +{ + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + struct ssh_crypto_struct *crypto; + struct ssh_cipher_struct *in_cipher; + struct ssh_cipher_struct *out_cipher; + int rc; + int sockets[2]; + uint8_t buffer[1024]; + uint8_t response[1024]; + size_t encrypted_packet_len; + size_t processed; + ssh_packet_callback callbacks[]={copy_packet_data}; + struct ssh_packet_callbacks_struct cb = { + .start=2, + .n_callbacks=1, + .callbacks=callbacks, + .user=response + }; + int cmp; + + assert_non_null(session); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + crypto = session->next_crypto; + + rc = socketpair(AF_UNIX, SOCK_STREAM, 0, sockets); + assert_int_equal(rc, 0); + + crypto->kex_methods[SSH_KEX] = strdup("curve25519-sha256@libssh.org"); + crypto->kex_methods[SSH_HOSTKEYS] = strdup("ssh-rsa"); + crypto->kex_methods[SSH_CRYPT_C_S] = strdup(cipher); + crypto->kex_methods[SSH_CRYPT_S_C] = strdup(cipher); + crypto->kex_methods[SSH_MAC_C_S] = strdup(mac_type); + crypto->kex_methods[SSH_MAC_S_C] = strdup(mac_type); + crypto->kex_methods[SSH_COMP_C_S] = strdup(comp_type); + crypto->kex_methods[SSH_COMP_S_C] = strdup(comp_type); + crypto->kex_methods[SSH_LANG_C_S] = strdup("none"); + crypto->kex_methods[SSH_LANG_S_C] = strdup("none"); + rc = crypt_set_algorithms_client(session); + assert_int_equal(rc, SSH_OK); + session->current_crypto = session->next_crypto; + session->next_crypto = crypto_new(); + crypto->encryptkey = copy_data(key, sizeof(key)); + crypto->decryptkey = copy_data(key, sizeof(key)); + crypto->encryptIV = copy_data(iv, sizeof(iv)); + crypto->decryptIV = copy_data(iv, sizeof(iv)); + crypto->encryptMAC = copy_data(mac, sizeof(mac)); + crypto->decryptMAC = copy_data(mac, sizeof(mac)); + + in_cipher = session->current_crypto->in_cipher; + if (in_cipher->set_decrypt_key != NULL) { + rc = in_cipher->set_decrypt_key(in_cipher, + session->current_crypto->decryptkey, + session->current_crypto->decryptIV); + assert_int_equal(rc, SSH_OK); + } + + out_cipher = session->current_crypto->out_cipher; + if (out_cipher->set_decrypt_key != NULL) { + rc = out_cipher->set_encrypt_key(out_cipher, + session->current_crypto->encryptkey, + session->current_crypto->encryptIV); + assert_int_equal(rc, SSH_OK); + } + session->current_crypto->used = SSH_DIRECTION_BOTH; + + assert_non_null(session->out_buffer); + ssh_buffer_add_data(session->out_buffer, test_data, payload_len); + session->socket->fd = sockets[0]; + session->socket->write_wontblock = 1; + rc = ssh_packet_send(session); + assert_int_equal(rc, SSH_OK); + + rc = recv(sockets[1], buffer, sizeof(buffer), 0); + assert_true(rc > 0); + encrypted_packet_len = rc; + cmp = strcmp(comp_type, "none"); + if (cmp == 0) { + assert_in_range(encrypted_packet_len, + payload_len + 4, + payload_len + (32 * 3)); + } + rc = send(sockets[0], buffer, encrypted_packet_len, 0); + assert_int_equal(rc, encrypted_packet_len); + + ssh_packet_set_callbacks(session, &cb); + ssh_burn(response, sizeof(response)); + processed = + ssh_packet_socket_callback(buffer, encrypted_packet_len, session); + assert_int_equal(processed, encrypted_packet_len); + if(payload_len > 0){ + assert_memory_equal(response, test_data+1, payload_len-1); + } + close(sockets[0]); + close(sockets[1]); + session->socket->fd = SSH_INVALID_SOCKET; + ssh_free(session); +} + +static void torture_packet_aes128_ctr_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes128-ctr", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes192_ctr_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes192-ctr", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes256_ctr_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes256-ctr", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +#ifdef WITH_INSECURE_NONE +static void torture_packet_none_sha1(UNUSED_PARAM(void **state)) +{ + int i; + + for (i = 1; i < 256; ++i) { + torture_packet("none", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes128_ctr_none(UNUSED_PARAM(void **state)) +{ + int i; + + for (i = 1; i < 256; ++i) { + torture_packet("aes128-ctr", "none", "none", i); + } +} + +static void torture_packet_none_none(UNUSED_PARAM(void **state)) +{ + int i; + + for (i = 1; i < 256; ++i) { + torture_packet("none", "none", "none", i); + } +} +#endif /* WITH_INSECURE_NONE */ + +static void torture_packet_aes128_ctr(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes128-ctr", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes192_ctr(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes192-ctr", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes256_ctr(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-ctr", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes128_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes128-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes192_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes192-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes256_cbc(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_aes128_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes128-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes192_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes192-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_aes256_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + for (i = 1; i < 256; ++i) { + torture_packet("aes256-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_3des_cbc(UNUSED_PARAM(void **state)) +{ + int i; + + /* 3des is not completely FIPS-allowed cipher since 140-3 */ + if (ssh_fips_mode()) { + skip(); + } + + for (i=1;i<256;++i){ + torture_packet("3des-cbc", "hmac-sha1", "none", i); + } +} + +static void torture_packet_3des_cbc_etm(UNUSED_PARAM(void **state)) +{ + int i; + + /* 3des is not completely FIPS-allowed cipher since 140-3 */ + if (ssh_fips_mode()) { + skip(); + } + + for (i = 1; i < 256; ++i) { + torture_packet("3des-cbc", "hmac-sha1-etm@openssh.com", "none", i); + } +} + +static void torture_packet_chacha20(void **state) +{ + int i; + (void)state; /* unused */ + + /* Chacha20-poly1305 is not FIPS-allowed cipher */ + if (ssh_fips_mode()) { + skip(); + } + + for (i=1;i<256;++i){ + torture_packet("chacha20-poly1305@openssh.com", "none", "none", i); + } +} + +static void torture_packet_aes128_gcm(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes128-gcm@openssh.com", "none", "none", i); + } +} + +static void torture_packet_aes256_gcm(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-gcm@openssh.com", "none", "none", i); + } +} + +#ifdef WITH_ZLIB +static void torture_packet_compress_zlib(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-ctr", "hmac-sha1", "zlib", i); + } +} + +static void torture_packet_compress_zlib_openssh(void **state) +{ + int i; + (void)state; /* unused */ + for (i=1;i<256;++i){ + torture_packet("aes256-ctr", "hmac-sha1", "zlib@openssh.com", i); + } +} +#endif /* WITH_ZLIB */ + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_packet_aes128_ctr), + cmocka_unit_test(torture_packet_aes192_ctr), + cmocka_unit_test(torture_packet_aes256_ctr), + cmocka_unit_test(torture_packet_aes128_ctr_etm), + cmocka_unit_test(torture_packet_aes192_ctr_etm), + cmocka_unit_test(torture_packet_aes256_ctr_etm), + cmocka_unit_test(torture_packet_aes128_cbc), + cmocka_unit_test(torture_packet_aes192_cbc), + cmocka_unit_test(torture_packet_aes256_cbc), + cmocka_unit_test(torture_packet_aes128_cbc_etm), + cmocka_unit_test(torture_packet_aes192_cbc_etm), + cmocka_unit_test(torture_packet_aes256_cbc_etm), + cmocka_unit_test(torture_packet_3des_cbc), + cmocka_unit_test(torture_packet_3des_cbc_etm), + cmocka_unit_test(torture_packet_chacha20), + cmocka_unit_test(torture_packet_aes128_gcm), + cmocka_unit_test(torture_packet_aes256_gcm), +#ifdef WITH_ZLIB + cmocka_unit_test(torture_packet_compress_zlib), + cmocka_unit_test(torture_packet_compress_zlib_openssh), +#endif /* WITH_ZLIB */ +#ifdef WITH_INSECURE_NONE + cmocka_unit_test(torture_packet_none_sha1), + cmocka_unit_test(torture_packet_aes128_ctr_none), + cmocka_unit_test(torture_packet_none_none), +#endif + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_packet_filter.c b/src/libs/libssh-0.12.2/tests/unittests/torture_packet_filter.c new file mode 100644 index 000000000000..06cd74a77ce4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_packet_filter.c @@ -0,0 +1,631 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +/* + * This test checks if the messages accepted by the packet filter were intended + * to be accepted. + * + * The process consists in 2 steps: + * - Try the filter with a message type in an arbitrary state + * - If the message is accepted by the filter, check if the message is in the + * set of accepted states. + * + * Only the values selected by the flag (COMPARE_*) are considered. + * */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/priv.h" +#include "libssh/libssh.h" +#include "libssh/session.h" +#include "libssh/auth.h" +#include "libssh/ssh2.h" +#include "libssh/packet.h" + +#include "packet.c" + +#define COMPARE_SESSION_STATE 1 +#define COMPARE_ROLE (1 << 1) +#define COMPARE_DH_STATE (1 << 2) +#define COMPARE_AUTH_STATE (1 << 3) +#define COMPARE_GLOBAL_REQ_STATE (1 << 4) +#define COMPARE_CURRENT_METHOD (1 << 5) + +#define SESSION_STATE_COUNT 11 +#define DH_STATE_COUNT 4 +#define AUTH_STATE_COUNT 15 +#define GLOBAL_REQ_STATE_COUNT 5 +#define MESSAGE_COUNT 100 // from 1 to 100 + +#define ROLE_CLIENT 0 +#define ROLE_SERVER 1 + +/* + * This is the list of currently unfiltered message types. + * Only unrecognized types should be in this list. + * */ +static uint8_t unfiltered[] = { + 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, + 22, 23, 24, 25, 26, 27, 28, 29, + 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, + 54, 55, 56, 57, 58, 59, + 62, + 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, + 83, 84, 85, 86, 87, 88, 89, +}; + +typedef struct global_state_st { + /* If the bit in this flag is zero, the corresponding state is not + * considered, working as a wildcard (meaning any value is accepted) */ + uint32_t flags; + uint8_t role; + enum ssh_session_state_e session; + enum ssh_dh_state_e dh; + enum ssh_auth_state_e auth; + enum ssh_channel_request_state_e global_req; +} global_state; + +static int cmp_state(const void *e1, const void *e2) +{ + global_state *s1 = (global_state *) e1; + global_state *s2 = (global_state *) e2; + + /* Compare role (client == 0 or server == 1)*/ + if (s1->role < s2->role) { + return -1; + } + else if (s1->role > s2->role) { + return 1; + } + + /* Compare session state */ + if (s1->session < s2->session) { + return -1; + } + else if (s1->session > s2->session) { + return 1; + } + + /* Compare DH state */ + if (s1->dh < s2->dh) { + return -1; + } + else if (s1->dh > s2->dh) { + return 1; + } + + /* Compare auth */ + if (s1->auth < s2->auth) { + return -1; + } + else if (s1->auth > s2->auth) { + return 1; + } + + /* Compare global_req */ + if (s1->global_req < s2->global_req) { + return -1; + } + else if (s1->global_req > s2->global_req) { + return 1; + } + + /* If all equal, they are equal */ + return 0; +} + +static int cmp_state_search(const void *key, const void *array_element) +{ + global_state *s1 = (global_state *) key; + global_state *s2 = (global_state *) array_element; + + int result = 0; + + if (s2->flags & COMPARE_ROLE) { + /* Compare role (client == 0 or server == 1)*/ + if (s1->role < s2->role) { + return -1; + } + else if (s1->role > s2->role) { + return 1; + } + } + + if (s2->flags & COMPARE_SESSION_STATE) { + /* Compare session state */ + if (s1->session < s2->session) { + result = -1; + goto end; + } + else if (s1->session > s2->session) { + result = 1; + goto end; + } + } + + if (s2->flags & COMPARE_DH_STATE) { + /* Compare DH state */ + if (s1->dh < s2->dh) { + result = -1; + goto end; + } + else if (s1->dh > s2->dh) { + result = 1; + goto end; + } + } + + if (s2->flags & COMPARE_AUTH_STATE) { + /* Compare auth */ + if (s1->auth < s2->auth) { + result = -1; + goto end; + } + else if (s1->auth > s2->auth) { + result = 1; + goto end; + } + } + + if (s2->flags & COMPARE_GLOBAL_REQ_STATE) { + /* Compare global_req */ + if (s1->global_req < s2->global_req) { + result = -1; + goto end; + } + else if (s1->global_req > s2->global_req) { + result = 1; + goto end; + } + } + +end: + return result; +} + +static int is_state_accepted(global_state *tested, global_state *accepted, + int accepted_len) +{ + global_state *found = NULL; + + found = bsearch(tested, accepted, accepted_len, sizeof(global_state), + cmp_state_search); + + if (found != NULL) { + return 1; + } + + return 0; +} + +static int cmp_uint8(const void *i, const void *j) +{ + uint8_t e1 = *((uint8_t *)i); + uint8_t e2 = *((uint8_t *)j); + + if (e1 < e2) { + return -1; + } + else if (e1 > e2) { + return 1; + } + + return 0; +} + +static int check_unfiltered(uint8_t msg_type) +{ + uint8_t *found; + + found = bsearch(&msg_type, unfiltered, sizeof(unfiltered)/sizeof(uint8_t), + sizeof(uint8_t), cmp_uint8); + + if (found != NULL) { + return 1; + } + + return 0; +} + +static void torture_packet_filter_check_unfiltered(void **state) +{ + ssh_session session; + + int role_c; + int auth_c; + int session_c; + int dh_c; + int global_req_c; + + uint8_t msg_type; + + enum ssh_packet_filter_result_e rc; + int in_unfiltered; + + (void)state; + + session = ssh_new(); + + for (msg_type = 1; msg_type <= MESSAGE_COUNT; msg_type++) { + session->in_packet.type = msg_type; + for (role_c = 0; role_c < 2; role_c++) { + session->server = role_c; + for (session_c = 0; session_c < SESSION_STATE_COUNT; session_c++) { + session->session_state = session_c; + for (dh_c = 0; dh_c < DH_STATE_COUNT; dh_c++) { + session->dh_handshake_state = dh_c; + for (auth_c = 0; auth_c < AUTH_STATE_COUNT; auth_c++) { + session->auth.state = auth_c; + for (global_req_c = 0; + global_req_c < GLOBAL_REQ_STATE_COUNT; + global_req_c++) + { + session->global_req_state = global_req_c; + + rc = ssh_packet_incoming_filter(session); + + if (rc == SSH_PACKET_UNKNOWN) { + in_unfiltered = check_unfiltered(msg_type); + + if (!in_unfiltered) { + fprintf(stderr, "Message type %d UNFILTERED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_unfiltered, 1); + } + else { + in_unfiltered = check_unfiltered(msg_type); + + if (in_unfiltered) { + fprintf(stderr, "Message type %d NOT UNFILTERED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_unfiltered, 0); + } + } + } + } + } + } + } + ssh_free(session); +} + +static int check_message_in_all_states(global_state accepted[], + int accepted_count, uint8_t msg_type) +{ + ssh_session session; + + int role_c; + int auth_c; + int session_c; + int dh_c; + int global_req_c; + + enum ssh_packet_filter_result_e rc; + int in_accepted; + + global_state key; + + session = ssh_new(); + + /* Sort the accepted array so that the elements can be searched using + * bsearch */ + qsort(accepted, accepted_count, sizeof(global_state), cmp_state); + + session->in_packet.type = msg_type; + + for (role_c = 0; role_c < 2; role_c++) { + session->server = role_c; + key.role = role_c; + for (session_c = 0; session_c < SESSION_STATE_COUNT; session_c++) { + session->session_state = session_c; + key.session = session_c; + for (dh_c = 0; dh_c < DH_STATE_COUNT; dh_c++) { + session->dh_handshake_state = dh_c; + key.dh = dh_c; + for (auth_c = 0; auth_c < AUTH_STATE_COUNT; auth_c++) { + session->auth.state = auth_c; + key.auth = auth_c; + for (global_req_c = 0; + global_req_c < GLOBAL_REQ_STATE_COUNT; + global_req_c++) + { + session->global_req_state = global_req_c; + key.global_req = global_req_c; + + rc = ssh_packet_incoming_filter(session); + + if (rc == SSH_PACKET_ALLOWED) { + in_accepted = is_state_accepted(&key, accepted, + accepted_count); + + if (!in_accepted) { + fprintf(stderr, "Message type %d ALLOWED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_accepted, 1); + } + else if (rc == SSH_PACKET_DENIED) { + in_accepted = is_state_accepted(&key, accepted, accepted_count); + + if (in_accepted) { + fprintf(stderr, "Message type %d DENIED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + assert_int_equal(in_accepted, 0); + } + else { + fprintf(stderr, "Message type %d UNFILTERED " + "in state: role %d, session %d, dh %d, auth %d\n", + msg_type, role_c, session_c, dh_c, auth_c); + } + } + } + } + } + } + + ssh_free(session); + return 0; +} + +static void torture_packet_filter_check_auth_success(void **state) +{ + int rc; + + global_state accepted[] = { + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_PUBKEY_AUTH_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_PASSWORD_AUTH_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_GSSAPI_MIC_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_KBDINT_SENT, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_ROLE | + COMPARE_AUTH_STATE | + COMPARE_DH_STATE | + COMPARE_CURRENT_METHOD), + .role = ROLE_CLIENT, + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + .auth = SSH_AUTH_STATE_AUTH_NONE_SENT, + } + }; + + int accepted_count = 5; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_USERAUTH_SUCCESS); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_msg_ext_info(void **state) +{ + int rc; + + global_state accepted[] = { + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_DH_STATE), + .session = SSH_SESSION_STATE_AUTHENTICATING, + .dh = DH_STATE_FINISHED, + }, + { + .flags = (COMPARE_SESSION_STATE | + COMPARE_DH_STATE), + .session = SSH_SESSION_STATE_AUTHENTICATED, + .dh = DH_STATE_FINISHED, + }, + }; + + int accepted_count = 2; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_EXT_INFO); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_channel_open(void **state) +{ + int rc; + + /* The only condition to accept a CHANNEL_OPEN is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_CHANNEL_OPEN); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_channel_success(void **state) +{ + int rc; + + /* The only condition to accept a CHANNEL_SUCCESS is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_CHANNEL_SUCCESS); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_channel_failure(void **state) +{ + int rc; + + /* The only condition to accept a CHANNEL_FAILURE is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_CHANNEL_FAILURE); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_request_success(void **state) +{ + int rc; + + /* The only condition to accept a REQUEST_SUCCESS is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_REQUEST_SUCCESS); + + assert_int_equal(rc, 0); +} + +static void torture_packet_filter_check_request_failure(void **state) +{ + int rc; + + /* The only condition to accept a REQUEST_FAILURE is to be authenticated */ + global_state accepted[] = { + { + .flags = COMPARE_SESSION_STATE, + .session = SSH_SESSION_STATE_AUTHENTICATED, + } + }; + + int accepted_count = 1; + + /* Unused */ + (void) state; + + rc = check_message_in_all_states(accepted, accepted_count, + SSH2_MSG_REQUEST_FAILURE); + + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_packet_filter_check_auth_success), + cmocka_unit_test(torture_packet_filter_check_channel_open), + cmocka_unit_test(torture_packet_filter_check_channel_success), + cmocka_unit_test(torture_packet_filter_check_channel_failure), + cmocka_unit_test(torture_packet_filter_check_request_success), + cmocka_unit_test(torture_packet_filter_check_request_failure), + cmocka_unit_test(torture_packet_filter_check_unfiltered), + cmocka_unit_test(torture_packet_filter_check_msg_ext_info) + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki.c new file mode 100644 index 000000000000..c610e8a2f2e4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki.c @@ -0,0 +1,428 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_cert_dir(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + return 0; +} + +static int teardown_cert_dir(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_keytype(void **state) { + enum ssh_keytypes_e type; + const char *type_c; + + (void) state; /* unused */ + + type = ssh_key_type(NULL); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type = ssh_key_type_from_name(NULL); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type = ssh_key_type_from_name("42"); + assert_true(type == SSH_KEYTYPE_UNKNOWN); + + type_c = ssh_key_type_to_char(SSH_KEYTYPE_UNKNOWN); + assert_null(type_c); + + type_c = ssh_key_type_to_char(42); + assert_null(type_c); +} + +static void torture_pki_signature(void **state) +{ + ssh_signature sig; + + (void) state; /* unused */ + + sig = ssh_signature_new(); + assert_non_null(sig); + + ssh_signature_free(sig); +} + +struct key_attrs { + int sign; + int verify; + const char *type_c; + int size_arg; + int sig_length; + const char *sig_type_c; + int expect_success; +}; + +struct key_attrs key_attrs_list[][5] = { + { + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* UNKNOWN, SHA512 */ + }, + /* Cannot remove this as it will break the array indexing used */ + { + {0, 0, "", 0, 0, "", 0}, /* DSS, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* DSS, SHA512 */ + }, + { + {1, 1, "ssh-rsa", 2048, 0, "", 0}, /* RSA, AUTO */ + {1, 1, "ssh-rsa", 2048, 20, "ssh-rsa", 1}, /* RSA, SHA1 */ + {1, 1, "ssh-rsa", 2048, 32, "rsa-sha2-256", 1}, /* RSA, SHA256 */ + {1, 1, "ssh-rsa", 2048, 0, "", 0}, /* RSA, SHA384 */ + {1, 1, "ssh-rsa", 2048, 64, "rsa-sha2-512", 1}, /* RSA, SHA512 */ + }, + { + {0, 0, "", 0, 0, "", 0}, /* RSA1, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* RSA1, SHA512 */ + }, + { + {0, 1, "", 256, 0, "", 0}, /* ECDSA, AUTO */ + {0, 1, "", 256, 0, "", 0}, /* ECDSA, SHA1 */ + {0, 1, "", 256, 0, "", 0}, /* ECDSA, SHA256 */ + {0, 1, "", 384, 0, "", 0}, /* ECDSA, SHA384 */ + {0, 1, "", 521, 0, "", 0}, /* ECDSA, SHA512 */ + }, + { + {1, 1, "ssh-ed25519", 255, 33, "ssh-ed25519", 1}, /* ED25519, AUTO */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA1 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA256 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA384 */ + {1, 1, "ssh-ed25519", 255, 0, "", 0}, /* ED25519, SHA512 */ + }, + { + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, AUTO */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA1 */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA256 */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA384 */ + {0, 0, "", 0, 0, "", 0}, /* DSS CERT, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* RSA CERT, SHA512 */ + }, +#ifdef HAVE_ECC + { + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, AUTO */ + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, SHA1 */ + {1, 1, "ecdsa-sha2-nistp256", 256, 32, "ecdsa-sha2-nistp256", 1}, /* ECDSA P256, SHA256 */ + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, SHA384 */ + {1, 1, "ecdsa-sha2-nistp256", 256, 0, "", 0}, /* ECDSA P256, SHA512 */ + }, + { + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, AUTO */ + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, SHA1 */ + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, SHA256 */ + {1, 1, "ecdsa-sha2-nistp384", 384, 48, "ecdsa-sha2-nistp384", 1}, /* ECDSA P384, SHA384 */ + {1, 1, "ecdsa-sha2-nistp384", 384, 0, "", 0}, /* ECDSA P384, SHA512 */ + }, + { + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, AUTO */ + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, SHA1 */ + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, SHA256 */ + {1, 1, "ecdsa-sha2-nistp521", 521, 0, "", 0}, /* ECDSA P521, SHA384 */ + {1, 1, "ecdsa-sha2-nistp521", 521, 64, "ecdsa-sha2-nistp521", 1}, /* ECDSA P521, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P256 CERT, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P384 CERT, SHA512 */ + }, + { + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ECDSA P521 CERT, SHA512 */ + }, +#endif /* HAVE_ECC */ + { + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, AUTO */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA1 */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA256 */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA384 */ + {0, 1, "", 0, 0, "", 0}, /* ED25519 CERT, SHA512 */ + }, +}; + +/* This tests all the base types and their signatures against each other */ +static void torture_pki_verify_mismatch(void **state) +{ + int rc; + int verbosity = torture_libssh_verbosity(); + ssh_key key = NULL, verify_key = NULL, pubkey = NULL, verify_pubkey = NULL; + ssh_signature sign = NULL, import_sig = NULL, new_sig = NULL; + ssh_string blob; + ssh_session session = ssh_new(); + enum ssh_keytypes_e key_type, sig_type; + enum ssh_digest_e hash; + size_t input_length = sizeof(INPUT); + struct key_attrs skey_attrs, vkey_attrs; + int bits; + + (void) state; + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + for (sig_type = SSH_KEYTYPE_RSA; + sig_type <= SSH_KEYTYPE_ED25519_CERT01; + sig_type++) + { + for (hash = SSH_DIGEST_AUTO; + hash <= SSH_DIGEST_SHA512; + hash++) + { + if (ssh_fips_mode()) { + if (sig_type == SSH_KEYTYPE_ED25519 || + hash == SSH_DIGEST_SHA1) + { + /* In FIPS mode, skip unsupported algorithms */ + continue; + } + } + + skey_attrs = key_attrs_list[sig_type][hash]; + + if (!skey_attrs.sign) { + continue; + } + + rc = ssh_pki_generate(sig_type, skey_attrs.size_arg, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + assert_int_equal(key->type, sig_type); + assert_string_equal(key->type_c, skey_attrs.type_c); + bits = ssh_key_size(key); + assert_int_equal(bits, skey_attrs.size_arg); + + SSH_LOG(SSH_LOG_TRACE, "Creating signature %d with hash %d", + sig_type, hash); + + if (skey_attrs.expect_success == 0) { + /* Expect error */ + sign = pki_do_sign(key, INPUT, input_length, hash); + assert_null(sign); + + SSH_KEY_FREE(key); + continue; + } + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Create a valid signature using this key */ + sign = pki_do_sign(key, INPUT, input_length, hash); + assert_non_null(sign); + assert_int_equal(sign->type, key->type); + assert_string_equal(sign->type_c, skey_attrs.sig_type_c); + + /* Create a signature blob that can be imported and verified */ + blob = pki_signature_to_blob(sign); + assert_non_null(blob); + + /* Import and verify with current key + * (this is not tested anywhere else yet) */ + import_sig = pki_signature_from_blob(key, + blob, + sig_type, + hash); + assert_non_null(import_sig); + assert_int_equal(import_sig->type, key->type); + assert_string_equal(import_sig->type_c, skey_attrs.sig_type_c); + + rc = ssh_pki_signature_verify(session, + import_sig, + pubkey, + INPUT, + input_length); + assert_true(rc == SSH_OK); + + for (key_type = SSH_KEYTYPE_RSA; + key_type <= SSH_KEYTYPE_ED25519_CERT01; + key_type++) + { + if (ssh_fips_mode()) { + if (key_type == SSH_KEYTYPE_ED25519) + { + /* In FIPS mode, skip unsupported algorithms */ + continue; + } + } + + vkey_attrs = key_attrs_list[key_type][hash]; + if (!vkey_attrs.verify) { + continue; + } + + SSH_LOG(SSH_LOG_TRACE, "Trying key %d with signature %d", + key_type, sig_type); + + if (is_cert_type(key_type)) { + torture_write_file("libssh_testkey-cert.pub", + torture_get_testkey_pub(key_type)); + rc = ssh_pki_import_cert_file("libssh_testkey-cert.pub", &verify_pubkey); + verify_key = NULL; + } else { + rc = ssh_pki_generate(key_type, vkey_attrs.size_arg, &verify_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_key); + rc = ssh_pki_export_privkey_to_pubkey(verify_key, &verify_pubkey); + } + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_pubkey); + + /* Should gracefully fail, but not crash */ + rc = ssh_pki_signature_verify(session, + sign, + verify_pubkey, + INPUT, + input_length); + assert_true(rc != SSH_OK); + + /* Try the same with the imported signature */ + rc = ssh_pki_signature_verify(session, + import_sig, + verify_pubkey, + INPUT, + input_length); + assert_true(rc != SSH_OK); + + /* Try to import the signature blob with different key */ + new_sig = pki_signature_from_blob(verify_pubkey, + blob, + sig_type, + import_sig->hash_type); + if (ssh_key_type_plain(verify_pubkey->type) == sig_type) { + /* Importing with the same key type should work */ + assert_non_null(new_sig); + assert_int_equal(new_sig->type, key->type); + assert_string_equal(new_sig->type_c, skey_attrs.sig_type_c); + + /* The verification should not work */ + rc = ssh_pki_signature_verify(session, + new_sig, + verify_pubkey, + INPUT, + input_length); + assert_true(rc != SSH_OK); + + ssh_signature_free(new_sig); + } else { + assert_null(new_sig); + } + SSH_KEY_FREE(verify_key); + SSH_KEY_FREE(verify_pubkey); + } + + ssh_string_free(blob); + ssh_signature_free(sign); + ssh_signature_free(import_sig); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + } + } + ssh_free(session); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_keytype), + cmocka_unit_test(torture_pki_signature), + cmocka_unit_test_setup_teardown(torture_pki_verify_mismatch, + setup_cert_dir, + teardown_cert_dir), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_dsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_dsa.c new file mode 100644 index 000000000000..dfd59a64a184 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_dsa.c @@ -0,0 +1,220 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#define LIBSSH_DSA_TESTKEY "libssh_testkey.id_dsa" +#define LIBSSH_DSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_dsa" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char INPUT[] = "12345678901234567890"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_dsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_DSS, 0)); + torture_write_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, + torture_get_testkey(SSH_KEYTYPE_DSS, 1)); + torture_write_file(LIBSSH_DSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS)); + torture_write_file(LIBSSH_DSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS_CERT01)); + + return 0; +} + +static int setup_openssh_dsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + torture_write_file(LIBSSH_DSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_DSS, 0)); + torture_write_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, + torture_get_openssh_testkey(SSH_KEYTYPE_DSS, 1)); + torture_write_file(LIBSSH_DSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS)); + torture_write_file(LIBSSH_DSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_DSS_CERT01)); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_dsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_DSA_TESTKEY ".pub", &pubkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(pubkey); +} + +static void torture_pki_dsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_DSA_TESTKEY_PASSPHRASE, &pubkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(pubkey); +} + +static void torture_pki_dsa_import_privkey_base64(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_DSS, 0), + passphrase, + NULL, + NULL, + &key); + assert_int_equal(rc, SSH_ERROR); + assert_null(key); +} + +static void torture_pki_generate_dsa(void **state) +{ + int rc; + ssh_key key = NULL; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_DSS, 2048, &key); + assert_int_equal(rc, SSH_ERROR); + assert_null(key); +} + +static void torture_pki_dsa_import_cert_file(void **state) +{ + int rc; + ssh_key cert = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_DSA_TESTKEY "-cert.pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_pubkey_file, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_pubkey_from_openssh_privkey, + setup_openssh_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_privkey_base64, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_privkey_base64, + setup_openssh_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_dsa_import_cert_file, + setup_dsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_generate_dsa, + setup_dsa_key, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa.c new file mode 100644 index 000000000000..8149ca192ba1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa.c @@ -0,0 +1,1297 @@ +#include "config.h" +#include "libssh/libssh.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#define LIBSSH_ECDSA_TESTKEY "libssh_testkey.id_ecdsa" +#define LIBSSH_ECDSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ecdsa" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char INPUT[] = "12345678901234567890"; + +struct pki_st { + char *cwd; + char *temp_dir; + enum ssh_keytypes_e type; +}; + +static int setup_ecdsa_key(void **state, int ecdsa_bits) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + switch (ecdsa_bits) { + case 521: + test_state->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 384: + test_state->type = SSH_KEYTYPE_ECDSA_P384; + break; + default: + test_state->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + torture_write_file(LIBSSH_ECDSA_TESTKEY, + torture_get_testkey(test_state->type, 0)); + torture_write_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + torture_get_testkey(test_state->type, 1)); + torture_write_file(LIBSSH_ECDSA_TESTKEY ".pub", + torture_get_testkey_pub(test_state->type)); + torture_write_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(test_state->type+3)); + return 0; +} + +static int setup_openssh_ecdsa_key(void **state, int ecdsa_bits) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + const char *keystring = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + switch (ecdsa_bits) { + case 521: + test_state->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 384: + test_state->type = SSH_KEYTYPE_ECDSA_P384; + break; + default: + test_state->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + keystring = torture_get_openssh_testkey(test_state->type, 0); + torture_write_file(LIBSSH_ECDSA_TESTKEY, keystring); + + keystring = torture_get_openssh_testkey(test_state->type, 1); + torture_write_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, keystring); + torture_write_file(LIBSSH_ECDSA_TESTKEY ".pub", + torture_get_testkey_pub(test_state->type)); + torture_write_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(test_state->type+3)); + return 0; +} + +static int setup_ecdsa_key_521(void **state) +{ + setup_ecdsa_key(state, 521); + + return 0; +} + +static int setup_ecdsa_key_384(void **state) +{ + setup_ecdsa_key(state, 384); + + return 0; +} + +static int setup_ecdsa_key_256(void **state) +{ + setup_ecdsa_key(state, 256); + + return 0; +} + +static int setup_openssh_ecdsa_key_521(void **state) +{ + setup_openssh_ecdsa_key(state, 521); + + return 0; +} + +static int setup_openssh_ecdsa_key_384(void **state) +{ + setup_openssh_ecdsa_key(state, 384); + + return 0; +} + +static int setup_openssh_ecdsa_key_256(void **state) +{ + setup_openssh_ecdsa_key(state, 256); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ecdsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_format(void **state, + enum ssh_file_format_e format) +{ + int rc; + char *key_str = NULL, *new_key_str = NULL; + ssh_key key = NULL, new_key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void)state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + /* Export */ + rc = ssh_pki_export_privkey_base64_format(key, + passphrase, + NULL, + NULL, + &new_key_str, + format); + assert_int_equal(rc, SSH_OK); + assert_non_null(new_key_str); + + /* and import again */ + rc = ssh_pki_import_privkey_base64(new_key_str, passphrase, NULL, NULL, + &new_key); + assert_int_equal(rc, 0); + assert_non_null(new_key); + + rc = ssh_key_is_private(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PRIVATE | SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + free(key_str); + free(new_key_str); + SSH_KEY_FREE(key); + SSH_KEY_FREE(new_key); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_default(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_DEFAULT); +} + +static void torture_pki_ecdsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + + +static void torture_pki_ecdsa_publickey_from_privatekey(void **state) +{ + int rc; + char *key_str = NULL; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + free(key_str); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_cert_file(void **state) +{ + int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + enum ssh_keytypes_e type, exp_cert_type; + struct pki_st *test_state = *((struct pki_st **)state); + + exp_cert_type = test_state->type + 3; + + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); + assert_int_equal(rc, 0); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + type = ssh_key_type(cert); + assert_int_equal(type, exp_cert_type); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == test_state->type); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + assert_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_int_equal(rc, SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == exp_cert_type); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + /* Generate different key and try to assign it this certificate */ + rc = ssh_pki_generate_key(test_state->type, NULL, &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_int_equal(rc, SSH_ERROR); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key = NULL; + int rc; + struct pki_st *test_state = *((struct pki_st **)state); + + key_buf = torture_pki_read_file(LIBSSH_ECDSA_TESTKEY ".pub"); + assert_non_null(key_buf); + + q = p = key_buf; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_int_equal(type, test_state->type); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_int_equal(rc, 0); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_generate_pubkey_from_privkey(void **state) +{ + char pubkey_original[4096] = {0}; + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + int len; + + (void) state; /* unused */ + + rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", + pubkey_original, + sizeof(pubkey_original)); + assert_int_equal(rc, 0); + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_ECDSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ECDSA_TESTKEY ".pub"); + assert_int_equal(rc, 0); + + rc = torture_read_one_line(LIBSSH_ECDSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_int_equal(rc, 0); + len = torture_pubkey_len(pubkey_original); + assert_int_equal(len, torture_pubkey_len(pubkey_generated)); + assert_memory_equal(pubkey_original, pubkey_generated, len); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_duplicate_key(void **state) +{ + int rc; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_ECDSA_TESTKEY ".pub", &pubkey); + assert_int_equal(rc, 0); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_int_equal(rc, 0); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_return_code(rc, errno); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_int_equal(rc, 0); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +/* Test case for bug #147: Private ECDSA key duplication did not carry + * over parts of the key that then caused subsequent key demotion to + * fail. + */ +static void torture_pki_ecdsa_duplicate_then_demote(void **state) +{ + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + int rc; + + (void) state; + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + assert_int_equal(privkey->ecdsa_nid, privkey_dup->ecdsa_nid); + + rc = ssh_pki_export_privkey_to_pubkey(privkey_dup, &pubkey); + assert_int_equal(rc, 0); + assert_non_null(pubkey); + assert_int_equal(pubkey->ecdsa_nid, privkey->ecdsa_nid); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); +} + +static void torture_pki_generate_key_ecdsa(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + const char *type_char = NULL; + const char *etype_char = NULL; + ssh_session session=ssh_new(); + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 0, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp256"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp256"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* deprecated */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 256, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P256); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp256"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp256"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P384, 0, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P384); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp384"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp384"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* deprecated */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 384, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P384); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp384"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp384"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P521, 0, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P521); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp521"); + etype_char =ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp521"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* deprecated */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 521, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ECDSA_P521); + type_char = ssh_key_type_to_char(type); + assert_string_equal(type_char, "ecdsa-sha2-nistp521"); + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, "ecdsa-sha2-nistp521"); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_ecdsa_cert_verify(void **state) +{ + int rc; + ssh_key privkey = NULL, cert = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + enum ssh_digest_e hash_type; + (void) state; + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_pki_import_cert_file(LIBSSH_ECDSA_TESTKEY "-cert.pub", &cert); + assert_int_equal(rc, 0); + assert_non_null(cert); + + /* Get the hash type to be used in the signature based on the key type */ + hash_type = ssh_key_type_to_hash(session, privkey->type); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), hash_type); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); + + ssh_free(session); +} + +static int test_sign_verify_data(ssh_key key, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + ssh_signature sig; + ssh_key pubkey = NULL; + int rc; + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, hash_type, input, input_len); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, input, input_len); + assert_int_equal(rc, SSH_OK); + + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + + return rc; +} + +static void torture_pki_sign_data_ecdsa(void **state) +{ + int rc; + ssh_key key = NULL; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA, 256, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Test using SHA256 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + SSH_KEY_FREE(key); +} + +static void torture_pki_fail_sign_with_incompatible_hash(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + ssh_signature sig, bad_sig; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_ECDSA_P256, 256, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_AUTO */ + bad_sig = pki_sign_data(key, SSH_DIGEST_AUTO, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_AUTO */ + sig->hash_type = SSH_DIGEST_AUTO; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA1 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA1, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA1 */ + sig->hash_type = SSH_DIGEST_SHA1; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA384 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA384, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA384 */ + sig->hash_type = SSH_DIGEST_SHA384; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_SHA512 */ + bad_sig = pki_sign_data(key, SSH_DIGEST_SHA512, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_SHA512 */ + sig->hash_type = SSH_DIGEST_SHA512; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(key); +} + +static void +torture_pki_ecdsa_write_privkey_format(void **state, + enum ssh_file_format_e format) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_int_equal(rc, 0); + assert_non_null(origkey); + + unlink(LIBSSH_ECDSA_TESTKEY); + + rc = ssh_pki_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY, + format); + assert_int_equal(rc, 0); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_int_equal(rc, 0); + assert_non_null(origkey); + + unlink(LIBSSH_ECDSA_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + format); + assert_int_equal(rc, 0); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} + +static void +torture_pki_ecdsa_write_privkey(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#ifdef HAVE_LIBCRYPTO +static void +torture_pki_ecdsa_write_privkey_pem(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ecdsa_write_privkey_openssh(void **state) +{ + torture_pki_ecdsa_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_pem(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ecdsa_import_export_privkey_base64_openssh(void **state) +{ + torture_pki_ecdsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_OPENSSH); +} +#endif /* HAVE_LIBCRYPTO */ + +static void torture_pki_ecdsa_name(void **state, const char *expected_name) +{ + int rc; + ssh_key key = NULL; + const char *etype_char = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_ECDSA_TESTKEY, NULL, NULL, NULL, &key); + assert_int_equal(rc, 0); + assert_non_null(key); + + etype_char = ssh_pki_key_ecdsa_name(key); + assert_string_equal(etype_char, expected_name); + + SSH_KEY_FREE(key); +} + +static void torture_pki_ecdsa_name256(void **state) +{ + torture_pki_ecdsa_name(state, "ecdsa-sha2-nistp256"); +} + +static void torture_pki_ecdsa_name384(void **state) +{ + torture_pki_ecdsa_name(state, "ecdsa-sha2-nistp384"); +} + +static void torture_pki_ecdsa_name521(void **state) +{ + torture_pki_ecdsa_name(state, "ecdsa-sha2-nistp521"); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_pubkey_from_openssh_privkey, + setup_openssh_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_pubkey_from_openssh_privkey, + setup_openssh_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_pubkey_file, + setup_openssh_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_comment, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_privkey_base64_whitespace, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_default, + setup_openssh_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_publickey_from_privatekey, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_import_cert_file, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_then_demote, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_then_demote, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_then_demote, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_publickey_base64, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_generate_pubkey_from_privkey, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_duplicate_key, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test(torture_pki_generate_key_ecdsa), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_cert_verify, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey, + setup_ecdsa_key_521, + teardown), +#ifdef HAVE_LIBCRYPTO + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_pem, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_write_privkey_openssh, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_pem, + setup_ecdsa_key_521, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ecdsa_import_export_privkey_base64_openssh, + setup_ecdsa_key_521, + teardown), +#endif /* HAVE_LIBCRYPTO */ + cmocka_unit_test(torture_pki_sign_data_ecdsa), + cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_name256, + setup_ecdsa_key_256, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_name384, + setup_ecdsa_key_384, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ecdsa_name521, + setup_ecdsa_key_521, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa_uri.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa_uri.c new file mode 100644 index 000000000000..7e6cb8bf41d0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ecdsa_uri.c @@ -0,0 +1,584 @@ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#define LIBSSH_ECDSA_TESTKEY "libssh_testkey.id_" +#define LIBSSH_ECDSA_TESTKEY_PEM "libssh_testkey_pem.id_" +#define LABEL_256 "ecdsa256" +#define LABEL_384 "ecdsa384" +#define LABEL_521 "ecdsa521" +#define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" +#define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=1234" +#define PRIV_URI_NO_PUB_FMT "pkcs11:token=%s_no_pub_uri;object=%s_no_pub_uri;type=private?pin-value=1234" + +/** PKCS#11 URIs with invalid fields**/ + +#define PRIV_URI_FMT_384_INVALID_TOKEN "pkcs11:token=ecdsa521;object=ecdsa384;type=private?pin-value=1234" +#define PRIV_URI_FMT_521_INVALID_OBJECT "pkcs11:token=ecdsa521;object=ecdsa384;type=private?pin-value=1234" +#define PUB_URI_FMT_384_INVALID_TOKEN "pkcs11:token=ecdsa521;object=ecdsa384;type=public" +#define PUB_URI_FMT_521_INVALID_OBJECT "pkcs11:token=ecdsa521;object=ecdsa384;type=public" + +const char template[] = "/tmp/temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; +struct pki_st { + char *orig_dir; + char *temp_dir; + enum ssh_keytypes_e type; +}; + +static int setup_tokens_ecdsa(void **state, int ecdsa_bits, const char *obj_tempname, const char *load_public) +{ + + struct pki_st *test_state = *state; + char priv_filename[1024]; + char pub_filename[1024]; + char *cwd = NULL; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + snprintf(priv_filename, sizeof(priv_filename), "%s%s%s%s", cwd, "/", LIBSSH_ECDSA_TESTKEY, obj_tempname); + snprintf(pub_filename, sizeof(pub_filename), "%s%s%s%s%s", cwd, "/", LIBSSH_ECDSA_TESTKEY, obj_tempname, ".pub"); + + switch (ecdsa_bits) { + case 521: + test_state->type = SSH_KEYTYPE_ECDSA_P521; + break; + case 384: + test_state->type = SSH_KEYTYPE_ECDSA_P384; + break; + default: + test_state->type = SSH_KEYTYPE_ECDSA_P256; + break; + } + + torture_write_file(priv_filename, + torture_get_testkey(test_state->type, 0)); + torture_write_file(pub_filename, + torture_get_testkey_pub_pem(test_state->type)); + torture_setup_tokens(cwd, priv_filename, obj_tempname, load_public); + + return 0; +} + +static int setup_directory_structure(void **state) +{ + struct pki_st *test_state = NULL; + char *temp_dir; + int rc; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + SAFE_FREE(temp_dir); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + *state = test_state; + + setup_tokens_ecdsa(state, 256, "ecdsa256", "1"); + setup_tokens_ecdsa(state, 384, "ecdsa384", "1"); + setup_tokens_ecdsa(state, 521, "ecdsa521", "1"); + setup_tokens_ecdsa(state, 256, "ecdsa256_no_pub_uri", "0"); + setup_tokens_ecdsa(state, 384, "ecdsa384_no_pub_uri", "0"); + setup_tokens_ecdsa(state, 521, "ecdsa521_no_pub_uri", "0"); + + return 0; +} + +static int teardown_directory_structure(void **state) +{ + struct pki_st *test_state = *state; + int rc; + + torture_cleanup_tokens(test_state->temp_dir); + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ecdsa_import_pubkey_uri(void **state, const char *label) +{ + char uri[128] = {0}; + ssh_key pubkey = NULL; + int rc; + + rc = snprintf(uri, sizeof(uri), PUB_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + + rc = ssh_pki_import_pubkey_file(uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_import_pubkey_uri_256(void **state) +{ + torture_pki_ecdsa_import_pubkey_uri(state, LABEL_256); +} + +static void torture_pki_ecdsa_import_pubkey_uri_384(void **state) +{ + torture_pki_ecdsa_import_pubkey_uri(state, LABEL_384); +} + +static void torture_pki_ecdsa_import_pubkey_uri_521(void **state) +{ + torture_pki_ecdsa_import_pubkey_uri(state, LABEL_521); +} + +static void +torture_pki_ecdsa_publickey_from_privatekey_uri(void **state, + const char *label, + const char *type) +{ + int rc; + char uri[128] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_string pblob = NULL; + char pubkey_original[4096] = {0}; + char pubkey_generated[4096] = {0}; + char convert_key_to_pem[4096]; + char pub_filename[1024]; + char pub_filename_generated[1024]; + char pub_filename_pem[1024]; + + rc = snprintf(uri, sizeof(uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + + rc = ssh_pki_import_privkey_file(uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_export_pubkey_blob(privkey, &pblob); + assert_return_code(rc, errno); + assert_non_null(pblob); + ssh_string_free(pblob); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + snprintf(pub_filename, sizeof(pub_filename), "%s%s%s", LIBSSH_ECDSA_TESTKEY, type, ".pub"); + snprintf(pub_filename_generated, sizeof(pub_filename_generated), "%s%s%s", + LIBSSH_ECDSA_TESTKEY_PEM, type, "generated.pub"); + snprintf(pub_filename_pem, sizeof(pub_filename_pem), "%s%s%s", LIBSSH_ECDSA_TESTKEY_PEM, type, ".pub"); + + rc = torture_read_one_line(pub_filename, + pubkey_original, + sizeof(pubkey_original)); + assert_return_code(rc, errno); + + rc = ssh_pki_export_pubkey_file(pubkey, pub_filename_generated); + assert_return_code(rc, errno); + + /* remove the public key, generate it from the private key and write it. */ + unlink(pub_filename); + + snprintf(convert_key_to_pem, sizeof(convert_key_to_pem), "ssh-keygen -e -f %s -m PKCS8 > %s ", + pub_filename_generated, pub_filename_pem); + + system(convert_key_to_pem); + + rc = torture_read_one_line(pub_filename_pem, + pubkey_generated, + sizeof(pubkey_generated)); + assert_return_code(rc, errno); + + assert_memory_equal(pubkey_original, pubkey_generated, strlen(pubkey_original)); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_256(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, LABEL_256, "ecdsa256"); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_384(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, LABEL_384, "ecdsa384"); +} + +static void torture_pki_ecdsa_publickey_from_privatekey_uri_521(void **state) +{ + torture_pki_ecdsa_publickey_from_privatekey_uri(state, LABEL_521, "ecdsa521"); +} + +static void +import_pubkey_without_loading_public_uri(void **state, const char *label) +{ + int rc; + char uri[128] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_string pblob = NULL; + + rc = snprintf(uri, sizeof(uri), PRIV_URI_NO_PUB_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + + rc = ssh_pki_import_privkey_file(uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_export_pubkey_blob(privkey, &pblob); + assert_int_not_equal(rc, 0); + assert_null(pblob); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + SSH_KEY_FREE(privkey); +} + +static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_256(void **state) +{ + import_pubkey_without_loading_public_uri(state, LABEL_256); +} + +static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_384(void **state) +{ + import_pubkey_without_loading_public_uri(state, LABEL_384); +} + +static void torture_pki_ecdsa_import_pubkey_without_loading_public_uri_521(void **state) +{ + import_pubkey_without_loading_public_uri(state, LABEL_521); +} + +static void +torture_ecdsa_sign_verify_uri(void **state, + const char *label, + enum ssh_digest_e dig_type) +{ + int rc; + char uri[128] = {0}; + ssh_key privkey = NULL, pubkey = NULL; + ssh_signature sign = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + const char *type_char = NULL; + const char *etype_char = NULL; + ssh_session session = ssh_new(); + + assert_non_null(session); + + rc = snprintf(uri, sizeof(uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(uri) - 1); + + rc = ssh_pki_import_privkey_file(uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), dig_type); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + + type = ssh_key_type(privkey); + type_char = ssh_key_type_to_char(type); + etype_char = ssh_pki_key_ecdsa_name(privkey); + + switch (dig_type) { + case SSH_DIGEST_SHA256: + assert_true(type == SSH_KEYTYPE_ECDSA_P256); + assert_string_equal(type_char, "ecdsa-sha2-nistp256"); + assert_string_equal(etype_char, "ecdsa-sha2-nistp256"); + break; + case SSH_DIGEST_SHA384: + assert_true(type == SSH_KEYTYPE_ECDSA_P384); + assert_string_equal(type_char, "ecdsa-sha2-nistp384"); + assert_string_equal(etype_char, "ecdsa-sha2-nistp384"); + break; + case SSH_DIGEST_SHA512: + assert_true(type == SSH_KEYTYPE_ECDSA_P521); + assert_string_equal(type_char, "ecdsa-sha2-nistp521"); + assert_string_equal(etype_char, "ecdsa-sha2-nistp521"); + break; + default: + printf("Invalid hash type: %d\n", dig_type); + } + + ssh_free(session); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_ecdsa_sign_verify_uri_256(void **state) +{ + torture_ecdsa_sign_verify_uri(state, LABEL_256, SSH_DIGEST_SHA256); +} + +static void torture_ecdsa_sign_verify_uri_384(void **state) +{ + torture_ecdsa_sign_verify_uri(state, LABEL_384, SSH_DIGEST_SHA384); +} + +static void torture_ecdsa_sign_verify_uri_521(void **state) +{ + torture_ecdsa_sign_verify_uri(state, LABEL_521, SSH_DIGEST_SHA512); +} + +static void torture_pki_ecdsa_duplicate_key_uri(void **state, const char *label) +{ + int rc; + char pub_uri[128] = {0}; + char priv_uri[128] = {0}; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = snprintf(pub_uri, sizeof(pub_uri), PUB_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(pub_uri) - 1); + rc = snprintf(priv_uri, sizeof(priv_uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(priv_uri) - 1); + + rc = ssh_pki_import_pubkey_file(pub_uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_return_code(rc, errno); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_return_code(rc, errno); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_return_code(rc, errno); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +static void torture_pki_ecdsa_duplicate_key_uri_256(void **state) +{ + torture_pki_ecdsa_duplicate_key_uri(state, LABEL_256); +} + +static void torture_pki_ecdsa_duplicate_key_uri_384(void **state) +{ + torture_pki_ecdsa_duplicate_key_uri(state, LABEL_384); +} + +static void torture_pki_ecdsa_duplicate_key_uri_521(void **state) +{ + torture_pki_ecdsa_duplicate_key_uri(state, LABEL_521); +} + +static void +torture_pki_ecdsa_duplicate_then_demote_uri(void **state, const char *label) +{ + char priv_uri[128] = {0}; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + int rc; + + (void) state; + + rc = snprintf(priv_uri, sizeof(priv_uri), PRIV_URI_FMT, label, label); + assert_in_range(rc, 0, sizeof(priv_uri) - 1); + + rc = ssh_pki_import_privkey_file(priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + assert_int_equal(privkey->ecdsa_nid, privkey_dup->ecdsa_nid); + + rc = ssh_pki_export_privkey_to_pubkey(privkey_dup, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + assert_int_equal(pubkey->ecdsa_nid, privkey->ecdsa_nid); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri_256(void **state) +{ + torture_pki_ecdsa_duplicate_then_demote_uri(state, LABEL_256); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri_384(void **state) +{ + torture_pki_ecdsa_duplicate_then_demote_uri(state, LABEL_384); +} + +static void torture_pki_ecdsa_duplicate_then_demote_uri_521(void **state) +{ + torture_pki_ecdsa_duplicate_then_demote_uri(state, LABEL_521); +} + +static void torture_pki_ecdsa_import_pubkey_uri_invalid_configurations(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + + /** invalid token for already setup Private PKCS #11 URI */ + rc = ssh_pki_import_privkey_file(PRIV_URI_FMT_384_INVALID_TOKEN, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + + /** invalid object for already setup Private PKCS #11 URI */ + rc = ssh_pki_import_privkey_file(PRIV_URI_FMT_521_INVALID_OBJECT, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + /** invalid token for already setup Public PKCS #11 URI */ + rc = ssh_pki_import_pubkey_file(PUB_URI_FMT_384_INVALID_TOKEN, + &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + /** invalid object for already setup Public PKCS #11 URI */ + rc = ssh_pki_import_pubkey_file(PUB_URI_FMT_521_INVALID_OBJECT, + &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_256), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_384), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_521), + cmocka_unit_test(torture_pki_ecdsa_publickey_from_privatekey_uri_256), + cmocka_unit_test(torture_pki_ecdsa_publickey_from_privatekey_uri_384), + cmocka_unit_test(torture_pki_ecdsa_publickey_from_privatekey_uri_521), + cmocka_unit_test(torture_ecdsa_sign_verify_uri_256), + cmocka_unit_test(torture_ecdsa_sign_verify_uri_384), + cmocka_unit_test(torture_ecdsa_sign_verify_uri_521), + cmocka_unit_test(torture_pki_ecdsa_duplicate_key_uri_256), + cmocka_unit_test(torture_pki_ecdsa_duplicate_key_uri_384), + cmocka_unit_test(torture_pki_ecdsa_duplicate_key_uri_521), + cmocka_unit_test(torture_pki_ecdsa_duplicate_then_demote_uri_256), + cmocka_unit_test(torture_pki_ecdsa_duplicate_then_demote_uri_384), + cmocka_unit_test(torture_pki_ecdsa_duplicate_then_demote_uri_521), + + /** Expect fail on these negative test cases **/ + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_uri_invalid_configurations), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_256), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_384), + cmocka_unit_test(torture_pki_ecdsa_import_pubkey_without_loading_public_uri_521), + }; + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + + ssh_init(); + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, setup_directory_structure, teardown_directory_structure); + + ssh_free(session); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519.c new file mode 100644 index 000000000000..6a8d04e81fe8 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519.c @@ -0,0 +1,1210 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include +#include + +#define LIBSSH_ED25519_TESTKEY "libssh_testkey.id_ed25519" +#define LIBSSH_ED25519_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ed25519" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char HASH[] = "12345678901234567890"; +const uint8_t ref_signature[ED25519_SIG_LEN]= + "\xbb\x8d\x55\x9f\x06\x14\x39\x24\xb4\xe1\x5a\x57\x3d\x9d\xbe\x22" + "\x1b\xc1\x32\xd5\x55\x16\x00\x64\xce\xb4\xc3\xd2\xe3\x6f\x5e\x8d" + "\x10\xa3\x18\x93\xdf\xa4\x96\x81\x11\x8e\x1e\x26\x14\x8a\x08\x1b" + "\x01\x6a\x60\x59\x9c\x4a\x55\xa3\x16\x56\xf6\xc4\x50\x42\x7f\x03"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_ed25519_key(void **state) +{ + const char *keystring = NULL; + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + torture_write_file(LIBSSH_ED25519_TESTKEY, keystring); + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + torture_write_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, keystring); + + torture_write_file(LIBSSH_ED25519_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + torture_write_file(LIBSSH_ED25519_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519_CERT01)); + + return 0; +} + +static int teardown(void **state) { + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ed25519_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_privkey_base64(void **state) +{ + int rc; + char *key_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + SSH_KEY_FREE(key); + +} + +static void torture_pki_ed25519_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); + +} + +static void torture_pki_ed25519_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + key_str = torture_pki_read_file(LIBSSH_ED25519_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + assert_non_null(key); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_key_is_public(key); + assert_true(rc == 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); + +} + +static void torture_pki_ed25519_import_export_privkey_base64(void **state) +{ + char *b64_key = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + int rc; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_privkey_base64(torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, + false), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_pki_export_privkey_base64(key, + passphrase, + NULL, + NULL, + &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + SSH_KEY_FREE(key); + + rc = ssh_pki_import_privkey_base64(b64_key, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_ED25519); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + SSH_STRING_FREE_CHAR(b64_key); + SSH_KEY_FREE(key); +} + +static void torture_pki_ed25519_publickey_from_privatekey(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + const char *keystring = NULL; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_cert_file(void **state) +{ + int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_ED25519_CERT01); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + SSH_KEY_FREE(cert); + SSH_KEY_FREE(pubkey); + skip(); + } + + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == SSH_KEYTYPE_ED25519); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + assert_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_int_equal(rc, SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == SSH_KEYTYPE_ED25519_CERT01); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + /* Generate different key and try to assign it this certificate */ + rc = ssh_pki_generate_key(SSH_KEYTYPE_ED25519, NULL, &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_int_equal(rc, SSH_ERROR); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key = NULL; + int rc; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + assert_non_null(key_buf); + + q = p = key_buf; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_ED25519); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_ed25519_generate_pubkey_from_privkey(void **state) +{ + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + int len; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_ED25519_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_ED25519_TESTKEY ".pub"); + assert_return_code(rc, errno); + + rc = torture_read_one_line(LIBSSH_ED25519_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_return_code(rc, errno); + + len = torture_pubkey_len(torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), + pubkey_generated, + len); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_generate_key(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + enum ssh_keytypes_e type = SSH_KEYTYPE_UNKNOWN; + const char *type_char = NULL; + ssh_session session=ssh_new(); + uint8_t *raw_sig_data = NULL; + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + assert_non_null(session); + + rc = ssh_pki_generate(SSH_KEYTYPE_ED25519, 256, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, HASH, 20, SSH_DIGEST_AUTO); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, HASH, 20); + assert_true(rc == SSH_OK); + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_ED25519); + type_char = ssh_key_type_to_char(type); + assert_true(strcmp(type_char, "ssh-ed25519") == 0); + + /* try an invalid signature */ +#ifdef HAVE_LIBCRYPTO + raw_sig_data = ssh_string_data(sign->raw_sig); +#else + raw_sig_data = (uint8_t *)sign->ed25519_sig; +#endif + assert_non_null(raw_sig_data); + (raw_sig_data)[3]^= 0xff; + rc = ssh_pki_signature_verify(session, sign, pubkey, HASH, 20); + assert_true(rc == SSH_ERROR); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_ed25519_cert_verify(void **state) +{ + int rc; + ssh_key privkey = NULL, cert = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + assert_non_null(session); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_import_cert_file(LIBSSH_ED25519_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + sign = pki_do_sign(privkey, HASH, 20, SSH_DIGEST_AUTO); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, cert, HASH, 20); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(cert); + + ssh_free(session); +} + +static void +torture_pki_ed25519_write_privkey_format(void **state, + enum ssh_file_format_e format) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_return_code(rc, errno); + assert_non_null(origkey); + + unlink(LIBSSH_ED25519_TESTKEY); + + rc = ssh_pki_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_ED25519_TESTKEY, + format); + assert_return_code(rc, errno); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + unlink(LIBSSH_ED25519_TESTKEY); + SSH_KEY_FREE(privkey); + /* do the same with passphrase */ + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY, + format); + assert_return_code(rc, errno); + + /* Opening passphrase protected key will prompt for the pin interactively, + * which would hang in the test */ + if (format != SSH_FILE_FORMAT_PEM) { + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + /* opening without passphrase should fail */ + assert_int_equal(rc, SSH_ERROR); + } + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + unlink(LIBSSH_ED25519_TESTKEY); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_return_code(rc, errno); + assert_non_null(origkey); + + unlink(LIBSSH_ED25519_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_ED25519_TESTKEY_PASSPHRASE, + format); + assert_return_code(rc, errno); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_import_privkey_file(LIBSSH_ED25519_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_int_equal(rc, 0); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} + +static void +torture_pki_ed25519_write_privkey(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#ifdef HAVE_LIBCRYPTO +static void +torture_pki_ed25519_write_privkey_pem(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_ed25519_write_privkey_openssh(void **state) +{ + torture_pki_ed25519_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} +#endif + +static void torture_pki_ed25519_sign(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); + +} + +static void torture_pki_ed25519_sign_openssh_privkey_passphrase(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(keystring, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} + +#ifdef HAVE_LIBCRYPTO +static void torture_pki_ed25519_sign_pkcs8_privkey(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_testkey(SSH_KEYTYPE_ED25519, 0); + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} + +static void torture_pki_ed25519_sign_pkcs8_privkey_passphrase(void **state) +{ + ssh_key privkey = NULL; + ssh_signature sig = NULL; + ssh_string blob = NULL; + const char *keystring = NULL; + int rc; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + (void)state; + + keystring = torture_get_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(keystring, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + sig = pki_do_sign(privkey, HASH, sizeof(HASH), SSH_DIGEST_AUTO); + assert_non_null(sig); + + blob = pki_signature_to_blob(sig); + assert_non_null(blob); + assert_int_equal(ssh_string_len(blob), sizeof(ref_signature)); + assert_memory_equal(ssh_string_data(blob), ref_signature, + sizeof(ref_signature)); + + ssh_signature_free(sig); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE(blob); +} +#endif /* HAVE_LIBCRYPTO */ + +static void torture_pki_ed25519_verify(void **state){ + ssh_key pubkey = NULL; + ssh_signature sig = NULL; + ssh_session session = NULL; + ssh_string blob = ssh_string_new(ED25519_SIG_LEN); + char *pkey_ptr = strdup(strchr(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), ' ') + 1); + char *ptr = NULL; + uint8_t *raw_sig_data = NULL; + int rc; + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + session = ssh_new(); + assert_non_null(session); + + /* remove trailing comment */ + ptr = strchr(pkey_ptr, ' '); + if(ptr != NULL){ + *ptr = '\0'; + } + rc = ssh_pki_import_pubkey_base64(pkey_ptr, SSH_KEYTYPE_ED25519, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_string_fill(blob, ref_signature, ED25519_SIG_LEN); + assert_int_equal(rc, 0); + sig = pki_signature_from_blob(pubkey, blob, SSH_KEYTYPE_ED25519, SSH_DIGEST_AUTO); + assert_non_null(sig); + + rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); + assert_return_code(rc, errno); + + /* Alter signature and expect verification error */ +#ifdef HAVE_LIBCRYPTO + raw_sig_data = ssh_string_data(sig->raw_sig); +#else + raw_sig_data = (uint8_t *)sig->ed25519_sig; +#endif + assert_non_null(raw_sig_data); + (raw_sig_data)[3]^= 0xff; + rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); + assert_true(rc == SSH_ERROR); + + ssh_signature_free(sig); + + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE(blob); + free(pkey_ptr); + ssh_free(session); +} + +static void torture_pki_ed25519_verify_bad(void **state){ + ssh_key pubkey = NULL; + ssh_signature sig = NULL; + ssh_session session = NULL; + ssh_string blob = ssh_string_new(ED25519_SIG_LEN); + char *pkey_ptr = strdup(strchr(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), ' ') + 1); + char *ptr = NULL; + int rc; + int i; + (void) state; + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + session = ssh_new(); + assert_non_null(session); + + /* remove trailing comment */ + ptr = strchr(pkey_ptr, ' '); + if(ptr != NULL){ + *ptr = '\0'; + } + rc = ssh_pki_import_pubkey_base64(pkey_ptr, SSH_KEYTYPE_ED25519, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + /* alter signature and expect false result */ + + for (i=0; i < ED25519_SIG_LEN; ++i){ + rc = ssh_string_fill(blob, ref_signature, ED25519_SIG_LEN); + assert_int_equal(rc, 0); + ((uint8_t *)ssh_string_data(blob))[i] ^= 0xff; + sig = pki_signature_from_blob(pubkey, blob, SSH_KEYTYPE_ED25519, SSH_DIGEST_AUTO); + assert_non_null(sig); + + rc = ssh_pki_signature_verify(session, sig, pubkey, HASH, sizeof(HASH)); + assert_true(rc == SSH_ERROR); + ssh_signature_free(sig); + + } + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE(blob); + free(pkey_ptr); + ssh_free(session); +} + +static void torture_pki_ed25519_import_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey = NULL; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + /* same for ED25519 */ + testkey = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(testkey, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); +} + +static void torture_pki_ed25519_privkey_dup(void **state) +{ + const char *passphrase = torture_get_testkey_passphrase(); + ssh_key key = NULL; + ssh_key dup = NULL; + const char *testkey = NULL; + int rc; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + testkey = torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 1); + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + dup = ssh_key_dup(key); + assert_non_null(dup); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(dup); +} + +static void torture_pki_ed25519_pubkey_dup(void **state) +{ + ssh_key pubkey = NULL; + ssh_key dup = NULL; + const char *p = strchr(torture_get_testkey_pub(SSH_KEYTYPE_ED25519), ' '); + char *pub_str = NULL; + char *q = NULL; + int rc; + + (void) state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + pub_str = strdup(p + 1); + assert_non_null(pub_str); + + q = strchr(pub_str, ' '); + assert_non_null(q); + *q = '\0'; + + rc = ssh_pki_import_pubkey_base64(pub_str, + SSH_KEYTYPE_ED25519, + &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_true(rc == 1); + + dup = ssh_key_dup(pubkey); + assert_non_null(dup); + + rc = ssh_key_is_public(dup); + assert_true(rc == 1); + + SAFE_FREE(pub_str); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(dup); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_pubkey_file, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_pubkey_from_openssh_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_privkey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_privkey_base64_comment, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_privkey_base64_whitespace, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_import_export_privkey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_publickey_from_privatekey, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_import_cert_file, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_publickey_base64, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_generate_pubkey_from_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test(torture_pki_ed25519_generate_key), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_cert_verify, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_write_privkey, + setup_ed25519_key, + teardown), + cmocka_unit_test(torture_pki_ed25519_import_privkey_base64_passphrase), + cmocka_unit_test(torture_pki_ed25519_sign), + cmocka_unit_test(torture_pki_ed25519_sign_openssh_privkey_passphrase), +#ifdef HAVE_LIBCRYPTO + cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey), + cmocka_unit_test(torture_pki_ed25519_sign_pkcs8_privkey_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_ed25519_write_privkey_pem, + setup_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_ed25519_write_privkey_openssh, + setup_ed25519_key, + teardown), +#endif + cmocka_unit_test(torture_pki_ed25519_verify), + cmocka_unit_test(torture_pki_ed25519_verify_bad), + cmocka_unit_test(torture_pki_ed25519_privkey_dup), + cmocka_unit_test(torture_pki_ed25519_pubkey_dup), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519_uri.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519_uri.c new file mode 100644 index 000000000000..09ccab4f87d2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_ed25519_uri.c @@ -0,0 +1,357 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2024 by Red Hat, Inc. + * + * Authors: Jakub Jelen + * Sahana Prasad + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#define LIBSSH_EDDSA_TESTKEY "libssh_testkey.id_ed25519" +#define LIBSSH_EDDSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_ed25519" +#define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" +#define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=%s" + +const char template[] = "/tmp/temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; +struct pki_st { + char *orig_dir; + char *temp_dir; + char *pub_uri; + char *priv_uri; + char *priv_uri_invalid_object; + char *priv_uri_invalid_token; + char *pub_uri_invalid_object; + char *pub_uri_invalid_token; +}; + +static int setup_tokens(void **state) +{ + char keys_path[1024] = {0}; + char keys_path_pub[1024] = {0}; + char *cwd = NULL; + struct pki_st *test_state = *state; + char obj_tempname[] = "label_XXXXXX"; + char pub_uri[1024] = {0}; + char priv_uri[1024] = {0}; + char pub_uri_invalid_object[1024] = {0}; + char priv_uri_invalid_object[1024] = {0}; + char pub_uri_invalid_token[1024] = {0}; + char priv_uri_invalid_token[1024] = {0}; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + ssh_tmpname(obj_tempname); + + snprintf(pub_uri, sizeof(pub_uri), PUB_URI_FMT, obj_tempname, obj_tempname); + + snprintf(priv_uri, + sizeof(priv_uri), + PRIV_URI_FMT, + obj_tempname, + obj_tempname, + "1234"); + + snprintf(pub_uri_invalid_token, + sizeof(pub_uri_invalid_token), + PUB_URI_FMT, + "invalid", + obj_tempname); + + snprintf(priv_uri_invalid_token, + sizeof(priv_uri_invalid_token), + PRIV_URI_FMT, + "invalid", + obj_tempname, + "1234"); + + snprintf(pub_uri_invalid_object, + sizeof(pub_uri_invalid_object), + PUB_URI_FMT, + obj_tempname, + "invalid"); + + snprintf(priv_uri_invalid_object, + sizeof(priv_uri_invalid_object), + PRIV_URI_FMT, + obj_tempname, + "invalid", + "1234"); + + snprintf(keys_path, sizeof(keys_path), "%s/%s", cwd, LIBSSH_EDDSA_TESTKEY); + + snprintf(keys_path_pub, + sizeof(keys_path_pub), + "%s/%s.pub", + cwd, + LIBSSH_EDDSA_TESTKEY); + + test_state->pub_uri = strdup(pub_uri); + test_state->priv_uri = strdup(priv_uri); + test_state->pub_uri_invalid_token = strdup(pub_uri_invalid_token); + test_state->pub_uri_invalid_object = strdup(pub_uri_invalid_object); + test_state->priv_uri_invalid_token = strdup(priv_uri_invalid_token); + test_state->priv_uri_invalid_object = strdup(priv_uri_invalid_object); + + torture_write_file(keys_path, torture_get_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file(keys_path_pub, + torture_get_testkey_pub_pem(SSH_KEYTYPE_ED25519)); + + torture_setup_tokens(cwd, keys_path, obj_tempname, "1"); + + return 0; +} + +static int setup_directory_structure(void **state) +{ + struct pki_st *test_state = NULL; + char *temp_dir = NULL; + int rc; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + free(temp_dir); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + *state = test_state; + + rc = setup_tokens(state); + assert_int_equal(rc, 0); + + return 0; +} + +static int teardown_directory_structure(void **state) +{ + struct pki_st *test_state = *state; + int rc; + + torture_cleanup_tokens(test_state->temp_dir); + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state->priv_uri); + SAFE_FREE(test_state->pub_uri); + SAFE_FREE(test_state->priv_uri_invalid_object); + SAFE_FREE(test_state->pub_uri_invalid_object); + SAFE_FREE(test_state->priv_uri_invalid_token); + SAFE_FREE(test_state->pub_uri_invalid_token); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_ed25519_import_pubkey_uri(void **state) +{ + ssh_key pubkey = NULL; + int rc; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_import_privkey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(privkey); +} + +static void torture_pki_sign_verify_uri(void **state) +{ + int rc; + ssh_key privkey = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session = ssh_new(); + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), SSH_DIGEST_AUTO); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_ed25519_publickey_from_privatekey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_ed25519_uri_invalid_configurations(void **state) +{ + int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + + struct pki_st *test_state = *state; + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_object, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_token, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_object, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_token, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_ed25519_import_pubkey_uri), + cmocka_unit_test(torture_pki_ed25519_import_privkey_uri), + cmocka_unit_test(torture_pki_sign_verify_uri), + cmocka_unit_test(torture_pki_ed25519_publickey_from_privatekey_uri), + cmocka_unit_test(torture_pki_ed25519_uri_invalid_configurations), + }; + + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + + /* Skip test FIPS mode altogether. */ + if (ssh_fips_mode()) { + return 0; + } + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_directory_structure, + teardown_directory_structure); + + ssh_free(session); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa.c new file mode 100644 index 000000000000..44d445fc3fb5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa.c @@ -0,0 +1,1259 @@ + +#include "config.h" +#include "libssh/libssh.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_rsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + torture_write_file(LIBSSH_RSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA_CERT01)); + + return 0; +} + +static int setup_openssh_rsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + torture_write_file(LIBSSH_RSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA_CERT01)); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_rsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state) +{ + int rc; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + /* test if it returns -1 if key is NULL */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + passphrase, + NULL, + NULL, + NULL); + assert_int_equal(rc, -1); + +} + +static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + /* test if it returns -1 if key_str is NULL */ + rc = ssh_pki_import_privkey_base64(NULL, passphrase, NULL, NULL, &key); + assert_int_equal(rc, -1); + + SSH_KEY_FREE(key); +} + +static void +torture_pki_rsa_import_export_privkey_base64_format(void **state, + enum ssh_file_format_e format) +{ + int rc; + char *key_str = NULL, *new_key_str = NULL; + ssh_key key = NULL, new_key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + /* Import test key */ + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(key); + assert_int_equal(rc, 1); + + /* Export */ + rc = ssh_pki_export_privkey_base64_format(key, + passphrase, + NULL, + NULL, + &new_key_str, + format); + assert_int_equal(rc, SSH_OK); + assert_non_null(new_key_str); + + /* and import again */ + rc = ssh_pki_import_privkey_base64(new_key_str, + passphrase, + NULL, + NULL, + &new_key); + assert_int_equal(rc, 0); + assert_non_null(new_key); + + type = ssh_key_type(new_key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(new_key); + assert_int_equal(rc, 1); + + rc = ssh_key_cmp(key, new_key, SSH_KEY_CMP_PRIVATE|SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + free(key_str); + free(new_key_str); + SSH_KEY_FREE(key); + SSH_KEY_FREE(new_key); +} + +static void +torture_pki_rsa_import_export_privkey_base64(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_DEFAULT); +} + +static void torture_pki_rsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(comment_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(key); + assert_int_equal(rc, 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \n\t\t\t\t\t\n\n\n\n\n"; + char *key_str = NULL, *file_str = NULL; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + file_str_len = strlen(whitespace_str) + strlen(key_str) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", whitespace_str, key_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, passphrase, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + type = ssh_key_type(key); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_key_is_public(key); + assert_int_equal(rc, 1); + + free(key_str); + free(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_publickey_from_privatekey(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + const char *passphrase = NULL; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_copy_cert_to_privkey(void **state) +{ + /* + * Tests copying a cert loaded into a public key to a private key. + * The function is encryption type agnostic, no need to run this against + * all supported key types. + */ + int rc; + const char *passphrase = torture_get_testkey_passphrase(); + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void)state; /* unused */ + + /* Importing public key as cert should fail */ + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY ".pub", &cert); + assert_int_equal(rc, SSH_ERROR); + assert_null(cert); + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + /* Import matching private key file and verify the pubkey matches */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + passphrase, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + type = ssh_key_type(privkey); + assert_true(type == SSH_KEYTYPE_RSA); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + assert_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_int_equal(rc, SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey->cert); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_return_code(rc, errno); + assert_non_null(privkey->cert); + assert_true(privkey->cert_type == SSH_KEYTYPE_RSA_CERT01); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 0); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 0); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + /* Generate different key and try to assign it this certificate */ + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_int_equal(rc, SSH_ERROR); + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_int_equal(rc, SSH_ERROR); + + assert_int_equal(ssh_key_cmp(privkey, cert, SSH_KEY_CMP_PUBLIC), 1); + assert_int_equal(ssh_key_cmp(cert, privkey, SSH_KEY_CMP_PUBLIC), 1); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_cert_file(void **state) { + int rc; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) state; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + type = ssh_key_type(cert); + assert_int_equal(type, SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(cert); +} + +static void torture_pki_rsa_publickey_base64(void **state) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key = NULL; + int rc; + + (void) state; /* unused */ + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_non_null(key_buf); + + q = p = key_buf; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + type = ssh_key_type_from_name(q); + assert_int_equal(type, SSH_KEYTYPE_RSA); + + q = ++p; + while (p != NULL && *p != '\0' && *p != ' ') p++; + if (p != NULL) { + *p = '\0'; + } + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); +} + +static void torture_pki_rsa_generate_pubkey_from_privkey(void **state) { + char pubkey_generated[4096] = {0}; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + int rc; + int len; + + (void) state; /* unused */ + + /* remove the public key, generate it from the private key and write it. */ + unlink(LIBSSH_RSA_TESTKEY ".pub"); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_file(pubkey, LIBSSH_RSA_TESTKEY ".pub"); + assert_return_code(rc, errno); + + rc = torture_read_one_line(LIBSSH_RSA_TESTKEY ".pub", + pubkey_generated, + sizeof(pubkey_generated)); + assert_return_code(rc, errno); + + len = torture_pubkey_len(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_memory_equal(torture_get_testkey_pub(SSH_KEYTYPE_RSA), + pubkey_generated, + len); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_duplicate_key(void **state) +{ + int rc; + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key pubkey_dup = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + + (void) state; + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey_dup); + assert_return_code(rc, errno); + assert_non_null(pubkey_dup); + + rc = ssh_pki_export_pubkey_base64(pubkey_dup, &b64_key_gen); + assert_return_code(rc, errno); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + rc = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + rc = ssh_key_cmp(pubkey, pubkey_dup, SSH_KEY_CMP_PUBLIC); + assert_return_code(rc, errno); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(pubkey_dup); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); +} + +/** + * @brief Test RSA key generation using the deprecated ssh_pki_generate API. + * + * This test is kept for backward compatibility testing of the legacy API. + * For testing the new context-based API, see torture_pki_generate_key_rsa(). + */ +static void torture_pki_generate_rsa_deprecated(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + + (void) state; + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + if (!ssh_fips_mode()) { + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + } + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + ssh_free(session); +} + +static void torture_pki_rsa_sha2(void **state) +{ + int rc; + ssh_key key = NULL, cert = NULL, pubkey = NULL; + ssh_signature sign; + ssh_session session=ssh_new(); + (void) state; + + assert_non_null(session); + + /* Setup */ + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_return_code(rc, errno); + assert_non_null(cert); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + if (!ssh_fips_mode()) { + /* Sign using old SHA1 digest */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA1); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + ssh_signature_free(sign); + } + + /* Sign using new SHA256 digest */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + ssh_signature_free(sign); + + /* Sign using rsa-sha2-512 algorithm */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA512); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + rc = ssh_pki_signature_verify(session, sign, cert, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + ssh_signature_free(sign); + + /* Test that it fails when using DIGEST_AUTO */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_AUTO); + assert_null(sign); + + /* Test that it fails when using SHA384 */ + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA384); + assert_null(sign); + + /* Cleanup */ + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(cert); + ssh_free(session); +} + +static void torture_pki_rsa_key_size(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + unsigned int length = 4096; + + (void) state; + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_return_code(rc, errno); + assert_non_null(key); + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + sign = pki_do_sign(key, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_ssh_return_code(session, rc); + + /* Set the minimum RSA key size to 4k */ + rc = ssh_options_set(session, SSH_OPTIONS_RSA_MIN_SIZE, &length); + assert_ssh_return_code(session, rc); + + /* the verification should fail now */ + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_ERROR); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + key = NULL; + pubkey = NULL; + + ssh_free(session); +} + +/** + * @brief Test RSA key generation using the new context-based + * ssh_pki_generate_key API. + * + * This test validates the new ssh_pki_ctx-based key generation API with + * both positive and negative test cases. + * + * For testing the old/deprecated API, see torture_pki_generate_rsa(). + */ +static void torture_pki_generate_key_rsa(void **state) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_pki_ctx ctx = NULL; + int desired = 4096; + int invalid_size = 512; + + (void)state; + + /* Test with NULL context - should use default size */ + rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, NULL, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + assert_int_equal(ssh_key_type(key), SSH_KEYTYPE_RSA); + assert_int_equal(ssh_key_size(key), RSA_DEFAULT_KEY_SIZE); + SSH_KEY_FREE(key); + + /* Test with NULL key pointer - should fail */ + ctx = ssh_pki_ctx_new(); + assert_non_null(ctx); + + rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &desired); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, NULL); + assert_int_equal(rc, SSH_ERROR); + + /* Test with invalid RSA key size (too small) - should fail */ + rc = ssh_pki_ctx_options_set(ctx, + SSH_PKI_OPTION_RSA_KEY_SIZE, + &invalid_size); + assert_int_equal(rc, SSH_ERROR); + + rc = ssh_pki_ctx_options_set(ctx, SSH_PKI_OPTION_RSA_KEY_SIZE, &desired); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_generate_key(SSH_KEYTYPE_RSA, ctx, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + assert_int_equal(ssh_key_type(key), SSH_KEYTYPE_RSA); + assert_int_equal(ssh_key_size(key), desired); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + SSH_PKI_CTX_FREE(ctx); +} + +static int test_sign_verify_data(ssh_key key, + enum ssh_digest_e hash_type, + const unsigned char *input, + size_t input_len) +{ + ssh_signature sig; + ssh_key pubkey = NULL; + int rc; + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, hash_type, input, input_len); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, input, input_len); + assert_int_equal(rc, SSH_OK); + + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + + return rc; +} + +static void torture_pki_sign_data_rsa(void **state) +{ + int rc; + ssh_key key = NULL; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + if (!ssh_fips_mode()) { + /* Test using SHA1 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA1, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + } + + /* Test using SHA256 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test using SHA512 */ + rc = test_sign_verify_data(key, SSH_DIGEST_SHA512, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + SSH_KEY_FREE(key); +} + +static void torture_pki_fail_sign_with_incompatible_hash(void **state) +{ + int rc; + ssh_key key = NULL; + ssh_key pubkey = NULL; + ssh_signature sig, bad_sig; + + (void) state; + + /* Setup */ + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_int_equal(rc, SSH_OK); + assert_non_null(key); + + /* Get the public key to verify signature */ + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Sign the buffer */ + sig = pki_sign_data(key, SSH_DIGEST_SHA256, INPUT, sizeof(INPUT)); + assert_non_null(sig); + + /* Verify signature */ + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_equal(rc, SSH_OK); + + /* Test if signature fails with SSH_DIGEST_AUTO */ + bad_sig = pki_sign_data(key, SSH_DIGEST_AUTO, INPUT, sizeof(INPUT)); + assert_null(bad_sig); + + /* Test if verification fails with SSH_DIGEST_AUTO */ + sig->hash_type = SSH_DIGEST_AUTO; + rc = pki_verify_data_signature(sig, pubkey, INPUT, sizeof(INPUT)); + assert_int_not_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_signature_free(sig); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(key); +} + +static void +torture_pki_rsa_write_privkey_format(void **state, + enum ssh_file_format_e format) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + int rc; + + (void) state; /* unused */ + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &origkey); + assert_return_code(rc, errno); + assert_non_null(origkey); + + unlink(LIBSSH_RSA_TESTKEY); + + rc = ssh_pki_export_privkey_file_format(origkey, + NULL, + NULL, + NULL, + LIBSSH_RSA_TESTKEY, + format); + assert_return_code(rc, errno); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + + /* Test with passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &origkey); + assert_return_code(rc, errno); + assert_non_null(origkey); + + unlink(LIBSSH_RSA_TESTKEY_PASSPHRASE); + rc = ssh_pki_export_privkey_file_format(origkey, + torture_get_testkey_passphrase(), + NULL, + NULL, + LIBSSH_RSA_TESTKEY_PASSPHRASE, + format); + assert_return_code(rc, errno); + + /* Test with invalid passphrase */ + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + "invalid secret", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey_passphrase(), + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_return_code(rc, errno); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); +} + +static void +torture_pki_rsa_write_privkey(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_DEFAULT); +} + +#if defined(HAVE_LIBCRYPTO) +static void +torture_pki_rsa_write_privkey_pem(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_PEM); +} + +static void +torture_pki_rsa_write_privkey_openssh(void **state) +{ + torture_pki_rsa_write_privkey_format(state, SSH_FILE_FORMAT_OPENSSH); +} + +static void +torture_pki_rsa_import_export_privkey_base64_pem(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_PEM); +} +static void +torture_pki_rsa_import_export_privkey_base64_openssh(void **state) +{ + torture_pki_rsa_import_export_privkey_base64_format(state, + SSH_FILE_FORMAT_OPENSSH); +} +#endif /* HAVE_LIBCRYPTO */ + +static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) state; /* unused */ + + + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 1), + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 1), + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); + +#ifndef HAVE_LIBCRYPTO + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 1), + NULL, + NULL, + NULL, + &key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); +#endif +} + +static void +torture_pki_rsa_import_openssh_privkey_base64_passphrase(void **state) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *keystring = NULL; + + (void) state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_RSA, 1); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + assert_non_null(key); + + rc = ssh_key_is_private(key); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(keystring, + NULL, + NULL, + NULL, + &key); + assert_int_equal(rc, -1); + SSH_KEY_FREE(key); +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_pubkey_from_openssh_privkey, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_comment, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_whitespace, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_publickey_from_privatekey, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_rsa_import_privkey_base64_passphrase), + cmocka_unit_test( + torture_pki_rsa_import_openssh_privkey_base64_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_rsa_copy_cert_to_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_cert_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_generate_pubkey_from_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_duplicate_key, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_generate_rsa_deprecated), + cmocka_unit_test(torture_pki_rsa_key_size), + cmocka_unit_test(torture_pki_generate_key_rsa), + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey, + setup_rsa_key, + teardown), +#if defined(HAVE_LIBCRYPTO) + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey_pem, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_write_privkey_openssh, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64_pem, + setup_openssh_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_export_privkey_base64_openssh, + setup_openssh_rsa_key, + teardown), +#endif /* HAVE_LIBCRYPTO */ + cmocka_unit_test(torture_pki_sign_data_rsa), + cmocka_unit_test(torture_pki_fail_sign_with_incompatible_hash), + cmocka_unit_test_setup_teardown(torture_pki_rsa_sha2, + setup_rsa_key, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa_uri.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa_uri.c new file mode 100644 index 000000000000..645f546536ce --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_rsa_uri.c @@ -0,0 +1,310 @@ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" +#define PUB_URI_FMT "pkcs11:token=%s;object=%s;type=public" +#define PRIV_URI_FMT "pkcs11:token=%s;object=%s;type=private?pin-value=%s" + +const char template[] = "/tmp/temp_dir_XXXXXX"; +const unsigned char INPUT[] = "1234567890123456789012345678901234567890" + "123456789012345678901234"; +struct pki_st { + char *orig_dir; + char *temp_dir; + char *pub_uri; + char *priv_uri; + char *priv_uri_invalid_object; + char *priv_uri_invalid_token; + char *pub_uri_invalid_object; + char *pub_uri_invalid_token; +}; + +static int setup_tokens(void **state) +{ + char keys_path[1024] = {0}; + char keys_path_pub[1024] = {0}; + char *cwd = NULL; + struct pki_st *test_state = *state; + char obj_tempname[] = "label_XXXXXX"; + char pub_uri[1024] = {0}; + char priv_uri[1024] = {0}; + char pub_uri_invalid_object[1024] = {0}; + char priv_uri_invalid_object[1024] = {0}; + char pub_uri_invalid_token[1024] = {0}; + char priv_uri_invalid_token[1024] = {0}; + + cwd = test_state->temp_dir; + assert_non_null(cwd); + + ssh_tmpname(obj_tempname); + + snprintf(pub_uri, sizeof(pub_uri), PUB_URI_FMT, obj_tempname, obj_tempname); + + snprintf(priv_uri, sizeof(priv_uri), PRIV_URI_FMT, obj_tempname, obj_tempname, "1234"); + + snprintf(pub_uri_invalid_token, sizeof(pub_uri_invalid_token), PUB_URI_FMT, "invalid", + obj_tempname); + + snprintf(priv_uri_invalid_token, sizeof(priv_uri_invalid_token), PRIV_URI_FMT, "invalid", + obj_tempname, "1234"); + + snprintf(pub_uri_invalid_object, sizeof(pub_uri_invalid_object), PUB_URI_FMT, obj_tempname, + "invalid"); + + snprintf(priv_uri_invalid_object, sizeof(priv_uri_invalid_object), PRIV_URI_FMT, obj_tempname, + "invalid", "1234"); + + snprintf(keys_path, sizeof(keys_path), "%s%s%s", cwd, "/", LIBSSH_RSA_TESTKEY); + + snprintf(keys_path_pub, sizeof(keys_path_pub), "%s%s%s%s", cwd, "/", LIBSSH_RSA_TESTKEY, ".pub"); + + test_state->pub_uri = strdup(pub_uri); + test_state->priv_uri = strdup(priv_uri); + test_state->pub_uri_invalid_token = strdup(pub_uri_invalid_token); + test_state->pub_uri_invalid_object = strdup(pub_uri_invalid_object); + test_state->priv_uri_invalid_token = strdup(priv_uri_invalid_token); + test_state->priv_uri_invalid_object = strdup(priv_uri_invalid_object); + + torture_write_file(keys_path, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(keys_path_pub, + torture_get_testkey_pub_pem(SSH_KEYTYPE_RSA)); + + torture_setup_tokens(cwd, keys_path, obj_tempname, "1"); + + return 0; +} + +static int setup_directory_structure(void **state) +{ + struct pki_st *test_state = NULL; + char *temp_dir = NULL; + int rc; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + test_state->orig_dir = torture_get_current_working_dir(); + assert_non_null(test_state->orig_dir); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + free(temp_dir); + + test_state->temp_dir = torture_get_current_working_dir(); + assert_non_null(test_state->temp_dir); + + *state = test_state; + + rc = setup_tokens(state); + assert_int_equal(rc, 0); + + return 0; +} + +static int teardown_directory_structure(void **state) +{ + struct pki_st *test_state = *state; + int rc; + + torture_cleanup_tokens(test_state->temp_dir); + + rc = torture_change_dir(test_state->orig_dir); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->orig_dir); + SAFE_FREE(test_state->priv_uri); + SAFE_FREE(test_state->pub_uri); + SAFE_FREE(test_state->priv_uri_invalid_object); + SAFE_FREE(test_state->pub_uri_invalid_object); + SAFE_FREE(test_state->priv_uri_invalid_token); + SAFE_FREE(test_state->pub_uri_invalid_token); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_rsa_import_pubkey_uri(void **state) +{ + ssh_key pubkey = NULL; + int rc; + struct pki_st *test_state = *state; + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + rc = ssh_key_is_public(pubkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_import_privkey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + SSH_KEY_FREE(privkey); +} + + +static void torture_pki_sign_verify_uri(void **state) +{ + int rc; + ssh_key privkey = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session=ssh_new(); + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + sign = pki_do_sign(privkey, INPUT, sizeof(INPUT), SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, INPUT, sizeof(INPUT)); + assert_return_code(rc, errno); + + ssh_signature_free(sign); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + ssh_free(session); +} + +static void torture_pki_rsa_publickey_from_privatekey_uri(void **state) +{ + int rc; + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + struct pki_st *test_state = *state; + + rc = ssh_pki_import_privkey_file(test_state->priv_uri, + NULL, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_non_null(privkey); + + rc = ssh_key_is_private(privkey); + assert_int_equal(rc, 1); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_rsa_uri_invalid_configurations(void **state) +{ + int rc; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + + struct pki_st *test_state = *state; + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_object, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_pubkey_file(test_state->pub_uri_invalid_token, &pubkey); + assert_int_not_equal(rc, 0); + assert_null(pubkey); + + rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_object, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); + + rc = ssh_pki_import_privkey_file(test_state->priv_uri_invalid_token, + NULL, + NULL, + NULL, + &privkey); + assert_int_not_equal(rc, 0); + assert_null(privkey); +} + +int +torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_rsa_import_pubkey_uri), + cmocka_unit_test(torture_pki_rsa_import_privkey_uri), + cmocka_unit_test(torture_pki_sign_verify_uri), + cmocka_unit_test(torture_pki_rsa_publickey_from_privatekey_uri), + cmocka_unit_test(torture_pki_rsa_uri_invalid_configurations), + }; + + ssh_session session = ssh_new(); + int verbosity = torture_libssh_verbosity(); + + /* Do not use system openssl.cnf for the pkcs11 uri tests. + * It can load a pkcs11 provider too early before we will set up environment + * variables that are needed for the pkcs11 provider to access correct + * tokens, causing unexpected failures. + * Make sure this comes before ssh_init(), which initializes OpenSSL! + */ + setenv("OPENSSL_CONF", SOURCEDIR "/tests/etc/openssl.cnf", 1); + + ssh_options_set(session, SSH_OPTIONS_LOG_VERBOSITY, &verbosity); + + ssh_init(); + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_directory_structure, + teardown_directory_structure); + + ssh_free(session); + + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk.c new file mode 100644 index 000000000000..10fadc24137f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk.c @@ -0,0 +1,536 @@ +/* + * torture_pki_sk.c - Torture tests for PKI security key functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "sk_common.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_sk.h" + +#include + +/** + * These tests can also be configured to run with the sk-usbhid callbacks + * instead of the default sk-dummy callbacks which can run in a CI + * environment. + * + * To run these tests with the sk-usbhid callbacks, at least one FIDO2 device + * must be connected and the environment variables TORTURE_SK_USBHID and + * TORTURE_SK_PIN must be set. + * + * The TORTURE_SK_PIN environment variable should contain the PIN used to + * unlock the FIDO2 device for operations. + * + * Note that these tests must be run in the order that they are defined in, as + * the signing tests rely on the output of the enrollment tests. + */ + +/* Test constants */ + +/* Default PIN value which will be overridden with the PIN set in the + * environment variable. */ +static const char *test_pin = NULL; +static const char *test_application = "ssh:test@example.com"; +static const unsigned char test_message[] = "Test signing data for SK keys"; + +static const char test_challenge[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + +/* Global keys for testing */ +static ssh_key g_ecdsa_key = NULL; +static ssh_key g_ed25519_key = NULL; + +static const struct ssh_sk_callbacks_struct *g_sk_callbacks = NULL; +bool valid_sk_callbacks = false; + +static int test_pin_callback(UNUSED_PARAM(const char *prompt), + char *buf, + size_t len, + UNUSED_PARAM(int echo), + UNUSED_PARAM(int verify), + UNUSED_PARAM(void *userdata)) +{ + size_t pin_len; + + if (test_pin == NULL) { + return SSH_ERROR; + } + + pin_len = strlen(test_pin); + if (pin_len + 1 > len) { + return -1; /* buffer too small */ + } + + memcpy(buf, test_pin, pin_len); + buf[pin_len] = '\0'; + return SSH_OK; +} + +static void torture_pki_sk_enroll_generic_key(enum ssh_keytypes_e key_type) +{ + ssh_key pubkey = NULL, reimported_privkey = NULL, reimported_pubkey = NULL; + ssh_pki_ctx enroll_ctx = NULL; + const char *privkey_filename = NULL; + const char *pubkey_filename = NULL; + const char *test_user_id = NULL; + ssh_key *ptr_to_g_key = NULL; + ssh_auth_callback pin_callback = NULL; + int rc; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Setup based on key type */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + privkey_filename = "test_sk_ecdsa_private.key"; + pubkey_filename = "test_sk_ecdsa_public.pub"; + test_user_id = "libssh_test_ecdsa_sk"; + ptr_to_g_key = &g_ecdsa_key; + break; + + case SSH_KEYTYPE_SK_ED25519: + privkey_filename = "test_sk_ed25519_private.key"; + pubkey_filename = "test_sk_ed25519_public.pub"; + test_user_id = "libssh_test_ed25519_sk"; + ptr_to_g_key = &g_ed25519_key; + break; + + default: + /* Should never reach here */ + assert_true(0); + return; + } + + if (test_pin != NULL) { + pin_callback = test_pin_callback; + } + + enroll_ctx = torture_create_sk_pki_ctx(test_application, + SSH_SK_USER_PRESENCE_REQD, + test_challenge, + sizeof(test_challenge), + pin_callback, + NULL, + test_user_id, + g_sk_callbacks); + assert_non_null(enroll_ctx); + + rc = ssh_pki_generate_key(key_type, enroll_ctx, ptr_to_g_key); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(*ptr_to_g_key, key_type, true); + + /* Export private key to file */ + rc = ssh_pki_export_privkey_file(*ptr_to_g_key, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + privkey_filename); + assert_int_equal(rc, SSH_OK); + + /* Extract public key from private key */ + rc = ssh_pki_export_privkey_to_pubkey(*ptr_to_g_key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + /* Export public key to file */ + rc = ssh_pki_export_pubkey_file(pubkey, pubkey_filename); + assert_int_equal(rc, SSH_OK); + + /* Verify exported files by importing them back */ + rc = ssh_pki_import_privkey_file(privkey_filename, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &reimported_privkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(reimported_privkey); + + rc = ssh_pki_import_pubkey_file(pubkey_filename, &reimported_pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(reimported_pubkey); + + /* Verify keys match */ + rc = ssh_key_cmp(*ptr_to_g_key, reimported_privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(pubkey, reimported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(*ptr_to_g_key, reimported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + rc = ssh_key_cmp(reimported_privkey, pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + /* Cleanup */ + unlink(privkey_filename); + unlink(pubkey_filename); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(reimported_privkey); + SSH_KEY_FREE(reimported_pubkey); + SSH_PKI_CTX_FREE(enroll_ctx); +} + +static void torture_pki_sk_enroll_ecdsa_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_key(SSH_KEYTYPE_SK_ECDSA); +} + +static void torture_pki_sk_enroll_ed25519_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_key(SSH_KEYTYPE_SK_ED25519); +} + +static void +torture_pki_sk_enroll_generic_resident_key(enum ssh_keytypes_e key_type) +{ + ssh_key resident_key = NULL; + ssh_pki_ctx enroll_ctx = NULL; + const char *test_user_id = NULL; + ssh_auth_callback pin_callback = NULL; + int rc, flags; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Setup based on key type */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + test_user_id = "libssh_test_ecdsa_sk"; + break; + + case SSH_KEYTYPE_SK_ED25519: + test_user_id = "libssh_test_ed25519_sk"; + break; + + default: + /* Should never reach here */ + assert_true(0); + return; + } + + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY | + SSH_SK_FORCE_OPERATION; + + if (test_pin != NULL) { + pin_callback = test_pin_callback; + } + + enroll_ctx = torture_create_sk_pki_ctx(test_application, + flags, + test_challenge, + sizeof(test_challenge), + pin_callback, + NULL, + test_user_id, + g_sk_callbacks); + assert_non_null(enroll_ctx); + + rc = ssh_pki_generate_key(key_type, enroll_ctx, &resident_key); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(resident_key, key_type, true); + + SSH_KEY_FREE(resident_key); + SSH_PKI_CTX_FREE(enroll_ctx); +} + +static void torture_pki_sk_enroll_ecdsa_resident_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_resident_key(SSH_KEYTYPE_SK_ECDSA); +} + +static void +torture_pki_sk_enroll_ed25519_resident_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_enroll_generic_resident_key(SSH_KEYTYPE_SK_ED25519); +} + +static void torture_pki_sk_sign_generic_key(enum ssh_keytypes_e key_type) +{ + ssh_signature signature = NULL; + ssh_key public_key = NULL; + ssh_pki_ctx sign_ctx = NULL; + ssh_key *ptr_to_g_key = NULL; + ssh_auth_callback pin_callback = NULL; + int rc; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks) { + skip(); + } + + if (key_type == SSH_KEYTYPE_SK_ED25519 && ssh_fips_mode()) { + skip(); + } + + /* Select the appropriate global key based on key type */ + switch (key_type) { + case SSH_KEYTYPE_SK_ECDSA: + ptr_to_g_key = &g_ecdsa_key; + break; + + case SSH_KEYTYPE_SK_ED25519: + ptr_to_g_key = &g_ed25519_key; + break; + + default: + /* Should never reach here */ + assert_true(0); + return; + } + + assert_non_null(*ptr_to_g_key); + + rc = ssh_pki_export_privkey_to_pubkey(*ptr_to_g_key, &public_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(public_key); + + if (test_pin != NULL) { + pin_callback = test_pin_callback; + } + + sign_ctx = torture_create_sk_pki_ctx(test_application, + SSH_SK_USER_PRESENCE_REQD, + test_challenge, + sizeof(test_challenge), + pin_callback, + NULL, + NULL, + g_sk_callbacks); + assert_non_null(sign_ctx); + + signature = pki_sk_do_sign(sign_ctx, + *ptr_to_g_key, + test_message, + sizeof(test_message) - 1); + assert_non_null(signature); + assert_sk_signature_valid(signature, + key_type, + public_key, + test_message, + sizeof(test_message) - 1); + + SSH_SIGNATURE_FREE(signature); + SSH_KEY_FREE(public_key); + SSH_PKI_CTX_FREE(sign_ctx); +} + +static void torture_pki_sk_sign_ecdsa_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_sign_generic_key(SSH_KEYTYPE_SK_ECDSA); +} + +static void torture_pki_sk_sign_ed25519_key(UNUSED_PARAM(void **state)) +{ + torture_pki_sk_sign_generic_key(SSH_KEYTYPE_SK_ED25519); +} + +static void torture_pki_sk_load_resident_keys(UNUSED_PARAM(void **state)) +{ + ssh_pki_ctx load_ctx = NULL; + ssh_key *resident_keys = NULL; + size_t num_keys = 0; + size_t i; + int rc; + + /* Conditions to skip the test */ + if (!valid_sk_callbacks || torture_sk_is_using_sk_dummy()) { + skip(); + } + + load_ctx = ssh_pki_ctx_new(); + assert_non_null(load_ctx); + + assert_non_null(test_pin); + rc = ssh_pki_ctx_set_sk_pin_callback(load_ctx, test_pin_callback, NULL); + assert_int_equal(rc, SSH_OK); + + if (g_sk_callbacks != NULL) { + rc = ssh_pki_ctx_options_set(load_ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + g_sk_callbacks); + assert_int_equal(rc, SSH_OK); + } + + rc = ssh_sk_resident_keys_load(load_ctx, &resident_keys, &num_keys); + assert_int_equal(rc, SSH_OK); + assert_non_null(resident_keys); + assert_true(num_keys > 0); + + for (i = 0; i < num_keys; i++) { + ssh_key key = resident_keys[i]; + assert_non_null(key); + + assert_true(key->type == SSH_KEYTYPE_SK_ECDSA || + key->type == SSH_KEYTYPE_SK_ED25519); + + assert_true(key->sk_flags & SSH_SK_RESIDENT_KEY); + + assert_true(key->sk_flags & SSH_SK_USER_PRESENCE_REQD); + + if (key->type == SSH_KEYTYPE_SK_ECDSA) { + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + } else if (key->type == SSH_KEYTYPE_SK_ED25519) { + if (!ssh_fips_mode()) { + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + } + } + } + + for (i = 0; i < num_keys; i++) { + SSH_KEY_FREE(resident_keys[i]); + } + SAFE_FREE(resident_keys); + + SSH_PKI_CTX_FREE(load_ctx); +} + +static void +torture_pki_ctx_sk_callbacks_options_clear(UNUSED_PARAM(void **state)) +{ + ssh_pki_ctx ctx = NULL; + int rc; + + /* Test with NULL context - should return SSH_ERROR */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(NULL); + assert_int_equal(rc, SSH_ERROR); + + /* Create a new PKI context */ + ctx = ssh_pki_ctx_new(); + assert_non_null(ctx); + + /* Test clearing options on a context with no options set - should succeed + */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + /* Add some options to the context */ + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + "/dev/hidraw0", + false); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_USER_ID, + "test_user", + true); + assert_int_equal(rc, SSH_OK); + + /* Clear all options - should succeed */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + /* Verify that we can add options again after clearing */ + rc = ssh_pki_ctx_sk_callbacks_option_set(ctx, + SSH_SK_OPTION_NAME_DEVICE_PATH, + "/dev/hidraw1", + false); + assert_int_equal(rc, SSH_OK); + + /* Clear options again */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + /* Test multiple clears on same context - should succeed */ + rc = ssh_pki_ctx_sk_callbacks_options_clear(ctx); + assert_int_equal(rc, SSH_OK); + + SSH_PKI_CTX_FREE(ctx); +} + +/* Setup function to run before all tests */ +static int setup_global_state(UNUSED_PARAM(void **state)) +{ + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; + const char *test_pin_env = NULL; + + sk_callbacks = torture_get_sk_callbacks(); + if (sk_callbacks != NULL) { + g_sk_callbacks = sk_callbacks; + valid_sk_callbacks = true; + } + + test_pin_env = torture_get_sk_pin(); + if (test_pin_env != NULL) { + test_pin = test_pin_env; + } + + return 0; +} + +/* Teardown function to run after all tests */ +static int teardown_global_state(UNUSED_PARAM(void **state)) +{ + + /* Clean up global keys */ + SSH_KEY_FREE(g_ecdsa_key); + SSH_KEY_FREE(g_ed25519_key); + + return 0; +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_sk_enroll_ecdsa_key), + cmocka_unit_test(torture_pki_sk_enroll_ed25519_key), + cmocka_unit_test(torture_pki_sk_enroll_ecdsa_resident_key), + cmocka_unit_test(torture_pki_sk_enroll_ed25519_resident_key), + cmocka_unit_test(torture_pki_sk_sign_ecdsa_key), + cmocka_unit_test(torture_pki_sk_sign_ed25519_key), + cmocka_unit_test(torture_pki_sk_load_resident_keys), + cmocka_unit_test(torture_pki_ctx_sk_callbacks_options_clear), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, + setup_global_state, + teardown_global_state); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ecdsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ecdsa.c new file mode 100644 index 000000000000..83e4e4c3f7d4 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ecdsa.c @@ -0,0 +1,487 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include "torture_sk.h" + +/* Test constants */ +#define LIBSSH_SK_ECDSA_TESTKEY "libssh_testkey.id_ecdsa_sk" +#define LIBSSH_SK_ECDSA_TESTKEY_PASSPHRASE \ + "libssh_testkey_passphrase.id_ecdsa_sk" + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_sk_ecdsa_key(void **state) +{ + const char *keystring = NULL; + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + torture_write_file(LIBSSH_SK_ECDSA_TESTKEY, keystring); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 1); + torture_write_file(LIBSSH_SK_ECDSA_TESTKEY_PASSPHRASE, keystring); + + keystring = torture_get_testkey_pub(SSH_KEYTYPE_SK_ECDSA); + torture_write_file(LIBSSH_SK_ECDSA_TESTKEY ".pub", keystring); + + return 0; +} + +static int teardown(void **state) +{ + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_sk_ecdsa_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + rc = ssh_pki_import_pubkey_file(LIBSSH_SK_ECDSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_sk_ecdsa_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + rc = + ssh_pki_import_pubkey_file(LIBSSH_SK_ECDSA_TESTKEY_PASSPHRASE, &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64(void **state) +{ + ssh_key privkey = NULL; + char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_pki_read_file(LIBSSH_SK_ECDSA_TESTKEY); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + SAFE_FREE(keystring); + SSH_KEY_FREE(privkey); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + keystring = torture_pki_read_file(LIBSSH_SK_ECDSA_TESTKEY); + assert_non_null(keystring); + + file_str_len = strlen(comment_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, keystring); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \t\t\t\n\n\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + keystring = torture_pki_read_file(LIBSSH_SK_ECDSA_TESTKEY); + assert_non_null(keystring); + + file_str_len = 2 * strlen(whitespace_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, + file_str_len, + "%s%s%s", + whitespace_str, + keystring, + whitespace_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_sk_ecdsa_import_export_privkey_base64(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + char *key_buf = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &origkey); + assert_return_code(rc, errno); + assert_sk_key_valid(origkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_base64(origkey, NULL, NULL, NULL, &key_buf); + assert_return_code(rc, errno); + assert_non_null(key_buf); + + rc = ssh_pki_import_privkey_base64(key_buf, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE_CHAR(key_buf); +} + +static void torture_pki_sk_ecdsa_publickey_from_privatekey(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + rc = ssh_key_cmp(privkey, pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_sk_ecdsa_import_privkey_base64_passphrase(void **state) +{ + ssh_key privkey = NULL; + const char *keystring = NULL; + const char *passphrase = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 1); + assert_non_null(keystring); + + passphrase = torture_get_testkey_passphrase(); + assert_non_null(passphrase); + + /* Import with a passphrase */ + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + SSH_KEY_FREE(privkey); + + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); +} + +static void torture_pki_sk_ecdsa_duplicate_key(void **state) +{ + ssh_key privkey = NULL; + ssh_key duplicated = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + duplicated = ssh_key_dup(privkey); + assert_sk_key_valid(duplicated, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_key_cmp(privkey, duplicated, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(duplicated); +} + +static void torture_pki_sk_ecdsa_import_pubkey_base64(void **state) +{ + ssh_key key = NULL; + ssh_key pubkey = NULL; + char *b64_key = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + /* Import private key to extract public key */ + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + /* Export public key to base64 */ + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* Import public key from base64 */ + rc = ssh_pki_import_pubkey_base64(b64_key, SSH_KEYTYPE_SK_ECDSA, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE_CHAR(b64_key); +} + +static void torture_pki_sk_ecdsa_pubkey_blob(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_key imported_pubkey = NULL; + ssh_string pub_blob = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ECDSA, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ECDSA, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + /* Export public key to blob */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pub_blob); + assert_int_equal(rc, SSH_OK); + assert_non_null(pub_blob); + + /* Import public key from blob */ + rc = ssh_pki_import_pubkey_blob(pub_blob, &imported_pubkey); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(imported_pubkey, SSH_KEYTYPE_SK_ECDSA, false); + + /* Compare keys */ + rc = ssh_key_cmp(pubkey, imported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + ssh_string_free(pub_blob); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(imported_pubkey); +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_sk_ecdsa_import_pubkey_file, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_pubkey_from_openssh_privkey, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64_comment, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64_whitespace, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_export_privkey_base64, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_publickey_from_privatekey, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_pubkey_base64, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ecdsa_import_privkey_base64_passphrase, + setup_sk_ecdsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_sk_ecdsa_duplicate_key, + setup_sk_ecdsa_key, + teardown), + + cmocka_unit_test_setup_teardown(torture_pki_sk_ecdsa_pubkey_blob, + setup_sk_ecdsa_key, + teardown), + + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ed25519.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ed25519.c new file mode 100644 index 000000000000..8641b5f5b83f --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sk_ed25519.c @@ -0,0 +1,543 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" +#include "torture_sk.h" + +/* Test constants */ +#define LIBSSH_SK_ED25519_TESTKEY "libssh_testkey.id_ed25519_sk" +#define LIBSSH_SK_ED25519_TESTKEY_PASSPHRASE \ + "libssh_testkey_passphrase.id_ed25519_sk" + +const char template[] = "temp_dir_XXXXXX"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int setup_sk_ed25519_key(void **state) +{ + const char *keystring = NULL; + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + torture_write_file(LIBSSH_SK_ED25519_TESTKEY, keystring); + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 1); + torture_write_file(LIBSSH_SK_ED25519_TESTKEY_PASSPHRASE, keystring); + + keystring = torture_get_testkey_pub(SSH_KEYTYPE_SK_ED25519); + torture_write_file(LIBSSH_SK_ED25519_TESTKEY ".pub", keystring); + + return 0; +} + +static int teardown(void **state) +{ + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void torture_pki_sk_ed25519_import_pubkey_file(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_pubkey_file(LIBSSH_SK_ED25519_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_sk_ed25519_import_pubkey_from_openssh_privkey(void **state) +{ + ssh_key pubkey = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + rc = ssh_pki_import_pubkey_file(LIBSSH_SK_ED25519_TESTKEY_PASSPHRASE, + &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + SSH_KEY_FREE(pubkey); +} + +static void torture_pki_sk_ed25519_import_privkey_base64(void **state) +{ + ssh_key privkey = NULL; + char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_pki_read_file(LIBSSH_SK_ED25519_TESTKEY); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + SAFE_FREE(keystring); + SSH_KEY_FREE(privkey); +} + +static void torture_pki_sk_ed25519_import_privkey_base64_comment(void **state) +{ + int rc, file_str_len; + const char *comment_str = "#this is line-comment\n#this is another\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_pki_read_file(LIBSSH_SK_ED25519_TESTKEY); + assert_non_null(keystring); + + file_str_len = strlen(comment_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, file_str_len, "%s%s", comment_str, keystring); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void +torture_pki_sk_ed25519_import_privkey_base64_whitespace(void **state) +{ + int rc, file_str_len; + const char *whitespace_str = " \t\t\t\n\n\n"; + char *file_str = NULL; + ssh_key key = NULL; + char *keystring = NULL; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_pki_read_file(LIBSSH_SK_ED25519_TESTKEY); + assert_non_null(keystring); + + file_str_len = 2 * strlen(whitespace_str) + strlen(keystring) + 1; + file_str = malloc(file_str_len); + assert_non_null(file_str); + rc = snprintf(file_str, + file_str_len, + "%s%s%s", + whitespace_str, + keystring, + whitespace_str); + assert_int_equal(rc, file_str_len - 1); + + rc = ssh_pki_import_privkey_base64(file_str, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + + SAFE_FREE(keystring); + SAFE_FREE(file_str); + SSH_KEY_FREE(key); +} + +static void torture_pki_sk_ed25519_import_export_privkey_base64(void **state) +{ + ssh_key origkey = NULL; + ssh_key privkey = NULL; + char *key_buf = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &origkey); + assert_return_code(rc, errno); + assert_sk_key_valid(origkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_base64(origkey, NULL, NULL, NULL, &key_buf); + assert_return_code(rc, errno); + assert_non_null(key_buf); + + rc = ssh_pki_import_privkey_base64(key_buf, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_key_cmp(origkey, privkey, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(origkey); + SSH_KEY_FREE(privkey); + SSH_STRING_FREE_CHAR(key_buf); +} + +static void torture_pki_sk_ed25519_publickey_from_privatekey(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + rc = ssh_key_cmp(privkey, pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); +} + +static void +torture_pki_sk_ed25519_import_privkey_base64_passphrase(void **state) +{ + ssh_key privkey = NULL; + const char *keystring = NULL; + const char *passphrase = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 1); + assert_non_null(keystring); + + passphrase = torture_get_testkey_passphrase(); + assert_non_null(passphrase); + + /* Import with a passphrase */ + rc = ssh_pki_import_privkey_base64(keystring, + passphrase, + NULL, + NULL, + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + SSH_KEY_FREE(privkey); + + rc = ssh_pki_import_privkey_base64(keystring, + "wrong passphrase", + NULL, + NULL, + &privkey); + assert_int_equal(rc, SSH_ERROR); + assert_null(privkey); +} + +static void torture_pki_sk_ed25519_duplicate_key(void **state) +{ + ssh_key privkey = NULL; + ssh_key duplicated = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, + NULL, /* no passphrase */ + NULL, /* no auth callback */ + NULL, /* no auth data */ + &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + duplicated = ssh_key_dup(privkey); + assert_sk_key_valid(duplicated, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_key_cmp(privkey, duplicated, SSH_KEY_CMP_PRIVATE); + assert_int_equal(rc, 0); + + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(duplicated); +} + +static void torture_pki_sk_ed25519_import_pubkey_base64(void **state) +{ + ssh_key key = NULL; + ssh_key pubkey = NULL; + char *b64_key = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + /* Import private key to extract public key */ + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &key); + assert_return_code(rc, errno); + assert_sk_key_valid(key, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + /* Export public key to base64 */ + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_return_code(rc, errno); + assert_non_null(b64_key); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + /* Import public key from base64 */ + rc = ssh_pki_import_pubkey_base64(b64_key, SSH_KEYTYPE_SK_ED25519, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + SSH_KEY_FREE(pubkey); + SSH_STRING_FREE_CHAR(b64_key); +} + +static void torture_pki_sk_ed25519_pubkey_blob(void **state) +{ + ssh_key privkey = NULL; + ssh_key pubkey = NULL; + ssh_key imported_pubkey = NULL; + ssh_string pub_blob = NULL; + const char *keystring = NULL; + int rc; + + (void)state; /* unused */ + + /* Skip test if in FIPS mode */ + if (ssh_fips_mode()) { + skip(); + } + + keystring = torture_get_openssh_testkey(SSH_KEYTYPE_SK_ED25519, 0); + assert_non_null(keystring); + + rc = ssh_pki_import_privkey_base64(keystring, NULL, NULL, NULL, &privkey); + assert_return_code(rc, errno); + assert_sk_key_valid(privkey, SSH_KEYTYPE_SK_ED25519, true); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_return_code(rc, errno); + assert_sk_key_valid(pubkey, SSH_KEYTYPE_SK_ED25519, false); + + /* Export public key to blob */ + rc = ssh_pki_export_pubkey_blob(pubkey, &pub_blob); + assert_int_equal(rc, SSH_OK); + assert_non_null(pub_blob); + + /* Import public key from blob */ + rc = ssh_pki_import_pubkey_blob(pub_blob, &imported_pubkey); + assert_int_equal(rc, SSH_OK); + assert_sk_key_valid(imported_pubkey, SSH_KEYTYPE_SK_ED25519, false); + + /* Compare keys */ + rc = ssh_key_cmp(pubkey, imported_pubkey, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + ssh_string_free(pub_blob); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(imported_pubkey); +} + +int torture_run_tests(void) +{ + int rc; + + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_pubkey_file, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_pubkey_from_openssh_privkey, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64_comment, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64_whitespace, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_export_privkey_base64, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_publickey_from_privatekey, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_pubkey_base64, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_sk_ed25519_import_privkey_base64_passphrase, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_sk_ed25519_duplicate_key, + setup_sk_ed25519_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_sk_ed25519_pubkey_blob, + setup_sk_ed25519_key, + teardown), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sshsig.c b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sshsig.c new file mode 100644 index 000000000000..85b07a22ec6d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_pki_sshsig.c @@ -0,0 +1,831 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/pki.h" +#include "pki.c" +#include "torture.h" +#include "torture_key.h" +#include "torture_pki.h" + +#ifdef WITH_FIDO2 +#include "libssh/libssh.h" +#include "torture_sk.h" +#endif + +#include +#include +#include + +/** + * The tests for the sk-type keys can also be configured to run with + * the sk-usbhid callbacks instead of the default sk-dummy callbacks which can + * run in a CI environment. + * + * To run these tests with the sk-usbhid callbacks, at least one FIDO2 device + * must be connected and the environment variables TORTURE_SK_USBHID must be + * set. + */ + +static const char template[] = "tmp_XXXXXX"; +static const char input[] = "Test input\0string with null byte"; +static const size_t input_len = sizeof(input) - 1; /* -1 to exclude final \0 */ +static const char *test_namespace = "file"; + +struct key_hash_combo { + enum ssh_keytypes_e key_type; + enum sshsig_digest_e hash_alg; + const char *key_name; +}; + +struct sshsig_st { + /* + * The original current working directory at the start of the test. + * + * During setup, the current working directory is changed to a newly + * created temporary directory (temp_dir). + * + * During cleanup, the current working directory is restored back + * to original_cwd. + */ + char *original_cwd; + char *temp_dir; + ssh_key rsa_key; + ssh_key ed25519_key; + ssh_key ecdsa_key; + +#ifdef WITH_FIDO2 + ssh_pki_ctx pki_ctx; + ssh_key sk_ecdsa_key; + ssh_key sk_ed25519_key; +#endif + + const char *ssh_keygen_path; + const struct key_hash_combo *test_combinations; + size_t num_combinations; +}; + +static struct key_hash_combo test_combinations[] = { + {SSH_KEYTYPE_RSA, SSHSIG_DIGEST_SHA2_256, "rsa"}, + {SSH_KEYTYPE_RSA, SSHSIG_DIGEST_SHA2_512, "rsa"}, + {SSH_KEYTYPE_ED25519, SSHSIG_DIGEST_SHA2_256, "ed25519"}, + {SSH_KEYTYPE_ED25519, SSHSIG_DIGEST_SHA2_512, "ed25519"}, +#ifdef HAVE_ECC + {SSH_KEYTYPE_ECDSA_P256, SSHSIG_DIGEST_SHA2_256, "ecdsa"}, + {SSH_KEYTYPE_ECDSA_P256, SSHSIG_DIGEST_SHA2_512, "ecdsa"}, +# ifdef WITH_FIDO2 + {SSH_KEYTYPE_SK_ECDSA, SSHSIG_DIGEST_SHA2_256, "sk_ecdsa"}, + {SSH_KEYTYPE_SK_ECDSA, SSHSIG_DIGEST_SHA2_512, "sk_ecdsa"}, +# endif /* WITH_FIDO2 */ +#endif /* HAVE_ECC */ + +#ifdef WITH_FIDO2 + {SSH_KEYTYPE_SK_ED25519, SSHSIG_DIGEST_SHA2_256, "sk_ed25519"}, + {SSH_KEYTYPE_SK_ED25519, SSHSIG_DIGEST_SHA2_512, "sk_ed25519"}, +#endif +}; + +static ssh_key get_test_key(struct sshsig_st *test_state, + enum ssh_keytypes_e type) +{ + switch (type) { + case SSH_KEYTYPE_RSA: + return test_state->rsa_key; + case SSH_KEYTYPE_ED25519: + if (ssh_fips_mode()) { + return NULL; + } else { + return test_state->ed25519_key; + } +#ifdef HAVE_ECC + case SSH_KEYTYPE_ECDSA_P256: + return test_state->ecdsa_key; +# ifdef WITH_FIDO2 + case SSH_KEYTYPE_SK_ECDSA: + return test_state->sk_ecdsa_key; +# endif /* WITH_FIDO2 */ +#endif /* HAVE_ECC */ + +#ifdef WITH_FIDO2 + case SSH_KEYTYPE_SK_ED25519: + if (ssh_fips_mode()) { + return NULL; + } else { + return test_state->sk_ed25519_key; + } +#endif + default: + return NULL; + } +} + +static int setup_sshsig_compat(void **state) +{ + struct sshsig_st *test_state = NULL; + char *original_cwd = NULL; + char *temp_dir = NULL; + int rc = 0; + +#ifdef WITH_FIDO2 + const struct ssh_sk_callbacks_struct *sk_callbacks = NULL; +#endif + + test_state = calloc(1, sizeof(struct sshsig_st)); + assert_non_null(test_state); + + original_cwd = torture_get_current_working_dir(); + assert_non_null(original_cwd); + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + test_state->original_cwd = original_cwd; + test_state->temp_dir = temp_dir; + test_state->test_combinations = test_combinations; + test_state->num_combinations = + sizeof(test_combinations) / sizeof(test_combinations[0]); + + *state = test_state; + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + /* Check if openssh is available and supports SSH signatures */ +#ifdef OPENSSH_SUPPORTS_SSHSIG + test_state->ssh_keygen_path = SSH_KEYGEN_EXECUTABLE; +#else + test_state->ssh_keygen_path = NULL; + printf("OpenSSH version does not support SSH signatures (requires " + "8.1+), skipping compatibility tests\n"); +#endif /* OPENSSH_SUPPORTS_SSHSIG */ + + /* Load pre-generated test keys using torture functions */ + rc = ssh_pki_import_privkey_base64(torture_get_testkey(SSH_KEYTYPE_RSA, 0), + NULL, + NULL, + NULL, + &test_state->rsa_key); + assert_int_equal(rc, SSH_OK); + + /* Skip ed25519 if in FIPS mode */ + if (!ssh_fips_mode()) { + /* mbedtls and libgcrypt don't fully support PKCS#8 PEM */ + /* thus parse the key with OpenSSH */ + rc = ssh_pki_import_privkey_base64( + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0), + NULL, + NULL, + NULL, + &test_state->ed25519_key); + assert_int_equal(rc, SSH_OK); + } + +#ifdef HAVE_ECC + rc = ssh_pki_import_privkey_base64( + torture_get_testkey(SSH_KEYTYPE_ECDSA_P256, 0), + NULL, + NULL, + NULL, + &test_state->ecdsa_key); + assert_int_equal(rc, SSH_OK); +#endif + +#ifdef WITH_FIDO2 + /* Create and configure PKI context for SK operations */ + sk_callbacks = torture_get_sk_callbacks(); + if (sk_callbacks != NULL) { + test_state->pki_ctx = ssh_pki_ctx_new(); + assert_non_null(test_state->pki_ctx); + + rc = ssh_pki_ctx_options_set(test_state->pki_ctx, + SSH_PKI_OPTION_SK_CALLBACKS, + sk_callbacks); + assert_int_equal(rc, SSH_OK); + +# ifdef HAVE_ECC + rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ECDSA, + test_state->pki_ctx, + &test_state->sk_ecdsa_key); + assert_int_equal(rc, SSH_OK); +# endif /* HAVE_ECC */ + + if (!ssh_fips_mode()) { + rc = ssh_pki_generate_key(SSH_KEYTYPE_SK_ED25519, + test_state->pki_ctx, + &test_state->sk_ed25519_key); + assert_int_equal(rc, SSH_OK); + } + } + +#endif /* WITH_FIDO2 */ + + /* Write keys to files for openssh compatibility testing */ + if (test_state->ssh_keygen_path != NULL) { + torture_write_file("test_rsa", torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file("test_rsa.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + + if (!ssh_fips_mode()) { + torture_write_file( + "test_ed25519", + torture_get_openssh_testkey(SSH_KEYTYPE_ED25519, 0)); + torture_write_file("test_ed25519.pub", + torture_get_testkey_pub(SSH_KEYTYPE_ED25519)); + } + +#ifdef HAVE_ECC + torture_write_file("test_ecdsa", + torture_get_testkey(SSH_KEYTYPE_ECDSA_P256, 0)); + torture_write_file("test_ecdsa.pub", + torture_get_testkey_pub(SSH_KEYTYPE_ECDSA_P256)); +#endif /* HAVE_ECC */ + +#ifdef WITH_FIDO2 +# ifdef HAVE_ECC + /* Write SK keys to files if they were successfully generated */ + if (test_state->sk_ecdsa_key != NULL) { + char *sk_ecdsa_priv = NULL; + char *sk_ecdsa_pub = NULL; + + rc = ssh_pki_export_privkey_base64(test_state->sk_ecdsa_key, + NULL, + NULL, + NULL, + &sk_ecdsa_priv); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_export_pubkey_base64(test_state->sk_ecdsa_key, + &sk_ecdsa_pub); + assert_int_equal(rc, SSH_OK); + + torture_write_file("test_sk_ecdsa", sk_ecdsa_priv); + torture_write_file("test_sk_ecdsa.pub", sk_ecdsa_pub); + + SAFE_FREE(sk_ecdsa_priv); + SAFE_FREE(sk_ecdsa_pub); + } +# endif /* HAVE_ECC */ + + if (!ssh_fips_mode() && test_state->sk_ed25519_key != NULL) { + char *sk_ed25519_priv = NULL; + char *sk_ed25519_pub = NULL; + + rc = ssh_pki_export_privkey_base64(test_state->sk_ed25519_key, + NULL, + NULL, + NULL, + &sk_ed25519_priv); + assert_int_equal(rc, SSH_OK); + + rc = ssh_pki_export_pubkey_base64(test_state->sk_ed25519_key, + &sk_ed25519_pub); + assert_int_equal(rc, SSH_OK); + + torture_write_file("test_sk_ed25519", sk_ed25519_priv); + torture_write_file("test_sk_ed25519.pub", sk_ed25519_pub); + + SAFE_FREE(sk_ed25519_priv); + SAFE_FREE(sk_ed25519_pub); + } +#endif /* WITH_FIDO2 */ + + rc = chmod("test_rsa", 0600); + assert_return_code(rc, errno); + if (!ssh_fips_mode()) { + rc = chmod("test_ed25519", 0600); + assert_return_code(rc, errno); + } +#ifdef HAVE_ECC + rc = chmod("test_ecdsa", 0600); + assert_return_code(rc, errno); +#endif + +#ifdef WITH_FIDO2 + /* Set permissions for SK key files */ +# ifdef HAVE_ECC + if (test_state->sk_ecdsa_key != NULL) { + rc = chmod("test_sk_ecdsa", 0600); + assert_return_code(rc, errno); + } +# endif /* HAVE_ECC */ + if (!ssh_fips_mode() && test_state->sk_ed25519_key != NULL) { + rc = chmod("test_sk_ed25519", 0600); + assert_return_code(rc, errno); + } +#endif /* WITH_FIDO2 */ + } + + return 0; +} + +static int teardown_sshsig_compat(void **state) +{ + struct sshsig_st *test_state = *state; + int rc = 0; + + assert_non_null(test_state); + + ssh_key_free(test_state->rsa_key); + ssh_key_free(test_state->ed25519_key); + ssh_key_free(test_state->ecdsa_key); + +#ifdef WITH_FIDO2 + SSH_PKI_CTX_FREE(test_state->pki_ctx); + ssh_key_free(test_state->sk_ecdsa_key); + ssh_key_free(test_state->sk_ed25519_key); +#endif + + rc = torture_change_dir(test_state->original_cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->original_cwd); + SAFE_FREE(test_state); + + return 0; +} + +static int run_openssh_command(const char *cmd) +{ + char full_cmd[2048]; + int rc; + +#if defined(WITH_FIDO2) && defined(SK_DUMMY_LIBRARY_PATH) + /* Set SSH_SK_PROVIDER to sk-dummy library when using sk-dummy callbacks */ + if (torture_sk_is_using_sk_dummy()) { + snprintf(full_cmd, + sizeof(full_cmd), + "SSH_SK_PROVIDER=\"%s\" %s", + SK_DUMMY_LIBRARY_PATH, + cmd); + } else { + snprintf(full_cmd, sizeof(full_cmd), "%s", cmd); + } +#else + snprintf(full_cmd, sizeof(full_cmd), "%s", cmd); +#endif + + rc = system(full_cmd); + return WIFEXITED(rc) ? WEXITSTATUS(rc) : -1; +} + +static void torture_pki_sshsig_armor_dearmor(UNUSED_PARAM(void **state)) +{ + ssh_buffer test_buffer = NULL; + ssh_buffer dearmored_buffer = NULL; + char *armored_sig = NULL; + const char test_data[] = "test signature data"; + int rc; + + test_buffer = ssh_buffer_new(); + assert_non_null(test_buffer); + + rc = ssh_buffer_add_data(test_buffer, test_data, strlen(test_data)); + assert_int_equal(rc, SSH_OK); + + rc = sshsig_armor(test_buffer, &armored_sig); + assert_int_equal(rc, SSH_OK); + assert_non_null(armored_sig); + + /* Test with NULL armored_sig */ + rc = sshsig_armor(test_buffer, NULL); + assert_int_equal(rc, SSH_ERROR); + + assert_non_null(strstr(armored_sig, SSHSIG_BEGIN_SIGNATURE)); + assert_non_null(strstr(armored_sig, SSHSIG_END_SIGNATURE)); + + /* Test with NULL dearmored_buffer */ + rc = sshsig_dearmor(armored_sig, NULL); + assert_int_equal(rc, SSH_ERROR); + + rc = sshsig_dearmor(armored_sig, &dearmored_buffer); + assert_int_equal(rc, SSH_OK); + assert_non_null(dearmored_buffer); + + assert_int_equal(ssh_buffer_get_len(test_buffer), + ssh_buffer_get_len(dearmored_buffer)); + assert_memory_equal(ssh_buffer_get(test_buffer), + ssh_buffer_get(dearmored_buffer), + ssh_buffer_get_len(test_buffer)); + + ssh_buffer_free(test_buffer); + ssh_buffer_free(dearmored_buffer); + free(armored_sig); +} + +static void torture_pki_sshsig_armor_dearmor_invalid(UNUSED_PARAM(void **state)) +{ + ssh_buffer dearmored_buffer = NULL; + char *armored_sig = NULL; + int rc; + const char *invalid_sig = "-----BEGIN INVALID SIGNATURE-----\n" + "data\n" + "-----END INVALID SIGNATURE-----\n"; + + const char *incomplete_sig = "-----BEGIN SSH SIGNATURE----\n" + "U1NIU0lH\n"; + + /* Test with NULL buffer */ + rc = sshsig_armor(NULL, &armored_sig); + assert_int_equal(rc, SSH_ERROR); + + /* Test dearmoring with invalid signature */ + rc = sshsig_dearmor(invalid_sig, &dearmored_buffer); + assert_int_equal(rc, SSH_ERROR); + + /* Test dearmoring with NULL input */ + rc = sshsig_dearmor(NULL, &dearmored_buffer); + assert_int_equal(rc, SSH_ERROR); + + /* Test dearmoring with missing end marker */ + rc = sshsig_dearmor(incomplete_sig, &dearmored_buffer); + assert_int_equal(rc, SSH_ERROR); +} + +static void test_libssh_sign_verify_combo(struct sshsig_st *test_state, + const struct key_hash_combo *combo) +{ + char *signature = NULL; + ssh_key verify_key = NULL; + ssh_key test_key = NULL; + ssh_pki_ctx pki_context = NULL; + int rc; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + skip(); + } + + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not available + */ + skip(); + } + + assert_non_null(test_key); + +#ifdef WITH_FIDO2 + /* Use PKI context for SK keys */ + if (is_sk_key_type(combo->key_type)) { + pki_context = test_state->pki_ctx; + } +#endif + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + test_namespace, + combo->hash_alg, + &signature); + assert_int_equal(rc, SSH_OK); + assert_non_null(signature); + + rc = + sshsig_verify(input, input_len, signature, test_namespace, &verify_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_key); + + rc = ssh_key_cmp(test_key, verify_key, SSH_KEY_CMP_PUBLIC); + assert_int_equal(rc, 0); + + ssh_key_free(verify_key); + free(signature); +} + +static void +test_openssh_sign_libssh_verify_combo(struct sshsig_st *test_state, + const struct key_hash_combo *combo) +{ + char cmd[1024]; + char *openssh_sig = NULL; + ssh_key verify_key = NULL; + ssh_key test_key = NULL; + FILE *fp = NULL; + int rc; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + skip(); + } + + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not available + */ + skip(); + } + + fp = fopen("test_message.txt", "wb"); + assert_non_null(fp); + /* Write binary data including null byte */ + rc = fwrite(input, input_len, 1, fp); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + snprintf(cmd, + sizeof(cmd), + "%s -Y sign -f test_%s -n %s test_message.txt", + test_state->ssh_keygen_path, + combo->key_name, + test_namespace); + rc = run_openssh_command(cmd); + + assert_int_equal(rc, 0); + openssh_sig = torture_pki_read_file("test_message.txt.sig"); + assert_non_null(openssh_sig); + + rc = sshsig_verify(input, + input_len, + openssh_sig, + test_namespace, + &verify_key); + assert_int_equal(rc, SSH_OK); + assert_non_null(verify_key); + + ssh_key_free(verify_key); + free(openssh_sig); + rc = unlink("test_message.txt.sig"); + assert_return_code(rc, errno); + rc = unlink("test_message.txt"); + assert_return_code(rc, errno); +} + +static void +test_libssh_sign_openssh_verify_combo(struct sshsig_st *test_state, + const struct key_hash_combo *combo) +{ + char *libssh_sig = NULL; + char cmd[1024]; + FILE *fp = NULL; + int rc; + char *pubkey_b64 = NULL; + ssh_key test_key = NULL; + ssh_pki_ctx pki_context = NULL; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + skip(); + } + + printf("Testing key type: %s\n", combo->key_name); + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not available + */ + skip(); + } + assert_non_null(test_key); + +#ifdef WITH_FIDO2 + /* Use PKI context for SK keys */ + if (is_sk_key_type(combo->key_type)) { + pki_context = test_state->pki_ctx; + } +#endif + + fp = fopen("test_message.txt", "wb"); + assert_non_null(fp); + /* Write binary data including null byte */ + rc = fwrite(input, input_len, 1, fp); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + test_namespace, + combo->hash_alg, + &libssh_sig); + assert_int_equal(rc, SSH_OK); + assert_non_null(libssh_sig); + + fp = fopen("test_message.txt.sig", "w"); + assert_non_null(fp); + rc = fputs(libssh_sig, fp); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + rc = ssh_pki_export_pubkey_base64(test_key, &pubkey_b64); + assert_int_equal(rc, SSH_OK); + + fp = fopen("allowed_signers", "w"); + assert_non_null(fp); + rc = fprintf(fp, "test %s %s\n", test_key->type_c, pubkey_b64); + assert_return_code(rc, errno); + rc = fclose(fp); + assert_return_code(rc, errno); + + snprintf(cmd, + sizeof(cmd), + "%s -Y verify -f allowed_signers -I test -n %s -s " + "test_message.txt.sig < test_message.txt", + test_state->ssh_keygen_path, + test_namespace); + rc = run_openssh_command(cmd); + assert_int_equal(rc, 0); + + free(libssh_sig); + free(pubkey_b64); + rc = unlink("test_message.txt.sig"); + assert_return_code(rc, errno); + rc = unlink("allowed_signers"); + assert_return_code(rc, errno); + rc = unlink("test_message.txt"); + assert_return_code(rc, errno); +} + +static void torture_sshsig_libssh_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + size_t i; + + for (i = 0; i < test_state->num_combinations; i++) { + test_libssh_sign_verify_combo(test_state, + &test_state->test_combinations[i]); + } +} + +static void torture_sshsig_openssh_libssh_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + size_t i; + + if (test_state->ssh_keygen_path == NULL) { + skip(); + } + + for (i = 0; i < test_state->num_combinations; i++) { + test_openssh_sign_libssh_verify_combo( + test_state, + &test_state->test_combinations[i]); + } +} + +static void torture_sshsig_libssh_openssh_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + size_t i; + + if (test_state->ssh_keygen_path == NULL) { + skip(); + } + + for (i = 0; i < test_state->num_combinations; i++) { + test_libssh_sign_openssh_verify_combo( + test_state, + &test_state->test_combinations[i]); + } +} + +static void torture_sshsig_error_cases_all_combinations(void **state) +{ + struct sshsig_st *test_state = *state; + char *signature = NULL; + ssh_key verify_key = NULL; + int rc; + size_t i; + char tampered_data[] = "Tampered\0data"; + + for (i = 0; i < test_state->num_combinations; i++) { + const struct key_hash_combo *combo = &test_state->test_combinations[i]; + ssh_key test_key = NULL; + ssh_pki_ctx pki_context = NULL; + + if ((combo->key_type == SSH_KEYTYPE_ED25519 || + combo->key_type == SSH_KEYTYPE_SK_ED25519) && + ssh_fips_mode()) { + continue; + } + + test_key = get_test_key(test_state, combo->key_type); + if (is_sk_key_type(combo->key_type) && test_key == NULL) { + /* Skip if SK key type is requested but SK callbacks are not + * available */ + continue; + } + assert_non_null(test_key); + +#ifdef WITH_FIDO2 + if (is_sk_key_type(combo->key_type)) { + pki_context = test_state->pki_ctx; + } +#endif + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + "", /* Test empty string namespace */ + combo->hash_alg, + &signature); + assert_int_equal(rc, SSH_ERROR); + assert_null(signature); + + rc = sshsig_sign(input, + input_len, + test_key, + pki_context, + test_namespace, + combo->hash_alg, + &signature); + assert_int_equal(rc, SSH_OK); + assert_non_null(signature); + + rc = sshsig_verify(input, + input_len, + signature, + "wrong_namespace", + &verify_key); + assert_int_equal(rc, SSH_ERROR); + assert_null(verify_key); + + rc = sshsig_verify(input, + input_len, + signature, + "", /* Test empty string namespace */ + &verify_key); + assert_int_equal(rc, SSH_ERROR); + assert_null(verify_key); + + rc = sshsig_verify(tampered_data, + sizeof(tampered_data) - 1, + signature, + test_namespace, + &verify_key); + assert_int_equal(rc, SSH_ERROR); + assert_null(verify_key); + + free(signature); + signature = NULL; + } + + /* Test invalid hash algorithm */ + rc = sshsig_sign(input, + input_len, + test_state->rsa_key, + NULL, /* pki_context */ + test_namespace, + 2, + &signature); + assert_int_equal(rc, SSH_ERROR); + + /* Test NULL parameters */ + rc = sshsig_sign(input, + input_len, + NULL, + NULL, /* pki_context */ + test_namespace, + SSHSIG_DIGEST_SHA2_256, + &signature); + assert_int_equal(rc, SSH_ERROR); + + rc = + sshsig_verify(input, input_len, "invalid", test_namespace, &verify_key); + assert_int_equal(rc, SSH_ERROR); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_pki_sshsig_armor_dearmor), + cmocka_unit_test(torture_pki_sshsig_armor_dearmor_invalid), + /* Comprehensive combination tests */ + cmocka_unit_test_setup_teardown(torture_sshsig_libssh_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + cmocka_unit_test_setup_teardown( + torture_sshsig_openssh_libssh_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + cmocka_unit_test_setup_teardown( + torture_sshsig_libssh_openssh_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + + /* Comprehensive error case testing */ + cmocka_unit_test_setup_teardown( + torture_sshsig_error_cases_all_combinations, + setup_sshsig_compat, + teardown_sshsig_compat), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_push_pop_dir.c b/src/libs/libssh-0.12.2/tests/unittests/torture_push_pop_dir.c new file mode 100644 index 000000000000..faf1086606b5 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_push_pop_dir.c @@ -0,0 +1,78 @@ +#include "config.h" + +#include "torture.h" +#define LIBSSH_STATIC + +const char template[] = "temp_dir_XXXXXX"; + +static int setup(void **state) +{ + char *temp_dir = NULL; + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + *state = (void *)temp_dir; + + return 0; +} + +static int teardown(void **state) +{ + char *temp_dir = *((char **)state); + + torture_rmdirs((const char *)temp_dir); + + free(temp_dir); + + return 0; +} + +static void torture_back_and_forth(void **state) +{ + char *temp_dir = *((char **)state); + char *cwd = NULL; + char *after_change = NULL; + char *after_changing_back = NULL; + int rc = 0; + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + printf("Current dir: %s\n", cwd); + + rc = torture_change_dir(temp_dir); + assert_int_equal(rc, 0); + + after_change = torture_get_current_working_dir(); + assert_non_null(after_change); + + printf("Current dir after change: %s\n", after_change); + + rc = torture_change_dir(cwd); + assert_int_equal(rc, 0); + + after_changing_back = torture_get_current_working_dir(); + assert_non_null(after_changing_back); + + printf("Back to dir: %s\n", after_changing_back); + + SAFE_FREE(cwd); + SAFE_FREE(after_change); + SAFE_FREE(after_changing_back); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_back_and_forth, + setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_rand.c b/src/libs/libssh-0.12.2/tests/unittests/torture_rand.c new file mode 100644 index 000000000000..58abc7c9593d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_rand.c @@ -0,0 +1,83 @@ +#include "config.h" + +#define LIBSSH_STATIC +#include +#include +#include +#include +#include "torture.h" + +#ifdef HAVE_LIBGCRYPT +#define NUM_LOOPS 1000 +#else +/* openssl is much faster */ +#define NUM_LOOPS 20000 +#endif +#define NUM_THREADS 100 + +static int setup(void **state) { + int rc; + + (void) state; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + return 0; +} + +static int teardown(void **state) { + (void) state; + + ssh_finalize(); + + return 0; +} + +static void *torture_rand_thread(void *threadid) { + char buffer[12]; + int i; + int ok; + + (void) threadid; + + buffer[0] = buffer[1] = buffer[10] = buffer[11] = 'X'; + for(i = 0; i < NUM_LOOPS; ++i) { + ok = ssh_get_random(&buffer[2], i % 8 + 1, 0); + assert_true(ok); + } + + pthread_exit(NULL); +} + +static void torture_rand_threading(void **state) { + pthread_t threads[NUM_THREADS]; + int i; + int err; + + (void) state; + + for(i = 0; i < NUM_THREADS; ++i) { + err = pthread_create(&threads[i], NULL, torture_rand_thread, NULL); + assert_int_equal(err, 0); + } + for(i = 0; i < NUM_THREADS; ++i) { + err=pthread_join(threads[i], NULL); + assert_int_equal(err, 0); + } +} + +int torture_run_tests(void) { + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_rand_threading, setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_server_direct_tcpip.c b/src/libs/libssh-0.12.2/tests/unittests/torture_server_direct_tcpip.c new file mode 100644 index 000000000000..bad0896b0cf0 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_server_direct_tcpip.c @@ -0,0 +1,265 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" + +#include + +#define TEST_SERVER_HOST "127.0.0.1" +#define TEST_SERVER_PORT 2222 +#define TEST_DEST_HOST "127.0.0.1" +#define TEST_DEST_PORT 12345 +#define TEST_ORIG_HOST "127.0.0.1" +#define TEST_ORIG_PORT 54321 + +struct hostkey_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) +{ + struct hostkey_state *h = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + h = malloc(sizeof(struct hostkey_state)); + assert_non_null(h); + + h->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + assert_non_null(h->hostkey_path); + + mask = umask(S_IRWXO | S_IRWXG); + h->fd = mkstemp(h->hostkey_path); + umask(mask); + assert_return_code(h->fd, errno); + close(h->fd); + + h->key_type = SSH_KEYTYPE_ECDSA_P256; + h->hostkey = torture_get_testkey(h->key_type, 0); + + torture_write_file(h->hostkey_path, h->hostkey); + + *state = h; + + return 0; +} + +static int teardown(void **state) +{ + struct hostkey_state *h = (struct hostkey_state *)*state; + + unlink(h->hostkey_path); + free(h->hostkey_path); + free(h); + + ssh_finalize(); + + return 0; +} + +static void *client_thread(void *arg) +{ + unsigned int test_port = TEST_SERVER_PORT; + int rc; + ssh_session session = NULL; + ssh_channel channel = NULL; + bool should_accept = *(bool *)arg; + + session = + torture_ssh_session(NULL, TEST_SERVER_HOST, &test_port, "foo", "bar"); + assert_non_null(session); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + /* Open a direct-tcpip channel instead of a session channel */ + rc = ssh_channel_open_forward(channel, + TEST_DEST_HOST, + TEST_DEST_PORT, + TEST_ORIG_HOST, + TEST_ORIG_PORT); + if (should_accept) { + assert_int_equal(rc, SSH_OK); + } else { + assert_int_equal(rc, SSH_ERROR); + } + + /* Close the channel and session */ + ssh_channel_close(channel); + ssh_channel_free(channel); + ssh_free(session); + + return NULL; +} + +static int auth_password_accept(ssh_session session, + const char *user, + const char *password, + void *userdata) +{ + /* unused */ + (void)session; + (void)user; + (void)password; + (void)userdata; + + return SSH_AUTH_SUCCESS; +} + +struct channel_data { + /* Whether the callback should accept the channel open request */ + bool should_accept; + + int req_seen; + char *dest_host; + uint32_t dest_port; + char *orig_host; + uint32_t orig_port; +}; + +static ssh_channel channel_direct_tcpip_callback(ssh_session session, + const char *dest_host, + int dest_port, + const char *orig_host, + int orig_port, + void *userdata) +{ + struct channel_data *channel_data = userdata; + ssh_channel channel = NULL; + + /* Record that we've seen a direct-tcpip request and store the parameters */ + channel_data->req_seen = 1; + channel_data->dest_host = strdup(dest_host); + channel_data->dest_port = dest_port; + channel_data->orig_host = strdup(orig_host); + channel_data->orig_port = orig_port; + + /* Create and return a new channel for this request */ + if (channel_data->should_accept) { + channel = ssh_channel_new(session); + } + return channel; +} + +static void torture_ssh_channel_direct_tcpip(void **state, int should_accept) +{ + struct hostkey_state *h = (struct hostkey_state *)*state; + int rc, event_rc; + pthread_t client_pthread; + ssh_bind sshbind = NULL; + ssh_session server = NULL; + ssh_event event = NULL; + + struct channel_data channel_data; + struct ssh_server_callbacks_struct server_cb = { + .userdata = &channel_data, + .auth_password_function = auth_password_accept, + .channel_open_request_direct_tcpip_function = + channel_direct_tcpip_callback, + }; + + memset(&channel_data, 0, sizeof(channel_data)); + ssh_callbacks_init(&server_cb); + + /* Create server */ + sshbind = torture_ssh_bind(TEST_SERVER_HOST, + TEST_SERVER_PORT, + h->key_type, + h->hostkey_path); + assert_non_null(sshbind); + + channel_data.should_accept = should_accept; + + /* Get client to connect */ + rc = pthread_create(&client_pthread, + NULL, + client_thread, + &channel_data.should_accept); + assert_return_code(rc, errno); + + server = ssh_new(); + assert_non_null(server); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Handle client connection */ + ssh_set_server_callbacks(server, &server_cb); + + rc = ssh_handle_key_exchange(server); + assert_int_equal(rc, SSH_OK); + + event = ssh_event_new(); + assert_non_null(event); + + ssh_event_add_session(event, server); + + event_rc = SSH_OK; + while (!channel_data.req_seen && event_rc == SSH_OK) { + event_rc = ssh_event_dopoll(event, -1); + } + + /* Cleanup */ + ssh_event_free(event); + ssh_free(server); + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); + + /* Verify direct-tcpip request parameters */ + assert_true(channel_data.req_seen); + assert_string_equal(channel_data.dest_host, TEST_DEST_HOST); + assert_int_equal(channel_data.dest_port, TEST_DEST_PORT); + assert_string_equal(channel_data.orig_host, TEST_ORIG_HOST); + assert_int_equal(channel_data.orig_port, TEST_ORIG_PORT); + + /* Free allocated memory */ + free(channel_data.dest_host); + free(channel_data.orig_host); +} + +static void torture_ssh_channel_direct_tcpip_success(void **state) +{ + torture_ssh_channel_direct_tcpip(state, true); +} + +static void torture_ssh_channel_direct_tcpip_failure(void **state) +{ + torture_ssh_channel_direct_tcpip(state, false); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown( + torture_ssh_channel_direct_tcpip_success, + setup, + teardown), + cmocka_unit_test_setup_teardown( + torture_ssh_channel_direct_tcpip_failure, + setup, + teardown), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_server_x11.c b/src/libs/libssh-0.12.2/tests/unittests/torture_server_x11.c new file mode 100644 index 000000000000..98423cf786c9 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_server_x11.c @@ -0,0 +1,238 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include + +#include "torture.h" +#include "torture_key.h" +#include + +#define TEST_SERVER_HOST "127.0.0.1" +#define TEST_SERVER_PORT 2222 + +struct hostkey_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) { + struct hostkey_state *h; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + h = malloc(sizeof(struct hostkey_state)); + assert_non_null(h); + + h->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + + mask = umask(S_IRWXO | S_IRWXG); + h->fd = mkstemp(h->hostkey_path); + umask(mask); + assert_return_code(h->fd, errno); + close(h->fd); + + h->key_type = SSH_KEYTYPE_ECDSA_P256; + h->hostkey = torture_get_testkey(h->key_type, 0); + + torture_write_file(h->hostkey_path, h->hostkey); + + *state = h; + + return 0; +} + +static int teardown(void **state) { + struct hostkey_state *h = (struct hostkey_state *)*state; + + unlink(h->hostkey); + free(h->hostkey_path); + free(h); + + ssh_finalize(); + + return 0; +} + +/* For x11_screen_number, need something that is not equal to htonl + itself */ +static const uint32_t x11_screen_number = 1; + +static void *client_thread(void *arg) { + unsigned int test_port = TEST_SERVER_PORT; + int rc; + ssh_session session; + ssh_channel channel; + + /* unused */ + (void)arg; + + usleep(200); + session = torture_ssh_session(NULL, TEST_SERVER_HOST, + &test_port, + "foo", "bar"); + assert_non_null(session); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_int_equal(rc, SSH_OK); + + rc = ssh_channel_request_x11(channel, 0, NULL, NULL, + (uint32_t)x11_screen_number); + assert_int_equal(rc, SSH_OK); + + ssh_free(session); + return NULL; +} + +static int auth_password_accept(ssh_session session, + const char *user, + const char *password, + void *userdata) { + /* unused */ + (void)session; + (void)user; + (void)password; + (void)userdata; + + return SSH_AUTH_SUCCESS; +} + +struct channel_data { + int req_seen; + uint32_t screen_number; +}; + +static void ssh_channel_x11_req(ssh_session session, + ssh_channel channel, + int single_connection, + const char *auth_protocol, + const char *auth_cookie, + uint32_t screen_number, + void *userdata) { + struct channel_data *channel_data = userdata; + + /* unused */ + (void)session; + (void)channel; + (void)single_connection; + (void)auth_protocol; + (void)auth_cookie; + + /* We've seen an x11 request. Record the screen number */ + channel_data->req_seen = 1; + channel_data->screen_number = screen_number; +} + +static ssh_channel channel_open(ssh_session session, void *userdata) { + ssh_channel channel = NULL; + ssh_channel_callbacks channel_cb = userdata; + + /* unused */ + (void)userdata; + + channel = ssh_channel_new(session); + if (channel == NULL) { + goto out; + } + ssh_set_channel_callbacks(channel, channel_cb); + + out: + return channel; +} + +static void test_ssh_channel_request_x11(void **state) { + struct hostkey_state *h = (struct hostkey_state *)*state; + int rc, event_rc; + pthread_t client_pthread; + ssh_bind sshbind; + ssh_session server; + ssh_event event; + + struct channel_data channel_data; + struct ssh_channel_callbacks_struct channel_cb = { + .userdata = &channel_data, + .channel_x11_req_function = ssh_channel_x11_req + }; + struct ssh_server_callbacks_struct server_cb = { + .userdata = &channel_cb, + .auth_password_function = auth_password_accept, + .channel_open_request_session_function = channel_open + }; + + memset(&channel_data, 0, sizeof(channel_data)); + ssh_callbacks_init(&channel_cb); + ssh_callbacks_init(&server_cb); + + /* Create server */ + sshbind = torture_ssh_bind(TEST_SERVER_HOST, + TEST_SERVER_PORT, + h->key_type, + h->hostkey_path); + assert_non_null(sshbind); + + /* Get client to connect */ + rc = pthread_create(&client_pthread, NULL, client_thread, NULL); + assert_return_code(rc, errno); + + server = ssh_new(); + assert_non_null(server); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Handle client connection */ + ssh_set_server_callbacks(server, &server_cb); + + rc = ssh_handle_key_exchange(server); + assert_int_equal(rc, SSH_OK); + + event = ssh_event_new(); + assert_non_null(event); + + ssh_event_add_session(event, server); + + event_rc = SSH_OK; + while (!channel_data.req_seen && event_rc == SSH_OK) { + event_rc = ssh_event_dopoll(event, -1); + } + + /* Cleanup */ + ssh_event_free(event); + ssh_free(server); + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); + + assert_true(channel_data.req_seen); + assert_int_equal(channel_data.screen_number, + x11_screen_number); +} + +int torture_run_tests(void) { + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(test_ssh_channel_request_x11, + setup, + teardown) + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_session_keys.c b/src/libs/libssh-0.12.2/tests/unittests/torture_session_keys.c new file mode 100644 index 000000000000..1ae3831213c1 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_session_keys.c @@ -0,0 +1,107 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/bignum.h" +#include "libssh/crypto.h" +#include "libssh/dh.h" + +uint8_t key[32] = + "\xf7\xa0\xe6\xdf\x1f\x87\x7d\x22\x68\xd2\xc4\xb0\xc5\x93\xa4" + "\x8e\x30\x17\xc6\xab\xca\xf3\x9a\xa4\x9f\x7b\xed\x51\xb1\xe8" + "\x8a\x42"; +uint8_t secret[32] = + "\x33\x64\x8e\x7f\xea\xd9\xd7\xee\x89\x4f\xd8\xd0\xe5\x83\x00" + "\x3d\x53\x17\xbc\xa8\x8b\x6b\x2a\x31\x50\xcc\x08\xe9\xea\x87" + "\xb4\x23"; + +uint8_t eIV[32] = + "\x9a\x2b\x40\x9d\x29\x8e\x22\x70\x86\xdf\x0e\x72\x9b\x91\x31" + "\x90\x5d\x69\xc5\x87\x79\x83\x72\x63\x4e\x67\xf5\x9e\x00\x77" + "\x8c\x7f"; +uint8_t dIV[32] = + "\x10\xdd\x7f\x31\x6d\xe3\x49\x28\xbf\x99\x80\x08\x16\xb3\x99" + "\xff\x8c\x61\x9b\xb9\xc2\xdd\x40\xfb\x36\xf9\x97\xd8\x8c\x55" + "\xbf\xa0"; +uint8_t eK[24] = + "\xe1\x99\x36\xb8\xe6\x1f\x3d\x54\xc3\xa2\xdd\x79\xf0\xfe\x78" + "\x9e\x87\xd5\x05\x54\x26\x34\x21\xd0"; +uint8_t dK[24] = + "\xf8\xdd\xc3\xea\x5a\x59\x98\xb9\x86\xaa\x77\x29\x67\x51\x46" + "\x21\x73\xc2\x6a\x6b\xed\xf2\x49\x98"; +uint8_t encrypt_MAC[32] = + "\x0f\xbd\x1f\xe9\x2a\xaa\x84\xdc\xb5\xfc\xfb\x68\x2c\xa5\xe0" + "\xba\xf2\x6f\xe5\x80\xee\x8f\x5c\x5b\x30\x55\x25\xb3\x7b\x21" + "\xdc\xe5"; +uint8_t decrypt_MAC[32] = + "\xa3\x52\x6e\x72\xa8\x8b\xde\xc5\x68\x66\x89\xae\x0a\xd2\x83" + "\x23\x21\x4b\x3f\x04\x2e\x7f\x86\x04\x0f\xa8\x04\x3c\x62\xad" + "\x74\x91"; + +struct ssh_cipher_struct fake_in_cipher = { + .keysize = 192 +}; + +struct ssh_cipher_struct fake_out_cipher = { + .keysize = 192 +}; + +struct ssh_crypto_struct test_crypto = { + .session_id_len = 32, + .session_id = secret, + .digest_len = 32, + .secret_hash = secret, + .in_cipher = &fake_in_cipher, + .out_cipher = &fake_out_cipher, + .in_hmac = SSH_HMAC_SHA256, + .out_hmac = SSH_HMAC_SHA256, + .digest_type = SSH_KDF_SHA256, +}; + +struct ssh_session_struct session = { + .next_crypto = &test_crypto +}; + +static void torture_session_keys(UNUSED_PARAM(void **state)) +{ + ssh_string k_string; + int rc; + + k_string = ssh_string_new(32); + rc = ssh_string_fill(k_string, key, 32); + assert_int_equal(rc, 0); + + test_crypto.shared_secret = ssh_make_string_bn(k_string); + SSH_STRING_FREE(k_string); + + rc = ssh_generate_session_keys(&session); + assert_int_equal(rc, 0); + + assert_memory_equal(test_crypto.encryptIV, eIV, 32); + assert_memory_equal(test_crypto.decryptIV, dIV, 32); + assert_memory_equal(test_crypto.encryptkey, eK, 24); + assert_memory_equal(test_crypto.decryptkey, dK, 24); + assert_memory_equal(test_crypto.encryptMAC, encrypt_MAC, 32); + assert_memory_equal(test_crypto.decryptMAC, decrypt_MAC, 32); + + bignum_safe_free(test_crypto.shared_secret); + SAFE_FREE(test_crypto.encryptIV); + SAFE_FREE(test_crypto.decryptIV); + SAFE_FREE(test_crypto.encryptkey); + SAFE_FREE(test_crypto.decryptkey); + SAFE_FREE(test_crypto.encryptMAC); + SAFE_FREE(test_crypto.decryptMAC); +} + +int torture_run_tests(void) { + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_session_keys), + }; + + ssh_init(); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_sk_usbhid.c b/src/libs/libssh-0.12.2/tests/unittests/torture_sk_usbhid.c new file mode 100644 index 000000000000..19ab720d3dda --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_sk_usbhid.c @@ -0,0 +1,383 @@ +/* + * torture_sk_usbhid.c - Torture tests for security key USB-HID + * callbacks. + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "libssh/sk_common.h" +#include "torture.h" +#include "torture_sk.h" + +/** + * These tests require at least one FIDO2 device to be connected + * and the environment variables TORTURE_SK_USBHID and TORTURE_SK_PIN to be set. + * + * If TORTURE_SK_USBHID is not set, these tests will be skipped. + * To enable these tests, set both environment variables before running: + * + * export TORTURE_SK_USBHID=1 + * export TORTURE_SK_PIN=your_device_pin + * + * The TORTURE_SK_PIN environment variable should contain the PIN used to + * unlock the FIDO2 device for operations. + * + * Note that these tests must be run in the order that they are defined in, as + * the signing tests rely on the output of the enrollment tests. + */ + +static const char *test_pin = NULL; +static const char *test_application = "ssh:test@example.com"; + +static const uint8_t dummy_data[] = { + 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0a, 0x0b, + 0x0c, 0x0d, 0x0e, 0x0f, 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, + 0x17, 0x18, 0x19, 0x1a, 0x1b, 0x1c, 0x1d, 0x1e, 0x1f, 0x20}; + +/* Global variables to store key handles for signing tests */ +static uint8_t *ecdsa_key_handle = NULL; +static size_t ecdsa_key_handle_len = 0; +static uint8_t *ed25519_key_handle = NULL; +static size_t ed25519_key_handle_len = 0; + +/* Check if tests should run */ +static bool should_run_tests(void) +{ + char *env = getenv("TORTURE_SK_USBHID"); + return (env != NULL && env[0] != '\0'); +} + +static struct sk_option **create_user_id_option(const char *user_id) +{ + struct sk_option **array = NULL, *option = NULL; + + array = calloc(2, sizeof(struct sk_option *)); + assert_non_null(array); + + option = calloc(1, sizeof(struct sk_option)); + assert_non_null(option); + + option->name = strdup(SSH_SK_OPTION_NAME_USER_ID); + assert_non_null(option->name); + option->value = strdup(user_id); + assert_non_null(option->value); + option->required = 0; + + array[0] = option; + array[1] = NULL; + + return array; +} + +static void torture_sk_usbhid_enroll_generic_key(enum ssh_keytypes_e key_type) +{ + const struct ssh_sk_callbacks_struct *callbacks = NULL; + struct sk_enroll_response *response = NULL; + struct sk_option **options = NULL; + const char *user_id = NULL; + uint8_t **key_handle_out = NULL; + size_t *key_handle_len_out = NULL; + int rc, flags; + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, enroll)); + + /* Setup based on key type */ + switch (key_type) { + case SSH_SK_ECDSA: + user_id = "libssh_test_ecdsa_sk"; + key_handle_out = &ecdsa_key_handle; + key_handle_len_out = &ecdsa_key_handle_len; + break; + case SSH_SK_ED25519: + user_id = "libssh_test_ed25519_sk"; + key_handle_out = &ed25519_key_handle; + key_handle_len_out = &ed25519_key_handle_len; + break; + default: + /* Should never reach here */ + assert_true(0); + return; + } + + options = create_user_id_option(user_id); + + /* Enroll non-resident key */ + flags = SSH_SK_USER_PRESENCE_REQD; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_enroll_response(response, flags); + + /* Store the non-resident key handle for signing tests */ + *key_handle_out = calloc(response->key_handle_len, 1); + assert_non_null(*key_handle_out); + memcpy(*key_handle_out, response->key_handle, response->key_handle_len); + *key_handle_len_out = response->key_handle_len; + + SK_ENROLL_RESPONSE_FREE(response); + SK_OPTIONS_FREE(options); +} + +static void +torture_sk_usbhid_enroll_generic_resident_key(enum ssh_keytypes_e key_type) +{ + const struct ssh_sk_callbacks_struct *callbacks = NULL; + struct sk_enroll_response *response = NULL; + struct sk_option **options = NULL; + const char *user_id = NULL; + int rc, flags; + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, enroll)); + + /* Setup based on key type */ + switch (key_type) { + case SSH_SK_ECDSA: + user_id = "libssh_test_ecdsa_sk"; + break; + case SSH_SK_ED25519: + user_id = "libssh_test_ed25519_sk"; + break; + default: + /* Should never reach here */ + assert_true(0); + return; + } + + options = create_user_id_option(user_id); + + /* Enroll first resident key */ + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY | + SSH_SK_FORCE_OPERATION; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_enroll_response(response, flags); + SK_ENROLL_RESPONSE_FREE(response); + + /* Try to enroll same resident key again - should fail with + * SSH_SK_ERR_CREDENTIAL_EXISTS */ + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_SK_ERR_CREDENTIAL_EXISTS); + SK_ENROLL_RESPONSE_FREE(response); + + /* The force operation flag should overwrite the existing resident key with + * new one */ + flags = SSH_SK_USER_PRESENCE_REQD | SSH_SK_RESIDENT_KEY | + SSH_SK_FORCE_OPERATION; + rc = callbacks->enroll(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + flags, + test_pin, + options, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_enroll_response(response, flags); + SK_ENROLL_RESPONSE_FREE(response); + SK_OPTIONS_FREE(options); +} + +static void torture_sk_usbhid_enroll_ecdsa_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_key(SSH_SK_ECDSA); +} + +static void torture_sk_usbhid_enroll_ed25519_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_key(SSH_SK_ED25519); +} + +static void +torture_sk_usbhid_enroll_ecdsa_resident_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_resident_key(SSH_SK_ECDSA); +} + +static void +torture_sk_usbhid_enroll_ed25519_resident_key(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_enroll_generic_resident_key(SSH_SK_ED25519); +} + +static void torture_sk_usbhid_sign_generic(enum ssh_keytypes_e key_type) +{ + const struct ssh_sk_callbacks_struct *callbacks; + struct sk_sign_response *response = NULL; + uint8_t *key_handle = NULL; + size_t key_handle_len = 0; + int rc, flags; + + /* Setup based on key type */ + switch (key_type) { + case SSH_SK_ECDSA: + key_handle = ecdsa_key_handle; + key_handle_len = ecdsa_key_handle_len; + break; + case SSH_SK_ED25519: + key_handle = ed25519_key_handle; + key_handle_len = ed25519_key_handle_len; + break; + default: + /* Should never reach here */ + assert_true(0); + return; + } + + assert_non_null(key_handle); + assert_true(key_handle_len > 0); + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, sign)); + + flags = SSH_SK_USER_PRESENCE_REQD; + rc = callbacks->sign(key_type, + dummy_data, + sizeof(dummy_data), + test_application, + key_handle, + key_handle_len, + flags, + test_pin, + NULL, + &response); + assert_int_equal(rc, SSH_OK); + assert_sk_sign_response(response, key_type); + + SK_SIGN_RESPONSE_FREE(response); +} + +static void torture_sk_usbhid_sign_ecdsa(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_sign_generic(SSH_SK_ECDSA); +} + +static void torture_sk_usbhid_sign_ed25519(UNUSED_PARAM(void **state)) +{ + torture_sk_usbhid_sign_generic(SSH_SK_ED25519); +} + +static void torture_sk_usbhid_load_resident_keys(UNUSED_PARAM(void **state)) +{ + const struct ssh_sk_callbacks_struct *callbacks; + struct sk_resident_key **resident_keys = NULL; + size_t num_keys = 0; + int rc; + + callbacks = ssh_sk_get_default_callbacks(); + assert_non_null(callbacks); + assert_true(ssh_callbacks_exists(callbacks, load_resident_keys)); + + rc = callbacks->load_resident_keys(test_pin, + NULL, + &resident_keys, + &num_keys); + assert_int_equal(rc, SSH_OK); + assert_non_null(resident_keys); + assert_true(num_keys > 0); + + for (size_t i = 0; i < num_keys; i++) { + assert_sk_resident_key(resident_keys[i]); + SK_RESIDENT_KEY_FREE(resident_keys[i]); + } + + free(resident_keys); +} + +static int setup(UNUSED_PARAM(void **state)) +{ + const char *test_pin_env = NULL; + + test_pin_env = torture_get_sk_pin(); + if (test_pin_env != NULL) { + test_pin = test_pin_env; + } + + return 0; +} + +static int cleanup(UNUSED_PARAM(void **state)) +{ + SAFE_FREE(ecdsa_key_handle); + SAFE_FREE(ed25519_key_handle); + + return 0; +} + +int torture_run_tests(void) +{ + int rc; + bool should_run; + + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_sk_usbhid_enroll_ecdsa_key), + cmocka_unit_test(torture_sk_usbhid_enroll_ed25519_key), + cmocka_unit_test(torture_sk_usbhid_enroll_ecdsa_resident_key), + cmocka_unit_test(torture_sk_usbhid_enroll_ed25519_resident_key), + cmocka_unit_test(torture_sk_usbhid_sign_ecdsa), + cmocka_unit_test(torture_sk_usbhid_sign_ed25519), + cmocka_unit_test(torture_sk_usbhid_load_resident_keys), + }; + + /* + * Only run tests if TORTURE_SK_USBHID environment variable is set + * and we expect a FIDO2 device to be available. + */ + should_run = should_run_tests(); + if (!should_run) { + printf("Skipping sk_usbhid tests: TORTURE_SK_USBHID not set\n"); + return 0; /* Success, but no tests run */ + } + + ssh_init(); + rc = cmocka_run_group_tests(tests, setup, cleanup); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_string.c b/src/libs/libssh-0.12.2/tests/unittests/torture_string.c new file mode 100644 index 000000000000..404676cbe4fc --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_string.c @@ -0,0 +1,424 @@ +/* + * torture_string.c - torture tests for ssh_string functions + * + * This file is part of the SSH Library + * + * Copyright (c) 2025 Praneeth Sarode + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#include +#include + +#define LIBSSH_STATIC + +#include "libssh/string.h" +#include "string.c" +#include "torture.h" + +static void torture_ssh_string_new(void **state) +{ + struct ssh_string_struct *str = NULL; + + (void)state; + + /* Test normal allocation */ + str = ssh_string_new(100); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), 100); + ssh_string_free(str); + + /* Test zero size */ + str = ssh_string_new(0); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), 0); + ssh_string_free(str); + + /* Test maximum size */ + str = ssh_string_new(STRING_SIZE_MAX - 1); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), STRING_SIZE_MAX - 1); + ssh_string_free(str); + + /* Test size too large - should fail */ + str = ssh_string_new(STRING_SIZE_MAX + 1); + assert_null(str); + assert_int_equal(errno, EINVAL); +} + +static void torture_ssh_string_from_char(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_string = "Hello, World!"; + const char *empty_string = ""; + + (void)state; + + /* Test normal string */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), strlen(test_string)); + assert_memory_equal(ssh_string_data(str), test_string, strlen(test_string)); + ssh_string_free(str); + + /* Test empty string */ + str = ssh_string_from_char(empty_string); + assert_non_null(str); + assert_int_equal(ssh_string_len(str), 0); + ssh_string_free(str); + + /* Test NULL input */ + str = ssh_string_from_char(NULL); + assert_null(str); + assert_int_equal(errno, EINVAL); +} + +static void torture_ssh_string_from_data(void **state) +{ + ssh_string s; + const unsigned char raw[] = {0x00, 0x01, 0x00, 0x42, 0xFF}; + + (void)state; + + /* Basic: copy arbitrary binary data (with embedded NUL) */ + s = ssh_string_from_data(raw, sizeof(raw)); + assert_non_null(s); + assert_int_equal(ssh_string_len(s), sizeof(raw)); + assert_memory_equal(ssh_string_data(s), raw, sizeof(raw)); + ssh_string_free(s); + + /* Empty: len == 0 with NULL data returns empty string */ + s = ssh_string_from_data(NULL, 0); + assert_non_null(s); + assert_int_equal(ssh_string_len(s), 0); + ssh_string_free(s); + + /* Invalid: len > 0 with NULL data fails and sets errno */ + errno = 0; + s = ssh_string_from_data(NULL, 42); + assert_null(s); + assert_int_equal(errno, EINVAL); +} + +static void torture_ssh_string_fill(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_data = "Test data"; + int rc; + + (void)state; + + /* Test normal fill */ + str = ssh_string_new(20); + assert_non_null(str); + + rc = ssh_string_fill(str, test_data, strlen(test_data)); + assert_int_equal(rc, 0); + assert_memory_equal(ssh_string_data(str), test_data, strlen(test_data)); + ssh_string_free(str); + + /* Test fill with exact size */ + str = ssh_string_new(strlen(test_data)); + assert_non_null(str); + + rc = ssh_string_fill(str, test_data, strlen(test_data)); + assert_int_equal(rc, 0); + ssh_string_free(str); + + /* Test NULL data */ + str = ssh_string_new(10); + assert_non_null(str); + + rc = ssh_string_fill(str, NULL, 5); + assert_int_equal(rc, -1); + ssh_string_free(str); + + /* Test zero length */ + str = ssh_string_new(10); + assert_non_null(str); + + rc = ssh_string_fill(str, test_data, 0); + assert_int_equal(rc, -1); + ssh_string_free(str); +} + +static void torture_ssh_string_to_char(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_string = "Convert to char"; + char *result = NULL; + + (void)state; + + /* Test normal string */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + + result = ssh_string_to_char(str); + assert_non_null(result); + assert_string_equal(result, test_string); + + ssh_string_free_char(result); + ssh_string_free(str); + + /* Test empty string */ + str = ssh_string_from_char(""); + assert_non_null(str); + + result = ssh_string_to_char(str); + assert_non_null(result); + assert_string_equal(result, ""); + + ssh_string_free_char(result); + ssh_string_free(str); + + /* Test NULL string */ + result = ssh_string_to_char(NULL); + assert_null(result); +} + +static void torture_ssh_string_copy(void **state) +{ + struct ssh_string_struct *str = NULL, *copy = NULL; + const char *test_string = "Copy me!"; + + (void)state; + + /* Test normal copy */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + + copy = ssh_string_copy(str); + assert_non_null(copy); + assert_int_equal(ssh_string_len(copy), ssh_string_len(str)); + assert_memory_equal(ssh_string_data(copy), + ssh_string_data(str), + ssh_string_len(str)); + + /* Ensure they are different objects */ + assert_ptr_not_equal(str, copy); + assert_ptr_not_equal(ssh_string_data(str), ssh_string_data(copy)); + + ssh_string_free(str); + ssh_string_free(copy); + + /* Test copy of empty string */ + str = ssh_string_from_char(""); + assert_non_null(str); + + copy = ssh_string_copy(str); + assert_non_null(copy); + assert_int_equal(ssh_string_len(copy), 0); + + ssh_string_free(str); + ssh_string_free(copy); + + /* Test NULL string */ + copy = ssh_string_copy(NULL); + assert_null(copy); +} + +static void torture_ssh_string_burn(void **state) +{ + struct ssh_string_struct *str = NULL; + const char *test_string = "Secret data"; + void *data = NULL; + size_t len; + int i; + + (void)state; + + /* Test burning a string */ + str = ssh_string_from_char(test_string); + assert_non_null(str); + + data = ssh_string_data(str); + len = ssh_string_len(str); + + /* Verify data is there initially */ + assert_memory_equal(data, test_string, len); + + /* Burn the string */ + ssh_string_burn(str); + + /* Verify data is zeroed out */ + for (i = 0; i < (int)len; i++) { + assert_int_equal(((unsigned char *)data)[i], 0); + } + + ssh_string_free(str); + + /* Test burning NULL string (should not crash) */ + ssh_string_burn(NULL); + + /* Test burning zero-size string */ + str = ssh_string_new(0); + assert_non_null(str); + ssh_string_burn(str); + ssh_string_free(str); +} + +static void torture_ssh_string_cmp(void **state) +{ + struct ssh_string_struct *str1 = NULL, *str2 = NULL; + const char *test_string1 = "Hello, World!"; + const char *test_string2 = "Hello, libssh"; + const char *test_string3 = "Hello"; + const char *test_string4 = "Apple"; + + const char data1[] = "Hello\x00World!"; + const char data2[] = "Hello\x00libssh"; + const char data3[] = "Hello"; + + int rc; + (void)state; + + /* Test comparing two NULL strings - should be equal */ + assert_int_equal(ssh_string_cmp(NULL, NULL), 0); + + /* Test comparing NULL with non-NULL string - NULL should be less */ + str1 = ssh_string_from_char(test_string1); + assert_non_null(str1); + assert_true(ssh_string_cmp(NULL, str1) < 0); + assert_true(ssh_string_cmp(str1, NULL) > 0); + ssh_string_free(str1); + + /* Test comparing empty strings */ + str1 = ssh_string_from_char(""); + str2 = ssh_string_from_char(""); + assert_non_null(str1); + assert_non_null(str2); + + /* Both empty strings should be equal */ + assert_int_equal(ssh_string_cmp(str1, str2), 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing empty string with non-empty string */ + str1 = ssh_string_from_char(""); + str2 = ssh_string_from_char("test"); + assert_non_null(str1); + assert_non_null(str2); + + /* Empty string should be less than non-empty string */ + assert_true(ssh_string_cmp(str1, str2) < 0); + assert_true(ssh_string_cmp(str2, str1) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing strings where one is a prefix of another */ + str1 = ssh_string_from_char(test_string1); /* "Hello, World!" */ + str2 = ssh_string_from_char(test_string3); /* "Hello" - prefix */ + assert_non_null(str1); + assert_non_null(str2); + + /* "Hello" is shorter and a prefix, so it should be < "Hello, World!" */ + assert_true(ssh_string_cmp(str2, str1) < 0); + assert_true(ssh_string_cmp(str1, str2) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing different strings with same length */ + str1 = ssh_string_from_char(test_string1); /* "Hello, World!" */ + str2 = ssh_string_from_char(test_string2); /* "Hello, libssh" */ + assert_non_null(str1); + assert_non_null(str2); + + /* "Hello, World!" vs "Hello, libssh" - 'W' < 'l' */ + assert_true(ssh_string_cmp(str1, str2) < 0); + assert_true(ssh_string_cmp(str2, str1) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing strings with different lengths and different characters */ + str1 = ssh_string_from_char(test_string1); /* "Hello, World!" */ + str2 = ssh_string_from_char(test_string4); /* "Apple" */ + assert_non_null(str1); + assert_non_null(str2); + + /* 'A' < 'H' so "Apple" < "Hello, World!" */ + assert_true(ssh_string_cmp(str2, str1) < 0); + assert_true(ssh_string_cmp(str1, str2) > 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing identical strings - should be equal */ + str1 = ssh_string_from_char(test_string1); + str2 = ssh_string_from_char(test_string1); + assert_non_null(str1); + assert_non_null(str2); + assert_int_equal(ssh_string_cmp(str1, str2), 0); + assert_int_equal(ssh_string_cmp(str2, str1), 0); + ssh_string_free(str1); + ssh_string_free(str2); + + /* Test comparing strings with embedded null characters */ + str1 = ssh_string_new(sizeof(data1)); /* "Hello\x00World!" */ + str2 = ssh_string_new(sizeof(data3)); /* "Hello" */ + assert_non_null(str1); + assert_non_null(str2); + rc = ssh_string_fill(str1, data1, sizeof(data1)); + assert_int_equal(rc, 0); + rc = ssh_string_fill(str2, data3, sizeof(data3)); + assert_int_equal(rc, 0); + + /* "Hello\x00World!" > "Hello" because its length is greater */ + assert_true(ssh_string_cmp(str1, str2) > 0); /* data1 > data3 */ + assert_true(ssh_string_cmp(str2, str1) < 0); /* data3 < data1 */ + ssh_string_free(str1); + ssh_string_free(str2); + + /* Comparing binary strings with same length, but different characters */ + str1 = ssh_string_new(sizeof(data1)); /* "Hello\x00World!" */ + str2 = ssh_string_new(sizeof(data2)); /* "Hello\x00libssh" */ + assert_non_null(str1); + assert_non_null(str2); + rc = ssh_string_fill(str1, data1, sizeof(data1)); + assert_int_equal(rc, 0); + rc = ssh_string_fill(str2, data2, sizeof(data2)); + assert_int_equal(rc, 0); + + /* 'W' < 'l' so str1 < str2 */ + assert_true(ssh_string_cmp(str1, str2) < 0); /* data1 < data2 */ + assert_true(ssh_string_cmp(str2, str1) > 0); /* data2 > data1 */ + ssh_string_free(str1); + ssh_string_free(str2); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_string_new), + cmocka_unit_test(torture_ssh_string_from_char), + cmocka_unit_test(torture_ssh_string_from_data), + cmocka_unit_test(torture_ssh_string_fill), + cmocka_unit_test(torture_ssh_string_to_char), + cmocka_unit_test(torture_ssh_string_copy), + cmocka_unit_test(torture_ssh_string_burn), + cmocka_unit_test(torture_ssh_string_cmp), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_temp_dir.c b/src/libs/libssh-0.12.2/tests/unittests/torture_temp_dir.c new file mode 100644 index 000000000000..feff23c7f1ea --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_temp_dir.c @@ -0,0 +1,51 @@ +#include "config.h" + +#include "torture.h" +#define LIBSSH_STATIC + +const char template[] = "temp_dir_XXXXXX"; + +static int setup(void **state) +{ + char *temp_dir = NULL; + + temp_dir = torture_make_temp_dir(template); + assert_non_null(temp_dir); + + *state = (void *)temp_dir; + + return 0; +} + +static int teardown(void **state) +{ + char *temp_dir = *((char **)state); + + torture_rmdirs((const char *)temp_dir); + + free(temp_dir); + + return 0; +} + + +static void torture_create_temp_dir(void **state) +{ + char *temp_dir = *((char **)state); + + printf("Created temp dir: %s\n", temp_dir); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_create_temp_dir, setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_temp_file.c b/src/libs/libssh-0.12.2/tests/unittests/torture_temp_file.c new file mode 100644 index 000000000000..793d6c12ce5a --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_temp_file.c @@ -0,0 +1,63 @@ +#include "config.h" + +#include "torture.h" +#define LIBSSH_STATIC + +const char template[] = "temp_file_XXXXXX"; + +static int setup(void **state) +{ + char *file_name = NULL; + + file_name = torture_create_temp_file(template); + assert_non_null(file_name); + + *state = (void *)file_name; + + return 0; +} + +static int teardown(void **state) +{ + int rc; + char *file_name = *((char **)state); + + assert_non_null(file_name); + + rc = unlink(file_name); + assert_int_equal(rc, 0); + + SAFE_FREE(file_name); + + return 0; +} + + +static void torture_temp_file(void **state) +{ + char *file_name = *((char **)state); + FILE *fp = NULL; + + assert_non_null(file_name); + + fp = fopen(file_name, "r"); + assert_non_null(fp); + + fclose(fp); + + printf("Created temp file: %s\n", file_name); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_temp_file, setup, teardown), + }; + + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + + return rc; +} + diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_threads_buffer.c b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_buffer.c new file mode 100644 index 000000000000..dfd9c57186b2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_buffer.c @@ -0,0 +1,602 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#define DEBUG_BUFFER +#include "buffer.c" + +#include + +#define NUM_THREADS 20 + +#define BUFFER_LIMIT (8 * 1024 * 1024) + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result = NULL; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity + */ +static void *thread_growing_buffer(void *threadid) +{ + ssh_buffer buffer = NULL; + int i; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + for (i = 0; i < BUFFER_LIMIT; ++i) { + ssh_buffer_add_data(buffer,"A",1); + if (buffer->used >= 128) { + if (ssh_buffer_get_len(buffer) * 2 < buffer->allocated) { + assert_true(ssh_buffer_get_len(buffer) * 2 >= buffer->allocated); + } + } + } + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_growing_buffer(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_growing_buffer); + assert_int_equal(rc, 0); +} + +/* + * Test if the continuously growing buffer size never exceeds 2 time its + * real capacity, when we remove 1 byte after each call (sliding window) + */ +static void *thread_growing_buffer_shifting(void *threadid) +{ + ssh_buffer buffer; + int i; + unsigned char c; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + /* dummy analyzers ... */ + return NULL; + } + ssh_buffer_set_secure(buffer); + + + for (i = 0; i < 1024; ++i) { + ssh_buffer_add_data(buffer,"S",1); + } + + for (i = 0; i < BUFFER_LIMIT; ++i) { + ssh_buffer_get_u8(buffer,&c); + ssh_buffer_add_data(buffer,"A",1); + if (buffer->used >= 128) { + if (ssh_buffer_get_len(buffer) * 4 < buffer->allocated) { + assert_true(ssh_buffer_get_len(buffer) * 4 >= buffer->allocated); + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); + } + } + } + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_growing_buffer_shifting(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_growing_buffer_shifting); + assert_int_equal(rc, 0); +} + +/* + * Test the behavior of ssh_buffer_prepend_data + */ +static void *thread_buffer_prepend(void *threadid) +{ + ssh_buffer buffer = NULL; + uint32_t v; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + ssh_buffer_add_data(buffer, "abcdef", 6); + ssh_buffer_prepend_data(buffer, "xyz", 3); + assert_int_equal(ssh_buffer_get_len(buffer), 9); + assert_memory_equal(ssh_buffer_get(buffer), "xyzabcdef", 9); + + /* Now remove 4 bytes and see if we can replace them */ + ssh_buffer_get_u32(buffer, &v); + assert_int_equal(ssh_buffer_get_len(buffer), 5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer, "aris", 4); + assert_int_equal(ssh_buffer_get_len(buffer), 9); + assert_memory_equal(ssh_buffer_get(buffer), "arisbcdef", 9); + + /* same thing but we add 5 bytes now */ + ssh_buffer_get_u32(buffer, &v); + assert_int_equal(ssh_buffer_get_len(buffer), 5); + assert_memory_equal(ssh_buffer_get(buffer), "bcdef", 5); + + ssh_buffer_prepend_data(buffer, "12345", 5); + assert_int_equal(ssh_buffer_get_len(buffer), 10); + assert_memory_equal(ssh_buffer_get(buffer), "12345bcdef", 10); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_buffer_prepend(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_buffer_prepend); + assert_int_equal(rc, 0); +} + +/* + * Test the behavior of ssh_buffer_get_ssh_string with invalid data + */ +static void *thread_ssh_buffer_get_ssh_string(void *threadid) +{ + ssh_buffer buffer = NULL; + size_t i, j, k, l; + int rc; + /* some values that can go wrong */ + uint32_t values[] = { + 0xffffffff, 0xfffffffe, 0xfffffffc, 0xffffff00, + 0x80000000, 0x80000004, 0x7fffffff}; + char data[128] = {0}; + + /* Unused */ + (void)threadid; + + memset(data, 'X', sizeof(data)); + + for (i = 0; i < ARRAY_SIZE(values); ++i) { + for (j = 0; j < (int)sizeof(data); ++j) { + for (k = 1; k < 5; ++k) { + buffer = ssh_buffer_new(); + assert_non_null(buffer); + + for (l = 0; l < k; ++l) { + rc = ssh_buffer_add_u32(buffer, htonl(values[i])); + assert_int_equal(rc, 0); + } + rc = ssh_buffer_add_data(buffer,data,j); + assert_int_equal(rc, 0); + for (l = 0; l < k; ++l) { + ssh_string str = ssh_buffer_get_ssh_string(buffer); + assert_null(str); + SSH_STRING_FREE(str); + } + SSH_BUFFER_FREE(buffer); + } + } + } + + pthread_exit(NULL); +} + +static void torture_ssh_buffer_get_ssh_string(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_get_ssh_string); + assert_int_equal(rc, 0); +} + +static void *thread_ssh_buffer_add_format(void *threadid) +{ + ssh_buffer buffer = NULL; + uint8_t b; + uint16_t w; + uint32_t d; + uint64_t q; + ssh_string s = NULL; + int rc; + size_t len; + uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" + "\xac\xbd\xce\xdf" + "\x00\x00\x00\x06" "libssh" + "\x00\x00\x00\x05" "rocks" + "So much" + "Fun!"; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + b = 0x42; + w = 0x1337; + d = 0xbadc0de; + q = 0x13243546acbdcedf; + s = ssh_string_from_char("libssh"); + rc = ssh_buffer_pack(buffer, + "bwdqSsPt", + b, + w, + d, + q, + s, + "rocks", + (size_t)7, + "So much", + "Fun!"); + assert_int_equal(rc, SSH_OK); + + len = ssh_buffer_get_len(buffer); + assert_int_equal(len, sizeof(verif) - 1); + assert_memory_equal(ssh_buffer_get(buffer), verif, sizeof(verif) -1); + + SSH_STRING_FREE(s); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_ssh_buffer_add_format(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_add_format); + assert_int_equal(rc, 0); +} + +static void *thread_ssh_buffer_get_format(void *threadid) { + ssh_buffer buffer; + uint8_t b = 0; + uint16_t w = 0; + uint32_t d = 0; + uint64_t q = 0; + ssh_string s = NULL; + char *s1 = NULL, *s2 = NULL; + int rc; + size_t len; + uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" + "\xac\xbd\xce\xdf" + "\x00\x00\x00\x06" "libssh" + "\x00\x00\x00\x05" "rocks" + "So much"; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_unpack(buffer, + "bwdqSsP", + &b, + &w, + &d, + &q, + &s, + &s1, + (size_t)7, + &s2); + assert_int_equal(rc, SSH_OK); + + assert_int_equal(b, 0x42); + assert_int_equal(w, 0x1337); + + assert_true(d == 0xbadc0de); + assert_true(q == 0x13243546acbdcedf); + + assert_non_null(s); + assert_int_equal(ssh_string_len(s), 6); + assert_memory_equal(ssh_string_data(s), "libssh", 6); + + assert_non_null(s1); + assert_string_equal(s1, "rocks"); + + assert_non_null(s2); + assert_memory_equal(s2, "So much", 7); + + len = ssh_buffer_get_len(buffer); + assert_int_equal(len, 0); + SAFE_FREE(s); + SAFE_FREE(s1); + SAFE_FREE(s2); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_ssh_buffer_get_format(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_get_format); + assert_int_equal(rc, 0); +} + +static void *thread_ssh_buffer_get_format_error(void *threadid) +{ + ssh_buffer buffer = NULL; + uint8_t b = 0; + uint16_t w = 0; + uint32_t d = 0; + uint64_t q = 0; + ssh_string s = NULL; + char *s1 = NULL, *s2 = NULL; + int rc; + uint8_t verif[] = "\x42\x13\x37\x0b\xad\xc0\xde\x13\x24\x35\x46" + "\xac\xbd\xce\xdf" + "\x00\x00\x00\x06" "libssh" + "\x00\x00\x00\x05" "rocks" + "So much"; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + rc = ssh_buffer_add_data(buffer, verif, sizeof(verif) - 1); + assert_int_equal(rc, SSH_OK); + rc = ssh_buffer_unpack(buffer, + "bwdqSsPb", + &b, + &w, + &d, + &q, + &s, + &s1, + (size_t)7, + &s2, + &b); + assert_int_equal(rc, SSH_ERROR); + + assert_null(s); + assert_null(s1); + assert_null(s2); + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_ssh_buffer_get_format_error(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_ssh_buffer_get_format_error); + assert_int_equal(rc, 0); +} + +static void *thread_buffer_pack_badformat(void *threadid) +{ + ssh_buffer buffer = NULL; + uint8_t b = 42; + int rc; + + /* Unused */ + (void) threadid; + + /* Setup */ + buffer = ssh_buffer_new(); + if (buffer == NULL) { + pthread_exit((void *)-1); + } + ssh_buffer_set_secure(buffer); + + /* first with missing format */ + rc = ssh_buffer_pack(buffer, "b", b, b); + assert_int_equal(rc, SSH_ERROR); + ssh_buffer_reinit(buffer); + + /* with additional format */ + rc = ssh_buffer_pack(buffer, "bb", b); + /* check that we detect the missing parameter */ + assert_int_equal(rc, SSH_ERROR); + + /* unpack with missing format */ + ssh_buffer_reinit(buffer); + + rc = ssh_buffer_pack(buffer, "bb", 42, 43); + assert_int_equal(rc, SSH_OK); + + rc = ssh_buffer_unpack(buffer, "b", &b, &b); + assert_int_equal(rc, SSH_ERROR); + + /* not doing the test with additional format as + * it could crash the process */ + + /* Teardown */ + SSH_BUFFER_FREE(buffer); + pthread_exit(NULL); +} + +static void torture_buffer_pack_badformat(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_buffer_pack_badformat); + assert_int_equal(rc, 0); +} + +#define NUM_TESTS 8 + +static void torture_mixed(void **state) +{ + pthread_t threads[NUM_TESTS][NUM_THREADS]; + int i; + int f; + int rc; + + /* Array of functions to run on threads */ + static void *(*funcs[NUM_TESTS])(void *) = { + thread_growing_buffer, + thread_growing_buffer_shifting, + thread_buffer_prepend, + thread_ssh_buffer_get_ssh_string, + thread_ssh_buffer_add_format, + thread_ssh_buffer_get_format, + thread_ssh_buffer_get_format_error, + thread_buffer_pack_badformat + }; + + (void) state; + + /* Call tests in a round-robin fashion */ + for (i = 0; i < NUM_THREADS; ++i) { + for (f = 0; f < NUM_TESTS; f++) { + rc = pthread_create(&threads[f][i], NULL, funcs[f], NULL); + assert_int_equal(rc, 0); + } + } + + for (f = 0; f < NUM_TESTS; f++) { + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result = NULL; + + rc = pthread_join(threads[f][i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + } +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_growing_buffer), + cmocka_unit_test(torture_growing_buffer_shifting), + cmocka_unit_test(torture_buffer_prepend), + cmocka_unit_test(torture_ssh_buffer_get_ssh_string), + cmocka_unit_test(torture_ssh_buffer_add_format), + cmocka_unit_test(torture_ssh_buffer_get_format), + cmocka_unit_test(torture_ssh_buffer_get_format_error), + cmocka_unit_test(torture_buffer_pack_badformat), + cmocka_unit_test(torture_mixed), + }; + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_threads_crypto.c b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_crypto.c new file mode 100644 index 000000000000..871f29200858 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_crypto.c @@ -0,0 +1,205 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/crypto.h" + +#include + +#define NUM_THREADS 100 + +static int8_t key[32] = + "\x00\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e" + "\x0f\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d" + "\x1e\x1f"; + +static uint8_t IV[16] = + "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x1b\x1c\x1d\x1e" + "\x1f"; + +static uint8_t cleartext[144] = + "\xb4\xfc\x5d\xc2\x49\x8d\x2c\x29\x4a\xc9\x9a\xb0\x1b\xf8\x29" + "\xee\x85\x6d\x8c\x04\x34\x7c\x65\xf4\x89\x97\xc5\x71\x70\x41" + "\x91\x40\x19\x60\xe1\xf1\x8f\x4d\x8c\x17\x51\xd6\xbc\x69\x6e" + "\xf2\x21\x87\x18\x6c\xef\xc4\xf4\xd9\xe6\x1b\x94\xf7\xd8\xb2" + "\xe9\x24\xb9\xe7\xe6\x19\xf5\xec\x55\x80\x9a\xc8\x7d\x70\xa3" + "\x50\xf8\x03\x10\x35\x49\x9b\x53\x58\xd7\x4c\xfc\x5f\x02\xd6" + "\x28\xea\xcc\x43\xee\x5e\x2b\x8a\x7a\x66\xf7\x00\xee\x09\x18" + "\x30\x1b\x47\xa2\x16\x69\xc4\x6e\x44\x3f\xbd\xec\x52\xce\xe5" + "\x41\xf2\xe0\x04\x4f\x5a\x55\x58\x37\xba\x45\x8d\x15\x53\xf6" + "\x31\x91\x13\x8c\x51\xed\x08\x07\xdb"; + +static uint8_t aes256_cbc_encrypted[144] = + "\x7f\x1b\x92\xac\xc5\x16\x05\x55\x74\xac\xb4\xe0\x91\x8c\xf8" + "\x0d\xa9\x72\xa5\x09\xb8\x44\xee\x55\x02\x13\xb7\x52\x0a\xf0" + "\xac\xd0\x21\x0e\x58\x7b\x34\xfe\xdb\x36\x01\x60\x7d\x18\x3a" + "\xa9\x15\x18\x5b\x13\xca\xdd\x77\x7d\xdf\x64\xc6\xd5\x75\x4b" + "\x02\x02\x37\xb1\xf4\x33\xff\x93\xe6\x32\x08\xda\xcb\x5d\xa2" + "\x8f\x17\x1f\x99\x92\x60\x22\x9d\x6b\xe6\xb2\x5e\xb0\x5d\x26" + "\x3f\xde\xb8\xc1\xb0\x70\x80\x1c\x00\xd0\x93\x2b\xeb\x0f\xd7" + "\x70\x7a\x9a\x7a\xa6\x21\x23\x2c\x02\xb7\xcd\x88\x10\x9c\x2d" + "\x0c\xd3\xfa\xc1\x33\x5b\xe1\xa1\xd4\x3d\x8f\xb8\x50\xc5\xb5" + "\x72\xdd\x6d\x32\x1f\x58\x00\x48\xbe"; + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +static int get_cipher(struct ssh_cipher_struct *cipher, const char *ciphername) +{ + struct ssh_cipher_struct *ciphers = ssh_get_ciphertab(); + int i, cmp; + + for (i = 0; ciphers[i].name != NULL; i++) { + cmp = strcmp(ciphername, ciphers[i].name); + if (cmp == 0) { + memcpy(cipher, &ciphers[i], sizeof(*cipher)); + return SSH_OK; + } + } + + return SSH_ERROR; +} + +static void *thread_crypto_aes256_cbc(void *threadid) +{ + uint8_t output[sizeof(cleartext)] = {0}; + uint8_t iv[16] = {0}; + struct ssh_cipher_struct cipher = { + .name = NULL, + }; + int rc; + + /* Unused */ + (void) threadid; + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.encrypt); + + /* This is for dump static analizyer without modelling support */ + if (cipher.set_encrypt_key == NULL || + cipher.encrypt == NULL) { + return NULL; + } + + memcpy(iv, IV, sizeof(IV)); + cipher.set_encrypt_key(&cipher, + key, + iv + ); + + cipher.encrypt(&cipher, + cleartext, + output, + sizeof(cleartext) + ); + + assert_memory_equal(output, + aes256_cbc_encrypted, + sizeof(aes256_cbc_encrypted)); + ssh_cipher_clear(&cipher); + + rc = get_cipher(&cipher, "aes256-cbc"); + assert_int_equal(rc, SSH_OK); + assert_non_null(cipher.set_encrypt_key); + assert_non_null(cipher.encrypt); + + /* This is for dump static analizyer without modelling support */ + if (cipher.set_encrypt_key == NULL || + cipher.encrypt == NULL) { + return NULL; + } + + memcpy(iv, IV, sizeof(IV)); + cipher.set_decrypt_key(&cipher, + key, + iv + ); + + memset(output, '\0', sizeof(output)); + cipher.decrypt(&cipher, + aes256_cbc_encrypted, + output, + sizeof(aes256_cbc_encrypted) + ); + + assert_memory_equal(output, cleartext, sizeof(cleartext)); + + ssh_cipher_clear(&cipher); + + pthread_exit(NULL); +} + +static void torture_crypto_aes256_cbc(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_crypto_aes256_cbc); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(torture_crypto_aes256_cbc), + }; + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_threads_init.c b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_init.c new file mode 100644 index 000000000000..e49bde10c4b2 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_init.c @@ -0,0 +1,98 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/libssh.h" + +#include + +#define NUM_THREADS 20 + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + void *p = NULL; + uint64_t *result; + + rc = pthread_join(threads[i], &p); + assert_int_equal(rc, 0); + + result = (uint64_t *)p; + assert_null(result); + } + + return rc; +} + +static void *thread_ssh_init(UNUSED_PARAM(void *threadid)) +{ + int rc; + + (void) threadid; + + rc = ssh_init(); + assert_int_equal(rc, SSH_OK); + + rc = ssh_finalize(); + assert_int_equal(rc, SSH_OK); + + pthread_exit(NULL); +} + +static void torture_ssh_init(UNUSED_PARAM(void **state)) +{ + int rc; + + rc = run_on_threads(thread_ssh_init); + assert_int_equal(rc, 0); +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_ssh_init), + }; + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_threads_pki_rsa.c b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_pki_rsa.c new file mode 100644 index 000000000000..796748436229 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_threads_pki_rsa.c @@ -0,0 +1,791 @@ +/* + * This file is part of the SSH Library + * + * Copyright (c) 2018 by Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include + +#include "pki.c" +#include "torture.h" +#include "torture_pki.h" +#include "torture_key.h" + +#include + +#define LIBSSH_RSA_TESTKEY "libssh_testkey.id_rsa" +#define LIBSSH_RSA_TESTKEY_PASSPHRASE "libssh_testkey_passphrase.id_rsa" + +#define NUM_THREADS 10 + +const char template[] = "temp_dir_XXXXXX"; +const unsigned char RSA_HASH[] = "12345678901234567890"; + +struct pki_st { + char *cwd; + char *temp_dir; +}; + +static int run_on_threads(void *(*func)(void *)) +{ + pthread_t threads[NUM_THREADS]; + int rc; + int i; + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_create(&threads[i], NULL, func, NULL); + assert_int_equal(rc, 0); + } + + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_join(threads[i], NULL); + assert_int_equal(rc, 0); + } + + return rc; +} + +static int setup_rsa_key(void **state) +{ + struct pki_st *test_state = NULL; + char *cwd = NULL; + char *tmp_dir = NULL; + int rc = 0; + + test_state = (struct pki_st *)malloc(sizeof(struct pki_st)); + assert_non_null(test_state); + + cwd = torture_get_current_working_dir(); + assert_non_null(cwd); + + tmp_dir = torture_make_temp_dir(template); + assert_non_null(tmp_dir); + + test_state->cwd = cwd; + test_state->temp_dir = tmp_dir; + + *state = test_state; + + rc = torture_change_dir(tmp_dir); + assert_int_equal(rc, 0); + + printf("Changed directory to: %s\n", tmp_dir); + + torture_write_file(LIBSSH_RSA_TESTKEY, + torture_get_testkey(SSH_KEYTYPE_RSA, 0)); + torture_write_file(LIBSSH_RSA_TESTKEY_PASSPHRASE, + torture_get_testkey(SSH_KEYTYPE_RSA, 1)); + torture_write_file(LIBSSH_RSA_TESTKEY ".pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + torture_write_file(LIBSSH_RSA_TESTKEY "-cert.pub", + torture_get_testkey_pub(SSH_KEYTYPE_RSA_CERT01)); + + return 0; +} + +static int teardown(void **state) { + + struct pki_st *test_state = NULL; + int rc = 0; + + test_state = *((struct pki_st **)state); + + assert_non_null(test_state); + assert_non_null(test_state->cwd); + assert_non_null(test_state->temp_dir); + + rc = torture_change_dir(test_state->cwd); + assert_int_equal(rc, 0); + + rc = torture_rmdirs(test_state->temp_dir); + assert_int_equal(rc, 0); + + SAFE_FREE(test_state->temp_dir); + SAFE_FREE(test_state->cwd); + SAFE_FREE(test_state); + + return 0; +} + +static void +disable_secmem(void) +{ +#if defined(HAVE_LIBGCRYPT) + /* gcrypt currently is configured to use only 4kB of locked secmem + * (see ssh_crypto_init() in src/libcrypt.c) + * + * This is insufficient to run the RSA key generation in many threads. + * To avoid the expected warning, disable the secure memory. + * */ + + gcry_control(GCRYCTL_SUSPEND_SECMEM_WARN); + gcry_control(GCRYCTL_DISABLE_SECMEM); + gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0); +#endif +} + +static void *thread_pki_rsa_import_pubkey_file(void *threadid) +{ + ssh_key pubkey = NULL; + int rc; + + (void) threadid; + + /* The key doesn't have the hostname as comment after the key */ + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_return_code(rc, errno); + assert_non_null(pubkey); + + SSH_KEY_FREE(pubkey); + + return NULL; +} + +static void torture_pki_rsa_import_pubkey_file(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_pubkey_file); + assert_int_equal(rc, 0); +} + + +static void *thread_pki_rsa_import_privkey_base64_NULL_key(void *threadid) +{ + int rc; + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey; + + (void) threadid; /* unused */ + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + assert_non_null(testkey); + + /* test if it returns -1 if key is NULL */ + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + NULL); + assert_true(rc == -1); + return NULL; +} + +static void torture_pki_rsa_import_privkey_base64_NULL_key(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64_NULL_key); + assert_int_equal(rc, 0); +} + + +static void *thread_pki_rsa_import_privkey_base64_NULL_str(void *threadid) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + + (void) threadid; /* unused */ + + /* test if it returns -1 if key_str is NULL */ + rc = ssh_pki_import_privkey_base64(NULL, passphrase, NULL, NULL, &key); + assert_true(rc == -1); + + SSH_KEY_FREE(key); + + return NULL; +} + +static void torture_pki_rsa_import_privkey_base64_NULL_str(void **state){ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64_NULL_str); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_import_privkey_base64(void *threadid) +{ + const char *passphrase = torture_get_testkey_passphrase(); + char *key_str = NULL; + ssh_key key = NULL; + enum ssh_keytypes_e type; + int ok; + int rc; + + (void) threadid; /* unused */ + + key_str = torture_pki_read_file(LIBSSH_RSA_TESTKEY); + assert_non_null(key_str); + + rc = ssh_pki_import_privkey_base64(key_str, passphrase, NULL, NULL, &key); + assert_true(rc == 0); + + type = ssh_key_type(key); + assert_true(type == SSH_KEYTYPE_RSA); + + ok = ssh_key_is_private(key); + assert_true(ok); + + ok = ssh_key_is_public(key); + assert_true(ok); + + free(key_str); + SSH_KEY_FREE(key); + + return NULL; +} + +static void torture_pki_rsa_import_privkey_base64(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_publickey_from_privatekey(void *threadid) +{ + const char *passphrase = NULL; + const char *testkey; + ssh_key pubkey = NULL; + ssh_key key = NULL; + int rc; + int ok; + + (void) threadid; /* unused */ + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_true(rc == 0); + assert_non_null(key); + + ok = ssh_key_is_private(key); + assert_true(ok); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + return NULL; +} + +static void torture_pki_rsa_publickey_from_privatekey(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_publickey_from_privatekey); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_copy_cert_to_privkey(void *threadid) +{ + /* + * Tests copying a cert loaded into a public key to a private key. + * The function is encryption type agnostic, no need to run this against + * all supported key types. + */ + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey = NULL; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key cert = NULL; + int rc; + + (void) threadid; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == SSH_OK); + assert_non_null(cert); + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 0); + assert_non_null(testkey); + + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &privkey); + assert_true(rc == SSH_OK); + assert_non_null(privkey); + + /* Basic sanity. */ + rc = ssh_pki_copy_cert_to_privkey(NULL, privkey); + assert_true(rc == SSH_ERROR); + + rc = ssh_pki_copy_cert_to_privkey(pubkey, NULL); + assert_true(rc == SSH_ERROR); + + /* A public key doesn't have a cert, copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(pubkey, privkey); + assert_true(rc == SSH_ERROR); + + /* Copying the cert to non-cert keys should work fine. */ + rc = ssh_pki_copy_cert_to_privkey(cert, pubkey); + assert_true(rc == SSH_OK); + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_true(rc == SSH_OK); + + /* The private key's cert is already set, another copy should fail. */ + rc = ssh_pki_copy_cert_to_privkey(cert, privkey); + assert_true(rc == SSH_ERROR); + + SSH_KEY_FREE(cert); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(pubkey); + + return NULL; +} + +static void torture_pki_rsa_copy_cert_to_privkey(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_copy_cert_to_privkey); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_import_cert_file(void *threadid) +{ + int rc; + ssh_key cert = NULL; + enum ssh_keytypes_e type; + + (void) threadid; /* unused */ + + rc = ssh_pki_import_cert_file(LIBSSH_RSA_TESTKEY "-cert.pub", &cert); + assert_true(rc == 0); + assert_non_null(cert); + + type = ssh_key_type(cert); + assert_true(type == SSH_KEYTYPE_RSA_CERT01); + + rc = ssh_key_is_public(cert); + assert_true(rc == 1); + + SSH_KEY_FREE(cert); + + return NULL; +} + +static void torture_pki_rsa_import_cert_file(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_cert_file); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_publickey_base64(void *threadid) +{ + enum ssh_keytypes_e type; + char *b64_key = NULL, *key_buf = NULL, *p = NULL; + const char *q = NULL; + ssh_key key; + int rc; + + (void) threadid; /* unused */ + + key_buf = strdup(torture_get_testkey_pub(SSH_KEYTYPE_RSA)); + assert_non_null(key_buf); + + q = p = key_buf; + while (*p != ' ') p++; + *p = '\0'; + + type = ssh_key_type_from_name(q); + assert_true(type == SSH_KEYTYPE_RSA); + + q = ++p; + while (*p != ' ') p++; + *p = '\0'; + + rc = ssh_pki_import_pubkey_base64(q, type, &key); + assert_true(rc == 0); + assert_non_null(key); + + rc = ssh_pki_export_pubkey_base64(key, &b64_key); + assert_true(rc == 0); + assert_non_null(b64_key); + + assert_string_equal(q, b64_key); + + free(b64_key); + free(key_buf); + SSH_KEY_FREE(key); + + return NULL; +} + +static void torture_pki_rsa_publickey_base64(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_publickey_base64); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_duplicate_key(void *threadid) +{ + char *b64_key = NULL; + char *b64_key_gen = NULL; + ssh_key pubkey = NULL; + ssh_key privkey = NULL; + ssh_key privkey_dup = NULL; + int cmp; + int rc; + + (void) threadid; + + rc = ssh_pki_import_pubkey_file(LIBSSH_RSA_TESTKEY ".pub", &pubkey); + assert_true(rc == 0); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key); + assert_true(rc == 0); + SSH_KEY_FREE(pubkey); + assert_non_null(b64_key); + + rc = ssh_pki_import_privkey_file(LIBSSH_RSA_TESTKEY, + NULL, + NULL, + NULL, + &privkey); + assert_true(rc == 0); + assert_non_null(privkey); + + privkey_dup = ssh_key_dup(privkey); + assert_non_null(privkey_dup); + + rc = ssh_pki_export_privkey_to_pubkey(privkey, &pubkey); + assert_true(rc == SSH_OK); + assert_non_null(pubkey); + + rc = ssh_pki_export_pubkey_base64(pubkey, &b64_key_gen); + assert_true(rc == 0); + assert_non_null(b64_key_gen); + + assert_string_equal(b64_key, b64_key_gen); + + cmp = ssh_key_cmp(privkey, privkey_dup, SSH_KEY_CMP_PRIVATE); + assert_true(cmp == 0); + + SSH_KEY_FREE(pubkey); + SSH_KEY_FREE(privkey); + SSH_KEY_FREE(privkey_dup); + SSH_STRING_FREE_CHAR(b64_key); + SSH_STRING_FREE_CHAR(b64_key_gen); + + return NULL; +} + +static void torture_pki_rsa_duplicate_key(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_duplicate_key); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_generate_key(void *threadid) +{ + int rc; + ssh_key key = NULL, pubkey = NULL; + ssh_signature sign = NULL; + ssh_session session = NULL; + + (void) threadid; + + session = ssh_new(); + assert_non_null(session); + + if (!ssh_fips_mode()) { + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 1024, &key); + assert_ssh_return_code(session, rc); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); + assert_ssh_return_code(session, rc); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + } + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 2048, &key); + assert_ssh_return_code(session, rc); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); + assert_ssh_return_code(session, rc); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + rc = ssh_pki_generate(SSH_KEYTYPE_RSA, 4096, &key); + assert_true(rc == SSH_OK); + assert_non_null(key); + + rc = ssh_pki_export_privkey_to_pubkey(key, &pubkey); + assert_int_equal(rc, SSH_OK); + assert_non_null(pubkey); + + sign = pki_do_sign(key, RSA_HASH, 20, SSH_DIGEST_SHA256); + assert_non_null(sign); + + rc = ssh_pki_signature_verify(session, sign, pubkey, RSA_HASH, 20); + assert_true(rc == SSH_OK); + + ssh_signature_free(sign); + SSH_KEY_FREE(key); + SSH_KEY_FREE(pubkey); + + ssh_free(session); + + return NULL; +} + +static void torture_pki_rsa_generate_key(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_generate_key); + assert_int_equal(rc, 0); +} + +static void *thread_pki_rsa_import_privkey_base64_passphrase(void *threadid) +{ + int rc; + ssh_key key = NULL; + const char *passphrase = torture_get_testkey_passphrase(); + const char *testkey; + + (void) threadid; /* unused */ + + testkey = torture_get_testkey(SSH_KEYTYPE_RSA, 1); + assert_non_null(testkey); + + rc = ssh_pki_import_privkey_base64(testkey, + passphrase, + NULL, + NULL, + &key); + assert_return_code(rc, errno); + + rc = ssh_key_is_private(key); + assert_true(rc == 1); + + SSH_KEY_FREE(key); + + /* test if it returns -1 if passphrase is wrong */ + rc = ssh_pki_import_privkey_base64(testkey, + "wrong passphrase !!", + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); + +#ifndef HAVE_LIBCRYPTO + /* test if it returns -1 if passphrase is NULL */ + /* libcrypto asks for a passphrase, so skip this test */ + rc = ssh_pki_import_privkey_base64(testkey, + NULL, + NULL, + NULL, + &key); + assert_true(rc == -1); + SSH_KEY_FREE(key); +#endif + + return NULL; +} + +static void torture_pki_rsa_import_privkey_base64_passphrase(void **state) +{ + int rc; + + /* Unused */ + (void) state; + + rc = run_on_threads(thread_pki_rsa_import_privkey_base64_passphrase); + assert_int_equal(rc, 0); +} + +#define NUM_TESTS 11 + +static void torture_mixed(void **state) +{ + pthread_t threads[NUM_TESTS][NUM_THREADS]; + + int i; + int f; + int rc; + + /* Array of functions to run on threads */ + static void *(*funcs[NUM_TESTS])(void *) = { + thread_pki_rsa_import_pubkey_file, + thread_pki_rsa_import_privkey_base64_NULL_key, + thread_pki_rsa_import_privkey_base64_NULL_str, + thread_pki_rsa_import_privkey_base64, + thread_pki_rsa_publickey_from_privatekey, + thread_pki_rsa_import_privkey_base64_passphrase, + thread_pki_rsa_copy_cert_to_privkey, + thread_pki_rsa_import_cert_file, + thread_pki_rsa_publickey_base64, + thread_pki_rsa_duplicate_key, + thread_pki_rsa_generate_key, + }; + + (void) state; + + /* Call tests in a round-robin fashion */ + for (i = 0; i < NUM_THREADS; ++i) { + for (f = 0; f < NUM_TESTS; f++) { + rc = pthread_create(&threads[f][i], NULL, funcs[f], NULL); + assert_int_equal(rc, 0); + } + } + + for (f = 0; f < NUM_TESTS; f++) { + for (i = 0; i < NUM_THREADS; ++i) { + rc = pthread_join(threads[f][i], NULL); + assert_int_equal(rc, 0); + } + } +} + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_pubkey_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_key, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_import_privkey_base64_NULL_str, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_privkey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown( + torture_pki_rsa_publickey_from_privatekey, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_rsa_import_privkey_base64_passphrase), + cmocka_unit_test_setup_teardown(torture_pki_rsa_copy_cert_to_privkey, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_import_cert_file, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_publickey_base64, + setup_rsa_key, + teardown), + cmocka_unit_test_setup_teardown(torture_pki_rsa_duplicate_key, + setup_rsa_key, + teardown), + cmocka_unit_test(torture_pki_rsa_generate_key), + cmocka_unit_test_setup_teardown(torture_mixed, setup_rsa_key, teardown), + }; + + /* + * Not testing: + * - pki_rsa_generate_pubkey_from_privkey + * - pki_rsa_write_privkey + * + * The original tests in torture_pki_rsa.c require files to be erased + */ + + /* + * If the library is statically linked, ssh_init() is not called + * automatically + */ + disable_secmem(); + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_tokens.c b/src/libs/libssh-0.12.2/tests/unittests/torture_tokens.c new file mode 100644 index 000000000000..438538ded415 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_tokens.c @@ -0,0 +1,349 @@ +/* + * torture_tokens.c - Tests for tokens list handling + * + * This file is part of the SSH Library + * + * Copyright (c) 2019 by Red Hat, Inc. + * + * Author: Anderson Toshiyuki Sasaki + * + * The SSH Library is free software; you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation; either version 2.1 of the License, or (at your + * option) any later version. + * + * The SSH Library is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public + * License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with the SSH Library; see the file COPYING. If not, write to + * the Free Software Foundation, Inc., 59 Temple Place - Suite 330, Boston, + * MA 02111-1307, USA. + */ + +#include "config.h" + +#define LIBSSH_STATIC + +#include "torture.h" +#include "libssh/token.h" +#include "libssh/priv.h" + +static void torture_find_matching(UNUSED_PARAM(void **state)) +{ + char *matching; + + /* Match with single token */ + matching = ssh_find_matching("a,b,c", "b"); + assert_non_null(matching); + assert_string_equal(matching, "b"); + SAFE_FREE(matching); + + /* Match sequence, get first preferred */ + matching = ssh_find_matching("a,b,c", "b,c"); + assert_non_null(matching); + assert_string_equal(matching, "b"); + SAFE_FREE(matching); + + /* Only one token allowed */ + matching = ssh_find_matching("c", "a,b,c"); + assert_non_null(matching); + assert_string_equal(matching, "c"); + SAFE_FREE(matching); + + /* Different order in allowed and preferred; gets preferred */ + matching = ssh_find_matching("c,b,a", "a,b,c"); + assert_non_null(matching); + assert_string_equal(matching, "a"); + SAFE_FREE(matching); + + /* No matching returns NULL */ + matching = ssh_find_matching("c,b,a", "d,e,f"); + assert_null(matching); +} + +static void torture_find_all_matching(UNUSED_PARAM(void **state)) +{ + char *matching; + + /* Match with single token */ + matching = ssh_find_all_matching("a,b,c", "b"); + assert_non_null(matching); + assert_string_equal(matching, "b"); + SAFE_FREE(matching); + + /* Match sequence, get first preferred */ + matching = ssh_find_all_matching("a,b,c", "b,c"); + assert_non_null(matching); + assert_string_equal(matching, "b,c"); + SAFE_FREE(matching); + + /* Only one token allowed */ + matching = ssh_find_all_matching("c", "a,b,c"); + assert_non_null(matching); + assert_string_equal(matching, "c"); + SAFE_FREE(matching); + + /* Different order in allowed and preferred; gets preferred */ + matching = ssh_find_all_matching("c,b,a", "a,c,b"); + assert_non_null(matching); + assert_string_equal(matching, "a,c,b"); + SAFE_FREE(matching); + + /* No matching returns NULL */ + matching = ssh_find_all_matching("c,b,a", "d,e,f"); + assert_null(matching); +} + +static void tokenize_compare_expected(const char *chain, const char **expected, + size_t num_expected) +{ + struct ssh_tokens_st *tokens; + size_t i; + + tokens = ssh_tokenize(chain, ','); + assert_non_null(tokens); + + if (expected != NULL) { + assert_non_null(tokens->tokens); + for (i = 0; i < num_expected; i++) { + assert_non_null(tokens->tokens[i]); + assert_non_null(expected[i]); + assert_string_equal(tokens->tokens[i], expected[i]); + } + + assert_null(tokens->tokens[i]); + + i = 0; + printf("Tokenizing \"%s\" resulted in: ", chain); + while (tokens->tokens[i]) { + printf("\"%s\" ", tokens->tokens[i++]); + } + printf("\n"); + } + + ssh_tokens_free(tokens); +} + +static void torture_tokens_sanity(UNUSED_PARAM(void **state)) +{ + const char *simple[] = {"a", "b", "c"}; + const char *colon_first[] = {"", "a", "b", "c"}; + const char *colon_end[] = {"a", "b", "c"}; + const char *colon_both[] = {"", "a", "b", "c"}; + const char *single[] = {"abc"}; + const char *empty[] = {""}; + const char *single_colon[] = {""}; + + tokenize_compare_expected("a,b,c", simple, 3); + tokenize_compare_expected(",a,b,c", colon_first, 4); + tokenize_compare_expected("a,b,c,", colon_end, 3); + tokenize_compare_expected(",a,b,c,", colon_both, 4); + tokenize_compare_expected("abc", single, 1); + tokenize_compare_expected("", empty, 1); + tokenize_compare_expected(",", single_colon, 1); +} + +static void torture_remove_duplicate(UNUSED_PARAM(void **state)) +{ + + const char *simple[] = {"a,a,b,b,c,c", + "a,b,c,a,b,c", + "a,b,c,c,b,a", + "a,a,,b,b,,c,c", + ",a,a,b,b,c,c", + "a,a,b,b,c,c,"}; + const char *empty[] = {"", + ",,,,,,,,,", + NULL}; + char *ret = NULL; + int i; + + for (i = 0; i < 6; i++) { + ret = ssh_remove_duplicates(simple[i]); + assert_non_null(ret); + assert_string_equal("a,b,c", ret); + printf("simple[%d] resulted in '%s'\n", i, ret); + SAFE_FREE(ret); + } + + for (i = 0; i < 3; i++) { + ret = ssh_remove_duplicates(empty[i]); + if (ret != NULL) { + printf("empty[%d] resulted in '%s'\n", i, ret); + } + assert_null(ret); + } + + ret = ssh_remove_duplicates("a"); + assert_non_null(ret); + assert_string_equal("a", ret); + SAFE_FREE(ret); +} + +static void torture_append_without_duplicate(UNUSED_PARAM(void **state)) +{ + const char *s1[] = {"a,a,b,b,c,c", + "a,b,c,a,b,c", + "a,b,c,c,b,a", + "a,a,,b,b,,c,c", + ",a,a,b,b,c,c", + "a,a,b,b,c,c,"}; + const char *s2[] = {"a,a,b,b,c,c,d,d", + "a,b,c,d,a,b,c,d", + "a,b,c,d,d,c,b,a", + "a,a,,b,b,,c,c,,d,d", + ",a,a,b,b,c,c,d,d", + "a,a,b,b,c,c,d,d,", + "d"}; + const char *empty[] = {"", + ",,,,,,,,,", + NULL, + NULL}; + char *ret = NULL; + int i, j; + + ret = ssh_append_without_duplicates("a", "a"); + assert_non_null(ret); + assert_string_equal("a", ret); + SAFE_FREE(ret); + + ret = ssh_append_without_duplicates("a", "b"); + assert_non_null(ret); + assert_string_equal("a,b", ret); + SAFE_FREE(ret); + + ret = ssh_append_without_duplicates("a", NULL); + assert_non_null(ret); + assert_string_equal("a", ret); + SAFE_FREE(ret); + + ret = ssh_append_without_duplicates(NULL, "b"); + assert_non_null(ret); + assert_string_equal("b", ret); + SAFE_FREE(ret); + + for (i = 0; i < 6; i++) { + for (j = 0; j < 7; j++) { + ret = ssh_append_without_duplicates(s1[i], s2[j]); + assert_non_null(ret); + printf("s1[%d] + s2[%d] resulted in '%s'\n", i, j, ret); + assert_string_equal("a,b,c,d", ret); + SAFE_FREE(ret); + } + } + + for (i = 0; i < 6; i++) { + for (j = 0; j < 3; j++) { + ret = ssh_append_without_duplicates(s1[i], empty[j]); + assert_non_null(ret); + printf("s1[%d] + empty[%d] resulted in '%s'\n", i, j, ret); + assert_string_equal("a,b,c", ret); + SAFE_FREE(ret); + } + } + + for (i = 0; i < 3; i++) { + for (j = 0; j < 6; j++) { + ret = ssh_append_without_duplicates(empty[i], s1[j]); + assert_non_null(ret); + printf("empty[%d] + s1[%d] resulted in '%s'\n", i, j, ret); + assert_string_equal("a,b,c", ret); + SAFE_FREE(ret); + } + } + for (i = 0; i < 4; i++) { + for (j = 0; j < 4; j++) { + ret = ssh_append_without_duplicates(empty[i], empty[j]); + if (ret != NULL) { + printf("empty[%d] + empty[%d] resulted in '%s'\n", i, j, ret); + } + assert_null(ret); + } + } +} + +static void torture_remove_all_matching (UNUSED_PARAM(void** state)) { + char *p; + + p = ssh_remove_all_matching(NULL, NULL); + assert_null(p); + + p = ssh_remove_all_matching("don't remove", NULL); + assert_non_null(p); + assert_string_equal(p, "don't remove"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "b"); + assert_non_null(p); + assert_string_equal(p, "a,c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "a,b"); + assert_non_null(p); + assert_string_equal(p, "c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "d"); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_remove_all_matching("a,b,c", "a,b,c"); + assert_null(p); +} + +static void torture_prefix_without_duplicates (UNUSED_PARAM(void** state)) { + char *p; + + p = ssh_prefix_without_duplicates(NULL, NULL); + assert_null(p); + + p = ssh_prefix_without_duplicates("a,b,c", NULL); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "a"); + assert_non_null(p); + assert_string_equal(p, "a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "b"); + assert_non_null(p); + assert_string_equal(p, "b,a,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "x"); + assert_non_null(p); + assert_string_equal(p, "x,a,b,c"); + free(p); + + p = ssh_prefix_without_duplicates("a,b,c", "c,x"); + assert_non_null(p); + assert_string_equal(p, "c,x,a,b"); + free(p); +} + + +int torture_run_tests(void) +{ + int rc; + struct CMUnitTest tests[] = { + cmocka_unit_test(torture_tokens_sanity), + cmocka_unit_test(torture_find_matching), + cmocka_unit_test(torture_find_all_matching), + cmocka_unit_test(torture_remove_duplicate), + cmocka_unit_test(torture_append_without_duplicate), + cmocka_unit_test(torture_remove_all_matching), + cmocka_unit_test(torture_prefix_without_duplicates), + }; + + ssh_init(); + torture_filter_tests(tests); + rc = cmocka_run_group_tests(tests, NULL, NULL); + ssh_finalize(); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_unit_server.c b/src/libs/libssh-0.12.2/tests/unittests/torture_unit_server.c new file mode 100644 index 000000000000..2fd4be72d656 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_unit_server.c @@ -0,0 +1,195 @@ +#include "config.h" + +#define LIBSSH_STATIC + +#include +#include +#include +#include +#include +#include + +#include +#include +#include "torture.h" +#include "torture_key.h" + +#define TEST_SERVER_PORT 2222 + +#if 0 +struct test_state { + const char *hostkey; + char *hostkey_path; + enum ssh_keytypes_e key_type; + int fd; +}; + +static int setup(void **state) +{ + struct test_state *ts = NULL; + mode_t mask; + int rc; + + ssh_threads_set_callbacks(ssh_threads_get_pthread()); + rc = ssh_init(); + if (rc != SSH_OK) { + return -1; + } + + ts = malloc(sizeof(struct test_state)); + assert_non_null(ts); + + ts->hostkey_path = strdup("/tmp/libssh_hostkey_XXXXXX"); + + mask = umask(S_IRWXO | S_IRWXG); + ts->fd = mkstemp(ts->hostkey_path); + umask(mask); + assert_return_code(ts->fd, errno); + close(ts->fd); + + ts->key_type = SSH_KEYTYPE_ECDSA_P256; + ts->hostkey = torture_get_testkey(ts->key_type, 0); + + torture_write_file(ts->hostkey_path, ts->hostkey); + + *state = ts; + + return 0; +} + +static int teardown(void **state) +{ + struct test_state *ts = (struct test_state *)*state; + + unlink(ts->hostkey); + free(ts->hostkey_path); + free(ts); + + ssh_finalize(); + + return 0; +} + +/* TODO the signals are handled by cmocka so they are not testable her :( */ +static void *int_thread(void *arg) +{ + usleep(1); + kill(getpid(), SIGUSR1); + return NULL; +} + +static void *client_thread(void *arg) +{ + unsigned int test_port = TEST_SERVER_PORT; + int rc; + ssh_session session; + ssh_channel channel; + + /* unused */ + (void)arg; + + usleep(200); + session = torture_ssh_session(NULL, "localhost", + &test_port, + "foo", "bar"); + assert_non_null(session); + + channel = ssh_channel_new(session); + assert_non_null(channel); + + rc = ssh_channel_open_session(channel); + assert_int_equal(rc, SSH_OK); + + ssh_free(session); + return NULL; +} + +static void test_ssh_accept_interrupt(void **state) +{ + struct test_state *ts = (struct test_state *)*state; + int rc; + pthread_t client_pthread, interrupt_pthread; + ssh_bind sshbind = NULL; + ssh_session server; + + /* Create server */ + sshbind = torture_ssh_bind("localhost", + TEST_SERVER_PORT, + ts->key_type, + ts->hostkey_path); + assert_non_null(sshbind); + + server = ssh_new(); + assert_non_null(server); + + /* Send interrupt in 1 second */ + rc = pthread_create(&interrupt_pthread, NULL, int_thread, NULL); + assert_return_code(rc, errno); + + rc = pthread_join(interrupt_pthread, NULL); + assert_int_equal(rc, 0); + + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_ERROR); + assert_int_equal(ssh_get_error_code(sshbind), SSH_EINTR); + + /* Get client to connect now */ + rc = pthread_create(&client_pthread, NULL, client_thread, NULL); + assert_return_code(rc, errno); + + /* Now, try again */ + rc = ssh_bind_accept(sshbind, server); + assert_int_equal(rc, SSH_OK); + + /* Cleanup */ + ssh_bind_free(sshbind); + + rc = pthread_join(client_pthread, NULL); + assert_int_equal(rc, 0); +} +#endif + + +static void test_default_hostkey_paths(void **state) +{ + int rc; + ssh_bind sshbind = NULL; + + /* state not used */ + (void)state; + + /* Create server */ + rc = ssh_init(); + assert_int_equal(rc, 0); + + sshbind = ssh_bind_new(); + assert_non_null(sshbind); + + /* This will fail because we don't have permission to import keys unless we run as root + * TODO: Implement some filesystem wrapper, that would allow this check to pass by + * reading the keys from some accessible test location */ + ssh_bind_listen(sshbind); + + assert_string_equal(sshbind->rsakey, "/etc/ssh/ssh_host_rsa_key"); + assert_string_equal(sshbind->ecdsakey, "/etc/ssh/ssh_host_ecdsa_key"); + assert_string_equal(sshbind->ed25519key, "/etc/ssh/ssh_host_ed25519_key"); + + /* Cleanup */ + ssh_bind_free(sshbind); + ssh_finalize(); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_default_hostkey_paths), + /* Not working correctly the signals are not testable under cmocka + cmocka_unit_test_setup_teardown(test_ssh_accept_interrupt, + setup, + teardown) */ + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/unittests/torture_unit_sftp.c b/src/libs/libssh-0.12.2/tests/unittests/torture_unit_sftp.c new file mode 100644 index 000000000000..12940039308d --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/unittests/torture_unit_sftp.c @@ -0,0 +1,86 @@ +#include "config.h" + +#include "sftp_common.c" +#include "torture.h" + +#define LIBSSH_STATIC + +static void test_sftp_parse_longname(void **state) +{ + const char *lname = NULL; + char *value = NULL; + + /* state not used */ + (void)state; + + /* Valid example from SFTP draft, page 18: + * https://datatracker.ietf.org/doc/draft-spaghetti-sshm-filexfer/ + */ + lname = "-rwxr-xr-x 1 mjos staff 348911 Mar 25 14:29 t-filexfer"; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, "-rwxr-xr-x"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_OWNER); + assert_string_equal(value, "mjos"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_GROUP); + assert_string_equal(value, "staff"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_SIZE); + assert_string_equal(value, "348911"); + free(value); + /* This function is broken further as the date contains space which breaks + * the parsing altogether */ + value = sftp_parse_longname(lname, SFTP_LONGNAME_DATE); + assert_string_equal(value, "Mar"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_TIME); + assert_string_equal(value, "25"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_string_equal(value, "14:29"); + free(value); +} + +static void test_sftp_parse_longname_invalid(void **state) +{ + const char *lname = NULL; + char *value = NULL; + + /* state not used */ + (void)state; + + /* Invalid inputs should not crash + */ + lname = NULL; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_null(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); + + lname = ""; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, ""); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); + + lname = "-rwxr-xr-x 1"; + value = sftp_parse_longname(lname, SFTP_LONGNAME_PERM); + assert_string_equal(value, "-rwxr-xr-x"); + free(value); + value = sftp_parse_longname(lname, SFTP_LONGNAME_NAME); + assert_null(value); +} + +int torture_run_tests(void) +{ + int rc; + const struct CMUnitTest tests[] = { + cmocka_unit_test(test_sftp_parse_longname), + cmocka_unit_test(test_sftp_parse_longname_invalid), + }; + + rc = cmocka_run_group_tests(tests, NULL, NULL); + return rc; +} diff --git a/src/libs/libssh-0.12.2/tests/valgrind.supp b/src/libs/libssh-0.12.2/tests/valgrind.supp new file mode 100644 index 000000000000..34f8b7c7b174 --- /dev/null +++ b/src/libs/libssh-0.12.2/tests/valgrind.supp @@ -0,0 +1,515 @@ +### GLIBC +{ + glibc_regcomp + Memcheck:Leak + fun:*alloc + ... + fun:regcomp +} +{ + glibc_getaddrinfo_leak + Memcheck:Leak + fun:malloc + fun:make_request + fun:__check_pf + fun:getaddrinfo + fun:getai + fun:ssh_connect_host_nonblocking +} + +{ + glibc_dlopen_getdelim_selinux + Memcheck:Leak + fun:malloc + fun:getdelim + obj:/lib64/libselinux.so.1 + fun:call_init + fun:_dl_init + obj:/lib64/ld-2.15.so +} + +{ + glibc_dlopen_alloc + Memcheck:Leak + fun:calloc + fun:_dlerror_run + fun:dlopen@@GLIBC_2.2.5 +} + +### VALGRIND +{ + valgrind_exit_free_bug + Memcheck:Free + fun:free + fun:__libc_freeres + fun:_vgnU_freeres + fun:__run_exit_handlers + fun:exit +} + + +### OPENSSL +{ + openssl_crypto_value8 + Memcheck:Value8 + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_crypto_value4 + Memcheck:Value4 + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_crypto_cond + Memcheck:Cond + fun:* + obj:/lib*/libcrypto.so* +} + +{ + openssl_BN_cond + Memcheck:Cond + fun:BN_* +} + +{ + openssl_bn_value8 + Memcheck:Value8 + fun:bn_* +} + +{ + openssl_bn_value4 + Memcheck:Value4 + fun:bn_* +} + +{ + openssl_AES_cond + Memcheck:Cond + fun:AES_* +} + +{ + openssl_DES_cond + Memcheck:Cond + fun:DES_* +} + +{ + openssl_DES_value8 + Memcheck:Value8 + fun:DES_* +} + +{ + openssl_DES_value4 + Memcheck:Value4 + fun:DES_* +} + +{ + openssl_BF_cond + Memcheck:Cond + fun:BF_* +} + +{ + openssl_SHA1_cond + Memcheck:Cond + fun:SHA1_* +} +{ + openssl_CRYPTO_leak + Memcheck:Cond + fun:OPENSSL_cleanse +} +{ + openssl_FIPS_dlopen_leak + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + fun:_dlerror_run + fun:dlopen* + obj:/lib64/libcrypto.so* + fun:FIPS_module_mode_set + fun:FIPS_mode_set + fun:OPENSSL_init_library +} +{ + Threads + Failed PEM decoder do not play well openssl/openssl#29077 + Memcheck:Leak + match-leak-kinds: definite + fun:malloc + fun:CRYPTO_malloc + fun:CRYPTO_zalloc + fun:ossl_rcu_read_lock + fun:module_find + fun:module_run + fun:CONF_modules_load + fun:CONF_modules_load_file_ex + fun:ossl_config_int + fun:ossl_config_int + fun:ossl_init_config + fun:ossl_init_config_ossl_ + fun:__pthread_once_slow.isra.0 + fun:pthread_once@@GLIBC_2.34 + fun:CRYPTO_THREAD_run_once + fun:OPENSSL_init_crypto + fun:ossl_provider_doall_activated + fun:ossl_algorithm_do_all + fun:ossl_method_construct.constprop.0 + fun:inner_evp_generic_fetch.constprop.0 + fun:evp_generic_do_all + fun:EVP_KEYMGMT_do_all_provided + fun:ossl_decoder_ctx_setup_for_pkey + fun:OSSL_DECODER_CTX_new_for_pkey + fun:pem_read_bio_key_decoder + fun:pem_read_bio_key + fun:PEM_read_bio_PrivateKey_ex + fun:pki_private_key_from_base64 + ... +} +# Cmocka +{ + This looks like leak from cmocka when the forked server is not properly terminated + Memcheck:Leak + match-leak-kinds: reachable + fun:calloc + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + +## libgcrypt +{ + Reachable allocations from libgcrypt + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_check_version + fun:ssh_crypto_init + fun:_ssh_init + fun:libssh_constructor + ... +} +{ + randomize in libgcrypt keeps some memory around + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:ssh_get_random + ... +} +{ + EC key operation allocs some reachable memory + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_pk_sign + ... +} +{ + EC key operation allocs some reachable memory + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_pk_verify + ... +} +{ + EC key generation allocs some reachable memory + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gcry_pk_genkey + ... +} +# NSS +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:__nss_module_get_function + ... + fun:getaddrinfo + ... + fun:torture_* + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} +## libkrb5 +# krb5_mcc_generate_new allocates a hashtab on a static global variable +# It doesn't get freed. +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:malloc + fun:strdup + fun:_dl_load_cache_lookup + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_exception + fun:_dl_catch_error + fun:dlerror_run + ... + fun:getaddrinfo + ... + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx + ... + fun:ssh_userauth_gssapi + fun:torture_gssapi_auth_server_identity + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + +{ + Reachable memory from getaddrinfo + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:UnknownInlinedFun + fun:_dl_new_object + fun:_dl_map_object_from_fd + fun:_dl_map_object + fun:dl_open_worker_begin + fun:_dl_catch_exception + fun:dl_open_worker + fun:_dl_catch_exception + fun:_dl_open + fun:do_dlopen + fun:_dl_catch_exception + fun:_dl_catch_error + fun:dlerror_run + ... + fun:getaddrinfo + ... + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx + ... + fun:ssh_userauth_gssapi + fun:torture_gssapi_auth_server_identity + ... + fun:_cmocka_run_group_tests + fun:torture_run_tests + fun:main +} + +{ + Reachable memory from libkrb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:k5_hashtab_create + ... + fun:krb5_mcc_generate_new* +} +{ + Error string from acquire creds in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:krb5_gss_save_error_string + ... + fun:acquire_cred_context.isra.0 + fun:acquire_cred_from.isra.0 + fun:gss_add_cred_from + fun:gss_acquire_cred_from +} +{ + error string from gss init sec context + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:krb5_gss_save_error_string + ... + fun:krb5_gss_init_sec_context_ext + fun:krb5_gss_init_sec_context + fun:gss_init_sec_context +} + + +## sk-dummy.so +# The sk-dummy.so enroll function allocates 1-byte memory for the signature, but marks the signature length as 0. +# Since, we use burn_free to free the signature, it skips the freeing because the size is 0, which results in a memory leak. +{ + sk-dummy.so memory leak in sk_enroll + Memcheck:Leak + match-leak-kinds: definite + fun:calloc + fun:sk_enroll + fun:pki_sk_enroll_key + ... +} + + +{ + malloc inside expand_hostname + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:expand_hostname + fun:canonicalize_princ + fun:krb5_sname_to_principal + fun:krb5_gss_import_name + fun:gssint_import_internal_name + fun:gss_init_sec_context + fun:ssh_gssapi_init_ctx +} +{ + malloc in krb5_build_principal + Memcheck:Leak + match-leak-kinds: indirect + fun:malloc + ... + fun:krb5_build_principal_alloc_va + fun:krb5_build_principal + ... + fun:gss_add_cred_from + fun:gss_acquire_cred_from + fun:gss_acquire_cred +} +{ + malloc in krb5_build_principal + Memcheck:Leak + match-leak-kinds: indirect,definite + fun:malloc + fun:krb5_build_principal_alloc_va + fun:krb5_build_principal + ... + fun:gss_add_cred_from + fun:gss_acquire_cred_from + fun:gss_acquire_cred +} +{ + calloc in krb5_build_principal + Memcheck:Leak + match-leak-kinds: indirect + fun:calloc + ... + fun:krb5_build_principal_alloc_va + fun:krb5_build_principal + ... + fun:gss_add_cred_from + fun:gss_acquire_cred_from + fun:gss_acquire_cred +} + +# Function mecherror_copy called in various +# functions of the krb5 library copies entries +# to the global error mapping table (mecherrmap m). +{ + Global error mapping table in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:mecherror_copy +} + +# Function add_error_table called in various +# functions of the krb5 library adds entries +# to a global list of error tables et_list. +{ + Global list of error tables in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + fun:add_error_table +} + +# Function build_mechSet builds the global +# gss_OID_set_desc g_mechSet which is only +# free'd when initialized again. +{ + Global OID set in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:build_mechSet +} + +# Function gssint_register_mechinfo() +# called from gssint_mechglue_init() adds +# entries to a global linked list g_mechList. +{ + Global list of gss_mech_info in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:gssint_register_mechinfo* + ... + fun:gssint_mechglue_init +} + +# Function addConfigEntry() called during +# updateMechList() adds entries to +# a global linked list g_mechList. +{ + Global list of gss_mech_info in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:addConfigEntry + ... + fun:updateMechList +} + +# Function loadInterMech() called during +# updateMechList() loops through the global +# linked list g_mechList and updates its entries +# with heap-alloced "interposer fields". +{ + Global list of gss_mech_info in krb5 + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:loadInterMech + ... + fun:updateMechList +} + +# Multiple krb5 functions call krb5int_open_plugin +# which opens shared libraries using dlopen. +# The plugin handle then seems to be stored in the +# main krb5 context. +{ + Plugin handles stored in the krb5 context + Memcheck:Leak + match-leak-kinds: reachable + fun:malloc + ... + fun:dlopen* + ... + fun:krb5int_open_plugin +}